查看全课大纲(第 80 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
病理切片分析
小象实战讲义 · 基于深度学习的计算机视觉
在医疗影像分析领域,病理切片分析是诊断癌症等疾病的关键环节。本节我们将聚焦于如何利用计算机视觉技术,特别是深度学习,对数字化的病理切片(Whole Slide Image, WSI)进行自动化分析。学完本节,你将理解处理海量高分辨率病理图像的标准流程,掌握从预处理、组织区域提取到分类与计数的完整技术路径。
💡 核心导读
- 病理切片与预处理工具:了解数字病理切片(WSI)的巨大尺寸,并学习使用 OpenSlide 和 ASAP 等专业工具进行读取、缩放和标注转换。
- 组织区域提取:掌握如何利用大津法(Otsu)等阈值分割技术,从整张切片中精确提取包含组织的感兴趣区域(ROI),并舍弃空白背景。
- Patch 划分与样本标记:学习将 ROI 区域划分为标准大小的图像块(patch),并根据标注信息(如肿瘤区域占比)对每个 patch 进行“肿瘤”或“正常”的标记。
- 分类与计数模型应用:探讨使用 ResNet、FCN 等深度网络进行肿瘤区域分类,以及使用 FPN、VGG 等网络进行细胞计数等具体分析方法。
- 数据增强与多尺度分析:针对病理图像的特点,学习包括色彩调整、随机旋转在内的数据增强技巧,以及单尺度和多尺度融合的网络架构设计。
病理切片分析概述与预处理工具
病理切片分析的目标是从细胞层面识别正常细胞与癌变细胞。数字病理切片通常采用全切片图像(WSI)格式存储,其分辨率极高,在最高级别可达20万×10万像素。如此巨大的图像无法直接输入深度学习模型,因此必须进行一系列预处理。
首先需要两个关键工具:
- OpenSlide:一个用于读取和操作各种格式WSI文件的Python库。它支持对图像进行多层级(金字塔式)的访问,即可以快速读取下采样后的概览图,也能读取高分辨率的局部区域。
# 原课基于 TensorFlow 1.x 时代代码,使用 openslide-python。 # 典型用法伪代码如下: # import openslide # slide = openslide.OpenSlide('path/to/wsi.svs') # 获取层级0(最高分辨率)的尺寸 # width, height = slide.dimensions # 读取指定区域(左上角坐标(x, y),层级,区域宽高) # region = slide.read_region((x, y), level, (width, height)) # 注意:openslide-python 依赖系统级的 openslide C 库。 - ASAP (Automated Slide Analysis Platform):一个专门用于病理切片处理的工具。它主要有两个用途:一是从完整的WSI中截取特定的小区域(patch);二是将常见的XML格式的病灶轮廓标注文件,直接转换为覆盖在原始图像上的掩码(mask)图片,使得标注可视化,便于后续处理。
组织区域提取与初筛
直接处理200k×100k像素的图像是不现实的,其数据量是普通256×256图像的数十万倍。幸运的是,WSI中包含大量无组织的空白区域(如图片边缘的纯黑色部分),这些区域可以直接舍弃。
提取包含组织的感兴趣区域(ROI)是关键的第一步。由于癌变区域常位于组织边缘,因此ROI提取必须精确。一种经典且有效的方法是大津法(Otsu’s method)。
大津法是一种自动确定图像二值化阈值的算法。其核心思想是遍历所有可能的灰度阈值,计算按该阈值分割出的前景(组织)和背景(空白)两类像素的类间方差。当类间方差最大时,意味着此时前景与背景的差异最大,该阈值即为最优分割阈值。
其数学表达如下:对于灰度级 的图像,设阈值为 ,将像素分为两类 (灰度值 ) 和 (灰度值 )。计算两类的像素数占比 , 和平均灰度 , 。图像总平均灰度为 。则类间方差 为: 寻找使 最大的 ,即为 Otsu 阈值。
下面我们用 numpy 和 cv2 实现一个简化版的大津法来感受其原理,并对一个小示例矩阵进行分割。
import numpy as np
import cv2
# 构造一个简单的8x8灰度图像模拟组织区域(高值)和背景(低值)
# 中间部分模拟组织(值较高),边缘模拟背景(值较低)
image = np.array([
[10, 10, 10, 10, 10, 10, 10, 10],
[10, 200, 210, 205, 195, 210, 200, 10],
[10, 205, 220, 215, 210, 220, 205, 10],
[10, 195, 210, 230, 225, 215, 195, 10],
[10, 190, 205, 225, 235, 210, 190, 10],
[10, 200, 215, 220, 215, 220, 200, 10],
[10, 195, 200, 205, 200, 195, 190, 10],
[10, 10, 10, 10, 10, 10, 10, 10]
], dtype=np.uint8)
# 使用OpenCV的Otsu阈值分割
otsu_thresh, binary_mask = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print("Otsu自动计算出的阈值为:", otsu_thresh)
print("二值化掩码(0为背景,255为前景):")
np.set_printoptions(linewidth=100)
print(binary_mask)Otsu自动计算出的阈值为: 10.0
二值化掩码(0为背景,255为前景):
[[ 0 0 0 0 0 0 0 0]
[ 0 255 255 255 255 255 255 0]
[ 0 255 255 255 255 255 255 0]
[ 0 255 255 255 255 255 255 0]
[ 0 255 255 255 255 255 255 0]
[ 0 255 255 255 255 255 255 0]
[ 0 255 255 255 255 255 255 0]
[ 0 0 0 0 0 0 0 0]]可以看到,算法成功地将中间高灰度值的“组织”区域(255)与边缘低灰度值的“背景”区域(0)分离开来。在实际的WSI处理中,这一步能得到组织的二进制掩码,用于后续的ROI提取。
Patch划分与样本标记
获得ROI掩码后,下一步是在ROI区域内,以固定的步幅(stride)滑动提取固定大小的图像块,例如256×256像素,这被称为 patch。这样做的目的是将巨大的WSI转化为深度学习模型可以接受的标准输入尺寸。
提取patch后,需要根据标注信息对每个patch进行标记。常见的规则是:如果一个patch中,被标注为肿瘤的像素比例超过某个阈值(例如75%),则整个patch被标记为“肿瘤样本”;否则标记为“正常样本”。这个阈值在不同研究中可能有所变化。
下面我们模拟这一过程。假设我们有一个小的二值掩码(代表ROI,其中1是组织区域)和一个对应的模拟图像。我们以较小的patch尺寸和步幅进行提取,并模拟一个简单的“肿瘤标注”区域来计算占比。
import numpy as np
# 1. 模拟一个20x20的ROI二进制掩码 (1为组织,0为背景)
roi_mask = np.zeros((20, 20), dtype=np.uint8)
roi_mask[5:15, 5:15] = 1 # 中间一个10x10的正方形为组织区域
# 2. 模拟对应的“肿瘤标注”区域(在组织区域内,部分区域被标注为肿瘤)
tumor_annotation = np.zeros((20, 20), dtype=np.uint8)
# 假设在组织区域的右下方有一块肿瘤
tumor_annotation[10:14, 10:14] = 1 # 一个4x4的区域为肿瘤
# 3. 设置patch参数
patch_size = 8
stride = 4
# 4. 滑动提取patch,并判断其是否为“肿瘤样本”
tumor_patches = []
normal_patches = []
for y in range(0, roi_mask.shape[0] - patch_size + 1, stride):
for x in range(0, roi_mask.shape[1] - patch_size + 1, stride):
# 提取当前patch的掩码和标注
patch_mask = roi_mask[y:y+patch_size, x:x+patch_size]
patch_annotation = tumor_annotation[y:y+patch_size, x:x+patch_size]
# 规则:仅当patch内组织像素数 > 0 时才考虑(避免纯背景patch)
if np.sum(patch_mask) > 0:
# 计算肿瘤标注像素在patch内的比例
tumor_ratio = np.sum(patch_annotation) / (patch_size * patch_size)
# 为便于演示,这里用 0.2 的简化阈值(真实常用 0.5~0.75)
if tumor_ratio > 0.2:
tumor_patches.append((y, x, tumor_ratio))
else:
normal_patches.append((y, x, tumor_ratio))
print(f"共提取到 {len(tumor_patches) + len(normal_patches)} 个组织patch。")
print(f"其中,肿瘤样本: {len(tumor_patches)} 个,正常样本: {len(normal_patches)} 个")
if tumor_patches:
print("前两个肿瘤样本的(左上角y, x)及肿瘤像素占比:", tumor_patches[:2])
if normal_patches:
print("前两个正常样本的(左上角y, x)及肿瘤像素占比:", normal_patches[:2])共提取到 16 个组织patch。
其中,肿瘤样本: 1 个,正常样本: 15 个
前两个肿瘤样本的(左上角y, x)及肿瘤像素占比: [(8, 8, np.float64(0.25))]
前两个正常样本的(左上角y, x)及肿瘤像素占比: [(0, 0, np.float64(0.0)), (0, 4, np.float64(0.0))]深度学习模型的应用与分析技巧
获得标记好的patch样本后,就可以将其输入深度学习模型进行训练和预测。主要有两种思路:
- 直接端到端分类:将每个256×256的patch直接输入一个深度卷积网络(如ResNet-101),输出二分类结果(肿瘤/正常)。这是最直接的方法。
- 热力图+传统分类器:使用全卷积网络(FCN)处理patch。FCN会输出一个尺寸缩小的“热力图”(heatmap),其中每个位置的值反映了该区域属于肿瘤的概率。然后将这张热力图的所有像素值展平,作为一个特征向量,输入到随机森林(Random Forest)等效果较好的传统分类器中进行最终判断。这种方法并非端到端,但有时能结合深度特征和传统分类器的优势。
在训练过程中,需要特别注意以下几点:
- 类别平衡:肿瘤组织通常远少于正常组织。在训练时,应对样本进行重采样,使两类样本数量大致相等,防止模型偏向多数类。
- 数据增强:针对病理图像特点进行增强。
- 色彩增强:由于苏木精-伊红(H&E)染色可能存在差异,可以随机调整图像的色调、饱和度、亮度和对比度。
- 几何增强:病理组织图像具有旋转对称性,即细胞方向无关紧要。因此可以施加大幅度的随机旋转(0-360度)和水平翻转。
- 多尺度分析:细胞结构在不同放大倍数下呈现不同信息。可以借鉴GoogLeNet的多分支思想,将同一区域在40倍、20倍、10倍等多个尺度下的图像分别输入网络的不同分支,最后将各分支的特征融合后进行全连接分类。单尺度分析则只使用最高倍数(如40倍)的图像。
除了分类,病理切片分析还包括细胞计数等定量任务。这可以看作一个分割问题。例如,可以使用特征金字塔网络(FPN)来同时利用低层精确定位和高层语义信息,实现细胞与背景的精确分割。分割出细胞后,再通过VGG等网络对分割出的连通区域进行计数,甚至可以估计计数的误差。
📝 动手练一练
- 概念辨析:在病理切片分析中,为什么不能直接将整张Whole Slide Image (WSI) 输入深度学习模型?请至少列出两个主要原因。
👉 点击查看参考答案
- 数据量过大,超出硬件和处理能力:WSI分辨率极高(可达20万×10万像素),数据量是普通网络输入(如256×256)的数十万倍。直接输入会导致显存爆炸,计算量无法承受。
- 尺寸不一且细节尺度太小:各WSI的尺寸互不相同,且都远超模型输入尺寸;若直接把整图缩放到网络输入大小,细胞级细节会被抹平,达不到诊断所需的精度。
- 包含大量无关信息:WSI中存在大量无组织的空白区域,这些区域对诊断无意义,直接输入会引入噪声,降低模型对关键组织区域的学习效率。
- 代码实践:假设我们有一个小的二值图像
mask(0代表背景,1代表组织),我们想计算其中值为1的像素(即组织区域)占总像素的比例。请补全以下代码。import numpy as np # 示例mask mask = np.array([ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0] ]) # 请计算组织像素比例 tissue_ratio = _______________________________ print(f"组织区域像素比例为: {tissue_ratio:.2f}")
👉 点击查看参考答案
import numpy as np
mask = np.array([
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]
])
# 计算组织像素比例:组织像素数 / 总像素数
tissue_ratio = np.sum(mask) / mask.size
print(f"组织区域像素比例为: {tissue_ratio:.2f}")组织区域像素比例为: 0.25本章小结
本节系统介绍了深度学习在病理切片分析中的完整流程。我们从认识海量数据的WSI和必备的OpenSlide、ASAP工具开始,学习了如何利用大津法精确提取组织ROI,并通过滑动窗口将其划分为标准patch进行标记。随后,探讨了应用ResNet、FCN等模型进行分类,以及使用FPN等进行细胞计数的具体方法。最后,强调了针对病理图像特点的类别平衡与数据增强等实用技巧。
今天,WSI 分类更多采用多示例学习(MIL)、TransMIL、CLAM 等弱监督方法与病理基础模型;但本节“ROI 提取 → patch 化 → 分类/计数”的流程仍是数字病理分析的标准范式。
📋 行动清单
- 尝试在本地安装OpenSlide的Python绑定,并查找一个公开的WSI样例(如Camelyon数据集),练习读取其不同层级的图像。
- 使用OpenCV的
cv2.threshold函数配合cv2.THRESH_OTSU标志,对你熟悉的一张灰度图进行自动阈值分割,观察效果。 - 思考:如果病理切片的标注不是矩形区域,而是不规则的多边形轮廓(保存在XML中),在划分patch并判断其是否为肿瘤样本时,计算“肿瘤像素占比”的方法需要如何调整?
—— 小象教研组