【计算机视觉】分水岭实现医学诊断
目录
一、引言
二、分水岭算法
(一)模拟浸水过程
(二)模拟降水过程
(三)过度分割问题
(四)标记分水岭算法
三、分水岭医学诊断案例分析
1.距离变换的分水岭分割
2.梯度的分水岭分割
3.分水岭实现医学诊断
四、本文用到的图片
五、总结
一、引言
近年来,肺癌的发病率和病死率均迅速上升,目前已居所有癌症之首。随着肺癌病人数量的增加,医生对肺癌 CT 图像进行研判的工作量也增加了不少,在这种情况下,难免工作效率降低甚至会出现误诊。为了帮助医生减少重复性工作,对肺部 CT 图像进行计算机辅助检测的技术就被广泛应用于对肺癌的诊断和治疗过程中。
医学 CT 图像处理主要是研究医学图像中的器官和组织之间的关系,并进行病理性分析。因此,借助计算机及图像处理技术对 CT 图像中医生所关注的区域进行精确的分割和定位是医学图像处理的关键步骤,在临床诊断中对于协助医生进行病理研判具有重要意义。
分水岭分割是一种强有力的图像分割方法,可以有效地提取图像中我们所关注的区域。在灰度图像中使用分水岭方法可以将图像分割成不同的区域,每个区域都可能对应一个我们所关注的对象,对于这些图像的子区域可以进行进一步的处理。除此之外,使用分水岭算法还可以提取目标的轮廓等特征。
二、分水岭算法
分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟近邻像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征。
其他图像分割方法,如阈值、边缘检测等都不会考虑像素在空间关系上的相似性和封闭性这一概念,彼此像素间互相独立,没有统一性。分水岭算法较其他分割方法更具有思想性,更符合人眼对图像的印象。
任意的灰度图像都可以被看作为地质学表面,高亮度的地方是山峰,低亮度的地方是山谷。如图所示。
(一)模拟浸水过程
给每个孤立的山谷(局部最小值)标注不同颜色的水(标签),当水涨起来,根据周围的山峰(梯度),不同的山谷也就是不同的颜色会开始合并,要避免这个问题,我们可以在水要漫过(合并)的地方建立障碍,直到所有山峰都被淹没,这就是模拟浸水过程。我们所创建的障碍就是分割结果,这就是分水岭的原理,但是这个方法会分割过度,因为有噪点或者其他图像上的错误。
(二)模拟降水过程
如果将图像视作地形图并建立地理模型,则当上空落下一滴雨珠时,雨珠降落到山体表面并顺山坡向下流,直到汇聚到相同的局部最低点。在地形图上,雨珠在山坡上经过的路线就是一个连通分支,通往局部最低点的所有连通分支就形成了一个聚水盆地,山坡就被称为分水岭,这就是模拟降水的过程。
(三)过度分割问题
分水岭变换的目标是求出梯度图像的 “分水岭线”,传统的差分梯度算法对近邻像素做差分运算,容易受到噪声和量化误差等因素的影响,往往会在灰度的均匀区域内部产生过多的局部梯度 “谷底”,这些在分水岭变换中对应 “集水盆地”。因此,传统的差分梯度算法最终将导致出现过分割(Over Segmentation)现象,即一个灰度均匀的区域可能被过度分成多个子区域,以致产生大量的虚假边缘,从而无法确认哪些是真正边缘,对算法的准确性造成了一定的不利影响,这就是过度分割。
(四)标记分水岭算法
直接应用分水岭算法的主要缺点是会产生过分割现象,即分割出大量的细小区域,而这些区域对于图像分析可以说是毫无意义的。图像噪声等因素往往会导致在图像中出现很多杂乱的低洼区域,而通过平滑滤波能减少局部最小点的数量,所以在分割前先对图像进行平滑是避免过分割的有效方法之一。此外,对分割后的图像按照某种准则进行相邻区域的合并也是一种过分割解决方法。
基于标记(Marker)的分水岭算法能够有效防止过分割现象的发生,该算法的标记包括内部标记(Internal Marker)和外部标记(External Marker)。其基本思想是通过引入标记来修正梯度图像,使得局部最小值仅出现在标记的位置,并设置阈值 h 来对像素值进行过滤,删除最小值深度小于阈值 h 的局部区域。
标记分水岭算法中的一个标记对应图像的一个连通成分,其内部标记与我们感兴趣的某个目标相关,外部标记与背景相关。对标记的选取一般包括预处理和定义选取准则两部分,其中选取准则可以是灰度值、连通性、大小、形状、纹理等特征。在选取内部标记之后,就能以其为基础对低洼进行分割,将分割区域对应的分水线作为外部标记,之后对每个分割出来的区域都利用其他分割技术(如二值化分割)将目标从背景中分离出来。
首先,假设将内部标记的选取准则定义为满足以下条件。
(1)区域周围由更高的 “海拔” 点组成。
(2)区域内的点可以组成一个连通分量。
(3)区域内连通分量的点具有相同或相近的灰度值。
然后,对平滑滤波后的图像应用分水岭算法,并将满足条件的内部标记为所允许的局部最小值,再将分水岭变换得到的分水线结果作为外部标记。
最后,内部标记对应每个感兴趣目标的内部,外部标记对应背景。根据这些标记结果将其分割成互不重叠的区域,每个区域都包含唯一的目标和背景。
因此,标记分水岭算法的显著特点和关键步骤就是获取标记的过程。
三、分水岭医学诊断案例分析
1.距离变换的分水岭分割
分水岭算法可以和距离变换结合,寻找 “汇水盆地” 和 “分水岭界限”,从而对图像进行分割。二值图像的距离变换就是每一个像素点到最近非零值像素点的距离,我们可以使用 scipy 包来计算距离变换。
基于距离变换的分水岭图像分割的Python代码:
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage as ndi
from skimage import morphology, feature, segmentation
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文标签# 创建两个带有重叠圆的图像
x, y = np.indices((80, 80))
x1, y1, x2, y2 = 28, 28, 44, 52
r1, r2 = 16, 20
mask_circle1 = (x - x1) **2 + (y - y1)** 2 < r1 **2
mask_circle2 = (x - x2)** 2 + (y - y2) **2 < r2** 2
image = np.logical_or(mask_circle1, mask_circle2)# 现在用分水岭算法分离两个圆
distance = ndi.distance_transform_edt(image) # 距离变换# 获取峰值坐标
local_maxi_coords = feature.peak_local_max(distance,footprint=np.ones((3, 3)),labels=image
)# 转换为布尔掩码
local_maxi = np.zeros_like(distance, dtype=bool)
local_maxi[tuple(local_maxi_coords.T)] = Truemarkers = ndi.label(local_maxi)[0] # 初始标记点
# 从segmentation模块调用watershed
labels = segmentation.watershed(-distance, markers, mask=image)# 可视化结果
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 8))
axes = axes.ravel()
ax0, ax1, ax2, ax3 = axesax0.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax0.set_title("原始图像")ax1.imshow(-distance, cmap=plt.cm.jet, interpolation='nearest')
ax1.set_title("距离变换")ax2.imshow(markers, cmap=plt.cm.Spectral, interpolation='nearest')
ax2.set_title("标记")ax3.imshow(labels, cmap=plt.cm.Spectral, interpolation='nearest')
ax3.set_title("分割")for ax in axes:ax.axis('off')fig.tight_layout()
plt.show()
2.梯度的分水岭分割
分水岭算法也可以和梯度相结合来实现图像分割。一般梯度图像在边缘处有较高的像素值,而在其他地方则有较低的像素值,理想情况下,分水岭恰好在边缘。因此,可以根据梯度来寻找分水岭。
基于梯度的分水岭图像分割的Python代码:
import matplotlib.pyplot as plt
from scipy import ndimage as ndi
from skimage import morphology, color, data, filters
from skimage import segmentation# 直接使用灰度图像(data.camera()返回的就是灰度图)
image = data.camera() # 形状为(512, 512)的灰度图plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文标签# 过滤噪声
denoised = filters.rank.median(image, morphology.disk(2))# 将梯度值低于10的作为开始标记点
markers = filters.rank.gradient(denoised, morphology.disk(5)) < 10
markers = ndi.label(markers)[0]# 计算梯度
gradient = filters.rank.gradient(denoised, morphology.disk(2))# 基于梯度的分水岭算法
labels = segmentation.watershed(gradient, markers, mask=image)# 可视化结果
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(6, 6))
axes = axes.ravel()
ax0, ax1, ax2, ax3 = axesax0.imshow(image, cmap=plt.cm.gray, interpolation='nearest')
ax0.set_title("原始图像")ax1.imshow(gradient, cmap=plt.cm.Spectral, interpolation='nearest')
ax1.set_title("梯度")ax2.imshow(markers, cmap=plt.cm.Spectral, interpolation='nearest')
ax2.set_title("标记")ax3.imshow(labels, cmap=plt.cm.Spectral, interpolation='nearest')
ax3.set_title("分割")for ax in axes:ax.axis('off')fig.tight_layout()
plt.show()
3.分水岭实现医学诊断
分水岭算法的主要目标在于找到图像的连通区域并进行分割。在实际处理过程中,如果直接以梯度图像作为输入,则容易受到噪声的干扰,产生多个分割区域;如果对原始图像进行平滑滤波处理后再进行梯度计算,则容易将某些原本独立的相邻区域合成一个区域。当然,这里的区域主要还是指图像内容变化不大或灰度值相近的连通区域。
利用分水岭对肺癌细胞进行分割诊断处理的Python代码:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as pltdef watershed_demo(img):"""分水岭分割核心函数:返回所有中间处理结果(用于matplotlib显示)"""# 1. 去噪声(彩色图像专用滤波)blurred = cv.pyrMeanShiftFiltering(img, 10, 100)# 2. 灰度化 + 二值化(OTSU自动阈值)gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)# 3. 开运算去黑点噪声(先腐蚀再膨胀)kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)# 4. 膨胀确定背景区域(扩大背景范围)sure_bg = cv.dilate(opening, kernel, iterations=3)# 5. 距离变换(突出前景核心)dist = cv.distanceTransform(opening, cv.DIST_L2, 3)dist_output = cv.normalize(dist, 0, 1.0, cv.NORM_MINMAX) # 归一化到0-1# 6. 阈值化确定前景区域ret, surface = cv.threshold(dist, dist.max() * 0.6, 255, cv.THRESH_BINARY)surface_fg = np.uint8(surface) # 转换为uint8类型# 7. 计算未知区域(背景与前景的过渡区)unknown = cv.subtract(sure_bg, surface_fg)# 8. 连通区域标记 + 分水岭分割ret, markers = cv.connectedComponents(surface_fg)markers = markers + 1 # 背景标签从0改为1(避免与未知区域冲突)markers[unknown == 255] = 0 # 未知区域标记为0markers = cv.watershed(img, markers) # 执行分水岭result_img = img.copy() # 复制原图,避免修改原数据result_img[markers == -1] = [255, 0, 0] # 分割边界标记为红色(BGR)# 9. 颜色空间转换(BGR→RGB,供matplotlib显示)blurred_rgb = cv.cvtColor(blurred, cv.COLOR_BGR2RGB)result_img_rgb = cv.cvtColor(result_img, cv.COLOR_BGR2RGB)# 返回所有需显示的结果(按显示顺序排列)return [("原始图像", cv.cvtColor(img, cv.COLOR_BGR2RGB)), # 原始图(BGR→RGB)("二值化图像", thresh), # 灰度图("开运算去噪", opening), # 灰度图("确定背景区域", sure_bg), # 灰度图("距离变换(归一化)", dist_output), # 浮点数灰度图("前景标记", surface), # 灰度图("前景二值图", surface_fg), # 灰度图("分水岭分割结果", result_img_rgb) # 彩色结果图(RGB)]# -------------------------- 主逻辑:图像读取 + matplotlib显示 --------------------------
if __name__ == "__main__":# 1. 读取图像img_path = "37.jpg"img = cv.imread(img_path)# 2. 执行分水岭分割,获取所有显示结果display_results = watershed_demo(img)# 3. matplotlib 子图布局(2行4列,容纳8个结果图)plt.figure(figsize=(16, 8)) # 窗口大小:宽16英寸,高8英寸plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文标题plt.rcParams['axes.unicode_minus'] = False # 解决负号显示异常# 4. 逐个绘制子图for idx, (title, img_data) in enumerate(display_results, 1):plt.subplot(2, 4, idx) # 2行4列,第idx个子图if len(img_data.shape) == 3: # 彩色图像(RGB格式)plt.imshow(img_data)else: # 灰度图像(二值图/距离变换图等)# 距离变换图用jet色表更直观,其他灰度图用gray色表cmap = 'jet' if "距离变换" in title else 'gray'plt.imshow(img_data, cmap=cmap)plt.title(title, fontsize=12) # 添加子图标题plt.axis('off') # 关闭坐标轴,聚焦图像# 5. 调整子图间距,避免标题/图像重叠plt.tight_layout()# 6. 显示所有子图(阻塞窗口,按关闭键退出)plt.show()
实验表明,采用标记分水岭算法对肺部图像进行分割具有良好的效果,能在一定程度上突出病变区域,起到辅助医学诊断的目的,具有一定的使用价值。
四、本文用到的图片
五、总结
本文探讨了分水岭算法在医学CT图像分割中的应用,重点解决肺癌诊断中的图像处理问题。针对传统分水岭算法存在的过分割问题,提出了标记分水岭算法,通过引入内部标记和外部标记来修正梯度图像。研究展示了距离变换和梯度两种分水岭分割方法的具体实现,并通过Python代码验证了其在医学图像处理中的有效性。实验结果表明,标记分水岭算法能有效分割肺部CT图像中的病变区域,为医生诊断提供辅助支持。该方法在降低误诊率、提高诊断效率方面具有临床价值。