3. WSI预处理与组织区域检测

一张全切片图像(WSI)在40倍扫描下往往包含数万×数万像素,其中真正含有诊断信息的组织区域通常只占整张切片的30%~70%,其余是白色玻璃背景、载玻片边缘、标签、笔迹和气泡。如果直接对整张WSI做分块推理,不仅浪费大量算力,还会让背景块干扰后续的染色归一化统计和肿瘤比例计算。因此,组织区域检测(Tissue Detection)是WSI分析流水线的第一个关键环节:它输出一张与原图同尺寸的二值掩膜(Tissue Mask),标记哪些像素属于有效组织。

一、为什么不能只靠“非白即组织”

最朴素的想法是:像素接近白色就判为背景。但实际切片中,组织边缘存在渐变过渡,气泡、折叠、刀痕会造成局部高亮,而某些淡染组织(如脂肪、黏液)本身也接近浅色。单纯按亮度阈值会同时产生两类错误:把淡染组织误删(漏检),或把气泡、标签误留(误检)。因此工程上通常采用“降采样粗筛 + 阈值分割 + 形态学清理 + 可选深度学习精修”的多级策略。

二、降采样与颜色空间选择

直接在原分辨率上做阈值运算代价过高。常规做法是先读取WSI的缩略图(thumbnail),例如把最长边缩放到 1000~2000 像素。以 OpenSlide 为例:

import openslide
import numpy as np
import cv2

slide = openslide.OpenSlide("case_001.svs")
thumb = slide.get_thumbnail((1024, 1024))   # 最长边约1024
rgb = np.array(thumb.convert("RGB"))

颜色空间上,RGB三通道相关性高,直接阈值不稳定。更常用的是转换到 HSV 或 Lab:HSV中的 S(饱和度)通道对“有色组织 vs 无色背景”区分度好;Lab中的 b 通道对蓝紫色染色(苏木素)敏感。实践中常把 RGB 转灰度后再做 Otsu,或对 S 通道做 Otsu,二者可结合。

三、Otsu阈值与形态学操作

Otsu 方法通过最大化类间方差自动寻找阈值,把灰度直方图分成前景/背景两类。对组织检测而言,它比固定阈值(如 220)更适应不同扫描仪的亮度差异。示例:

gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
# 轻微高斯模糊,抑制噪点
blur = cv2.GaussianBlur(gray, (5, 5), 0)
_, mask = cv2.threshold(blur, 0, 255,
                        cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 注意:Otsu后组织通常为暗,需确认前景方向
if mask.mean() > 127:      # 若白色占多数,说明前景反了
    mask = cv2.bitwise_not(mask)

阈值结果往往带有孔洞、毛刺和孤立小斑。形态学操作按“先开后闭”或“先闭后开”组合:

  • 开运算(先腐蚀后膨胀):去除小噪点和细毛刺,适合清理背景中的孤立亮点。
  • 闭运算(先膨胀后腐蚀):填补组织内部小孔洞,连接断裂区域。
  • 核大小:在1024缩略图上,常用 5×5 到 15×15 的椭圆核;核过大将吞掉细小组织。
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN,  kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

随后用连通域分析剔除面积过小的碎片,并保留最大连通域或面积超过阈值(如缩略图总面积的0.5%)的区域:

num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 8)
clean = np.zeros_like(mask)
for i in range(1, num):
    if stats[i, cv2.CC_STAT_AREA] >= 0.005 * mask.size:
        clean[labels == i] = 255

四、基于深度学习的组织分割

当切片存在大量脂肪、黏液、出血或强染色伪影时,Otsu+形态学容易失效。此时可用轻量语义分割网络(如 U-Net、DeepLabV3+、SegFormer)在缩略图或低倍图上做二分类(组织/背景)。训练数据可由人工勾画或由高质量阈值结果自动生成。推理时输入 512×512 的缩略图块,输出概率图,再以 0.5 为阈值二值化。相比传统方法,深度学习对淡染组织和复杂背景更鲁棒,但需要标注成本和推理时间,通常作为可选精修步骤。

WSI组织区域检测流水线 原始WSI 40× / 数万×数万px 含背景/标签/气泡 缩略图读取 最长边≈1024px RGB → 灰度/HSV Otsu阈值分割 自动类间方差最大 前景方向校验 形态学清理 开运算去噪 闭运算补孔 连通域过滤 剔除小面积碎片 保留主组织区域 深度学习精修(可选) U-Net / SegFormer 512×512块 → 概率图 组织掩膜 Tissue Mask 二值图 · 与原图同尺寸 · 标记有效组织 下游:分块推理 · 染色归一化 · 肿瘤比例计算 关键参数参考(示例) • 缩略图最长边:1024 px • 高斯模糊核:5×5 • 形态学核:椭圆 9×9 • 最小连通域面积:0.5% • DL输入块:512×512 • 二值化阈值:0.5 注:以上为教学示例值,需按扫描仪调整

五、实战:从WSI提取有效组织区域

下面给出一个可运行的完整流程,目标是从一张 SVS 切片中输出组织掩膜,并统计组织面积占比。步骤包括:读取缩略图、灰度化、Otsu、形态学、连通域过滤、面积统计。

import openslide, numpy as np, cv2

slide = openslide.OpenSlide("case_001.svs")
thumb = slide.get_thumbnail((1024, 1024))
rgb = np.array(thumb.convert("RGB"))
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)

# Otsu
_, mask = cv2.threshold(blur, 0, 255,
                        cv2.THRESH_BINARY + cv2.THRESH_OTSU)
if mask.mean() > 127:
    mask = cv2.bitwise_not(mask)

# 形态学
k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN,  k)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, k)

# 连通域过滤
num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 8)
clean = np.zeros_like(mask)
for i in range(1, num):
    if stats[i, cv2.CC_STAT_AREA] >= 0.005 * mask.size:
        clean[labels == i] = 255

ratio = clean.sum() / 255 / clean.size
print(f"组织面积占比: {ratio:.2%}")

运行后若输出“组织面积占比: 42.7%”,说明该切片约四成区域为有效组织。若占比异常偏高(如>90%),通常意味着阈值把背景误判为组织,应检查前景方向或改用S通道;若异常偏低(如<10%),可能是组织淡染被漏检,可尝试降低阈值或引入深度学习精修。最终掩膜可保存为PNG,供后续分块时按块内组织像素比例决定是否推理。

实用建议:在批量处理时,可先对每张切片计算组织占比,把占比低于5%的切片标记为“几乎无组织”,避免无效推理;同时把掩膜与原图缩略图叠加保存,便于人工快速复核。
注意:Otsu阈值是在缩略图上计算的,映射回原分辨率时需按缩放比例放大掩膜,或直接在原分辨率上按块采样判断。若切片存在多个不连续组织块,不要只保留最大连通域,否则会丢失有效区域。

至此,WSI预处理的第一步——组织区域检测已完成。得到的二值掩膜将作为后续分块、染色归一化和特征提取的空间约束,直接决定整个分析流水线的效率与准确性。