LetterBox图像缩放
LetterBox图像缩放
一、技术背景
在深度学习目标检测中,不同尺寸的输入图像需要统一缩放到模型要求的固定尺寸。直接缩放会导致图像变形,影响检测精度。LetterBox(信箱框)技术通过保持原始宽高比进行缩放,并用灰色填充空白区域,既保证了图像几何特性不变形,又满足了模型输入尺寸要求。
YOLO系列模型采用LetterBox预处理,在缩放图像时保持原始宽高比,不足部分用灰色(114, 114, 114)填充,确保检测结果可以准确还原到原始图像坐标。
二、数学原理
2.1 缩放比例计算
设原始图像尺寸为(Horig,Worig)(H_{orig}, W_{orig})(Horig,Worig),目标尺寸为(Htarget,Wtarget)(H_{target}, W_{target})(Htarget,Wtarget),缩放比例rrr取高度和宽度缩放比例的最小值:
r=min(HtargetHorig,WtargetWorig)r = \min\left(\frac{H_{target}}{H_{orig}}, \frac{W_{target}}{W_{orig}}\right)r=min(HorigHtarget,WorigWtarget)
2.2 新尺寸计算
根据缩放比例计算缩放后的图像尺寸:
Wnew=⌊Worig×r⌋W_{new} = \lfloor W_{orig} \times r \rfloorWnew=⌊Worig×r⌋
Hnew=⌊Horig×r⌋H_{new} = \lfloor H_{orig} \times r \rfloorHnew=⌊Horig×r⌋
2.3 填充量计算
计算需要在图像周围填充的灰色边框宽度:
dw=Wtarget−Wnewdw = W_{target} - W_{new}dw=Wtarget−Wnew
dh=Htarget−Hnewdh = H_{target} - H_{new}dh=Htarget−Hnew
填充分布在图像两侧:
- 左填充:left=⌊dw/2⌋left = \lfloor dw / 2 \rfloorleft=⌊dw/2⌋
- 右填充:right=dw−leftright = dw - leftright=dw−left
- 上填充:top=⌊dh/2⌋top = \lfloor dh / 2 \rfloortop=⌊dh/2⌋
- 下填充:bottom=dh−topbottom = dh - topbottom=dh−top
2.4 坐标还原公式
将LetterBox后的坐标(x′,y′)(x', y')(x′,y′)还原到原始图像坐标(x,y)(x, y)(x,y):
x=x′−padxrx = \frac{x' - pad_x}{r}x=rx′−padx
y=y′−padyry = \frac{y' - pad_y}{r}y=ry′−pady
其中padxpad_xpadx和padypad_ypady为水平、垂直方向的填充偏移量。
三、代码实现
3.1 LetterBox缩放函数
// 文件路径: e:\SEM\Yolo11_section\Utils.cs// 缩放并填充到要求输入大小publicstaticvoidSegLetterBox(Matimage,outMatoutImage,OpenCvSharp.SizenewShape,Scalarcolor,boolautoPad=true,boolscaleFill=false,boolscaleUp=true,intstride=32){// 计算缩放比例:取高度和宽度缩放比例的最小值floatr=Math.Min((float)newShape.Height/image.Rows,(float)newShape.Width/image.Cols);// 如果不允许放大,则限制比例不超过1.0if(!scaleUp)r=Math.Min(r,1.0f);// 计算缩放后的新尺寸intnewW=(int)Math.Round(image.Cols*r);intnewH=(int)Math.Round(image.Rows*r);// 计算需要填充的空白区域intdw=newShape.Width-newW;intdh=newShape.Height-newH;// 自动填充:确保填充量是stride的整数倍if(autoPad){dw%=stride;dh%=stride;}elseif(scaleFill){// 直接拉伸填充整个目标区域newW=newShape.Width;newH=newShape.Height;dw=dh=0;}// 使用线性插值缩放图像varresized=newMat();Cv2.Resize(image,resized,newOpenCvSharp.Size(newW,newH),0,0,InterpolationFlags.Linear);// 计算上下左右填充量(均匀分布)inttop=dh/2;intbottom=dh-top;intleft=dw/2;intright=dw-left;// 使用指定颜色填充边框outImage=newMat();Cv2.CopyMakeBorder(resized,outImage,top,bottom,left,right,BorderTypes.Constant,color);}3.2 坐标还原函数
// 文件路径: e:\SEM\Yolo11_section\Utils.cs// 将模型输出的坐标转换为原始图像的坐标publicstaticSegBoundingBoxScaleCoords(OpenCvSharp.SizeletterboxShape,SegBoundingBoxbox,OpenCvSharp.SizeorigShape,boolclip=true){// 计算缩放增益floatgain=Math.Min((float)letterboxShape.Height/origShape.Height,(float)letterboxShape.Width/origShape.Width);// 计算填充偏移量intpadX=(int)Math.Round((letterboxShape.Width-origShape.Width*gain)/2.0);intpadY=(int)Math.Round((letterboxShape.Height-origShape.Height*gain)/2.0);// 坐标还原公式intx=(int)Math.Round((box.X-padX)/gain);inty=(int)Math.Round((box.Y-padY)/gain);intw=(int)Math.Round(box.Width/gain);inth=(int)Math.Round(box.Height/gain);// 可选:裁剪到原始图像边界if(clip){x=Clamp(x,0,origShape.Width);y=Clamp(y,0,origShape.Height);w=Clamp(w,0,origShape.Width-x);h=Clamp(h,0,origShape.Height-y);}returnnewSegBoundingBox(x,y,w,h,box.ClassId,box.Score,box.MaskCoefficients);}3.3 预处理调用
// 文件路径: e:\SEM\Yolo11_section\Utils.cspublicstaticMatPreprocess(Matimage,OpenCvSharp.SizeinputSize,boolisDynamic,reffloat[]inputTensor,reflong[]inputShape){MatletterboxImage;// 调用LetterBox,使用灰色(114,114,114)填充SegLetterBox(image,outletterboxImage,inputSize,newScalar(114,114,114),isDynamic,false,true,32);inputShape[2]=letterboxImage.Rows;inputShape[3]=letterboxImage.Cols;// 归一化到[0,1]letterboxImage.ConvertTo(letterboxImage,MatType.CV_32FC3,1.0/255.0);// ... 后续转换为CHW格式张量returnletterboxImage;}四、参数调优
4.1 填充颜色选择
| 颜色值 | 适用场景 | 特点 |
|---|---|---|
| (114, 114, 114) | YOLO标准 | 与图像内容区分明显 |
| (0, 0, 0) | 深色背景 | 适合暗色图像 |
| (128, 128, 128) | 中性灰 | 通用性较好 |
| (255, 255, 255) | 白色背景 | 适合浅色图像 |
4.2 stride参数影响
stride参数确保输出尺寸是stride的整数倍,这对某些需要下采样的网络结构很重要:
- stride=32:适用于YOLO系列,因为最大下采样倍数为32
- stride=16:适用于较浅层网络
- stride=1:不进行约束
4.3 插值方法选择
// 不同插值方法InterpolationFlags.Linear// 线性插值(默认,速度快)InterpolationFlags.Area// 区域插值(缩小时质量好)InterpolationFlags.Cubic// 三次插值(放大时质量好)InterpolationFlags.Lanczos4// Lanczos插值(质量最高)五、常见问题
5.1 坐标还原偏差
问题描述:检测框还原后位置不准确,出现偏移。
解决方案:
- 确保缩放增益计算正确,使用
Math.Min取最小比例 - 填充偏移使用
Round四舍五入 - 检查
clip参数是否正确裁剪边界
5.2 填充区域检测问题
问题描述:模型在灰色填充区域产生误检测。
解决方案:
- 使用与训练数据一致的填充颜色(114, 114, 114)
- 在后处理阶段过滤掉完全位于填充区域的检测框
- 训练时对数据进行LetterBox增强
5.3 动态输入尺寸处理
问题描述:模型支持动态输入尺寸时,如何处理?
解决方案:
// 动态模式下stride约束生效if(autoPad){dw%=stride;// 确保尺寸是stride的整数倍dh%=stride;}5.4 高宽比极端情况
问题描述:图像高宽比极端时,填充区域过大。
解决方案:
- 使用
scaleFill=true放弃宽高比约束(会变形) - 调整输入尺寸使其与图像宽高比接近
- 分块处理极端尺寸图像
5.5 内存管理
问题描述:LetterBox操作产生大量临时Mat对象。
解决方案:
// 及时释放临时对象varresized=newMat();Cv2.Resize(image,resized,newOpenCvSharp.Size(newW,newH),0,0,InterpolationFlags.Linear);// ... 使用resizedresized.Dispose();// 手动释放