查看全课大纲(第 80 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

病理切片分析

小象实战讲义 · 基于深度学习的计算机视觉

在医疗影像分析领域,病理切片分析是诊断癌症等疾病的关键环节。本节我们将聚焦于如何利用计算机视觉技术,特别是深度学习,对数字化的病理切片(Whole Slide Image, WSI)进行自动化分析。学完本节,你将理解处理海量高分辨率病理图像的标准流程,掌握从预处理、组织区域提取到分类与计数的完整技术路径。

💡 核心导读

  • 病理切片与预处理工具:了解数字病理切片(WSI)的巨大尺寸,并学习使用 OpenSlide 和 ASAP 等专业工具进行读取、缩放和标注转换。
  • 组织区域提取:掌握如何利用大津法(Otsu)等阈值分割技术,从整张切片中精确提取包含组织的感兴趣区域(ROI),并舍弃空白背景。
  • Patch 划分与样本标记:学习将 ROI 区域划分为标准大小的图像块(patch),并根据标注信息(如肿瘤区域占比)对每个 patch 进行“肿瘤”或“正常”的标记。
  • 分类与计数模型应用:探讨使用 ResNet、FCN 等深度网络进行肿瘤区域分类,以及使用 FPN、VGG 等网络进行细胞计数等具体分析方法。
  • 数据增强与多尺度分析:针对病理图像的特点,学习包括色彩调整、随机旋转在内的数据增强技巧,以及单尺度和多尺度融合的网络架构设计。

病理切片分析概述与预处理工具

病理切片分析的目标是从细胞层面识别正常细胞与癌变细胞。数字病理切片通常采用全切片图像(WSI)格式存储,其分辨率极高,在最高级别可达20万×10万像素。如此巨大的图像无法直接输入深度学习模型,因此必须进行一系列预处理。

首先需要两个关键工具:

  1. 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 库。
  2. ASAP (Automated Slide Analysis Platform):一个专门用于病理切片处理的工具。它主要有两个用途:一是从完整的WSI中截取特定的小区域(patch);二是将常见的XML格式的病灶轮廓标注文件,直接转换为覆盖在原始图像上的掩码(mask)图片,使得标注可视化,便于后续处理。

组织区域提取与初筛

直接处理200k×100k像素的图像是不现实的,其数据量是普通256×256图像的数十万倍。幸运的是,WSI中包含大量无组织的空白区域(如图片边缘的纯黑色部分),这些区域可以直接舍弃。

提取包含组织的感兴趣区域(ROI)是关键的第一步。由于癌变区域常位于组织边缘,因此ROI提取必须精确。一种经典且有效的方法是大津法(Otsu’s method)。

大津法是一种自动确定图像二值化阈值的算法。其核心思想是遍历所有可能的灰度阈值,计算按该阈值分割出的前景(组织)和背景(空白)两类像素的类间方差。当类间方差最大时,意味着此时前景与背景的差异最大,该阈值即为最优分割阈值。

其数学表达如下:对于灰度级 LL 的图像,设阈值为 tt,将像素分为两类 C0C_0 (灰度值 [0,t][0, t]) 和 C1C_1 (灰度值 [t+1,L−1][t+1, L-1])。计算两类的像素数占比 w0w_0, w1w_1 和平均灰度 μ0\mu_0, μ1\mu_1。图像总平均灰度为 μT=w0μ0+w1μ1\mu_T = w_0 \mu_0 + w_1 \mu_1。则类间方差 σB2\sigma_B^2 为: σB2=w0(μ0−μT)2+w1(μ1−μT)2\sigma_B^2 = w_0 (\mu_0 - \mu_T)^2 + w_1 (\mu_1 - \mu_T)^2 寻找使 σB2\sigma_B^2 最大的 tt,即为 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样本后,就可以将其输入深度学习模型进行训练和预测。主要有两种思路:

  1. 直接端到端分类:将每个256×256的patch直接输入一个深度卷积网络(如ResNet-101),输出二分类结果(肿瘤/正常)。这是最直接的方法。
  2. 热力图+传统分类器:使用全卷积网络(FCN)处理patch。FCN会输出一个尺寸缩小的“热力图”(heatmap),其中每个位置的值反映了该区域属于肿瘤的概率。然后将这张热力图的所有像素值展平,作为一个特征向量,输入到随机森林(Random Forest)等效果较好的传统分类器中进行最终判断。这种方法并非端到端,但有时能结合深度特征和传统分类器的优势。

在训练过程中,需要特别注意以下几点:

  • 类别平衡:肿瘤组织通常远少于正常组织。在训练时,应对样本进行重采样,使两类样本数量大致相等,防止模型偏向多数类。
  • 数据增强:针对病理图像特点进行增强。
    • 色彩增强:由于苏木精-伊红(H&E)染色可能存在差异,可以随机调整图像的色调、饱和度、亮度和对比度。
    • 几何增强:病理组织图像具有旋转对称性,即细胞方向无关紧要。因此可以施加大幅度的随机旋转(0-360度)和水平翻转。
  • 多尺度分析:细胞结构在不同放大倍数下呈现不同信息。可以借鉴GoogLeNet的多分支思想,将同一区域在40倍、20倍、10倍等多个尺度下的图像分别输入网络的不同分支,最后将各分支的特征融合后进行全连接分类。单尺度分析则只使用最高倍数(如40倍)的图像。

除了分类,病理切片分析还包括细胞计数等定量任务。这可以看作一个分割问题。例如,可以使用特征金字塔网络(FPN)来同时利用低层精确定位和高层语义信息,实现细胞与背景的精确分割。分割出细胞后,再通过VGG等网络对分割出的连通区域进行计数,甚至可以估计计数的误差。

📝 动手练一练

  1. 概念辨析:在病理切片分析中,为什么不能直接将整张Whole Slide Image (WSI) 输入深度学习模型?请至少列出两个主要原因。
👉 点击查看参考答案
  1. 数据量过大,超出硬件和处理能力:WSI分辨率极高(可达20万×10万像素),数据量是普通网络输入(如256×256)的数十万倍。直接输入会导致显存爆炸,计算量无法承受。
  2. 尺寸不一且细节尺度太小:各WSI的尺寸互不相同,且都远超模型输入尺寸;若直接把整图缩放到网络输入大小,细胞级细节会被抹平,达不到诊断所需的精度。
  3. 包含大量无关信息:WSI中存在大量无组织的空白区域,这些区域对诊断无意义,直接输入会引入噪声,降低模型对关键组织区域的学习效率。
  1. 代码实践:假设我们有一个小的二值图像 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并判断其是否为肿瘤样本时,计算“肿瘤像素占比”的方法需要如何调整?

—— 小象教研组