基于OpenCV实现粘连反光物体分割的技术求助
粘连番茄检测分离的反光问题解决方案
问题背景
正在开发传送带场景下的单帧图像粘连番茄检测分离算法,遇到两个核心问题:
- 使用OpenCV的
findContours无法有效分割粘连番茄; - 采用
distance-transform+Watershed算法时,番茄表面反光会破坏掩码完整性,导致算法失效。
问题示例
- 原始图像:传送带场景中的红色番茄,部分番茄表面存在明显高光反光,且多个番茄相互粘连
- 掩码图像:因反光影响,掩码中部分番茄区域出现黑色孔洞,无法完整覆盖番茄主体
- 输入图像:典型的工业传送带番茄采集图像
核心优化方案
1. 改进掩码生成逻辑(针对反光的颜色过滤)
反光区域在HSV空间表现为高亮度、低饱和度,仅用饱和度通道过滤会遗漏反光区域的干扰,因此结合色调、饱和度、亮度三通道做精准过滤:
def create_mask(img): hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2] # 红色番茄的色调范围(红色跨0-10和170-180两个区间) h_mask1 = cv2.inRange(h, 0, 10) h_mask2 = cv2.inRange(h, 170, 180) h_mask = cv2.bitwise_or(h_mask1, h_mask2) # 过滤低饱和度的反光区域 s_mask = cv2.inRange(s, 30, 255) # 过滤过高亮度的反光区域 v_mask = cv2.inRange(v, 20, 230) # 合并三通道掩码 combined_mask = cv2.bitwise_and(h_mask, s_mask) combined_mask = cv2.bitwise_and(combined_mask, v_mask) # 形态学操作修复掩码孔洞 kernel = np.ones((3, 3), np.uint8) combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel, iterations=2) combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel, iterations=1) return combined_mask
2. 预处理消除反光(CLAHE亮度增强)
使用限制对比度自适应直方图均衡(CLAHE)对亮度通道做局部增强,抑制高光区域的同时保留番茄主体细节:
def preprocess_anti_glare(img): hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) v_channel = hsv[:, :, 2] clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) enhanced_v = clahe.apply(v_channel) hsv[:, :, 2] = enhanced_v return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
在主函数开头添加该预处理步骤,先消除反光再做后续处理。
3. 优化Watershed前景提取
调整距离变换的阈值,并对提取的前景做闭运算修复反光导致的孔洞:
# 生成前景后修复孔洞 sure_fg = np.uint8(sure_fg) kernel = np.ones((3,3), np.uint8) sure_fg = cv2.morphologyEx(sure_fg, cv2.MORPH_CLOSE, kernel, iterations=2)
同时可将距离变换的阈值从0.7*max调整为0.6*max,扩大前景区域的覆盖范围。
完整优化代码
import cv2 import numpy as np import matplotlib.pyplot as plt def display_image(img, title="Image"): plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.title(title) plt.axis('off') plt.show() def preprocess_anti_glare(img): hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) v_channel = hsv[:, :, 2] clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) enhanced_v = clahe.apply(v_channel) hsv[:, :, 2] = enhanced_v return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) def create_mask(img): hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2] h_mask1 = cv2.inRange(h, 0, 10) h_mask2 = cv2.inRange(h, 170, 180) h_mask = cv2.bitwise_or(h_mask1, h_mask2) s_mask = cv2.inRange(s, 30, 255) v_mask = cv2.inRange(v, 20, 230) combined_mask = cv2.bitwise_and(h_mask, s_mask) combined_mask = cv2.bitwise_and(combined_mask, v_mask) kernel = np.ones((3, 3), np.uint8) combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel, iterations=2) combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel, iterations=1) return combined_mask def apply_watershed(image_path): img = cv2.imread(image_path) img = preprocess_anti_glare(img) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) display_image(img, "Preprocessed Image") thresh = create_mask(img) display_image(thresh, "Optimized Mask Image") sure_bg = cv2.dilate(thresh, np.ones((5, 5), np.uint8), iterations=1) display_image(sure_bg, "Background Image") dist_transform = cv2.distanceTransform(thresh, cv2.DIST_L2, 5) _, sure_fg = cv2.threshold(dist_transform, 0.6 * dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) kernel = np.ones((3,3), np.uint8) sure_fg = cv2.morphologyEx(sure_fg, cv2.MORPH_CLOSE, kernel, iterations=2) display_image(sure_fg, "Optimized Foreground Image") unknown = cv2.subtract(sure_bg, sure_fg) _, markers = cv2.connectedComponents(sure_fg) markers = markers + 1 markers[unknown == 255] = 0 cv2.watershed(img, markers) img[markers == -1] = [0, 0, 255] display_image(img, "Watershed Result") cropped_objects = [] for label in np.unique(markers): if label == 0 or label == 1: continue mask = np.zeros(gray.shape, dtype="uint8") mask[markers == label] = 255 cnts, _ = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for c in cnts: x, y, w, h = cv2.boundingRect(c) cropped_objects.append(img[y:y+h, x:x+w]) for i, cropped in enumerate(cropped_objects): display_image(cropped, f"Cropped Tomato {i + 1}") return cropped_objects apply_watershed('T4.jpg')
内容的提问来源于stack exchange,提问作者BISHAL Adhikari
相关产品推荐
相关产品推荐

