重叠细胞分割求助:Watershed算法误分割非重叠细胞
问题:Watershed算法误分割非重叠细胞的解决方案
我正在使用Python脚本进行重叠细胞分割,这套方法在分离重叠细胞时效果不错,但误分割非重叠细胞的问题很头疼。我已经附上了输入图像、输出图像、标记错误分割细胞的输出图像以及阈值化图像,想请教如何避免这个问题,或者当前方法是不是不适用于这类图像?
我的代码
from skimage.feature import peak_local_max from skimage.morphology import watershed from scipy import ndimage import numpy as np import cv2 # load the image and perform pyramid mean shift filtering # to aid the thresholding step image = cv2.imread('C:/Users/Root/Desktop/image13.jpg') shifted = cv2.pyrMeanShiftFiltering(image, 41, 51) # convert the mean shift image to grayscale, then apply # Otsu's thresholding gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] im = gray.copy() D = ndimage.distance_transform_edt(thresh) localMax = peak_local_max(D, indices=False, min_distance=3, labels=thresh) # perform a connected component analysis on the local peaks, # using 8-connectivity, then apply the Watershed algorithm markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0] labels = watershed(-D, markers, mask=thresh) print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1)) conts=[] for label in np.unique(labels): # if the label is zero, we are examining the 'background' # so simply ignore it if label == 0: continue # otherwise, allocate memory for the label region and draw # it on the mask mask = np.zeros(gray.shape, dtype="uint8") mask[labels == label] = 255 # detect contours in the mask and grab the largest one cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] c = max(cnts, key=cv2.contourArea) rect = cv2.minAreaRect(c) box = cv2.boxPoints(rect) box = np.int0(box) if cv2.contourArea(c) > 150: #cv2.drawContours(image,c,-1,(0,255,0)) cv2.drawContours(image,[box],-1,(0,255,0)) cv2.imshow("output", image) cv2.waitKey()
问题根源分析
你的核心问题出在**peak_local_max的参数设置和阈值化环节的细节处理**上:
min_distance=3太小了——这个参数控制局部极大值之间的最小距离,值过小会导致单个非重叠细胞的距离变换图上出现多个局部峰值,Watershed算法自然会把一个细胞拆成多个区域。- 你做了均值漂移滤波(
pyrMeanShiftFiltering)但没用到实处:后续阈值化用的是原始灰度图,而非滤波后的shifted图像,导致阈值化结果残留大量噪声,进一步干扰局部峰值检测。 - 缺少形态学处理:阈值化后的图像可能存在小噪声点或细胞内部孔洞,这些都会误导距离变换和峰值检测。
改进方案
针对以上问题,你可以按以下步骤调整代码:
1. 用滤波后的图像做阈值化
把阈值化的输入换成shifted处理后的图像,能有效抑制背景噪声,让细胞边界更清晰:
# 修正阈值化步骤:使用均值漂移滤波后的图像转灰度 gray = cv2.cvtColor(shifted, cv2.COLOR_BGR2GRAY) thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
2. 调整peak_local_max的min_distance参数
根据你的细胞实际尺寸设置这个值——如果非重叠细胞直径在20像素左右,把min_distance调到10-15,确保单个细胞内只检测到一个局部峰值:
localMax = peak_local_max(D, indices=False, min_distance=12, labels=thresh)
你可以根据实际图像微调数值,直到不再出现单细胞被分割的情况。
3. 增加形态学处理优化阈值图像
在阈值化后加入开运算消除小噪声,闭运算填补细胞内部孔洞:
# 定义椭圆结构元素,大小根据细胞尺寸调整 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) # 开运算去除背景小噪声 thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1) # 闭运算填补细胞内部孔洞 thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)
4. 可选:过滤无效标记区域
在生成标记后,过滤掉对应过小区域的标记,避免噪声带来的假分割:
markers = ndimage.label(localMax, structure=np.ones((3,3)))[0] # 统计每个标记的像素数量 label_counts = np.bincount(markers.flatten()) # 过滤掉像素数过少的标记(比如小于50的) for label in range(1, len(label_counts)): if label_counts[label] < 50: markers[markers == label] = 0 # 重新标记,去除无效标记 markers = ndimage.label(markers > 0, structure=np.ones((3,3)))[0]
关于方法适用性的说明
Watershed算法本身非常适合细胞分割场景,尤其是重叠细胞的分离,只要参数和预处理环节匹配你的图像特征,完全可以适配你的需求。你当前的问题不是方法不适用,而是细节处理没到位。
内容的提问来源于stack exchange,提问作者Philipp Marquardt
相关产品推荐
相关产品推荐

