ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Faster R-CNN与YOLOv5飞机目标识别实战:从数据标注到部署

Faster R-CNN与YOLOv5飞机目标识别实战:从数据标注到部署 简介面向目标检测学习者与算法对比研究者这套工程以飞机目标识别为场景同时给出Faster R-CNN两阶段代表与YOLOv5单阶段代表的完整训练方案覆盖数据准备、模型训练与坐标结果输出。压缩包共616个文件、约26.36MB含220个jpg图像样本、99个xml与197个txt标签标注、41个Python脚本、36个yaml配置文件以及json、png、sh、Dockerfile等支持文件可支撑从数据预处理到模型评估的完整链路。其中xml与txt分别对应两种框架的标注格式yaml用于模型超参数与数据集路径配置py脚本涵盖训练、验证与推理环节便于直接运行或二次改造。目前已有808人学习下载适合需要对比两阶段与单阶段检测效果、快速搭建飞机目标识别实验的读者。包内还提供COCO样例图片与标签配合Dockerfile和训练脚本可复现两种算法的训练流程并观察它们在飞机目标上的识别精度与坐标回归差异为论文实验或课程设计提供实用参考。1. FasterRCNN与yolov5训练飞机目标识别项目从候选框到单阶段的工程适配飞机目标识别训练里最先翻车的不是模型而是标注格式不统一。同样是航拍图喂给Faster R-CNN用VOC XML喂给YOLOv5却要txt坐标还要归一化两个框架在一套数据上跑出两个结果最后对比根本没有意义。Faster R-CNN靠区域提议网络把候选框筛选两遍小目标和小交叠目标通常更稳YOLOv5用单阶段回归换取部署速度和易用性适合快速迭代。下面按数据准备、环境搭建、两套训练流程、评估导出的顺序讲清楚飞机目标识别项目从训练到落地需要动手调整的参数和容易踩的坑。这里写到的命令和配置都以单类“airplane”检测为前提如果你手里有多类目标改动范围也只在类别索引和data.yaml的names字段。2. 飞机目标识别的数据准备与FasterRCNN/yolov5训练环境搭建开始训练之前要把训练环境、标注格式和数据集划分三件事一次性理顺。飞机目标识别与通用检测最大的区别在于数据来源集中、目标尺度跨度大、背景纹理重复度高环境上的一个小冲突会在训练中期突然暴露出来排错成本比多花一天整理数据要高得多。2.1 图像目标识别标注把航拍原图变成VOC与YOLO txt常见做法是用LabelImg或X-AnyLabeling先拉框导出成Pascal VOC的XML再写脚本转成YOLO的txt。因为Faster R-CNN训练常用mmdetection它原生支持COCO和VOCYOLOv5则需要每张图对应一个同名的txt每行是“class cx cy w h”坐标都归一化到0到1。两类格式混在同一个训练流程里会非常麻烦所以我习惯在数据准备阶段就统一成“VOC为主、YOLO为副本”的目录结构。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text cls_id class_names.index(name) box obj.find(bndbox) x1, y1, x2, y2 [int(box.find(t).text) for t in (xmin, ymin, xmax, ymax)] cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines))这段逻辑说明先把XML里的bndbox字段读出来然后分别计算中心点与宽高相对于图片长宽的归一化值。class_names的顺序要固定比如[airplane]因为YOLO的类别索引是按配置文件里category顺序排列的。飞机目标的长宽比变化很大尤其是战斗机和民航机在转格式时不需要做任何旋转框处理水平框足够因为Faster R-CNN和YOLOv5默认都输出axis-aligned框。标注质量会影响后续所有环节螺旋桨、尾翼不应被包含在框内否则正样本的前景里掺入干扰边缘如果原图很大建议先切图再标注否则整机占整图比例过小时模型容易只学到背景。常见做法是按1024×1024或1536×1536滑窗切图重叠率设20%到30%避免飞机被切在边缘。切图后要同步修改XML中的宽高和坐标否则训练时会报坐标越界。2.2 用conda共用的FasterRCNN与yolov5训练环境配置两个框架对训练环境的要求很接近Linux机器、NVIDIA GPU、CUDA驱动、Python3.8。操作上先用conda创建独立环境避免mmdetection和yolov5的依赖互相打架尤其注意numpy与opencv的版本冲突。conda create -n plane python3.8 -y conda activate plane pip install torch1.10.0 torchvision0.11.0 pip install mmcv-full1.4.8 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt参数说明这里使用的PyTorch与CUDA版本组合是开源社区验证得比较多的组合实际版本要和你本机的CUDA驱动匹配。mmcv-full的安装必须和torch、cuda版本严格对应否则加载backbone时会出现算子编译错误。yolov5的requirements.txt会安装numpy、matplotlib、seaborn等依赖版本冲突最容易出现在opencv和numpy上。若启动时报“core dumped”先查看numpy是否被yolov5自动降级再手动固定成1.21.0版本。还要确认显卡驱动能支持当前CUDA版本运行nvidia-smi看顶部显示的CUDA Version是否不低于11.3。2.3 训练集与验证集划分按机场和飞行高度分层飞机目标识别和通用目标检测的一个明显区别是数据来源高度相关同一机场的重复架次会产生大量外观相似的样本。如果随机划分训练集和验证集验证集会包含和训练集几乎同角度的飞机mAP会虚高。更合理的做法是按拍摄时段或机场编号分组把整个连续片段分割到不同集合。下面是我常用目录组织方式数据集结构训练集验证集测试集FasterRCNN (mmdetection)annotations/voc格式XML JPEGImages独立VOC子目录独立VOC子目录yolov5images/train labels/trainimages/val labels/valimages/test labels/test公共字段同一批原图只改标注格式与训练集无重叠时段保留不同机场的样本划分代码可以用sklearn的GroupShuffleSplitgroup设为视频文件名或机场ID。核心逻辑是先让同一组数据只落入训练集或验证集不允许同一机场镜头中的相似帧同时出现在两边。建议单独保留约5%的样本作为最终模型对比这部分不参与任何超参数选择。如果数据量太少先用Faster R-CNN训练再用其结果做伪标注扩充YOLOv5训练集这类做法在工程上也常见。3. Faster R-CNN训练飞机目标识别mmdetection锚框与RPN配置Faster R-CNN在飞机目标识别上的优势是两阶段结构带来的高召回但默认参数并不是为小目标设计的。用mmdetection做训练时不需要改网络主体重点调整锚框、RPN采样和训练超参。否则一批小飞机全跑到负样本里mAP上不去。3.1 基于mmdetection的Faster R-CNN骨干网络与锚框设置Faster R-CNN网络结构由Backbone、RPN、ROI Head组成。默认ResNet50-FPN对多尺度目标表现平稳但飞机目标的长宽比经常在1:1到1:5之间默认anchor ratios[0.5,1.0,2.0]覆盖得不够。我一般把ratios扩展成[0.2,0.5,1.0,2.0,5.0]并缩小anchor scale以适配小目标。下面是一份直接能用的配置片段。# faster_rcnn_r50_fpn_plane.py _base_ [ ./_base_/datasets/voc0712.py, ./_base_/default_runtime.py ] model dict( typeFasterRCNN, backbonedict( typeResNet, depth50, out_indices(0, 1, 2, 3), frozen_stages1, norm_cfgdict(typeBN, requires_gradTrue), norm_evalTrue, init_cfgdict(typePretrained, checkpointtorchvision://resnet50)), neckdict( typeFPN, in_channels[256, 512, 1024, 2048], out_channels256, num_outs5), rpn_headdict( typeRPNHead, in_channels256, feat_channels256, anchor_generatordict( typeAnchorGenerator, scales[2, 4, 8, 16, 32], ratios[0.2, 0.5, 1.0, 2.0, 5.0], strides[4, 8, 16, 32, 64]), bbox_coderdict( typeDeltaXYWHBBoxCoder, target_means[0.0, 0.0, 0.0, 0.0], target_stds[1.0, 1.0, 1.0, 1.0])), roi_headdict( typeStandardRoIHead, bbox_roi_extractordict( typeSingleRoIExtractor, roi_layerdict(typeRoIAlign, output_size7, sampling_ratio2), out_channels256, featmap_strides[4, 8, 16, 32]), bbox_headdict( typeShared2FCBBoxHead, in_channels256, fc_out_channels1024, num_classes1)) )参数说明frozen_stages1只冻结第一层resnet权重能减少显存占用同时保留底层纹理学习。anchor strides必须和FPN输出的特征图步长对应strides[4,...,64]表示每个特征层级负责不同尺度目标把scales调低到[2,4,8,16,32]是为了让高分辨率特征层更多地响应在原始图上只有几十像素的小飞机。ratios从2.0扩到5.0匹配民航客机细长机身与机翼组合形成的3:1以上长宽框。3.2 训练Faster R-CNN学习率、训练轮数与损失曲线配置好模型后训练命令是python tools/train.py configs/plane/faster_rcnn_r50_fpn_plane.py \ --work-dir work_dirs/plane --gpus 1训练启动后日志会写到work_dirs/plane下的json文件。对于飞机数据集我建议把总epoch从默认12改成20到24因为单类目标的数据量往往只有几千张模型容易欠拟合而不是过拟合。学习率采用默认warmup策略时前500步只从0.002线性爬到0.02并配合step lr在16、22个epoch降低到原先的0.1倍。更实用的一个参数是img_scale原始航拍图分辨率偏高常见做法是设成多尺度训练显存吃紧时固定成较小尺寸并关闭多尺度。参数推荐值说明lr0.02单卡batch8时可用batch减半则lr按0.01起warmup_iters500避免第一轮梯度异常放大rpn.sampler.num256RPN采样前景背景框的总数roi_head.loss_cls.weight1.0分类损失权重fp16loss_scale512.显存不足再开梯度数值稳定设置batch大小的时候要保证每张图内至少包含若干个飞机框如果单张大图中飞机很少可调小batch但配合累积梯度把有效batch维持在8。否则BN统计量抖动损失曲线会像锯齿。训练过程里我不只看loss还看rpn_cls_loss是否持续下降。如果rpn分类损失降到一定范围就不再变化可以提前停止说明候选框已经提不出来足够多的正样本需要回去看锚框scales是否覆盖了飞机真实尺寸。3.3 小目标飞机识别RPN NMS阈值与RoI Align采样小目标在Faster R-CNN里通常最容易漏检。除了锚框设置外RPN的NMS阈值和RoI Align的采样点也会带来影响。对于小于32×32像素的飞机我把rpn的nms阈值从0.7降到0.6让更多重叠候选框进入后续精修代价是训练时间增加约20%。RoI Align的sampling_ratio设为2或4避免直接采样造成特征错位。代码中可以在train_cfg.rpn里配train_cfg dict( rpndict( assignerdict(pos_iou_thr0.7, neg_iou_thr0.3), samplerdict(typeRandomSampler, num256), allowed_border-1, pos_weight-1, nms_across_levelsFalse, nms_pre2000, nms_post1000, nms_thr0.6, max_num1000))这里nms_pre2000指每个特征层在NMS前最多保留2000个候选nms_post1000是NMS后保留数。飞机检测场景通常不需要把nms_pre调得更大反而会拖慢训练。验证阶段如果出现重复框考虑把test_cfg.rpn里的nms_thr也降到0.6而roi_head的nms阈值保留0.5。如果小目标仍然漏检另一个常见做法是在FPN输出中增加P2层即把backbone第一层特征也接入FPN这样能保留更高分辨率的特征图但显存占用会明显上升。4. YOLOv5训练飞机数据集超参数、增量训练与epoch搭配YOLOv5的训练流程与Faster R-CNN差异很大数据组织方式更简单但超参数影响面广。飞机目标识别用YOLOv5重点不是把网络跑通而是围绕anchor、mosaic和预训练权重做适配。4.1 yolov5训练自己的数据集目录结构、data.yaml与类别索引YOLOv5要求数据目录中images和labels分开且训练、验证、测试子目录平级。飞机项目我建议这样组织plane_dataset/ images/train/*.jpg images/val/*.jpg images/test/*.jpg labels/train/*.txt labels/val/*.txt labels/test/*.txt注意labels里txt文件名必须和jpg名一一对应。目录准备好后data/plane.yaml内容如下。path: ../plane_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: airplane参数说明path字段写相对yolov5仓库的路径train和val是path下的子目录nc是类别数。这里names索引必须从0开始和转出来的txt第一个数字一致。训练脚本会读取yaml内容并生成label缓存如果修改了names顺序必须重新生成缓存文件。飞机目标识别中如果使用COCO预训练权重需要注意COCO的类别与airplane并不完全对应但因为只是作为backbone初始化不需要修改权重结构。4.2 yolov5超参数调节与增量训练实战训练命令一般长得像这样python train.py --img 1024 --batch 16 --epochs 100 --data plane.yaml \ --weights yolov5s.pt --hyp hyp.scratch-low.yaml --cache ram --device 0每个参数的含义分别是--img控制输入分辨率航拍飞机多用1024或1280而不是默认640因为飞机小目标比例高--batch是总batch size多卡训练时除以卡数--cache ram把图像缓存到内存可以明显降低小batch下的磁盘IO瓶颈。--hyp指定超参数文件hyp.scratch-low.yaml专为小目标和低数据量场景准备它的mosaic、copy_paste设置更温和。对于几百张图片的训练集我建议用低增强文件不要一上来就开着heavy augmentation否则模型还没收敛就被增强噪声带偏。如果项目已经跑过一个版本新增了一批机场图像常见做法是增量训练而不是从零开始。命令里采用python train.py --img 1024 --batch 16 --epochs 30 --data plane.yaml \ --weights runs/train/exp/weights/best.pt --hyp hyp.scratch-low.yaml \ --freeze 10 --device 0参数说明--freeze 10表示冻结前10层对于yolov5s来说大致锁住了stem和前几个stage让模型在保留旧特征的前提下适配新场景。epoch不用太大30轮足够。这里需要警惕一个误区增量训练不是把数据合并后再从头训练而是基于旧权重微调如果旧权重已经对旧机场过拟合得很厉害新增数据占比又很小微调后可能发生灾难性遗忘需要把lr降到0.001并按新数据量重新评估验证集。超参数飞机小目标场景偏好通用目标场景原因img1024~1280640提升小目标的特征分辨率hyp中的mosaic0.81.0降低小目标拼接稀疏问题anchor自动计算可自定义默认飞机长宽比大自动计算更贴合epochs80~100300数据量小时防止过拟合4.3 数据被输入训练了几遍epoch、batch与mosaic的搭配YOLOv5训练里epoch和batch影响模型能看到多少遍数据。结合飞机数据量少、单类集中的特点epoch设100但batch设8到16比较常见。因为每一遍epoch都意味着模型完整看过一遍训练集100遍足够学出稳定特征再多容易在验证集mAP上看到明显过拟合。更需要注意mosaic增强会同时随机拼4张图相当于一个batch内替换了数据分布如果原图本身就是带标注的大图mosaic增强下小目标可能出现多次复制粘贴这时候关闭mosaic反而更稳。常见做法是在yolov5训练脚本里加一个“最后10轮关闭mosaic”的技巧python train.py --img 1024 --batch 16 --epochs 100 --data plane.yaml \ --weights yolov5s.pt --hyp hyp.scratch-low.yaml \ --cache ram --close-mosaic 10 --device 0约定俗成的经验是前90轮用mosaic让模型看到更多拼接样本最后10轮关闭mosaic让anchor回归和分类头从真实分布上做精调。训练结束后看runs/train/exp/results.csv里的mAP_0.5曲线如果最后阶段曲线仍在上升说明epoch不够如果val损失率先降后升说明出现过拟合优先减少epoch或把hyp里的hsv_h、degrees增强幅度调小。若发现某些飞机类型一直不出现先把类别样本数补齐再训练比单纯调超参数有效。5. 飞机目标识别模型评估、ONNX导出与TensorRT加速模型训练完先不要急着换框架重训。两个模型放在同一套测试集上跑一遍才能知道精度瓶颈在哪里。飞机目标识别的评估不只看mAP还要看漏检边界尤其是小目标和遮挡目标。5.1 用mAP和PR曲线对比Faster R-CNN与YOLOv5Faster R-CNN在mmdetection里运行test.pyYOLOv5用val.py二者都输出mAP和PR曲线。对比时保持输入图像分辨率一致否则小目标检测结果不可比。# Faster R-CNN python tools/test.py configs/plane/faster_rcnn_r50_fpn_plane.py \ work_dirs/plane/latest.pth --eval mAP --show-dir results/ # YOLOv5 python val.py --data plane.yaml --weights runs/train/exp/weights/best.pt --img 1024如果AP0.5高但AP0.75低基本可以断定是框偏大或偏小问题回去调anchor ratios比换模型更有效。Faster R-CNN在机翼与跑道边缘重叠时IoU更稳YOLOv5在高速推理时更高。实际项目里可以把两者做成级联YOLOv5做首轮粗筛Faster R-CNN对高置信候选做二次精修但耗时接近翻倍需要根据硬件条件权衡。5.2 把训练好的模型导出为ONNX并加入TensorRT工作流YOLOv5导出ONNX的命令是python export.py --weights runs/train/exp/weights/best.pt \ --img 1024 --include onnx --opset 12 --simplifyopset12是为兼容旧版TensorRT--simplify用onnx-simplifier去掉冗余节点。导出后检查ONNX文件里是否有Resize算子异常如果TensorRT报Unsupported ITensor建议升级TensorRT到8.5以上。Faster R-CNN导出ONNX不太直接通常走torch.jit.trace再配合TensorRT会更麻烦所以在部署需求明确时优先用YOLOv5。部署时用TensorRT FP16通常能让YOLOv5的推理速度提升一到两倍显存占用减少约一半。飞机识别落地时还要注意输入尺寸不要随意压缩航拍图像中的小飞机在640分辨率下可能只有几个像素直接丢掉参考价值。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进