DAMO-YOLO保姆级教程:模型输入尺寸适配与letterbox填充策略
1. 为什么输入尺寸和letterbox这么重要?
你可能已经成功跑通了DAMO-YOLO的Web界面,上传一张图,几秒后霓虹绿框就跳出来了——很酷。但当你换一张手机随手拍的竖屏照片,或者一张4K监控截图,结果却出现目标错位、框体变形、小物体漏检,甚至直接报错“input size mismatch”……这时候,问题往往不出在模型本身,而藏在图像送进模型前的那一步:尺寸预处理。
DAMO-YOLO不是“来者不拒”的万能接口。它像一台精密光学仪器,对输入图像有明确的“物理规格要求”:固定宽高比、统一分辨率、像素值归一化方式。而现实中的图片千差万别——横屏/竖屏、16:9/4:3/1:1、320×240到3840×2160……直接拉伸裁剪?会扭曲目标比例,让YOLO学过的“猫是长条形”、“车是扁平状”这些先验知识全部失效。
这就是letterbox填充策略存在的根本意义:不破坏原始长宽比的前提下,把任意尺寸图像“装进”模型要求的输入框里。它不是偷懒的填充,而是保持几何一致性的关键桥梁。本教程不讲抽象理论,只带你一步步看清:
- DAMO-YOLO实际要求的输入尺寸是多少(不是文档写的,是代码里跑出来的);
- letterbox具体怎么填、填什么颜色、边界怎么计算;
- 为什么检测框坐标要反向映射回原图,以及这一步出错会导致框“飞到天上去”;
- 如何用5行OpenCV代码手动复现整个流程,验证你的理解是否正确。
准备好你的终端和一张测试图,我们从最底层开始拆解。
2. 深入模型源码:确认真实输入尺寸
很多教程直接告诉你“DAMO-YOLO输入是640×640”,但这是经验性说法。我们要看真实部署环境中的实际值。进入你的模型路径:
cd /root/ai-models/iic/cv_tinynas_object-detection_damoyolo/查看模型配置文件(关键!):
cat config.py | grep -A 5 "input_size"你会看到类似这样的输出:
# config.py input_size = (640, 640) # (height, width) preprocess_cfg = dict( mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True, letterbox=True, scale_factors=[1.0, 1.0] )注意三点:
input_size = (640, 640)是模型真正“吃进去”的尺寸,单位是像素,顺序是(高, 宽);letterbox=True明确启用letterbox策略;mean/std是ImageNet标准归一化参数,后续做预处理时必须严格匹配。
重要提醒:这个640×640是推理时的固定输入尺寸,不是训练时的多尺度(training multi-scale)。部署阶段模型权重已固化,强行喂入其他尺寸(如480×480)会导致张量维度报错或结果不可信。
3. letterbox填充原理与手算演示
letterbox的核心思想,就是给图像加“黑边”(padding),像老式电影上下加的黑杠一样,让内容区域保持原始比例,同时整体尺寸刚好等于目标尺寸。
3.1 填充逻辑三步走
假设你有一张手机拍摄的图:1080×1920(竖屏,高>宽),目标输入是640×640(正方形):
- 计算缩放比例:取
min(640/1080, 640/1920) = min(0.593, 0.333) = 0.333→ 宽度方向是瓶颈; - 计算缩放后尺寸:
new_h = 1080 × 0.333 ≈ 360,new_w = 1920 × 0.333 ≈ 640; - 计算填充量:高度方向需补
640 − 360 = 280像素,上下各填140;宽度方向已满,不填充。
最终得到一个640×640图像:中间是缩放后的原图(360×640),上下各140像素黑色填充区。
3.2 用OpenCV亲手实现一次(验证用)
新建test_letterbox.py:
import cv2 import numpy as np def letterbox(img, new_shape=(640, 640), color=(0, 0, 0)): # img: BGR格式numpy array, shape (h, w, 3) shape = img.shape[:2] # (h, w) 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)) # 缩放图像 img_resized = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) # 创建新画布 dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] top, bottom = dh // 2, dh - (dh // 2) left, right = dw // 2, dw - (dw // 2) # 添加padding(BORDER_CONSTANT + color) img_letterboxed = cv2.copyMakeBorder( img_resized, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color ) return img_letterboxed, r, (dw, dh) # 测试 img = cv2.imread("test.jpg") # 替换为你自己的图 img_lb, ratio, (dw, dh) = letterbox(img, (640, 640)) print(f"原始尺寸: {img.shape[:2]} -> 填充后: {img_lb.shape[:2]}") print(f"缩放比: {ratio:.3f}, 填充量 (dw, dh): ({dw}, {dh})") cv2.imwrite("letterboxed.jpg", img_lb)运行后你会得到:
letterboxed.jpg:带黑边的640×640图;- 控制台输出精确的缩放比和填充像素数——这些数字将用于后续坐标映射。
关键洞察:letterbox不是简单“居中+黑边”。它的缩放比
r和填充偏移(dw//2, dh//2)是后续所有坐标转换的基石。漏掉任何一个,检测框就回不到原图。
4. 检测框坐标映射:从模型输出到原图的完整链条
DAMO-YOLO模型输出的检测框坐标(x1,y1,x2,y2)是相对于640×640 letterbox图的。要画在你原始的1080×1920图上,必须逆向执行letterbox操作:
4.1 坐标映射四步公式
设模型输出框为(x1, y1, x2, y2)(归一化到0~1范围,即YOLO标准输出):
反归一化到letterbox图像素坐标:
x1_px = x1 * 640,y1_px = y1 * 640,x2_px = x2 * 640,y2_px = y2 * 640减去padding偏移(把框从letterbox图“抠”出来):
x1_crop = x1_px - dw//2,y1_crop = y1_px - dh//2,x2_crop = x2_px - dw//2,y2_crop = y2_px - dh//2除以缩放比
r,还原到原始图尺寸:x1_orig = x1_crop / r,y1_orig = y1_crop / r,x2_orig = x2_crop / r,y2_orig = y2_crop / r截断到原图边界(防止负值或超界):
x1_final = max(0, min(x1_orig, orig_w)),其余同理
4.2 实战代码:封装映射函数
在你的Flask后端app.py中,找到模型推理后的结果处理部分,加入:
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None): # coords: shape (n, 4) or (n, 5), [x1,y1,x2,y2] or [x1,y1,x2,y2,conf] if ratio_pad is None: gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 else: gain = ratio_pad[0][0] pad = ratio_pad[1] coords[:, [0, 2]] -= pad[0] # x padding coords[:, [1, 3]] -= pad[1] # y padding coords[:, :4] /= gain coords[:, :4] = coords[:, :4].clip(0, img0_shape[1]) # x1,x2 in [0, w] coords[:, 1:4:2] = coords[:, 1:4:2].clip(0, img0_shape[0]) # y1,y2 in [0, h] return coords # 使用示例(在模型输出后) # outputs: (1, n, 6) tensor, last dim: [x1,y1,x2,y2,conf,cls] # img0: original cv2 image (h,w,3) # img1: letterboxed input to model (640,640,3) outputs = non_max_suppression(outputs) for i, det in enumerate(outputs): if len(det): det[:, :4] = scale_coords(img1.shape, det[:, :4], img0.shape).round()这段代码确保你看到的每一个霓虹绿框,都精准落在原始图像的目标上——而不是漂浮在黑边上。
5. 常见陷阱与避坑指南
即使完全按上述步骤操作,仍可能踩坑。以下是真实部署中高频问题:
5.1 陷阱1:OpenCV读图通道 vs PyTorch期望通道
- OpenCV默认读BGR,但DAMO-YOLO训练时用的是RGB(
to_rgb=True); - 错误写法:
img = cv2.imread("x.jpg")→ 直接送入模型 → 颜色错乱,检测性能暴跌; - 正确写法:
img = cv2.cvtColor(cv2.imread("x.jpg"), cv2.COLOR_BGR2RGB)
5.2 陷阱2:归一化顺序错误
- 必须先做letterbox,再做归一化(减mean/除std);
- 错误顺序:先归一化原图 → 再resize → 数值范围被破坏;
- 正确顺序:原图 → letterbox →
img.astype(np.float32)→(img - mean) / std
5.3 陷阱3:置信度过滤在映射前还是后?
- 过滤(如
conf > 0.5)必须在坐标映射之后进行; - 因为NMS(非极大值抑制)是在letterbox图上做的,过滤后再映射,会丢失低分但位置正确的框;
- 正确流程:模型输出 → NMS → 映射到原图 → 置信度过滤 → 绘制。
5.4 陷阱4:多尺度测试时letterbox失效
- 如果你修改了
config.py里的input_size为(416, 416),必须同步更新所有相关代码中的硬编码640; - 更安全的做法:从config动态读取:
from modelscope.pipelines import pipeline pipe = pipeline('object-detection', model='damo/cv_tinynas_object-detection_damoyolo') input_size = pipe.model.cfg.input_size # 动态获取
6. 进阶技巧:自定义填充色与动态尺寸适配
虽然默认是黑色填充(value=(0,0,0)),但你可以根据场景优化:
6.1 填充色选择建议
| 场景 | 推荐填充色 | 原因 |
|---|---|---|
| 白色背景文档检测 | value=(255,255,255) | 避免黑边干扰OCR区域分割 |
| 夜间监控图像 | value=(10,10,10)(深灰) | 比纯黑更接近真实暗场噪声 |
| 赛博朋克UI风格 | value=(5,5,5)(近似#050505) | 与UI主色调无缝融合,视觉更沉浸 |
6.2 动态尺寸适配(非640×640)
如果你需要适配不同硬件(如Jetson Nano内存受限),可修改config.py:
# 改为480×480(需重新导出onnx或torchscript) input_size = (480, 480) # 同时调整letterbox函数调用 img_lb, r, (dw, dh) = letterbox(img, (480, 480))注意:尺寸变小会损失小目标检测能力,需在精度与速度间权衡。
7. 总结:掌握预处理,才是掌控模型的第一步
到此,你应该彻底明白:
- DAMO-YOLO的输入尺寸不是魔法数字,而是从
config.py里挖出来的硬约束; - letterbox不是简单的“加黑边”,它是一套包含缩放比、填充量、坐标映射的完整数学协议;
- 每一个霓虹绿框的精准落位,都依赖于你对
r、dw、dh这三个数值的正确使用; - 部署中90%的“效果不好”,根源不在模型,而在预处理与后处理的微小偏差。
真正的工程能力,不在于调通一个demo,而在于当结果异常时,你能快速定位是OpenCV通道错了、归一化顺序反了、还是坐标映射漏了除以r。现在,打开你的终端,用test_letterbox.py跑一遍自己的图,亲眼看到那个r=0.333和dh=280——这就是你和模型之间,最实在的信任契约。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。