You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重叠细胞分割求助: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:52:52