无人机飞起来那一刻,取景器里满屏都是黑压压的人头。操场课间操、军训方阵、运动会入场式,这些俯视画面里“人”这个东西和平视行人检测里完全是两码事。我之前拿现成的YOLO行人检测模型直接往航拍视频上怼,结果不是漏检一大片,就是把塑胶跑道的白色弯道线、看台的台阶阴影全当成人。后来我老老实实从零做了一个航拍校园操场场景的人体检测数据集,用YOLOv8重新训练,才把问题真正解决掉。
这篇内容就是把整个过程中的数据集设计思路、标注规范、训练调参和排错经验完整写出来。适合正在做航拍目标检测、无人机巡检、校园安全监控,或者想用YOLO训练自定义数据集但被小目标问题折磨的朋友参考。核心关键词就三个:YOLO、航拍、人体检测,但“数据集”这三个字才是决定成败的地基。
1. 为什么航拍操场人体检测,不能直接拿通用行人模型顶上
先说结论:通用行人检测模型在平视街景、监控摄像头场景下确实很能打,但一上天就失灵。这不是模型不够强,而是数据分布发生了根本性变化。我在第一次把YOLOv8x在COCO预训练模型直接用于操场航拍视频时,mAP直接垮掉,原因是多方面的。
1.1 视角变化让目标形态完全变了
平视行人检测里,行人的标注框基本都是瘦长条,宽高比大概在0.3到0.5之间,模型学到的“人”这个概念的视觉特征,是全身轮廓、双腿、躯干和头部的比例关系。但无人机在100米高度俯拍操场时,看到的是头顶,身体被压缩成一个近似椭圆甚至接近正方形的块。如果镜头再带一点倾斜角度,人的形态会随位置变化而拉伸扭曲,同一个方阵里不同位置的人长相差很多。
这种形态变化直接影响Anchor设计。用736x1280输入跑COCO预训练模型时,默认Anchor主要适配瘦长目标,航拍俯视目标变成了“矮胖”目标,匹配率低是必然的。YOLOv5/v8虽然有AutoAnchor机制,但那是重新计算anchor用的,预训练模型本身的特征提取器学到的也是“平视人形”的中高层语义特征,两种视觉模式差异太大。
1.2 尺度问题:绝大多数目标只有几十个像素
COCO数据集里把小目标定义为边长小于32像素的物体。航拍操场画面里,100米高度、4K分辨率下,一个成年人也就占据40到70像素的尺寸,整张图几百号人全是小目标。更麻烦的是,小目标在特征金字塔里的语义信息非常弱,下采样到P3层(stride 8)时,目标已经缩成了4到8个像素,几乎就是一团噪声。
通用行人检测数据集CrowdHuman虽然也是密集人群,但那是平视视角,目标尺度大、可辨识纹理多。航拍场景不一样,目标的纹理信息就是头顶头发、肩部轮廓和衣服颜色,这些特征高度相似,区分“这是人”和“这是个人形阴影”全靠上下文。
1.3 密度和背景:操场是整个视觉任务里最不友好的场景之一
课间操2000人同时在操场上,一个画面里目标数量轻松超过300到500个。目标之间的遮挡、交叠非常严重,相邻两个人的边界框IoU可能超过0.5,这对训练时的正样本分配和推理时的NMS都是巨大考验。
背景干扰更是一言难尽:塑胶跑道的白色分道线在俯视下有人体宽度、有延伸方向;看台座椅的规则条纹在特定角度下看起来像一排排整齐的人头;树木和旗杆在阳光下的投影轮廓,和俯视人体的形状高度相似。COCO预训练模型在平视场景里没见过这些背景,自然会把它们当成“很像人的东西”输出来。
所以我才意识到,航拍校园操场的核心问题不是“怎么把YOLO调好”,而是“先造一份和这个场景匹配的数据集”。这也是为什么像DOTA这样的遥感旋转框数据集、CrowdHuman这样的密集人群数据集,各自都要独立构建,场景差异大到不能互相替代。
2. 数据集从零搭建:采集、筛选、标注与格式转换全流程
2.1 采集参数怎么定,别随便飞一圈就完事
航拍数据集的采集不是拿无人机出去绕一圈就行,采集参数直接决定后续标注和训练的上限。我当时用大疆无人机,主要控制四个变量。
飞行高度:60到120米之间多档位覆盖。60米高度人体大约80到120像素,目标更大、标注更容易;120米高度人体只有30到50像素,更接近真实巡检场景。模型训练时混入不同高度的数据,尺度的泛化能力才会好。如果只飞固定高度,模型一旦换算到别的飞行高度就废了。
云台角度:纯俯视(-90度)和斜视(-45度到-60度)都要拍。纯俯视适合数人头,但目标之间重叠严重;斜视角度能看到部分身体侧面,形态更丰富。最终数据集里俯视和斜视的比例控制在7比3左右,因为实际应用时无人机很少垂直正对操场一动不动。
时间段覆盖:上午课间操、中午体育课、下午课外活动、傍晚光线较暗的时段都拍。一天中阴影角度和位置变化极大,早上人的影子拉得老长,中午影子缩在脚下,这直接决定模型会不会把影子也当成正样本。
场景覆盖:课间操、升旗仪式、体育课自由活动、运动会进场、军训队列,不同活动对应的人群密度和排列规律完全不同。方阵队列整齐划一,目标间距相对均匀;自由活动则是三五成群、密度随机分布。只拍单一活动类型,模型很容易过拟合到特定排列模式。
2.2 抽帧和筛选:视频数据里百分之七八十都是废料
视频按帧连续采集会带来严重的帧间冗余问题。相邻两帧画面几乎一样,如果直接全部送入数据集,train和val里会出现大量“双胞胎”样本,验证集精度虚高到让你误以为自己已经完美了。
我的做法是:先按每2秒抽1帧,从原始视频里抽出候选帧;然后做三步筛选。第一步,剔除运动模糊严重的帧,无人机悬停不稳、学生快速跑动时尤其是卷帘快门会产生拖影。第二步,剔除画面中人数过少或密度过低的帧,保证每一帧的训练价值。第三步,手动检查飞行过程中的大角度旋转帧,画面旋转超过45度且没带对应的标注姿态时直接弃用。
最终数据规模定格在1562张有效标注帧,共标注34380个人体实例。单帧最多540人,最少11人,中位数在120人左右。这个规模对单类检测不算大,但对航拍小目标场景来说,实例数足够撑起一个可用的模型。
2.3 标注规范:边界怎么定,直接决定模型学什么
标注规范是数据集里最容易翻车、也最容易被忽视的环节。航拍人体检测里最大的争议是:人到底怎么圈?影子算不算?手臂展开算不算?
我的标注规范是这样定的:
- 目标框包含“可见身体部分的外接矩形”,从上往下看就是包含头肩和躯干展开的最小矩形。
- 手臂张开的瞬间,如果超出躯干范围,我选择包含进去,因为俯视视角下手臂本身就是身体轮廓的一部分。但严格排除地面阴影。
- 两个人交叠严重、边界难以区分时,只标注可见部分占整体比例超过30%的目标,低于这个比例的跳过不标。
- 被树木、旗杆、建筑物完全遮挡的目标不标,也不设ignore标签。不完全遮挡但可辨认头部的,必须标。
- 不做旋转框。虽然旋转框在遥感场景有优势,但校园操场人体目标外形接近椭圆,水平框在密集场景下的IoU计算更简单,而且YOLO系对旋转框的支持没那么原生,后续做跟踪、计数也更方便。
标注工具我前后试过labelImg和X-AnyLabeling。labelImg老牌稳定,但1562张图、3.4万实例全靠手标能把手标废掉。实际流程是先用一个在COCO上训练的YOLOv8m模型做自动预标注,生成粗框后导入X-AnyLabeling人工修正。预标注能省掉60%的框,但剩下的40%基本都要挪位置或者改大小。航拍小目标框的边界只要偏3到5个像素,IoU就能掉到0.7以下,所以人工修正这步省不得。
2.4 从标注格式到YOLO txt:坐标转换最容易算错的几行代码
标注软件通常导出COCO JSON或VOC XML,YOLO训练需要的是每张图一个txt文件,每行是 class x_center y_center width height,所有坐标都归一化到0到1。
转换公式本身很简单:
x_center = (x_min + x_max) / 2 / image_width y_center = (y_min + y_max) / 2 / image_height width = (x_max - x_min) / image_width height = (y_max - y_min) / image_height但这里有个隐蔽的坑:COCO的边界框坐标是 (x_min, y_min, width, height),而VOC是 (x_min, y_min, x_max, y_max)。如果你把两者混在一个中间数据里,没有统一成一种格式就去做归一化,训练时边框会整体偏半个身位。我吃过这个亏,第一次转完发现YOLO训练损失不降,检查数据集可视化才发现框全往右下角偏移了。
转换完一定要做可视化检查:把标注框画回原图,随机抽200张逐张看。这一步能发现坐标系混乱、归一化除错分母、类别索引从1开始而不是从0开始(YOLO类别索引从0开始)等一批低级错误。
2.5 数据划分不按随机分,按场景分
train/val/test的划分不能简单random split。如果同一个拍摄片段里的连续帧既出现在train又出现在val,由于帧间画面高度相似,val的mAP会虚高5到10个点。我按“拍摄片段”为单位划分:每段无人机视频抽出的所有帧作为一个整体,要么全进train,要么全进val,要么全进test。最终比例约8:1:1,并且test只包含与train完全不同的拍摄时段和活动类型,这样才能反映真实泛化能力。
3. 航拍场景专属的数据增强:哪些有效,哪些帮倒忙
数据增强不是开箱即用的工具箱,YOLO默认打开的那套增强组合是为通用场景设计的,放到航拍小目标密集场景里,有些不仅没用,还添乱。
3.1 Mosaic增强在航拍小目标场景的副作用
Mosaic把4张图缩放到一半尺寸再拼在一起,等于把本来就小的目标再缩一倍,四张图里大量目标直接降到10多像素,几乎不可辨识。在YOLOv8里默认开启mosaic,训练早期会让模型反复看到一批“模糊的色块”,小目标特征根本学不出来。
但直接全关mosaic也不好。经过实验对比,航拍数据集上mosaic=0.5,也就是训练轮次里一半用mosaic、一半用原始尺寸,比全开(mosaic=1.0)提升约3个点mAP0.5。另外还有一个更简单的替代方案:关闭mosaic,改用“裁切后拼接”——每张图先裁出包含目标的局部区域,再把裁切区域拼接成大图。这样目标不被缩小,但能保留上下文。缺点是拼出来的图背景分布不均匀,需要自己写预处理。
3.2 实测有效的几个增强操作
随机旋转和水平翻转:航拍视角下人的朝向没有“正立”的概念,旋转10到20度、水平翻转都能模拟不同飞行航向。注意旋转会改变标注框也旋转,但YOLO的水平框在旋转后目标会略微倾斜,如果旋转超过30度,框就会框进大量背景,此时反而有害。实测旋转角度上限15度效果最好。
HSV颜色扰动:操场塑胶跑道的红色、草坪的绿色在不同光照下饱和度差异极大。把饱和度和明度扰动范围调得比默认值大一倍,能让模型对光影变化更鲁棒。我用的参数是HSV_H=0.015,HSV_S=0.7,HSV_V=0.5,比YOLOv8默认值明显激进,但对于户外场景很有效。
Cutout随机遮挡:模拟目标被树木、旗杆、看台柱子遮挡的情况。在密集人群中卡一半的人被模仿遮挡,有助于模型学会从局部线索辨识目标。
在所有增强里收益最明显的是Copy-Paste。把标注好的目标实例随机复制粘贴到另一张图的空旷区域,同时增加人群密度和变化背景。这在航拍数据集里几乎是为密集场景量身定做的:同一批人,可以生成不同背景下的训练样本。复制粘贴时要注意对粘贴区域做小幅HSV微调,让复制过来的目标与目标区域的色调尽量融合,否则模型会学到“复制品周边有缝”这个伪特征。
3.3 增强完必须检查尺度分布
很多人做完增强直接开训,结果训练完发现模型对中等目标很好、对真正的小目标还是不行。我养成了增强后用脚本统计所有标注框像素尺寸分布的习惯。目的是确认:小目标(边长32像素以下)占比不能太低,至少要能撑起匹配层对小目标的采样。航拍场景下如果增强策略把小目标比例稀释到20%以下,模型就会严重偏向中等目标。
统计数据长这样(这是我自己数据集的情况):
| 目标尺寸 | 占比 |
|---|---|
| 小于32像素 | 49.6% |
| 32到96像素 | 43.2% |
| 大于96像素 | 7.2% |
小目标几乎占一半,如果Mosaic再往上叠加,这个比例会继续向微型目标倾斜,超出正常的可学习范围。
4. YOLO训练配置与调参实录:从选型到收敛问题排查
4.1 模型选型和输入分辨率:显存允许就上1280
我最终用的是YOLOv8m,输入分辨率1280x1280。选型逻辑是:YOLOv8n推理快但小目标检测能力有限,yolov8x精度最高但训练和推理成本都高,对1562张的数据集来说容易过拟合。YOLOv8m是精度、速度和显存需求的平衡点。
输入分辨率是航拍小目标场景最关键的超参数,没有之一。同样的模型,640输入和1280输入在小目标AP上的差距能超过10个点。原因是目标在P3特征层(stride 8)的分辨率直接翻倍,原本只有3到4个像素的目标变成6到8个像素,这多出来的几个像素决定了检测头能不能分辨“人”和“噪声”。代价是显存和训练时间增加,1280输入下8卡各24G显存起步,如果只有单卡16G显存,可以考虑开启梯度累积或者干脆用640训练再在1280上微调最后20个epoch。
4.2 预训练模型和类别数:COCO权重是优质的起点
很多人训练自定义数据集时纠结要不要从零开始训练。我的建议是:用COCO预训练权重。YOLOv8官方会自动下载yolov8m.pt,加载时即使你改成单类检测,它也会把COCO80类学到的通用视觉特征迁移过来。人体特征、边缘纹理、前景背景区分这些能力是通用的。实测对比中,用COCO预训练权重微调比从头训练多出差不多8个点的mAP,而且收敛速度快一倍。
预训练权重下载有个小坑:一定要去官方Release页面拿对应版本的权重文件,不要用第三方打包的“优化版”。我遇到过从网盘下载的所谓“预训练模型”改了网络头结构,加载后维度对不上,白折腾半天。
4.3 关键超参:epoch、batch、学习率怎么配合
训练收敛的稳定性主要取决于batch size和学习率的配合。YOLOv8默认lr0=0.01,这是针对batch 16到64设计的。我batch size压到8的时候,如果继续用0.01,loss大概率初期震荡甚至发散。经验公式是按batch变化比例缩放学习率:batch 8时lr0大约设为0.002到0.003,batch 64时设回0.01。训练过程用cosine annealing,warmup 3个epoch。
epoch数量我设了200,但实际在130到150个epoch时验证集mAP就开始平台期了。判断是否到位要看val loss和mAP曲线,不要死等epoch数跑完。best.pt保存在val mAP最高的权重,last.pt是最后一轮权重,如果出现过拟合,best.pt和last.pt的mAP差异会很明显。
4.4 损失函数和标签分配对小目标的影响
YOLOv8的box损失由CIoU和DFL两部分组成。对航拍小目标来说,几个像素的偏移就可能让CIoU从0.9掉到0.6,所以高分辨率输入是缓解这个问题的最直接手段。标签分配策略上,YOLOv8用TaskAlignedAssigner,它会根据分类和回归的联合得分选择正样本,对小目标比较友好,不需要额外改动。
如果你用的是YOLOv5,建议把anchor参数打开AutoAnchor,让它根据数据集重新计算anchor。3.4万个实例的分布和COCO很不一样,默认anchor对航拍俯视目标匹配率偏低。YOLOv8虽然anchor-free,但不同尺寸的模型对目标大小的匹配能力依然有限,这方面的兼容性稍好一些。
4.5 训练中的BN崩溃和loss不收敛排查
训练到第60个epoch左右,我遇到过loss突然跳到NaN、训练直接崩掉的情况。这就是常说的BN崩溃。排查下来原因有两个。
第一个原因:数据里有异常样本。某几张图在抽帧时遇到白平衡故障,几乎全白画面,BatchNorm在这些样本上统计出极端均值和方差,导致后续梯度爆炸。解决办法是检查图像质量、把异常图像剔除。
第二个原因:学习率过大加batch过小,BN的batch统计量波动太大。解决方法是降低学习率、增大batch或者用更小的模型。
如果不崩但loss就是一直不降,先看数据增强是否过强(把Mosaic、MixUp全关掉试一次),再看标注是否正确(可视化确认框有没有贴住目标),最后看loss曲线是震荡还是缓慢。震荡说明学习率偏高,收敛缓慢说明数据可能有问题。这一套排查顺序能解决80%的训练异常。
5. 评测和误报复盘:别只盯着mAP骗自己
5.1 该看哪些指标:mAP0.5和高标准AP都要看
训练完第一件事不是看总mAP,而是拆开看。
mAP0.5是IoU阈值0.5下的平均精度,反映“大概框住就行”的能力;mAP0.5到0.95是跨多个IoU阈值的平均,对框定位精度要求极高。航拍密集小目标场景,目标只有几十像素,想要达到0.5到0.95的高成绩非常困难。我的模型实测数据是mAP0.5接近0.82,但mAP0.5到0.95只有0.47。如果你只报mAP0.5,确实好看,但真实场景里那些框偏差、重叠框问题会被隐藏掉。
按尺寸分段看AP更重要。Ultralytics在验证集上会输出各个尺寸类别的AP,我的模型small AP是0.38左右,而medium AP是0.62,差距一目了然。优化目标就应该集中在small AP上,而不是总体数字。
5.2 混淆矩阵:为什么总和偏偏不是1,怎么读
YOLO训练后输出的混淆矩阵热力图,很多第一次用的人会发现一个问题:矩阵所有格子加起来不等于1,疑惑是不是bug。其实不是。Ultralytics的混淆矩阵是按真实类别(行)分别归一化,每一行代表这个类的样本被预测到各个类别(包括背景)的比例,所以每一行各自和为1,整个矩阵的总和自然不等于1。有些人看列方向做归一化的话列和为1,总之要注意归一的维度,别拿“总和”去判断模型好坏。
线下的思路:先看每个类别的召回和误检。如果真实“person”类别有相当比例被分到background,说明阈值太高或小目标漏检严重;如果background被大量预测为person,说明背景误检典型,接下来要分析具体是什么背景被误检。
5.3 典型误报复盘:操场上的“假人”从哪来
我逐帧检查了约300张误检严重的图片,误报集中在三类:
第一类,塑胶跑道的白色弯道线和直道分界线。两条白线并排时,俯视形状像一个狭长的人体框,置信度能到0.3到0.5。这一类靠单纯增大阈值消不掉,需要靠负样本或调整输入亮度缓解。
第二类,看台座椅。阶梯状的座椅在逆光下形成大量规则明暗条纹,局部纹理和人头密集排列高度相似。这是最顽固的误报来源。
第三类,树和旗杆的阴影。阴影的边界模糊、形状不规则,但模型会把“暗色块+长条形”判成人。
处理思路有三个:一是给训练集补充这些典型背景区域的裁剪图,标注为background,让模型看到“这些地方没有人”。二是推理阶段对特定区域(跑道、看台)加ROI遮罩,直接忽略这些区域的检测结果,这在固定机位的操场监控里非常有效。三是使用soft-NMS,保留低置信度但空间位置合理的框,避免密集人群时相邻目标的框被互相抑制掉。
5.4 置信度阈值和NMS参数怎么调
航拍密集人群场景下,NMS的IoU阈值直接决定最终输出框密度。默认NMS IoU=0.5在人群拥挤时会把大量重叠的相邻人体框合并成一个,漏检率飙升。我把IoU阈值调高到0.6到0.7,同时置信度阈值保持在0.25到0.30之间。结果就是输出的框更多、更贴近真实人数,虽然多了少量低置信度假阳框,但后续用Tracking结果做时间维过滤可以把假阳框抹掉。
如果用的是YOLOv8中默认的NMS,实测soft-NMS版本能进一步缓解密集场景的人框互相抑制。具体实现可以用带soft-nms的mmdeploy或者自己改写后处理,这部分需要一个下午的工程量,但对密集场景的改善值得。
6. 部署落地与数据集的后续演进
6.1 推理速度与硬件选型
模型最终export成ONNX和TensorRT,在不同硬件上实测了推理速度(1280输入、batch 1):
| 硬件 | 推理耗时 | 备注 |
|---|---|---|
| RTX 4090 TensorRT FP16 | 约2至3毫秒每帧 | 可实时处理25路视频流 |
| RTX 3060 ONNX FP32 | 约12毫秒每帧 | 适合单路实时检测 |
| Jetson Orin Nano | 约25至30毫秒每帧 | 边缘部署可接受但不能高并发 |
| 纯CPU OpenVINO | 约300至500毫秒每帧 | 只能做离线抽帧处理 |
如果你的应用是“课后分析一段无人机录的视频”,CPU离线处理完全够用,没必要上GPU。但如果是“操场实时监控+人数统计”,至少需要一块中端GPU或Jetson级别的边缘设备。
6.2 视频流抽帧和误检的时间维过滤
部署阶段最实用的技巧是用ByteTrack做目标跟踪。单帧检测总会有一些随机误检和漏检,但误检往往是孤立的、只连续出现一两帧;真实的人被跟踪后轨迹连续、跨帧稳定。我统计过,使用ByteTrack后,误检数量下降了70%,因为单帧假阳框很难形成稳定轨迹。
处理流程是:抽帧或实时取流 → YOLO检测 → ByteTrack关联 → 输出轨迹和计数。值得一提的是,航拍场景人体移动缓慢,目标的IoU连续性很好,ByteTrack的参数不需要大改,默认配置就能跑得动。
6.3 隐私合规:数据集发布前必须做的处理
校园场景涉及大量学生的个人信息,尤其是航拍俯视画面中虽然人脸较小,但运动轨迹和个人行为特征依然具备可识别性。在正式发布数据集或展示案例前,我做了三件事:
第一,对原始图片中所有人脸区域做不可逆的模糊化处理(高斯模糊+降采样),确保无法还原人脸细节。
第二,所有数据只用于算法研究和教学,不包含任何学生个人身份信息,不公开原始视频文件。
第三,对外发布的数据集,除了模糊化处理外,只开放检测标注框,不开放更高清的原图。
这些处理既是对数据主体的保护,也能让数据集在合法合规的前提下被更多人使用。发布时在数据说明文档里明确标注图像的采集区域、采集时间和已执行的隐私处理流程,这是做开源数据集的基本素养。
6.4 数据集的后续演进方向
这个数据集后续还可以沿几个方向扩展。一是旋转框检测,如果目标是精确统计方阵中每个人的朝向和队列对齐程度,水平框不够用,可以参考DOTA数据集的做法做旋转框标注,用mmrotate训练旋转框检测模型。二是多目标跟踪,直接把单帧检测扩展成视频级跟踪,用于统计人流密度、路径分析。三是人群密度估计,在密集场景下直接回归密度图,比逐人检测在极密集场景下更稳定。
新出现的Mamba类backbone开始被引入YOLO系检测器,对这种长距离依赖、小目标占比极高的场景有一些理论优势,我也打算尝试复现对比一下效果。不过这类新结构工程化还不够成熟,谨慎起见,量产版本还是用YOLOv8最稳。
最后再分享一点个人经验:做这个航拍人体检测数据集和YOLO训练项目,最大的收获不是最终跑通的那个模型,而是明白了“数据集决定上限,模型只是逼近这个上限”。1562张图上我花在筛选、标注规范制定和清洗上的时间远比训练调参多,但每在数据质量上花一小时,后面调参就少踩一个坑。如果你现在准备做类似的项目,建议把70%的时间留给数据,模型和超参反而是最不需要焦虑的部分。