yolov5 图像预处理letterbox
letterbox
被用于LoadImages
类的__next__
方法中, 用于输入图像尺寸的预处理。
letterbox
实现了图像调整技术,主要用于深度学习领域中的物体检测任务,确保不同尺寸的输入图像能被模型接受。
Letterbox的原理很简单。当输入图像的纵横比与模型所需不一致时,Letterbox会在图像的顶部和底部添加填充(通常是黑色或与背景色相同),以保持纵横比的一致性。填充的宽度会根据纵横比差异进行调整。这种方法可以确保模型能够处理各种纵横比的图像,并减少因纵横比差异导致的问题。
letterbox方法解析
这里函数传参如下(yolo使用的默认值)
im = letterbox(im0, new_shape=(640,640), stride=32, scaleup=True, scaleFill=False, auto=True)
方法解析如下:
- 计算缩放比例:首先计算图像高宽分别与目标尺寸的缩放比例,取其中较小的一个以保持原图的宽高比。如果
scaleup=False
,则限制缩放比例不超过1,即不允许放大图像。 - 计算无填充的新尺寸:根据缩放比例计算调整后图像的无填充尺寸。
- 计算填充量:计算需要在图像的宽和高方向上分别填充多少像素,以达到目标尺寸。如果
auto=True
,则确保填充后的尺寸是stride
的整数倍;如果scaleFill=True
,则直接拉伸图像至目标尺寸,无需额外填充。 - 调整图像尺寸和填充:
- 根据计算出的无填充尺寸对原图进行缩放。
- 使用
cv2.copyMakeBorder
函数在图像四周添加指定颜色的边框,实现填充操作。这里通过微调dh和dw(减去0.1再加回0.1)来确保精确填充,尽管这种做法在数学上等效于直接使用dh和dw,是为了代码的可读性或历史遗留。
- 返回结果:函数最后返回调整尺寸后的图像、原始图像与新图像的比例以及实际的填充宽度和高度。
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
"""
图像调整技术,通过在图像的边缘填充一定数量的像素,使图像的尺寸满足特定的要求
:param im: 原始图片numpy
:param new_shape: 目标尺寸,默认为(640, 640)
:param color: 填充颜色,默认为灰色(114, 114, 114)
:param auto: 自动最小外接矩形调整,保持原始比例且两边留有等宽的padding
:param scaleFill: 是否拉伸图像以完全填充新尺寸,忽略原始比例
:param scaleup: 是否允许放大图像,若为False,则只缩小不放大,有利于验证集mAP表现
:param stride: 确保最终尺寸是该值的倍数
:return:
"""
shape = im.shape[:2] # current shape [height, width] (2516,3770)
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# 计算缩放比例, 取其中较小的一个以保持原图的宽高比, 按默认值就是让图像宽或者高为640
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) # min(0.2543,0.1697) = 0.5925
# 限制缩放比例不超过1,即不允许放大图像
if not scaleup: # only scale down, do not scale up (for better val mAP)
r = min(r, 1.0)
# Compute padding 根据缩放比例计算调整后图像的无填充尺寸
ratio = r, r # width, height ratios (0.1697,0.1697)
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) # (640,427)
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # 0 213
if auto: # 确保填充后的尺寸是stride的整数倍
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding 0 21
elif scaleFill: # 直接拉伸图像至目标尺寸,无需额外填充
dw, dh = 0.0, 0.0
new_unpad = (new_shape[1], new_shape[0])
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
dw /= 2 # divide padding into 2 sides
dh /= 2
if shape[::-1] != new_unpad: # resize
# 根据计算出的无填充尺寸对原图进行缩放
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
# 图像四周添加指定颜色的边框,实现填充操作
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
return im, ratio, (dw, dh)
多种参数测试样例对比差异
我们将多种参数进行搭配, 通过对比看出差异
import cv2
import numpy as np
import matplotlib.pyplot as plt
from utils.augmentations import letterbox
path = r'.\data\images\img_1.png'
num = 8
point = 1
plt.figure(dpi=300,figsize=(8,24))
def get_img(new_shape=[640, 640], stride=32, scaleFill=False, scaleup=True, auto=True):
global point
im0 = cv2.imread(path) # BGR
im_row = im0[:, :, ::-1]
im = letterbox(im0, new_shape=new_shape, stride=stride, scaleup=scaleup, scaleFill=scaleFill, auto=auto)[
0] # padded resize
# im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
# im = np.flip(im, axis=2) # 第三个维度翻转
im = im[:, :, ::-1] # 第三个维度翻转
im = np.ascontiguousarray(im) # contiguous
plt.subplot(num, 2, point)
point += 1
plt.title(f"origin_{im0.shape[1]}_{im0.shape[0]}") # 标题 宽_高
plt.imshow(im_row) # 显示图片
plt.axis('off') # 不显示坐标轴
plt.subplot(num, 2, point)
point += 1
plt.title(f"process_{im.shape[1]}_{im.shape[0]}") # 标题 宽_高
plt.imshow(im) # 显示图片
plt.axis('off') # 不显示坐标轴
get_img(scaleFill=False, scaleup=True, auto=True)
get_img(scaleFill=False, scaleup=True, auto=False)
get_img(scaleFill=False, scaleup=False, auto=True)
get_img(scaleFill=False, scaleup=False, auto=False)
get_img(scaleFill=True, scaleup=True, auto=True)
get_img(scaleFill=True, scaleup=True, auto=False)
get_img(scaleFill=True, scaleup=False, auto=True)
get_img(scaleFill=True, scaleup=False, auto=False)
plt.show()