如何基于轮廓凹部在OpenCV中分割不同尺寸单色目标
针对无噪声1-bit黑白图像中白色目标的凹部分割需求,尤其是目标尺寸差异大、椭圆或挤压变形的场景,用distanceTransform结合分水岭的方案可以解决传统分水岭种子选取失效的问题,核心思路是通过距离变换生成自适应尺寸的前景种子,再引导分水岭完成精准分割。
核心逻辑
distanceTransform能计算前景像素到最近背景的距离,目标内部的距离值从中心到边缘递减,凹部位置的距离值会明显低于周围凸区域。基于这个特征,我们可以提取每个子目标的核心作为分割种子,这些种子的大小会自适应目标尺寸,不会因为目标大小差异导致种子丢失或重叠。
分步实现
1. 提取前景Mask
直接从输入的1-bit图像中提取白色目标的二值Mask(白色为前景,值为1;黑色为背景,值为0),因为无噪声,无需额外降噪处理:mask = (img == 255).astype(np.uint8)2. 计算距离变换图
使用L2距离类型计算距离变换,卷积核选5x5适配多数目标尺寸,输出32位浮点型距离图:dist_transform = cv2.distanceTransform(mask, cv2.DIST_L2, 5)距离图中,目标中心区域距离值最大,凹部区域距离值显著降低。
3. 生成自适应前景种子
用相对阈值提取距离图中高值区域作为确定前景(种子),阈值取距离最大值的70%(经验值,可根据凹部深浅调整):ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0) sure_fg = sure_fg.astype(np.uint8)这个相对阈值能自适应不同尺寸的目标:大目标的距离最大值高,种子区域范围合理;小目标的距离最大值低,种子也能精准定位核心。
4. 确定背景与不确定区域
对原始Mask做膨胀操作,得到覆盖凹部外侧的确定背景;再用背景减去前景种子,得到凹部所在的不确定区域:sure_bg = cv2.dilate(mask, np.ones((3,3), np.uint8), iterations=2) unknown = cv2.subtract(sure_bg, sure_fg)5. 标记连通区域
对前景种子做连通区域标记,调整标记值避免与背景(0)混淆,再将不确定区域设为0:ret, markers = cv2.connectedComponents(sure_fg) markers = markers + 1 # 背景标记为1,前景种子从2开始编号 markers[unknown == 255] = 06. 应用分水岭分割
将灰度图转为3通道图(分水岭要求输入为3通道),执行分水岭算法:img_color = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) markers = cv2.watershed(img_color, markers)最终
markers中,值为-1的是分割线,不同正整数对应不同的分割区域。
优化细节
- 若凹部极浅,可降低阈值比例(如0.5),或用局部极大值检测替代阈值:通过对距离变换图做膨胀,再与原图对比提取局部极大点,得到更精准的种子。
- 针对挤压变形的目标,可选用DIST_L1距离类型,计算更快且对变形区域的中心定位依然准确。
- 若目标存在极小的凸起,可对前景种子做一次开运算,去除小的干扰连通区域。
内容的提问来源于stack exchange,提问作者김은수

