畅联云平台丨01-YOLOv8人体检测与Letterbox

2026年8月14日
畅联云平台丨01-YOLOv8人体检测与Letterbox

YOLOv8 人体检测与 Letterbox:从像素到可用框

本文讲透一件事:在本项目里,一张监控帧如何变成一组可跟踪的人体框。重点不是「YOLO 很火」,而是 Letterbox 几何、张量布局、解码逆变换、阈值与 NMS 各自在算什么、错了会怎样。

一、问题定义:检测要交付什么

人体检测的交付物不是「热力图好看」,而是结构化框:

人体框:左上角坐标、宽高、置信度分数

坐标必须在原图像素系。后面跟踪、人脸 ROI、镜头中心全部依赖这组数。

所以链路的成功标准只有一句:框在原图上贴住人,且分数可排序、可阈值过滤。


二、 为什么不能直接 resize 成正方形

YOLOv8 导出模型通常接受固定边长输入(本项目默认 640×640)。监控源却是 1280×720、1920×1080、4K。若把宽高各自拉到 640:

  • 人体长宽比被破坏(人变胖/变瘦);
  • 训练分布是「比例正确的人」,推理分布变成「压扁的人」;
  • 召回与定位同时变差。

图:直接拉伸会压扁人体;Letterbox 先等比缩放再补边,比例不变。


Letterbox 做三件事:

  1. 用同一比例缩放,使缩放后的图能完整放进 input_size×input_size;
  2. 不足的边用常数色填充(本项目为灰色 114);
  3. 记录 scale, pad_x, pad_y,供框坐标还原。


三、本项目 Letterbox 的精确实现

要点拆解:

数值例子(1920×1080 → 640)

scale = min(640/1920, 640/1080) = min(0.3333, 0.5926) = 0.3333

rw = 640, rh = 360

pad_x = 0, pad_y = (640-360)/2 = 140

有效画面占中间 640×360,上下各 140 行灰边。

Letterbox 实现:

cv::Mat person_focus_yolo_letterbox_bgr(const cv::Mat &src, int input_size,
 float &scale, int &pad_x, int &pad_y) {
 scale = std::min((float)input_size / (float)std::max(1, src.cols),
 (float)input_size / (float)std::max(1, src.rows));
 const int rw = std::max(1, (int)(src.cols * scale));
 const int rh = std::max(1, (int)(src.rows * scale));
 pad_x = (input_size - rw) / 2;
 pad_y = (input_size - rh) / 2;
 cv::Mat resized;
 cv::resize(src, resized, cv::Size(rw, rh), 0, 0, cv::INTER_LINEAR);
 cv::Mat out(input_size, input_size, CV_8UC3, cv::Scalar(114, 114, 114));
 resized.copyTo(out(cv::Rect(pad_x, pad_y, rw, rh)));
 return out;
}

四、数值预处理:BGR 到模型输入

几何之后还有数值层(GPU 或 CPU 都要做同类数值预处理):

  1. BGR → RGB(多数 YOLO 权重按 RGB 训练;通道反了「有框但飘/分低」);
  2. 排成通道优先的张量:批量1、三通道、高640、宽640;
  3. 转 float,并按训练约定缩放(常见 /255.0 到 0~1)。

这一段错了的特点是:不报错,只变差。接入新环境时,应用同一张图对比官方推理 脚本的框与分数。


五、模型输出:84×N 张量到底是什么

COCO 80 类的 YOLOv8 检测头,每个候选给出 84 个数:

[cx, cy, bw, bh, score_cls0, score_cls1, …, score_cls79]

人体只要 class 0 = person,即索引 4 + 0。

图:每个候选 84 维;布局可能是「特征维在前」或「候选维在前」。


本项目用布局探测兼容两种内存排列。

读取时用统一的取值函数兼容两种排列:

若导出工具改了维度顺序而业务只按一种硬解码,会出现「分数全是 0 / 框飞到画外」——优先查输出布局,而不是先怪模型。


六、解码 + 逆 Letterbox:框如何回到原图

解码时按下面公式把框从 letterbox 坐标还原到原图。检测解析 / 逆变换的核心:

// 逆 Letterbox:网络框 → 原图
const int left = (int)std::lround((cx - 0.5f * bw - (float)pad_x) / scale);
const int top = (int)std::lround((cy - 0.5f * bh - (float)pad_y) / scale);
const int width = (int)std::lround(bw / scale);
const int height = (int)std::lround(bh / scale);

公式含义:

  1. (cx ± bw/2, cy ± bh/2) 是 letterbox 图上的角点;
  2. 先减 pad,回到「无灰边的缩放图」坐标;
  3. 再除以 scale,回到原图像素;
  4. 与 原图矩形范围 求交,丢掉出界残片。

图:还原公式及数值例子。


用上一节的例子验算

letterbox 上:cx=320, cy=300, bw=40, bh=80,pad_x=0, pad_y=140, scale=1/3:


left = round((320 - 20 - 0) / (1/3)) = 900

top = round((300 - 40 - 140) / (1/3)) = 360

width = round(40 / (1/3)) = 120

height= round(80 / (1/3)) = 240


这就是原图上大约 120×240 的站立人。


