ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

无人机航拍操场人体检测:自建数据集与YOLO训练部署全复盘

无人机航拍操场人体检测:自建数据集与YOLO训练部署全复盘 前阵子接了一个校园安防相关的小项目核心需求是“从航拍画面里准确找出操场上的人”。刚开始我以为这不就是常规的人体检测嘛直接拿现成的YOLO模型跑一跑就行。真正上手才发现无人机高空俯拍和普通监控平视完全是两个世界目标小、密度大、角度刁、还有一堆遮挡和阴影问题。最后只能老老实实自己构建一个“航拍校园操场人体检测数据集”用YOLO从零训练到落地部署。这篇就当作一次完整的技术复盘把我做的数据集方案、标注规范、训练调参和部署踩坑都写出来。如果你是做安防监控、活动人流统计或者只是想玩无人机视觉方向这篇内容可以直接拿来参考至少能帮你少走一半弯路。1. 项目需求分析与数据落地思路1.1 为什么航拍场景下的人体检测这么难普通人流检测用的监控摄像头视角基本是斜上方或平视人站在那里是完整的“直立人形”特征明显目标尺寸相对稳定。但无人机航拍操场视角是近乎垂直的俯视人形变成了一团深色或亮色的“块”头肩特征被压缩肢体轮廓几乎看不出来。我测试过直接用COCO预训练的YOLOv8模型推理中等高度30米下都能漏检一半以上。除了视角变化还有几个叠加难点。第一是目标极小一个1080P画面里30米高度下一个人可能只有20x40像素左右50米以上可能只有十几个像素小目标检测本身的特征提取就很吃亏。第二是密集遮挡课间操、运动会、足球赛人群挤在一起检测框互相重叠很多目标被部分遮挡模型容易只检出一个框而忽略旁边的人。第三是运动模糊足球、跑步场景下快门时间不够的话人就是一抹残影。第四是光照干扰中午阳光直射时人的影子非常长深色影子在YOLO眼里很接近人体前景经常被误检成行人。这些因素叠加起来直接导致两个结果公开数据集不够用通用预训练权重不够顶。所以这个项目的第一个关键决策就是必须自己做一个真实的、贴合校园操场场景的数据集而不是偷懒用VisDrone或者COCO凑合。1.2 自建数据集与公开数据集的差异对比我前期调研了三个公开数据集COCO平视为主、VisDrone航拍为主但城市场景多、CrowdHuman密集人群但监控视角。它们各有价值但对“校园操场”这个场景来说匹配度都不够高。数据集视角场景目标尺度是否适合本项目COCO平视/斜视日常生活中等/大不适合视角差异太大VisDrone航拍俯视城市街道、车辆行人小/极小场景不匹配操场过于空旷CrowdHuman平视/俯视商场/街头密集人群中等密度接近但视角偏监控自建数据集航拍俯视校园操场小/极小完全贴合公开数据集里标注质量参差不齐还有很多背景噪声比如车辆、树木、围栏等训练出来的模型往往分不清操场上的行人和树干。自建数据集虽然过程苦但可以精确控制三个维度一是拍摄角度完全贴合项目需求固定到垂直俯视和45度斜视两个范围二是背景种类可控主要就是草皮、塑胶跑道、水泥地、篮球场这几类三是标注规范可以统一针对遮挡、截断、密集目标制定专属规则让后续训练少出幺蛾子。最终我确定的思路是用自建数据集为主少量混合VisDrone和CrowdHuman的切图做预训练增强。这样既保持场景针对性又利用公开数据增强模型的泛化能力。后面实测证明混合部分公开数据确实能让模型在陌生校园场景下的误检率降低不少。2. 数据采集与标注规范设计2.1 航拍采集方案与实操记录数据采集是整个项目里最磨人的环节。我用的是一台搭载1英寸传感器、有效像素2000万级别的四旋翼无人机镜头焦距等效24mm拍摄模式设置为视频连续抓帧这样可以保证同一批画面有连续性也能在不同时间段获得不同光照的样本。飞行高度我实测了三个档位20米、35米、50米。20米高度的人体目标大检测容易但覆盖范围有限50米高度覆盖整个操场但人脸和肢体信息基本消失目标只有10-20像素35米是折中点既能覆盖大半个操场又能保留相对清晰的人形轮廓。训练集中这三个高度都要有因为实际部署时不会固定在一个高度巡逻。航线规划上我用了三条固定轨迹操场外圈环绕、对角线往复、中心区域悬停环拍。每次飞行持续8到10分钟视频帧率30fps保存为4K3840x2160。为了不污染数据我只在课间操、体育课、运动会三个典型时段采集在不同天气和日照条件下飞了四个上午和两个下午最终拿到原始视频累计约3小时。抽取帧率设置为每秒1帧避免相邻帧重复度太高这样处理完一共得到约8000多张原始图像。这里有个小经验不要每帧都存入训练集相邻帧背景几乎一样模型会严重过拟合到该场景按每秒抽1帧已经足够。2.2 数据清洗与样本统计抽完帧紧接着就要清洗。我先把肉眼清晰度明显不够、严重过曝或暗光下完全看不清人的图像剔除剩下约6400张有效图像。再检查画面重叠度对于同一批航迹中高度相似的画面只保留清晰度最高的一张最终剩下一共5600张可用样本。为了让训练集分布合理我按场景和高度粗略做了分层统计高度20米占30%35米占40%50米占30%。时间段上午占45%中午占15%下午占40%。人群密度单人样本占15%小群体2-5人占35%中等聚集6-15人占30%密集群体15人以上占20%。这个比例参考了真实使用场景。重点课间操和运动会是密集场景同时单人或小群体又考验模型在低密度下的精度所以两头都不能少。为保证多样性我又额外补充了一些雨后的潮湿地面、落叶覆盖的操场画面让人体与背景之间的对比度变化更大。2.3 标注工具与标注规则标注工具我用的是开源的LabelImg和Roboflow。LabelImg操作简单、本地化、生成Pascal VOC格式的XML后期转YOLO的txt很方便。Roboflow则用于在线协作和自动增强。标注类别只写一个person。不需要区分“学生”“老师”“奔跑者”因为底层视觉特征是一样的。多类别在人以群分的俯视画面里反而会带来不必要的边界模糊。标注规范是这次项目的重点。结合航拍俯视特征我定了三条原则。第一目标以像素为界小于8x8像素的人不标。8像素以下在缩放到训练尺寸时基本就成噪点了标了反而给模型错误信息。第二被遮挡超过70%的人不标只标可辨认主体边缘清晰的目标。操场人群密集时前面的人会挡住后面的人如果硬标被完全遮住的目标让模型学到“人体的一部分”也属于“人”会导致检测框位置‘漂移’。第三影子不做单独标注。影子虽然长得像人但它不等于人如果标注进去模型会把阴影当判别特征越学越偏。我要求标注框尽量贴合人体边界不要扩到影子范围同时通过数据增强增加不同方向影子的样本让模型学会忽略阴影。标注完成后统计5600张图像共标注了约48,000个人体实例平均每张约8.6个人密度最高的单张超过60个实例。这组数据基本覆盖了操场核心场景。2.4 数据增强与数据集划分因为目标检测对尺度变化非常敏感我先做了离线增强把原图的1/2和1/4分辨率各生成一份。这样模型训练时会看到不同尺度的人体提升泛化能力。在线增强方面我用了随机翻转、随机旋转90度/180度/270度、亮度扰动、对比度扰动、高斯模糊、运动模糊模拟。其中运动模糊对航拍场景特别重要无人机飞行快速移动时画面残影严重加入运动模糊增强后模型在飞行过快时的漏检率明显下降。数据集划分按照视频序列分组而不是随机抽帧。也就是说同一批航拍视频抽出的所有图像要么全部进训练集要么全部进验证集不能在train和val之间来回串。否则模型会通过记忆背景来达到高得分而不是真正学会检测人。最终训练集4800张验证集550张测试集250张比例约为8.510.5。测试集完全保留到模型最终评估时才用避免中间反复调参导致的‘数据泄漏’。3. YOLO模型选型与训练过程拆解3.1 模型选型YOLOv8n与YOLOv8m的对比模型选型我首先考虑的是部署平台的算力。这里最终要跑到无人机机载微型电脑或者地面服务器上功耗受限帧率要求30FPS以上。所以我只考虑YOLOv5s和YOLOv8家族的n/m两档。实测记录YOLOv8n参数量约3.2M计算量8.7GFLOPs中等batch下训练速度快推理在Jetson Orin Nano上能跑到30-40FPS。YOLOv8m参数量约25.9M计算量79.1GFLOPs精度提升明显但推理速度下降到15FPS左右。YOLOv5s和v8n差距不大但v8的Anchor-free设计对密集小人目标更友好。因为操场人体检测的目标非常小我最终采用了双模型策略训练阶段以YOLOv8m为主获取更高精度导出部署时同时导出n和m两个版本实际边缘设备上跑n版本来保障帧率地面工作站跑m版本做准确实时分析。这里额外说一句我用YOLOv8而不是YOLOv5的另一个原因是v8的检测头解耦成分类和回归两个分支在训练小目标时会稳定一些。如果模型过拟合明显随时可以回退到v5版本对比实验不花费太多时间。3.2 数据集格式与YAML配置手头的标注文件是XML格式需要先转成YOLO格式的txt每行“class_id x_center y_center width height”。我用一段Python脚本实现转换核心代码不复杂注意归一化的时候一定要用图像原始宽高不要在resize后才转换。import os import xml.etree.ElementTree as ET import cv2 def convert_xml_to_yolo(xml_file, img_width, img_height): tree ET.parse(xml_file) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name ! person: continue bndbox obj.find(bndbox) xmin int(float(bndbox.find(xmin).text)) ymin int(float(bndbox.find(ymin).text)) xmax int(float(bndbox.find(xmax).text)) ymax int(float(bndbox.find(ymax).text)) x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines # 批量处理脚本示意 for img_file in os.listdir(images): if not img_file.endswith(.jpg): continue img cv2.imread(os.path.join(images, img_file)) h, w, _ img.shape xml_file os.path.join(labels, img_file.replace(.jpg, .xml)) if not os.path.exists(xml_file): continue yolo_lines convert_xml_to_yolo(xml_file, w, h) with open(os.path.join(yolo_labels, img_file.replace(.jpg, .txt)), w) as f: f.write(\n.join(yolo_lines))在YOLOv8的训练配置文件里我新建了下面的data.yaml。这里要注意val路径必须和train路径分开不能指向同一个图像文件夹。path: /home/user/aerial_playground train: images/train val: images/val test: images/test names: 0: person建议在配置里增加一个scale参数用来控制训练时图像的缩放范围。我先把图像统一缩放到1280x1280但这对小目标来说显存压力大后来改成随机在640到1280之间缩放平衡了速度与效果。3.3 训练参数配置与多轮试错训练超参我参考了YOLOv8官方的默认设置但针对小目标做了调整。主要几个参数img: 1280。航拍俯视下人体目标很小用640输入会丢掉很多细节。实测用1280输入时mAP50能上涨5个百分点以上。batch: 8。这个和显存强相关我用的是单块24GB显存的服务器所以batch设成8。epochs: 200。数据量不算大200轮足够收敛设置早停策略patience20。lr0: 0.01。初始学习率太大容易震荡太小收敛慢0.01是YOLOv8默认值配合warmup效果比较好。mosaic: 0.5。Mosaic增强在密集小目标上很强大但使用率过高会导致模型过度拟合拼接特征我下调到0.5。mixup: 0.2。稍微加一点mixup可以进一步减小背景过拟合的风险。hsv_h/hsv_s/hsv_v: 默认即可我不希望颜色变换太剧烈免得模型从“草皮颜色”中找规律。训练过程分成了三阶段。第一阶段直接加载COCO预训练权重冻结前10层只训练检测头跑30轮得到一个基础模型做可行性验证。第二阶段全网络微调跑150轮直到mAP50-95不再上升。第三阶段用验证集效果最好的权重做推理测试根据badcase再回填补充数据微调。这里要提醒一个很容易踩的坑坐标和小目标mAP不能只盯一个。我第一版模型mAP50到了0.92但是mAP50-95只有0.48说明小目标检测框定位不准确框面积和真实目标重叠度低。后来把输入分辨率提升到1280并开启rectTrue让图像按长宽比BatchingmAP50-95涨到了0.60才勉强满足要求。3.4 损失函数分析与收敛诊断YOLOv8的损失由三部分组成分类损失BCE、回归损失CIoU或DFL、以及分布在检测头里的DFL损失。对航拍小目标来说回归损失的影响最大。我遇到过验证集loss怎么都降不下去训练集loss却正常的情况这基本就是过拟合的前兆。此时第一反应不是去调超参而是去看是否是训练集和验证集分布差异太大往往验证集里混入了与训练集背景差别巨大的场景比如教学楼上空旷的走廊模型无法泛化。收敛诊断我主要关注三张曲线图loss曲线、precision曲线、recall曲线。loss曲线必须平滑下降而不是锯齿状如果训练Loss下降但验证Loss上涨说明模型开始“背数据”。Precision和Recall的平衡需要看操场场景的要求漏检一个在跑道上摔倒的人比多报一个树影更严重所以我会刻意调高recall的权重降低置信度阈值到0.25左右宁可误检也不要漏检。一句话总结训练中期如果损失突然飙升一般不是超参问题而是数据里出现了不良标注比如有人站的位置被标成了两个人或者增强策略过于激进我的做法是终止训练检查最近20张增强后的图像往往一眼就能看出问题。4. 模型评估与部署实践4.1 评估指标与场景化取舍评估时常规指标都要看mAP50、mAP50-95、precision、recall、F1-score和推理FPS。我最终模型的验证集结果如下模型输入尺寸mAP50mAP50-95推理速度Orin NanoYOLOv8n6400.870.5542 FPSYOLOv8n12800.910.6125 FPSYOLOv8m12800.930.6712 FPS实测下来我并没有把mAP当作唯一标准。因为校园操场场景的目标是人漏检的代价远高于误检所以我在置信度阈值上做了偏移。实际使用时将默认0.25置信度降到了0.15配合NMS阈值0.6。这个调整让召回率从0.83提升到0.92代价是误检率上升了一点但通过部署端规则比如“连续5帧都出现的候选框才上报”就能把假阳性压制在可接受范围。另外还要看置信度分数分布。我跑了一批测试视频后统计发现模型对20米高度下人的置信度普遍在0.7以上50米高度下则在0.2到0.5之间徘徊。这个分布说明模型在对极小目标的判别上仍然偏弱所以部署时需要结合“运动目标检测”和“区域密度估计”来辅助不能完全依赖单帧检测。4.2 错误分析混淆矩阵与Bad Case混淆矩阵是必须看的。我的结果里最大的误检来源是“树影”和“跑道上的标记线”。塑胶跑道的红色和环绕白线在俯视画面中非常像人形轮廓特别是在强光下白线和深色草皮的对比度很高。模型会把长条形的亮斑识别成人。我又抽取了100个false positive样本逐一检查大概分布如下误检类型占比影子/树影35%跑道标记线/地面纹理28%篮球架/立柱15%飞鸟12%其他10%然后我做了两件事一是在训练集中加入“背景类”负样本比如没有行人的空旷操场、只有树影的空地二是把NMS的类别无关性打开减少几何形状相似目标之间的重叠框保留。同时把灰色增强应用加大让模型更关注“人形”而不是“色块”。这里要特别强调YOLO本身不做“是什么”的语义理解它只会学纹理和形状模式所以影子、地面标记、飞鸟是经典误检源。最有效的方法还是数据层解决而不是调后处理。4.3 导出ONNX与边缘设备部署训练完成后我使用ultralytics库的export接口导出ONNX再用TensorRT生成engine文件。导出时注意把opset设为12以上同时将输入尺寸固定为部署端使用的尺寸。yolo export modelruns/detect/train/weights/best.pt formatonnx dynamicTrue部署端选的是Jetson Orin Nano 8GB版本自己写了路由API接收无人机推流视频帧然后跑YOLOv8推理。这里有个小坑TensorRT只有在固定输入尺寸时才能达到最大优化所以我将输入固定为1280x1280虽然会浪费部分算力但精度更好。另外无人机画面实时推流帧率可能不恒定我用了一个队列缓存机制来平滑视频流检测线程和推流线程解耦这样在信号抖动时不容易丢帧。实测部署后平均推理时间约为38msYOLOv8nTensorRT FP16达到25FPS满足实时性需求。至于YOLOv8m的版本我放在地面服务器上用来处理批量离线回放毕竟边缘设备跑m版本太吃力。5. 实战中的高频问题与排查方法5.1 高度变化导致的目标尺寸剧变原以为模型能适应所有高度实际测试发现同一个权重在20米和50米高度上性能差异巨大。原因是训练集中的目标尺寸分布不均匀20米高度下平均人体框高约48像素50米高度下只有20像素左右模型对20像素以下的目标几乎没学到有效特征。解决办法有两个路径一是把训练图像分辨率再抬高到1600x1600让50米高度下的人体框也能达到30像素以上二是采用“切图检测”策略在推理时把原始大图切成多个1024x1024的patch分别做检测再合并。后者会显著增加推理时间但在无人机单帧画幅小、不算严格实时要求的任务下非常可靠。切图合并时还要注意相邻patch边界的目标会被切断导致漏检。我在合并时用了一个重叠裁剪策略patch之间重叠128像素重叠区域的检测框用NMS合并有效解决了边界断裂问题。5.2 训练时显存溢出与中断恢复初期训练时在1280分辨率下batch设为16直接爆显存后来降到8才能正常运行。如果你只有8GB显存建议用梯度累积或者直接调低输入尺寸到960不一定非要1280。训练中断也遇到过两次。YOLOv8支持断点续训只需要保持原有训练目录把resumeTrue传入即可。但如果训练中断后动了数据集或配置文件就会导致加载失败所以中断后千万不要动数据目录结构。我个人习惯是训练脚本里固定写死路径不给中断恢复留出错的机会。显存不足还有另一个隐藏坑Mosaic增强会在batch内拼4张图造成有效batch内的目标数量成倍增长显存占用远高于普通增强。这种情况下建议关闭Mosaic或者把mosaic从0.5降到0.2。这种操作对mAP影响不大但显存压力小很多。5.3 过拟合与泛化能力不足模型在验证集上mAP很高但换到另一个没有采集过的学校操场很快掉点。这个问题的根源是训练集背景太单一。我的解决方案是手动加入公开航拍数据中非操场场景域的图像比如停车场、屋顶球场、举行活动的广场让模型见过更多俯视结构的“背景纹理”。另外加入背景负样本能有效压低误检做法就是标注为background类别。在YOLO的单一类别模型里没有背景类一说而是把没有目标的空图像直接放进训练集让模型学习“这里没有物体”。我往训练集里塞了约300张完全没有人、但背景纹理和操场相似的图像误检率下降了近四个百分点。5.4 小目标漏检的紧急处理方案如果已经没有重新训练的时间最简单的提升小目标检测能力的方法是降低NMS阈值和置信度阈值同时在推理前对图像做一次轻度锐化增强。锐化能提升边缘对比度让模型更容易捕捉人形轮廓但记住不要做高锐化否则草皮纹理变成噪声一样反而扰乱了检测。还有一个小技巧训练时把scale设置成0.5让模型在训练数据里看到更多随机缩放的人体增强尺度鲁棒性。我后来把这两个trick同时用上效果虽然不如重新训个高分辨率模型但在紧急demo中足够用了。6. 数据与模型的版本管理经验6.1 数据迭代和模型回滚在项目推进中我一直在往数据集里补充新的场景和新的badcase数据。每当加数据后我都会把数据集整体打一个版本号比如v1.3.0_20250420同时保留对应的模型权重和配置文件。因为我发现偶尔新数据的加入会造成模型在某些旧场景上的精度回退而有了版本号之后随时可以回退到上一次表现更好的组合。实际操作中我用一个简单的目录结构管理所有产物dataset/ v1.0.0/ v1.1.0/ v1.2.0/ weights/ yolov8n_v1.2.0.pt yolov8m_v1.2.0.pt yolov8n_v1.3.0.pt runs/ detect_v1.3.0/每次训练后我会在训练结束后自动生成一个包含训练曲线、测试集badcase截图、数据增强样例的html报告这样下次回溯时不需要重新翻原始文件就能知道当前模型的能力边界在哪里。6.2 团队协作时的数据一致性如果你不是单人开发数据一致性是个非常容易被忽视的坑。我第一次和另一个标注小伙伴协作时他用的是另一个标注软件导出的格式基本一致但边界框坐标在转换时没有保留归一化长度导致一批标注框偏移了10%左右。后来我统一要求所有标注数据最终必须通过同一套转换脚本生成txt并在合并进主数据集前做一次自动校验——比如检查标注框是否超出图像范围、宽高是否为零等。还有一个团队协作细节不要直接修改主数据集文件而是先在各自分支上完成标注和初步训练再合并。合并时用文件名去重避免重复图像被重复统计否则验证集的划分就乱了。这些都是我自己踩过的坑写出来给后来人当参考。7. 个人总结与一些小建议回头来看这个项目最大的收获其实不是把YOLO用的多熟练而是真正意识到“数据集的构建质量决定模型的天花板”。航拍校园操场人体检测的痛点不在模型结构上而在于目标尺度、视角和背景的复杂组合。我之前用公开数据集随便跑出来的模型在自建数据集上一测就露馅后来花大力气把数据做扎实效果立竿见影。如果你想复现这个项目我最想掏心窝子给你的建议是标注规范一定要在第一次飞行采集后就开始制定不要等全部图像拍完再标因为你会在第一批标注里发现大量“原来还会有这种情况”的意外场景。先把一批300张图标完试训一版再回头调整标注规范这样后续4000张的标注效率会高很多。航拍校园操场的检测也许只是许多落地场景中的一个细分支但它的方法和经验完全可以复用到工地安全识别、体育赛事人流分析、大型集会密度估算这些相近领域。以后如果再做一个类似的场景我大概率会复用这套数据管线只是换一批采集地点和标注类别而已。
返回列表