第三章 图像分割基础:从像素到区域的实战之路

图像分割,说白了就是把图像里我们感兴趣的部分「抠」出来。在超声图像里,这步尤其关键——你要从一堆噪声和伪影中,找到那个病灶或者器官边界。我刚开始做超声算法时,觉得分割不就是调个阈值嘛,后来被现实狠狠教育了一顿。

这一章,我们聊聊三种最基础的分割方法:阈值分割、边缘检测、区域生长。它们虽然「老」,但直到今天,很多超声产品的预处理管线里,依然在用这些方法做快速初筛。

3.1 阈值分割:最简单的「一刀切」

阈值分割的原理,简单到令人发指:设定一个值T,像素灰度大于T的算一类,小于T的算另一类。在超声图像中,囊肿区域通常表现为低回声(暗区),而周围组织回声较强(亮区),这时候阈值分割就派上用场了。

3.1.1 全局阈值

全局阈值就是整张图用一个固定的T。我早期做过一个甲状腺结节分割项目,一开始就用全局阈值,结果发现不同探头、不同增益设置下,同一个病人的图像灰度分布差异巨大。固定阈值根本扛不住。

代码实现很简单:

import cv2
import numpy as np

def global_threshold(image, thresh=128):
    # 二值化:大于thresh的设为255,小于的设为0
    _, binary = cv2.threshold(image, thresh, 255, cv2.THRESH_BINARY)
    return binary

嗯,这里要注意:超声图像的灰度范围通常不是0-255,而是经过对数压缩后的16位数据。直接拿8位阈值去切,会丢失大量细节。我建议先做直方图均衡化,再设定阈值。

避坑指南: 我曾经在乳腺超声项目里,直接用全局阈值分割囊性区域,结果把大量噪声也当成了病灶。后来发现,超声图像的灰度直方图往往是多峰的,单阈值根本不够用。

3.1.2 Otsu算法:自动找最佳阈值

Otsu算法解决了「阈值怎么选」的问题。它的核心思想是:让分割后的两类像素,类内方差最小、类间方差最大。说白了,就是让「黑得更黑,白得更白」。

你想想看,如果直方图有两个明显的峰,Otsu会自动把阈值设在两个峰之间的谷底。这个特性在超声图像里特别有用——比如区分充满液体的囊肿(低回声)和实性组织(高回声)。

def otsu_threshold(image):
    # Otsu自动计算最优阈值
    _, binary = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    return binary

我个人习惯在预处理阶段先用Otsu做一次粗分割,把明显的前景和背景分开,然后再用更精细的方法做后续处理。这样能大幅减少计算量。

实战技巧: Otsu假设直方图是双峰的。如果超声图像的直方图是单峰(比如全是低回声区域),Otsu会失效。这时候可以先用直方图拉伸,人为制造双峰分布。

3.2 边缘检测:找到那些「突变」的像素

边缘检测,就是找到图像中灰度变化剧烈的位置。在超声图像里,器官边界、血管壁、病灶轮廓,都是边缘。我做过一个肝脏肿瘤分割项目,边缘检测的质量直接决定了后续分割的精度。

3.2.1 Sobel算子:一阶导数的朴素实现

Sobel算子计算的是图像在x方向和y方向的一阶导数。它用两个3x3的卷积核,分别检测水平和垂直边缘。

为什么用Sobel?因为它简单、快。在超声实时处理中,每帧图像只有几十毫秒的处理时间,Sobel的轻量级特性让它成为首选。

def sobel_edge(image):
    # 计算x和y方向的梯度
    grad_x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
    grad_y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)
    # 计算梯度幅值
    magnitude = np.sqrt(grad_x**2 + grad_y**2)
    return np.uint8(magnitude)

我记得有一次,用Sobel检测胎儿头围的边缘,结果发现噪声响应特别强。后来加了高斯滤波做平滑,效果才好了很多。记住:Sobel对噪声敏感,用之前一定要去噪。

3.2.2 Canny边缘检测:工业级标准

Canny可以说是边缘检测的「天花板」。它做了三件事:先用高斯滤波去噪,再用非极大值抑制细化边缘,最后用双阈值连接边缘。

双阈值是Canny的精髓。高阈值确定强边缘,低阈值保留弱边缘,但只有与强边缘相连的弱边缘才会被保留。这个机制在超声图像里特别有用——因为病灶边界往往有灰度渐变,单阈值很容易把边界切碎。

def canny_edge(image, low_thresh=50, high_thresh=150):
    # Canny自动完成去噪、非极大值抑制、双阈值连接
    edges = cv2.Canny(image, low_thresh, high_thresh)
    return edges