调试口诀:

  1. 在 640 letterbox 图上画框 → 若已偏,问题在推理/解码前;
  2. letterbox 对、原图偏 → 逆变换或 scale/pad 传错;
  3. 两套都对但业务 ROI 错 → 后续模块坐标系统一问题。


七、阈值过滤与 NMS

解码循环里先做 score 阈值(例如 0.25):低于阈值的候选直接丢。

同一人通常对应多个高分候选,再用 标准非极大值抑制:

  • NMS IoU 常见约 0.45~0.55;
  • IoU = 交集面积 / 并集面积;
  • IoU 阈值调高:更不容易删,并肩两人更安全,但重复框可能多;
  • IoU 阈值调低:删得更狠,重复少,贴靠两人易被合成一个。

Tile 场景还会在全局再跑一轮 NMS,并可能使用 prefer-face 变体(有脸的框优先保留)——那是切块专题的内容,但单帧整图路径同样依赖这一步压冗余。


八、端到端链路

图:原图 → Letterbox → 张量预处理 → 模型推理 → 解码与去重 → 原图框。


部署上模型路径由默认人体模型路径探测得到(如 yolov8s_person.onnx 等),推理后端可走 TensorRT / CUDA EP / CPU,但对业务框格式保持不变——换 EP 不该改坐标语义。


九、小目标天花板:算清 “人在网络里有多高”

图:4K 上 80px 高的人,整帧进 640 后只剩约 13px。

粗算:

原图人身高 H_src

网络内身高 ≈ H_src * (input_size / max(W,H)) // letterbox 近似

例:H_src=80, 4K 长边 3840, input=640

→ 80 * 640/3840 ≈ 13.3 px


13 像素高的「人」对卷积特征来说接近噪声。于是:

  • 只换更大 YOLO 结构,救不了「已经被缩没」的目标;
  • 有效手段是:提高 input_size(960/1280),或 Tile 切块让局部仍保有足够像素;
  • 本项目在宽≥1280 或高≥720 时由按帧规划 Tile 启用 tile plan,正是被这个数量级逼出来的。


十、参数与失败模式(对着参数调)

常见故障:

  1. 整图平移:pad 符号反了,或居中/右下 pad 与训练不一致;
  2. 只有近处大人:input 太小或阈值过高;
  3. 广告牌人形:语义误检,靠面积/宽高比过滤或后续人脸验证;
  4. 手机录像框旋转:元数据 rotation 未烧进像素,几何全错;
  5. TRT/CUDA 换后端后框飞了:先查输出 layout 与 FP16,再查预处理是否仍走同一套 letterbox。


十一、建议的最小验证集

固定 20~50 张图,覆盖:近景大人、走廊远人、侧身、遮挡、逆光、并排两人。每次改预处理或阈值:

  1. 叠加画出 letterbox 框与原图框;
  2. 对比改动前后同一张图的框列表;
  3. 记录 scale/pad/score_thresh/nms_iou 到日志(本项目定位阶段日志也依赖这些字段复盘)。

没有可视化回归,坐标类 bug 会在跟踪阶段以「ID 乱跳」的假象出现,极难从下游反查。


十二、 小结

本项目的人体检测,技术核心是:

  1. Letterbox 用 min 缩放比 + 居中 114 填充,保住人体比例并记下 scale/pad;
  2. 84×N 张量里取出 person 分数与 cx,cy,bw,bh,并兼容两种内存布局;
  3. 逆变换严格按 (cx - bw/2 - pad) / scale 回到原图,再 NMS;
  4. 小目标首先卡在「网络内像素高度」,逼出更大输入或 Tile。

把这四段各自钉死,YOLO 才从「能跑的模型」变成「可跟拍的框」。切块、跟踪、认脸都建立在这个交付物之上。


十三、与 Tile 的分界(本篇只点到为止)

当 frame_w >= 1280 或 frame_h >= 720 时,按帧规划 Tile 会返回非零 grid,检测改为「多块 letterbox + 核心区过滤 + 全局 NMS」。

单块内部仍然完整走本篇的 Letterbox→推理→逆变换;多出来的是 ROI 原点叠加 与 跨块去重。

因此:Letterbox 公式错,Tile 必然全错;Letterbox 对了,Tile 才有调网格与 overlap 的意义。


十四、实现检查清单

  1. scale/pad 是否从 letterbox 原样传到 检测解析(禁止中途被别的 resize 覆盖)。
  2. BGR/RGB、/255 是否与导出模型一致。
  3. 输出 layout 是否同时覆盖 特征维在前的布局 与 候选维在前的布局。
  4. 逆变换是否先减 pad 再除 scale(顺序反了框会漂)。
  5. NMS 前后是否做了与原图的 clip。
  6. 日志是否打印 scale,pad,score_thresh,nms_iou,box_count。
  7. 困难样张回归是否覆盖远人与并排两人。

满足以上,人体检测这一环才算「可维护地讲透」,而不是「碰巧能跑」。

美畅美畅物联畅联畅联云平台视频监控云平台云视频监控视频云平台视频开放平台视频感知云视频接入网关AIoT综合接入网关视频中台物联网中台视频上云网关EhomeI连锁行业智慧交通AI算法