如何使用OpenCV准确分割图像中重叠的水稻籽粒
如何使用OpenCV准确分割图像中重叠的水稻籽粒
我太懂你这种困扰了——处理重叠的水稻籽粒分割确实是个棘手的活儿,分水岭算法看似是标配,但遇到紧密挤在一起的颗粒时,很容易出现边界识别不准的情况。结合你给出的代码和当前的效果,我整理了几个实用的调整方向和补充技巧,应该能帮你提升分割精度:
一、优化背景与前景的初始分割
你目前用HSV阈值抠除蓝色背景的思路没问题,但可以再细化一下,让前景(水稻籽粒)的轮廓更清晰:
- 调整HSV阈值范围:有时候固定的HSV区间可能会因为光照、拍摄角度漏掉部分籽粒,你可以用
cv2.createTrackbar做一个交互式的阈值调整工具,找到最适合当前图像的lower_blue和upper_blue范围,确保所有籽粒都被包含在mask_grains里。 - 结合Otsu阈值二次优化:在得到
mask_grains后,可以再对灰度图做一次Otsu阈值分割,过滤掉籽粒上的杂色或阴影,让前景更纯净:gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, otsu_mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) mask_grains = cv2.bitwise_and(mask_grains, otsu_mask)
二、调整分水岭算法的核心参数
分水岭的效果很大程度上取决于sure_fg(确定前景)和sure_bg(确定背景)的准确性,你可以尝试这些调整:
- 优化距离变换与前景阈值:你当前用
0.3 * dist_transform.max()作为前景阈值,这个值偏保守,容易把重叠区域的前景也归为未知区域。可以尝试提高这个比例到0.5~0.7,或者根据距离变换的直方图来动态确定阈值,确保每个独立籽粒的中心都被标记为确定前景。 - 调整形态学操作的核与次数:你用的是3x3的椭圆核,可以试试更大的核(比如5x5)或者矩形核,针对不同的重叠程度调整膨胀/腐蚀的次数。比如在生成
sure_bg时,膨胀次数可以减少到2次,避免背景过度扩张吃掉部分籽粒边缘;而sure_fg的形态学操作可以增加一次腐蚀,让前景标记更精准。 - 改进未知区域的计算:可以尝试用
cv2.morphologyEx对sure_fg做一次细化(多次cv2.MORPH_ERODE),再计算未知区域,这样能让分水岭的边界更贴合籽粒实际轮廓。
三、尝试互补的分割方法
如果单纯调整分水岭效果有限,可以结合其他算法来增强:
- 基于轮廓的标记补充:在得到
sure_fg后,用cv2.findContours找到所有前景轮廓,然后对每个轮廓内的区域计算距离变换的局部最大值,把这些最大值点作为额外的标记点,添加到markers中,这样能帮助分水岭识别出重叠籽粒的边界。 - 尝试U-Net等深度学习模型:如果你的数据集足够大,训练一个轻量级的语义分割模型(比如U-Net)会比传统算法更擅长处理重叠物体的分割,尤其是复杂场景下的籽粒边界识别。
四、后处理优化分割结果
不管用哪种算法,后处理都能帮你修正小问题:
- 过滤小噪声区域:用
cv2.contourArea计算每个分割区域的面积,过滤掉面积远小于正常籽粒的噪声区域。 - 边界平滑:对分割后的轮廓用
cv2.approxPolyDP做多边形近似,或者用cv2.GaussianBlur模糊后再提取轮廓,让边界更平滑自然。
调整后的参考代码
这里给你修改了部分关键参数的代码版本,你可以试试:
import cv2 import numpy as np image = cv2.imread('images/155.png') # 优化背景分割:交互式调整HSV阈值(可选,先手动找到合适范围再固定) hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) lower_blue = np.array([105, 40, 40]) upper_blue = np.array([135, 255, 255]) mask_blue = cv2.inRange(hsv, lower_blue, upper_blue) mask_grains = cv2.bitwise_not(mask_blue) # 结合Otsu阈值优化前景 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, otsu_mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) mask_grains = cv2.bitwise_and(mask_grains, otsu_mask) # 形态学操作:调整核大小与迭代次数 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask_grains = cv2.morphologyEx(mask_grains, cv2.MORPH_OPEN, kernel, iterations=2) # 距离变换与前景阈值调整 sure_bg = cv2.dilate(mask_grains, kernel, iterations=2) dist_transform = cv2.distanceTransform(mask_grains, cv2.DIST_L2, 5) # 提高前景阈值比例 _, sure_fg = cv2.threshold(dist_transform, 0.6 * dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) # 增加一次腐蚀让前景标记更精准 sure_fg = cv2.erode(sure_fg, kernel, iterations=1) unknown = cv2.subtract(sure_bg, sure_fg) # 连通分量与分水岭 num_markers, markers = cv2.connectedComponents(sure_fg) markers = markers + 1 markers[unknown == 255] = 0 markers = cv2.watershed(image, markers) image[markers == -1] = [0, 0, 255] # 标记分水岭边界 # 后处理:过滤小区域 contours, _ = cv2.findContours(cv2.bitwise_not(mask_blue), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: if cv2.contourArea(cnt) < 100: # 根据实际籽粒大小调整阈值 cv2.drawContours(image, [cnt], 0, (255, 255, 255), -1) cv2.imshow('segment', image) cv2.waitKey(0) cv2.destroyAllWindows()
备注:内容来源于stack exchange,提问作者mj01
相关产品推荐
相关产品推荐

