OpenCV花卉分割IoU评估:图像边界触达时得分异常问题求解
解决花卉分割IoU评估边界触达图像边缘时得分异常问题
我用OpenCV Python构建图像处理流水线,从植物图像数据集分割花卉,将生成图像和真值图像做IoU评估时遇到问题:当真值图像中花卉边缘触达图像边界时,评估结果仅得到1.58%的相似度得分,而边缘未触达边界的图像得分均>90%。
相关评估代码如下:
# Function to find the largest contour which is assumed to be the flower def find_largest_contour(binary_image): # Find contours from the binary image contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: return max(contours, key=cv2.contourArea) else: return None # Function to calculate the Intersection over Union def calculate_iou(contourA, contourB, shape): maskA = np.zeros(shape, dtype=np.uint8) maskB = np.zeros(shape, dtype=np.uint8) cv2.drawContours(maskA, [contourA], -1, color=255, thickness=cv2.FILLED) cv2.drawContours(maskB, [contourB], -1, color=255, thickness=cv2.FILLED) intersection = np.logical_and(maskA, maskB) union = np.logical_or(maskA, maskB) iou_score = np.sum(intersection) / np.sum(union) return iou_score # Function to display similarity percentage based on IoU def display_similarity(image_name, iou_score): similarity_percentage = round(iou_score, 2) print(f"Similarity for {image_name}: {similarity_percentage}%") # Apply the processing and calculate IoU for each image ious = [] for input_path, ground_truth_path in zip(image_paths, ground_truth_image_paths): image_name = os.path.basename(input_path) original_image = cv2.imread(input_path) # Read the original image again for visualization processed_image = process_image(input_path, image_name) show_binary_image(processed_image, window_name=f"Binary: {image_name}") ground_truth_image = cv2.imread(ground_truth_path, cv2.IMREAD_GRAYSCALE) show_binary_image(ground_truth_image, window_name=f"Binary: {image_name}") if processed_image.shape != ground_truth_image.shape: ground_truth_image = cv2.resize(ground_truth_image, (processed_image.shape[1], processed_image.shape[0])) # Find largest contours contour_processed = find_largest_contour(processed_image) contours_ground_truth = process_red_edges(ground_truth_path) # Fix: Pass path instead of image # Find the largest contour among the contours found contour_ground_truth = max(contours_ground_truth, key=cv2.contourArea) # Calculate IoU iou_score = calculate_iou(contour_processed, contour_ground_truth, ground_truth_image.shape) * 100 ious.append(iou_score)
流水线生成的待对比图像:
真值图像:
问题根源分析
从代码和图像来看,核心问题出在轮廓提取环节:
- 当花卉边缘触达图像边界时,
cv2.findContours使用cv2.RETR_EXTERNAL模式提取外部轮廓,会因为图像边缘的截断,无法生成完整的闭合轮廓,反而将其拆分成多个分散的小轮廓。 - 代码中仅取最大的轮廓进行IoU计算,而真值图像的
process_red_edges函数同样会因为边界问题提取出不匹配的轮廓,最终导致两个轮廓几乎无交集,IoU得分骤降。
解决方案
方案1:修复边界轮廓提取逻辑
通过给二值图像添加一圈黑边框,让触达原边界的花卉形成完整闭合轮廓,再修正轮廓坐标偏移:
def find_largest_contour(binary_image): # 添加1像素黑边框,让边界处的轮廓闭合 bordered_image = cv2.copyMakeBorder(binary_image, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0) # 提取轮廓 contours, _ = cv2.findContours(bordered_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: # 找到最大轮廓后,减去之前添加的边框偏移量 largest_contour = max(contours, key=cv2.contourArea) largest_contour = largest_contour - [1, 1] # 修正坐标,还原到原图像坐标系 return largest_contour else: return None
同时需要对process_red_edges函数做相同的边框处理,确保真值图像的轮廓提取也能得到完整的花卉轮廓。
方案2:直接基于掩码计算IoU(推荐)
绕过轮廓提取环节,直接用二值掩码计算IoU,这是分割任务中IoU的标准计算方式,更稳定可靠:
def calculate_iou_from_masks(maskA, maskB): # 确保两个掩码尺寸一致 if maskA.shape != maskB.shape: maskB = cv2.resize(maskB, (maskA.shape[1], maskA.shape[0])) # 二值化处理,确保掩码为0/255或0/1格式 maskA = (maskA > 127).astype(np.uint8) maskB = (maskB > 127).astype(np.uint8) # 计算交集和并集 intersection = np.logical_and(maskA, maskB).sum() union = np.logical_or(maskA, maskB).sum() # 避免除以0的情况 if union == 0: return 0.0 return intersection / union
在主循环中替换原轮廓相关的IoU计算逻辑:
# 替换原轮廓提取和IoU计算部分 # 直接用二值图计算IoU iou_score = calculate_iou_from_masks(processed_image, ground_truth_image) * 100 ious.append(iou_score)
内容的提问来源于stack exchange,提问作者user19766923
相关产品推荐
相关产品推荐

