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提取有效组织区域
下面给出一个可运行的完整流程,目标是从一张 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,供后续分块时按块内组织像素比例决定是否推理。
至此,WSI预处理的第一步——组织区域检测已完成。得到的二值掩膜将作为后续分块、染色归一化和特征提取的空间约束,直接决定整个分析流水线的效率与准确性。