引言
在使用YOLOv8等分割模型进行推理时,许多开发者可能会发现分割结果中存在黑边问题。这些黑边可能会影响分割结果的后续处理,例如指标计算或实际应用效果。本文将从问题现象出发,分析导致黑边的原因,并给出解决方案,包括如何恢复正确的分割结果并提供相关代码。
问题
在使用YOLOv8分割模型推理时,生成的分割掩膜(mask)可能包含黑边,如下图所示(假设为示意图):
这些黑边并非模型本身的分割误差,而是由于推理过程中对输入图片的预处理(如 letterbox)导致的填充像素。这些填充的像素影响了分割掩膜的对齐,必须在后处理时去除。
问题定位
YOLOv8模型在推理时,为了将输入图片调整为固定大小(如 640×640),使用了一种常见的预处理方法——letterbox。letterbox 的作用是通过缩放和填充操作,保持图片纵横比不变,同时将图片调整到目标尺寸。
letterbox 的核心逻辑:
- 缩放图片,使其适应目标尺寸(如 640×640)。
- 在不足的部分添加填充(默认颜色为灰色 [114, 114, 114])。
- 填充的宽度和高度最终会导致分割掩膜的边缘出现黑边。、
去除黑边的思路
为了去掉分割结果中的黑边,可以按照以下步骤操作:
- 计算填充区域: 根据 letterbox 的缩放比例和填充大小,确定填充区域的范围。
- 裁剪掩膜: 从分割掩膜中去掉填充部分。
- 调整大小: 将裁剪后的掩膜调整为原始图片的尺寸。
实现代码:
def compute_and_resize_mask(self, mask, img, new_shape=(640, 640), stride=32):
"""
计算去掉黑边后的mask,并调整掩膜大小
"""
# Step 1: 计算填充信息
shape = img.shape[:2] # 原始图片尺寸 [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# 计算缩放比例
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) # 最小缩放比例
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) # 缩放后的未填充宽高
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # 计算宽高需要填充的像素
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # 确保填充符合 stride 的倍数
dw /= 2 # 左右填充
dh /= 2 # 上下填充
# 计算去掉的黑边范围
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
# Step 2: 去掉黑边(裁剪填充区域)
if top > 0 or bottom > 0 or left > 0 or right > 0:
mask = mask[top:mask.shape[0]-bottom, left:mask.shape[1]-right]
# Step 3: 调整掩膜大小到原始图片大小
mask = cv2.resize(mask, (img.shape[1], img.shape[0]), interpolation=cv2.INTER_NEAREST)
return mask
结果图:
总结
在分割任务中,黑边问题是由预处理(如 letterbox 填充)导致的。通过分析预处理逻辑,我们可以精准定位黑边范围,并通过裁剪和调整掩膜大小来恢复分割结果的正确性。