前言
一、letterbox自适应图片缩放技术
一,计算收缩比
二,计算收缩后图片的长宽
三,计算需要填充的像素
四,最后resize图片并填充像素
二、代码总和
二、使用步骤
1.引入库
2.读入数据
总结
YOLOV5中相比于之前的版本,有很多小trick,导致其性能和应用比较好。本文先讲讲在将图片输入网络前,对图片进行预处理的letterbox的自适应图片缩放技术
一、letterbox自适应图片缩放技术
在目标检测中,输入的图片尺寸有大有小,根据前人的实验结果,输入网络的尺寸统一缩放到同一个尺寸时,检测效果会更好(train中放入的图片并不经过letterbox,而是检测的时候使用letterbox)
但这时就有个问题,如果是简单的使用resize,很有可能就造成了图片信息的丢失,所以提出了letterbox自适应图片缩放技术。
letterbox的主要思想是尽可能的利用网络感受野的信息特征。比如在YOLOV5中最后一层的Stride=5,即最后一层的特征图中每个点,可以对应原图中32X32的区域信息,那么只要在保证整体图片变换比例一致的情况下,长宽均可以被32整除,那么就可以有效的利用感受野的信息。
具体来说,图片变换比例一致指的是,长宽的收缩比例应该采用相同的比例。有效利用感受野信息则指对于收缩后不满足条件的一边,用灰白填充至可以被感受野整除。下面举例说明。
下图即是经过letterbox处理的图片,假设图片原来尺寸为(1080, 1920),我们想要resize的尺寸为(640,640)。要想满足收缩的要求,应该选取收缩比例640/1920 = 0.33.则图片被缩放为(360,640).下一步则要填充灰白边至360可以被32整除,则应该填充至384,最终得到图片尺寸(384,640)
而其实letterbox的实现也十分简单,以下将结合代码讲解步骤。
shape = im.shape[:2] # current shape [height, width]
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]
# stride表示的即是模型下采样次数的2的次方,这个涉及感受野的问题,在YOLOV5中下采样次数为5
# 则stride为32
dw, dh = np.mod(dw, stride), np.mod(dh, stride)
dw /= 2 # 除以2即最终每边填充的像素
dh /= 2
if shape[::-1] != new_unpad: # resize
im = cv.resize(im, new_unpad, interpolation=cv.INTER_LINEAR)
# round(dw,dh - 0.1)直接让小于1的为0
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 = cv.copyMakeBorder(im, top, bottom, left, right, cv.BORDER_CONSTANT, value=color)
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, stride=32):
# Resize and pad image while meeting stride-multiple constraints
shape = im.shape[:2] # current shape [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# Scale ratio (new / old)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
# Compute padding
ratio = r, r # width, height ratios
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] # wh padding
if auto: # minimum rectangle
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
dw /= 2 # divide padding into 2 sides
dh /= 2
print(dw, dh)
if shape[::-1] != new_unpad: # resize
im = cv.resize(im, new_unpad, interpolation=cv.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 = cv.copyMakeBorder(im, top, bottom, left, right, cv.BORDER_CONSTANT, value=color) # add border
return im, ratio, (dw, dh)