无人机YOLOv7人体检测实战:从数据标注到TensorRT部署全解析
简介这是一份面向计算机视觉与无人机应用研究者的英文学术论文PDF聚焦YOLOv7在无人机热红外TIR图像与视频中的实时人体检测问题。文章完整介绍了基于CNN的YOLO检测框架构建过程涵盖FLIR相机数据采集、模型训练与验证实验并给出关键性能指标在IOU0.5下人体检测平均精度达72.5%检测速度约161FPS同时评估了不同无人机观测角度下的交叉检测表现。包体内为1个PDF文件大小2.01MB内容紧凑适合用于目标检测、深度学习与遥感交叉方向的参考学习或组会复现讨论。目前已有269人学习浏览适合具备一定CNN基础的研究生或工程师查阅。1. 无人机上的 YOLOv7 人体检测为什么常规 6ms 推理会变成一帧都跑不完把 YOLOv7 搬上无人机做实时人体检测听起来是经典目标检测的常规操作模型开源、权重好拿、训练代码现成。但真把相机挂到无人机下面你会立刻发现地面项目里的经验全废了。俯视视角下行人可能只有十几个像素机体抖动带来的运动模糊还有光照角度的剧烈变化都会让模型精度掉得厉害而机载算力往往只有桌面 GPU 的十分之一原本显卡上 6ms 一帧的推理在嵌入式平台可能直接变成几百毫秒飞控稍微一偏画面就飘了。这篇笔记要解决的就是这样一套从数据标注、模型训练到嵌入式部署的完整链路。读者是正在做无人机视觉相关课题或产品的人手里已经有一台能飞的机器甚至已经跑通过 YOLOv7 官方代码但卡在如何在算力受限的机载设备上把人体检测做得又快又稳。我会把参数设置、数据坑、TensorRT 部署和实际飞行中的玄学问题都讲透让你照着做能少翻几次车。2. 无人机视角下的人体数据长什么样标注规范和训练集构成的三个关键参数2.1 俯视场景为什么让 YOLOv7 检测模型集体翻车地面安防摄像头是平视或略俯视人在画面里的比例大、轮廓完整YOLOv7 训练出来的特征非常依赖头肩轮廓和双腿直立这类形状线索。但无人机在 50 米到 120 米高度飞行时相机视角几乎是垂直向下人变成一个小椭圆头部和肩膀的遮挡关系完全变了有时一帧里只有头和背包的俯视纹理。用 COCO 或 VisDrone 里的常见平视数据训练出来的模型召回率会低得吓人。更重要的是尺度问题。YOLOv7 的输入分辨率一般是 640×640在一个 4K 相机画面里50 米高度下一个成年人可能只占 40×20 像素。就算你原图缩放进网络这个目标在特征图上也只落在小目标分支里。YOLOv7 的 anchor 设计默认基于 COCO 的 80 类分布其中行人类别占了相当比例但尺度分布是地面视角的俯视小目标的框比例通常更扁更小。我一般拿到新无人机数据后第一步不是训练而是拿预训练权重跑一轮可视化看模型到底把哪些东西认成人、漏了哪些人再决定数据集怎么补。2.2 用 YOLOv7 格式组织无人机人体数据集YOLOv7 官方仓库使用的数据格式是每个文本文件对应一张图像每行是类别 cx cy w h坐标都是归一化到 0~1 的小数。无人机影像有个特点是分辨率高比如 3840×2160直接标注会非常耗时。常见做法是先切成 640×1280 的条带或瓦片再灌进标注工具。需要特别注意标注框必须紧贴目标可见部分不能像地面数据那样把被遮挡的肢体也圈进去因为俯视时目标可能被树冠、车辆遮挡框太大的话会把背景特征也学进去。# 数据集目录结构训练时 --data 指向这个 yaml datasets/ uav_person/ images/ train/ 001.jpg 002.jpg val/ 010.jpg labels/ train/ 001.txt 002.txt val/ 010.txt# images/train/001.txt 内容示例 # class_id cx cy w h 0 0.68203 0.41328 0.01328 0.02656 0 0.21484 0.56797 0.00938 0.01563这里类别 id 0 表示 person。w 和 h 是框宽高相对原图的比值一般无人机俯视人体框的 w/h 在 0.4 到 0.9 之间比地面视角更扁这也是之后调 anchor 的重要依据。如果目标太小比如 w 小于 0.005我会直接把这样的标注样本单拎出来统计数量太少的话就做数据过采样不要指望模型自己从零学会识别 10 像素的人。2.3 按飞行高度和云台角度拆分训练集与验证集无人机数据集和常规数据集之间最大的差别在于视角关联性。同一个飞行任务里飞控高度从 30 米升到 100 米人的尺度变化可能超过 5 倍云台角度从 90 度纯俯视切换到 45 度斜视人的外观差异也非常大。如果你把同一段航线里所有帧随机划分训练集和测试集模型很容易在高度和角度上过拟合换一条航线就崩。我一般会按以下几个维度切分数据集飞行高度分 30 米以下、30 到 80 米、80 米以上三档云台俯仰角按 90 度、60 度、45 度分光线条件按顺光、逆光、傍晚分。验证集必须保证每档都有样本而不是纯随机。这是 YOLOv7 在无人机任务上比地面任务更容易被低估的一点精度指标好看不等于实际飞行可用划分不当会让 mAP 虚高。3. 训练 YOLOv7 人体检测模型多尺度、锚框和蒸馏的落地方案3.1 基础训练命令里必调的六个参数基于官方仓库我通常会先在 640×640 输入下训练一个 baseline先确认数据没有大问题。训练命令里最关键的不是迭代次数而是这几个参数--batch-size需要根据显存调成 16 或 32--img-size固定 640--epochs至少 300--workers调到 CPU 不成为瓶颈--cache-images开启避免每次读盘--noautoanchor先关闭让 YOLOv7 自动重新聚类 anchor。python train.py --data uav_person.yaml --weights yolov7.pt \ --batch-size 32 --img-size 640 --epochs 300 \ --workers 8 --cache-images --device 0 \ --adam --sync-bn--sync-bn在多卡训练时才真正生效单卡不影响结果但不会报错可以留着。--adam在数据集规模不大时往往比默认 SGD 收敛更稳尤其当标注框普遍小于 20 像素时Adam 对小目标梯度更敏感这是我用无人机数据训练时的一个偏好。训练中途要盯的不是 Loss 曲线而是val/objectness和mAP0.5的每周下降幅度。如果 mAP 在 100 个 epoch 后还没有超过 0.5问题基本出在数据分布或标注边界框不干净继续加 epoch 是浪费电。3.2 多尺度训练和自适应锚框小目标召回率靠它们拉起来无人机俯视目标尺度跨度很大单一 640 输入会让 80 米高度的人缩到 12×6 像素在特征图上几乎只剩一个点。YOLOv7 自带多尺度随机采样训练命令里加--multi-scale之后每 10 个 batch 会在 0.5 到 1.5 倍输入尺度之间随机切换。这个过程相当于变相把训练集里的固定尺度打散让网络对不同高度的人体更加鲁棒。锚框方面YOLOv7 会在训练前用 k-means 重新统计你数据集中所有标注框的宽高比。由于无人机俯视人体框很扁重聚类出的锚框会和 COCO 预训练权重里的锚框差异明显。我建议训练前单独跑一次 anchor 统计用官方提供的utils/autoanchor.py脚本查看新数据聚类结果再决定是否手动覆盖。注意不要盲目把 anchor 改成特别小的值否则模型在低空大目标上又会丢失最好让聚类结果自己去和默认 anchor 折中。3.3 数据增强的取舍不要用马赛克和随机旋转YOLOv7 默认增强里有随机水平翻转、缩放、平移和 HSV 扰动但无人机俯视角下有两个增强必须谨慎。第一个是马赛克增强也就是把四张图拼成一张在 COCO 上它显著提升 mAP但无人机俯视目标本来就小拼接后目标边界被切碎模型很容易学到半个身体特征落地后反而误检多。第二个是随机旋转90 度旋转对无人机影像没有意义无人机传感器方向始终指向机头而 45 度以下的随机旋转会破坏行人头肩的方向先验。我常用的增强组合是饱和度扰动 1.5 倍、曝光扰动 1.1 倍、随机水平翻转、随机上下翻转以及 5% 概率的轻微高斯模糊模拟运动模糊。其中高斯模糊不要加太多否则模型会依赖模糊特征清晰帧反而检测不稳定。如果你用的是官方train.py可以通过修改data/hyp.scratch.yaml里的增强系数来控制不用改代码。3.4 如果精度不够先做模型蒸馏再做剪枝YOLOv7 本身有辅助训练头精度下限不低。但无人机机载平台通常只能跑到 Jetson Orin Nano 或更低功耗的芯片这时你可能需要把模型量化和剪枝到更小的体量。我的建议是先训练一个带完整辅助头的 736 输入教师模型再训练一个 320 或 384 输入的学生模型做蒸馏而不是直接在大模型上剪枝。因为剪枝掉的特征图通道对小目标影响太大剪完 mAP 可能从 0.8 直接掉到 0.6蒸馏可以在剪枝后用教师输出做软标签补偿。蒸馏的命令式和普通训练类似核心在于把教师网络的输出 logits 软标签混合进总损失。YOLOv7 仓库没有内置蒸馏脚本你可以用distill分支或自己写一个简单版本取教师模型的中间特征图和学生模型做 L2 对齐。这里不追求完全复现官方刷新记录的效果实际项目中只需要比直接训练小模型高 3~5 个点 mAP 就够了。4. 机载部署 YOLOv7把 PyTorch 模型变成 TensorRT 实时推理引擎4.1 平台选型和模型形态选择常见的机载部署平台是 NVIDIA Jetson 系列Orin Nano 或 Xavier NX 是性价比最高的两款。Jetson 上有两个部署姿势一个是直接把 PyTorch 模型转成 TorchScript用 LibTorch 推理开发最快但延迟不稳定另一个是转 ONNX 再转 TensorRT engine把网络结构和卷积算子做层融合这是我推荐的生产做法。TensorRT 对 YOLOv7 这种带重参数化卷积和辅助训练头的结构有额外的好处导出时去掉辅助头只保留主检测头的输出模型体积直接小一半。导出 ONNX 前要确认模型已经进入 half 精度可转换状态。YOLOv7 官方export.py脚本支持--grid和--simplify参数我一般会指定--img-size 640 --batch 1并加上--simplify用 onnx-simplifier 去掉容易导致 TensorRT 报错的冗余算子。python export.py --weights yolov7-uav.pt --grid \ --img-size 640 --batch 1 --simplify \ --dynamic这里--dynamic会把 batch 维度设为动态方便我们在机载端测试批大小 1 和批大小 4 的性能差异。--grid选项让输出张量带上每个位置的 anchor 解码结果避免在 TensorRT 里再写自定义 Decoder排错更快。导出完成后得到yolov7-uav.onnx大约 30~60 MB具体大小取决于你有没有剪枝。4.2 用 TensorRT 生成 engine 并验证输出拿到 ONNX 之后用 trtexec 转 engine 是最靠谱的路。FP16 对于人体检测足够大多数无人机场景光线充足FP16 精度损失可以接受INT8 能再快 20% 左右但校准集选择不当会让小目标直接消失我建议第一版先跑 FP16。/usr/src/tensorrt/bin/trtexec --onnxyolov7-uav.onnx \ --saveEngineyolov7-uav.engine \ --fp16 --workspace4096 \ --minShapesimages:1x3x640x640 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:1x3x640x640这段命令把动态 batch 固定为 1因为无人机推理通常是一次一帧多 batch 对实时管线没有意义。--workspace4096给 TensorRT 4GB 显存做算子优化如果目标平台只有 4GB 显存这个值可以降到 2048。生成 engine 后用 TensorRT 的 Python API 加载并跑一次推理对比 PyTorch 输出的框坐标和置信度偏差应该在 1% 以内。如果偏差过大优先怀疑 ONNX 导出时--grid没有加。4.3 机载推理管线预处理、NMS 和后处理的常见写法TensorRT 只负责卷积网络的计算输出仍然是一个包含所有 anchor 预测框的裸张量。YOLOv7 的输出格式是1 x (num_anchors * (5 num_classes)) x 特征图格点数在写部署代码时需要先把每个 anchor 的 cx、cy、w、h 从模型原始输出解码成真实像素坐标再做 NMS。import numpy as np import tensorrt as trt class YOLOv7TRT: def __init__(self, engine_path): with open(engine_path, rb) as f: runtime trt.Runtime(trt.Logger(trt.Logger.WARNING)) self.engine runtime.deserialize_cuda_engine(f.read()) self.context self.engine.create_execution_context() self.inputs [] self.outputs [] self.allocations [] for i in range(self.engine.num_bindings): shape self.engine.get_binding_shape(i) size trt.volume(shape) dtype trt.nptype(self.engine.get_binding_dtype(i)) host_mem np.empty(size, dtypedtype) device_mem cuda.mem_alloc(host_mem.nbytes) self.allocations.append(device_mem) if self.engine.binding_is_input(i): self.inputs.append((host_mem, device_mem, shape)) else: self.outputs.append((host_mem, device_mem, shape)) def infer(self, img_ndarray): # img_ndarray: 1x3x640x640 float32, 已做归一化到 [0,1] np.copyto(self.inputs[0][0], img_ndarray.ravel()) cuda_memcpy_htod(self.inputs[0][1], self.inputs[0][0]) self.context.execute_v2([mem for _, mem, _ in self.inputs self.outputs]) cuda_memcpy_dtoh(self.outputs[0][0], self.outputs[0][1]) # 后续做 anchor decode NMS return self.outputs[0][0]这段 Python 代码把 engine 的输入输出显存分配都放在初始化阶段避免每一帧重复申请显存和拷贝。机载端 CUDA 上下文切换开销很大first frame 和后续帧的延迟可能差 3 倍以上。实际项目里我一般会把infer函数用 C 重写并使用 CUDA Stream 把输入图像归一化、Resize 和推理放到同一个 GPU 队列里CPU 只负责从相机拉帧。Python 版本适合白天调试晚上性能调优时再换 C。4.4 相机、飞控与检测帧率怎么协同实时检测不只看模型推理速度整条链路是相机出帧 - ISP 输出图像 - 拷贝到 GPU - 预处理 - 推理 - NMS - 给飞控发送目标坐标。常见坑是相机帧率设置太高比如 30fps而检测只能跑到 10fps相机 buffer 会被塞满你拿到的 Frame 永远是老画面端到端延迟反而超过 200ms。推荐做法是把相机锁在 15fps 或 20fps让检测结果帧率等于输入帧率控制回路不要等检测。飞控与检测的同步重点看时间戳。无人机飞控的 IMU 采样率普遍到 200Hz 或更高而相机曝光时刻和检测完成时刻相差很大直接把检测框叠加在最新视频帧上看起来没问题但如果你想根据像素坐标换算目标经纬度必须用曝光时间戳去查飞控姿态插值。否则飞一个急转弯目标实际位置和画面上框的位置能偏出好几米。5. 无人机人体检测部署避坑五个你必须提前防住的翻车现场5.1 转换 TensorRT 后框全乱了现象PyTorch 模型在桌面 GPU 上检测正常转成 TensorRT engine 后输出一堆无规则框置信度也异常。原因YOLOv7 的检测头输出包含x * y * (num_anchors * (5 num_classes))的 reshape 和 anchor 网格偏移逻辑ONNX 转换时如果网格坐标是动态的TensorRT 会和 PyTorch 的网格排列顺序不一致常见于没加--grid导出参数。解决导出时用python export.py --grid强制把解码逻辑放进 ONNX。另外检查 TensorRT 的 plugin 是不是启用了 DLAJetson 设备上虽然 DLA 能省功耗但对这种 grid 操作的兼容性不好第一版部署先禁用 DLA。5.2 INT8 量化后小目标神秘消失现象FP16 engine 在 80 米高度还能认出人换 INT8 engine 后只有低空大目标有框高空的小点全被过滤掉了。原因INT8 量化会对每层激活值做动态范围映射小目标在低层特征图上的激活值响度本身就弱量化后小于阈值的部分被截断信噪比直接崩掉。解决不要贪 INT8 的速度提升FP16 已经足够实时。如果非要 INT8做校准集时不要用普通随机图片必须把无人机高空小目标的切片大量塞进校准集数量至少 100 张且包含黄昏和逆光样本否则就是自欺欺人。5.3 飞行中画面轻微抖动导致检测结果闪烁现象无人机悬停时人体检测框会竖直方向或横向小范围抖动某些帧明明人没动框却突然消失了。原因YOLOv7 在 640 输入下对小目标的预测框对 motion blur 非常敏感。无人机虽然看起来悬停但云台和飞控的微调还是会造成 1~2 像素的模糊而小目标的卷积特征在这个尺度下本来就是强噪声。解决在后处理里做时间域滤波。最简单的方法是连续三帧都检测到且中心点距离小于 15 像素才输出否则保持上一帧结果。这种方法不增加任何算力消耗但引入约一帧延迟对实时任务可接受。更稳的做法是把相机电子防抖开启减少传感器读出噪声。5.4 CPU 上跑预处理比 GPU 推理还要慢现象TensorRT FP16 推理只需要 12ms但整条管线测量下来一帧需要 45ms调用链路显示 20ms 花在图像 Resize 和归一化上。原因机载平台的 CPU 通常只是 ARM 核心直接用 OpenCV 的cv2.resize处理 1920×1080 图像再转 RGB单次操作就要 15ms 以上内存拷贝也占时间。解决先在相机驱动层把分辨率降到 1280×720 或直接 640×640 输出Resize 操作放到 CUDA 上用cudaMemcpy2D和cv::cuda::resize做。注意图像从 DMA buffer 转到统一内存时会锁页分配 host 内存时用cudaHostAlloc固定页避免每一帧都做双缓冲拷贝。5.5 无人机强逆光下误检率飙升现象面向太阳或者水面反射剧烈时模型把汽车车顶闪光、白色建筑边缘连续误报成人。原因YOLOv7 训练数据里光照变化有限逆光下人和非人目标在灰度纹理上高度相似模型分不清。无人机视角下常见反光材质又特别多比如金属屋顶、玻璃幕墙。解决在数据准备阶段刻意加入逆光和过曝样本通过 HSV 提升亮度扰动到 2.0 倍推理端把输入图像的曝光均衡化先做 CLAHE 再做归一化能显著压低这部分误检。不要指望只调置信度阈值因为降到 0.4 会把真人也漏掉最有效的还是数据侧补齐。6. 端到端延迟自测方法和一个让高空小目标起死回生的动态输入技巧验证实时不应该只看推理引擎的 FPS而要测量从相机获取图像到飞控收到目标坐标的端到端延迟。我常用的方式是让无人机悬停在一块黑色大板子上放一个闪烁的白色 LED 标记相机正对标记方向等检测框出现时瞬间改变标记状态用板上毫秒计时器记录变化时刻与检测结果变化的差值连续测 20 次取中位数。这个值能暴露预处理、排队、NMS 各环节的暗缓存问题比看几遍 TensorRT Profiling 都直观。调试到后期我惯用一个动态输入分辨率技巧根据飞控给出的高度信息在低空时把推理输入降到 480×480在高空时自动涨回 640×640 或 736×736。低空人像大不需要那么高分辨率省下来的算力可以降低整体延迟高空人像小用更高分辨率输入实际上是在放大目标比改 anchor 或增加模型体量收益更直接。配合 Jetson 上的动态 shape engine这个切换可以在两次推理之间无缝完成高度阈值一般设在 60 米。累计做过几轮户外飞行测试后我的习惯是每次飞行前先存 5 分钟原始视频回来后用同一段视频做离线回放验证。这样你能把模型在真实振动、曝光、快速转向下的表现反复回看特别是那些导致漏检的参数都能用视频回放定位到具体帧。无人机视觉感知这个方向真正难的不是把 YOLOv7 在某个芯片上跑起来而是让它在每一次起降、每一段航线的复杂光照里都保持稳定的表现。这需要一点点试错和积累希望我的这些踩坑经历能帮你在自己的无人机部署路上少折腾几个版本。本文还有配套的精品资源点击获取