核心经验: Canny的双阈值怎么调?我一般先看直方图,把高阈值设在梯度幅值的前10%位置,低阈值设在高阈值的1/3到1/2。这个经验值在超声图像上屡试不爽。

3.2.3 Laplacian算子:二阶导数的妙用

Laplacian算子是二阶导数,它检测的是灰度变化的「变化率」。说白了,Sobel找的是「哪里在变」,Laplacian找的是「哪里变得最快」。

Laplacian对孤立点和细线特别敏感。在超声图像里,我常用它来增强微钙化点——这些小亮点在乳腺超声中可能是恶性病变的标志。

def laplacian_edge(image):
    # Laplacian算子,ksize必须是奇数
    laplacian = cv2.Laplacian(image, cv2.CV_64F, ksize=3)
    return np.uint8(np.abs(laplacian))
避坑指南: 我曾经直接用Laplacian处理原始超声图像,结果噪声被放大了好几倍。后来发现,Laplacian对噪声的敏感度是Sobel的平方级别。一定要先做中值滤波或高斯滤波。

3.3 区域生长法:从种子到区域的「蔓延」

区域生长法的思路很直观:选一个种子点,然后不断把周围「相似」的像素加进来,直到没有新像素满足条件。这个「相似」可以是灰度值接近、纹理相似,甚至可以是距离相近。

在超声图像里,区域生长法特别适合分割那些边界模糊但内部均匀的结构——比如囊肿、积液区域。我做过一个肾囊肿分割项目,囊肿边界在超声上几乎看不清,但内部灰度非常均匀,区域生长法反而比深度学习效果好。

def region_growing(image, seed, threshold=10):
    # 种子点坐标 (x, y)
    h, w = image.shape
    segmented = np.zeros((h, w), dtype=np.uint8)
    seed_value = image[seed[1], seed[0]]
    
    # 使用队列实现BFS
    queue = [seed]
    visited = set()
    visited.add(seed)
    
    while queue:
        x, y = queue.pop(0)
        if segmented[y, x] == 0:
            segmented[y, x] = 255
            # 检查4邻域
            for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
                nx, ny = x+dx, y+dy
                if 0 <= nx < w and 0 <= ny < h and (nx, ny) not in visited:
                    if abs(int(image[ny, nx]) - int(seed_value)) < threshold:
                        queue.append((nx, ny))
                        visited.add((nx, ny))
    return segmented

嗯,这里要注意几个坑:

  • 种子点选择:种子点必须落在目标区域内。我一般让医生手动点一下,或者用Otsu粗分割后的最大连通域中心作为种子。
  • 阈值设定:阈值太小,区域长不开;阈值太大,会溢出到背景。我建议先做直方图分析,取目标区域灰度标准差的一半作为初始阈值。
  • 生长策略:4邻域还是8邻域?超声图像里我习惯用8邻域,因为边界往往有斜向连接。
实战技巧: 区域生长法在超声图像里容易「漏」——因为超声的斑点噪声会导致局部灰度突变,让生长提前停止。我的解决方案是:先做各向异性扩散滤波,平滑噪声的同时保留边缘。

3.4 三种方法的对比与选择

说了这么多,到底什么时候用哪种?我整理了一个表格,方便你快速决策:

方法 适用场景 优点 缺点 超声典型应用
全局阈值 目标与背景灰度差异明显 速度快,实现简单 对光照、增益变化敏感 囊肿/积液粗分割
Otsu 直方图双峰分布 自动选阈值,无需人工 单峰或平坦直方图失效 甲状腺结节初筛
Sobel 实时处理,快速边缘检测 计算量小,方向可控 噪声敏感,边缘较粗 血管壁检测
Canny 需要精细、连续的边缘 边缘定位准,抗噪性好 参数调优较麻烦 器官边界提取
Laplacian 检测孤立点、细线、角点 各向同性,旋转不变 噪声放大严重 微钙化点增强
区域生长 内部均匀、边界模糊的目标 分割结果连续、完整 种子点依赖,速度较慢 囊肿、积液分割

最后说一句:这些方法虽然基础,但组合起来威力巨大。我现在的很多超声分割管线,都是先用Otsu做粗分割,再用Canny提取边界,最后用区域生长做精细化调整。别小看这些「老方法」,它们往往是深度学习模型落地时的「救火队员」。

下一章,我们会聊聊更高级的分割方法——基于形态学的处理。到时候你会发现,很多今天讲的内容,都是那些高级方法的基石。