ARTICLE DETAIL

资讯详情

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

飞机目标检测双模型协同:YOLOv5与Faster R-CNN坐标对齐实战

飞机目标检测双模型协同:YOLOv5与Faster R-CNN坐标对齐实战 简介本资源是一个面向计算机视觉初学者与进阶学习者的飞机目标识别实战项目聚焦于Two-stage与One-stage两类主流检测范式对比实践适用于课程设计、毕业设计及小规模航拍图像分析场景。项目完整集成Faster R-CNN基于PyTorch实现与YOLOv5v6.1版本两套训练流程涵盖数据预处理、模型配置、训练脚本、评估工具及Docker容器化部署方案。压缩包共616个文件含220张飞机标注图像jpg、197份YOLO格式标签txt、99份PASCAL VOC格式标注xml、41个核心Python脚本含train/val/inference模块、36个配置文件yaml以及Dockerfile、JSON类别映射、Shell自动化脚本等总大小26.36MB。目前已有809人学习下载提供开箱即用的目录结构、跨框架统一数据组织方式、坐标识别关键日志输出示例及常见训练异常排查提示助读者深入理解目标检测算法差异与工程落地细节。1. 飞机目标识别实战为什么用 Faster R-CNN 和 YOLOv5 双模型跑同一套数据反而比单模型更稳你手头有一批航拍图像——机场停机坪、跑道边缘、低空巡检视频帧要自动框出每一架飞机的位置和类别客机/货机/直升机。这时候直接上 YOLOv5快是快但小尺寸飞机32×32 像素、密集停放的机翼遮挡、云层阴影下的灰度失真容易漏检或框偏换 Faster R-CNNmAP 高得漂亮可推理一帧要 300ms根本没法嵌入无人机图传链路做实时反馈。这不是“选哪个更好”的问题而是场景倒逼你必须同时跑两个模型YOLOv5 负责前端快速初筛Faster R-CNN 负责后端高精度复核。本项目不是教你怎么调参而是把一套真实飞机数据含 COCO 格式标注、Docker 封装环境、bus.jpg 这类典型干扰样本拆开揉碎——告诉你怎么让两个模型在同一个训练 pipeline 里不打架、不抢显存、不互相污染标签格式尤其解决「YOLOv5 输出的 xywh 坐标怎么喂给 Faster R-CNN 的 ROI Head」这个没人明说但人人踩坑的坐标对齐黑匣子。适合正在做低空安防、机场智能调度、无人机巡检的工程师也适合想拿真实小目标数据练手的 CV 新人。2. 模型选型与数据准备为什么飞机检测必须用 COCO 格式 自定义类别映射表2.1 两类算法对数据结构的底层诉求差异Faster R-CNN 和 YOLOv5 表面都是目标检测但数据流走向截然不同YOLOv5 要求.txt标签文件每张图对应一个同名.txt每行class_id center_x center_y width height归一化到 [0,1]坐标系原点在左上角宽高是相对图宽高的比例Faster R-CNNPyTorch 官方实现要求 COCO JSON所有标注打在一个instances_train2017.json里annotations字段里bbox是[x_min, y_min, width, height]像素绝对值坐标系原点也在左上角但不归一化且categories必须带id和name映射。提示很多人卡在第一步——把 YOLO 标签转 COCO 后Faster R-CNN 训练时 bbox 全飘到图外。原因就是没注意 YOLO 的center_x是中心点而 COCO 的x_min是左上角。强行用(center_x - width/2)转换错YOLO 的width是相对值COCO 的width是像素值必须先乘原图宽高再转换。2.2 本项目数据集结构解析从 bus.jpg 到 COCO_val2014_000000475710.jpg 的真实含义项目目录里混着bus.jpg和一堆COCO_val2014_xxx.jpg这不是乱放——它暴露了数据构建的真实路径bus.jpg干扰样本negative sample。飞机检测最怕把机场大巴、油罐车、廊桥当飞机框出来。本项目特意保留这张图用于测试模型泛化性后续会放进val集但不打标签COCO_val2014_*.jpg正样本裁剪源。这些文件名来自 COCO val2014 子集但实际内容已被人工筛选重标注——只保留含飞机的图像并用labelImg或CVAT重新画框类别统一设为airplaneid1.DS_StoreMac 系统生成的元数据训练前必须删干净否则某些 DataLoader 会报OSError: [Errno 20] Not a directoryDockerfile关键它声明了torch1.13.1cu117和torchvision0.14.1这两个版本与官方 Faster R-CNN 实现强绑定——用torch 2.x会导致roi_align函数签名不匹配训练直接崩。2.3 构建双模型兼容的数据目录树一个文件夹两套读取逻辑最终数据组织必须满足 YOLOv5 和 Faster R-CNN 同时加载目录结构如下绝对路径以/data/airplane为例/data/airplane/ ├── images/ │ ├── train/ │ │ ├── COCO_val2014_000000475710.jpg │ │ └── ... │ └── val/ │ ├── bus.jpg # 无标签仅用于验证泛化 │ └── COCO_val2014_000000052412.jpg ├── labels/ # YOLOv5 专用 │ ├── train/ │ │ ├── COCO_val2014_000000475710.txt │ │ └── ... │ └── val/ │ └── COCO_val2014_000000052412.txt └── annotations/ # Faster R-CNN 专用 ├── instances_train2017.json └── instances_val2017.json注意instances_train2017.json中images字段的file_name必须严格匹配images/train/下的文件名含大小写annotations中的image_id必须与images列表索引一致。我一般用coco_utils.py脚本校验from pycocotools.coco import COCO coco COCO(annotations/instances_train2017.json) print(fTotal images: {len(coco.getImgIds())}, Total annotations: {len(coco.getAnnIds())})2.4 类别映射表为什么 airplane 的 id 必须是 1且不能跳号YOLOv5 的data/airplane.yaml和 Faster R-CNN 的 COCO JSON 必须共享同一套类别 ID# data/airplane.yaml train: ../images/train val: ../images/val nc: 1 names: [airplane] # 顺序即 idairplane - 0但 Faster R-CNN 的 COCO JSON 要求categories中id从 1 开始COCO 规范且name必须小写{ categories: [ { id: 1, name: airplane, supercategory: vehicle } ] }矛盾点来了YOLOv5 默认class_id0Faster R-CNN 期望category_id1。解决方案不是改模型代码而是在 YOLOv5 的dataset.py里加一行偏移# yolov5/utils/datasets.py 中的 LoadImagesAndLabels.__getitem__ labels[:, 0] 1 # 把 0→1对齐 COCO id这样 YOLOv5 训练时 loss 计算仍按 0-based但输出的 class_id 已对齐 COCO后续做模型融合时 bbox 坐标和类别能直接拼接。3. Docker 环境封装与双模型训练流程如何避免 CUDA 版本地狱3.1 Dockerfile 深度解析为什么 base image 选 ubuntu20.04 而非 22.04项目提供的Dockerfile关键段FROM nvidia/cuda:11.7.1-devel-ubuntu20.04 RUN apt-get update apt-get install -y python3-pip python3-dev RUN pip3 install torch1.13.1cu117 torchvision0.14.1 --extra-index-url https://download.pytorch.org/whl/cu117 COPY requirements.txt . RUN pip3 install -r requirements.txt WORKDIR /workspace COPY . .nvidia/cuda:11.7.1-devel-ubuntu20.04Ubuntu 20.04 内核5.4与 CUDA 11.7 兼容性经过 NVIDIA 官方认证Ubuntu 22.04 默认内核 5.15部分驱动模块如nvidia-uvm在容器内加载失败导致torch.cuda.is_available()返回 Falsetorch1.13.1cu117这是 PyTorch 官方为 CUDA 11.7 编译的最后一个稳定版torchvision0.14.1与其 ABI 完全匹配若强行升级到torch 2.0torchvision.models.detection.fasterrcnn_resnet50_fpn的backbone会因FrozenBatchNorm2d接口变更而报AttributeError: FrozenBatchNorm2d object has no attribute num_batches_tracked。3.2 双模型训练命令如何用同一份数据启动两个独立进程进入容器后分别执行# 启动 YOLOv5 训练使用默认超参数但修改 epochs cd yolov5 python train.py \ --data ../data/airplane.yaml \ --cfg models/yolov5s.yaml \ --weights \ --batch-size 16 \ --epochs 100 \ --name yolov5s_airplane \ --project /workspace/runs # 启动 Faster R-CNN 训练PyTorch 官方 detection API cd .. python train_faster_rcnn.py \ --data-path ../data \ --output-dir /workspace/runs/faster_rcnn \ --device cuda \ --epochs 25 \ --lr 0.02 \ --momentum 0.9 \ --weight-decay 0.0001 \ --lr-step-size 15 \ --lr-gamma 0.1逻辑说明train_faster_rcnn.py是 PyTorch 官方torchvision/references/detection的精简版已适配本项目数据路径。关键参数--lr-step-size 15表示第 15 轮 epoch 后学习率 ×0.1这对飞机这类小目标收敛至关重要——前期用大 lr 快速定位后期用小 lr 精修 bbox 四角。3.3 训练日志监控如何从 tensorboard 判断模型是否真正收敛YOLOv5 和 Faster R-CNN 的 loss 曲线形态完全不同不能简单比数值指标YOLOv5yolov5sFaster R-CNNResNet50-FPN判定标准总 loss从 5.0→1.2 波动下降从 1.8→0.4 平缓下降YOLOv5 允许小幅震荡因多尺度预测Faster R-CNN 必须单调下降否则检查 ROI Align 是否被破坏box_loss占总 loss 40%~50%占总 loss 60%~70%飞机目标 box_loss 长期 0.3说明 anchor 尺寸未适配需改models/yolov5s.yaml中anchors或 Faster R-CNN 的rpn_anchor_generatorcls_loss早期下降快后期趋平始终低于 box_loss若 cls_loss box_loss大概率是类别不平衡正负样本比 1:3需在train_faster_rcnn.py中调整box_sampler的positive_fraction实操技巧用tensorboard --logdir/workspace/runs同时看两个模型重点关注Precision/Recall曲线。飞机检测的 Recall0.5 应 ≥0.85否则说明小目标漏检严重——此时不要调 learning rate先检查images/train/下是否有尺寸 64px 的飞机图若有必须用mosaic1YOLOv5或min_size320Faster R-CNN增强小目标可见性。3.4 模型权重导出为什么 .pt 和 .pth 不能混用YOLOv5 导出的是yolov5s_airplane/weights/best.pt本质是torch.save({ model: model.state_dict(), ... })含模型结构权重训练状态Faster R-CNN 导出的是faster_rcnn/model_0025.pth仅含model.state_dict()无优化器状态。部署时必须剥离# YOLOv5 加载正确 model torch.load(yolov5s_airplane/weights/best.pt, map_locationcpu)[model].float() model.eval() # Faster R-CNN 加载正确 model get_model_instance_segmentation(num_classes2) # background airplane checkpoint torch.load(faster_rcnn/model_0025.pth, map_locationcpu) model.load_state_dict(checkpoint[model]) # 注意 key 是 model model.eval()错误示范直接torch.load(model_0025.pth)会得到 dict但 key 是model而非state_dict导致load_state_dict()报错KeyError: backbone.body.layer1.0.conv1.weight。4. 坐标识别与后处理如何把 YOLOv5 的 xywh 和 Faster R-CNN 的 [x,y,w,h] 对齐成同一套物理坐标4.1 坐标系本质差异为什么直接拼接 bbox 会导致框偏移 20 像素YOLOv5 输出归一化[0.452, 0.318, 0.124, 0.086] # cx, cy, w, h (relative to img_w, img_h)Faster R-CNN 输出像素值[215.3, 182.7, 148.2, 97.5] # x_min, y_min, w, h (absolute pixel)表面看只是单位不同实则隐藏三个陷阱原点偏移YOLOv5 的cx,cy是中心点Faster R-CNN 的x_min,y_min是左上角 → 转换必须x_min cx - w/2归一化基准YOLOv5 的w,h是相对于原始图宽高但训练时用了imgsz640推理时若输入图是 1280×720必须用原始尺寸反算插值误差Faster R-CNN 的 ROI Align 在 feature map 上采样坐标有 ±0.5 像素抖动YOLOv5 的 grid cell 是整数对齐。4.2 统一坐标转换脚本用 OpenCV 做亚像素级对齐import cv2 import numpy as np def yolo_to_abs(bbox_yolo, img_shape): Convert YOLO format [cx,cy,w,h] to absolute [x_min,y_min,x_max,y_max] h, w img_shape[:2] cx, cy, bw, bh bbox_yolo x_min max(0, int((cx - bw/2) * w)) y_min max(0, int((cy - bh/2) * h)) x_max min(w, int((cx bw/2) * w)) y_max min(h, int((cy bh/2) * h)) return [x_min, y_min, x_max, y_max] def faster_rcnn_to_abs(bbox_frcnn, img_shape): Faster R-CNN outputs [x_min,y_min,w,h] in pixels, but may be float x_min, y_min, bw, bh bbox_frcnn x_max min(img_shape[1], int(x_min bw 0.5)) # 0.5 for round y_max min(img_shape[0], int(y_min bh 0.5)) return [int(x_min), int(y_min), x_max, y_max] # 实际使用对同一张图做双模型推理后统一转为 int 坐标 img cv2.imread(test.jpg) yolo_boxes model_yolo(img) # list of [cx,cy,w,h] frcnn_boxes model_frcnn([img])[0][boxes].cpu().numpy() # [x_min,y_min,x_max,y_max] abs_yolo [yolo_to_abs(b, img.shape) for b in yolo_boxes] abs_frcnn [faster_rcnn_to_abs(b, img.shape) for b in frcnn_boxes]参数说明yolo_to_abs中max(0, ...)和min(w, ...)防止坐标越界faster_rcnn_to_abs中0.5是 OpenCV 坐标取整惯例避免因浮点误差导致x_max x_min。4.3 双模型结果融合策略NMS 之外的置信度加权法单纯用cv2.dnn.NMSBoxes会丢弃大量重叠框尤其对密集停放的飞机。本项目采用置信度加权中心点融合def fuse_boxes(boxes1, scores1, boxes2, scores2, iou_thresh0.5): boxes: list of [x1,y1,x2,y2], scores: list of float Returns fused boxes with weighted center and union area all_boxes boxes1 boxes2 all_scores scores1 scores2 fused [] while all_boxes: # pick highest score idx np.argmax(all_scores) best all_boxes[idx] best_score all_scores[idx] # find overlaps ious [iou(best, b) for b in all_boxes] overlap_idxs [i for i, iou_val in enumerate(ious) if iou_val iou_thresh] # weighted center: (score * center_x, score * center_y) / sum(scores) centers [] weights [] for i in overlap_idxs: x1, y1, x2, y2 all_boxes[i] cx, cy (x1x2)/2, (y1y2)/2 centers.append([cx, cy]) weights.append(all_scores[i]) if len(weights) 1: weighted_center np.average(centers, weightsweights, axis0) # expand box to cover all overlapping boxes x_coords [b[0] for b in [all_boxes[i] for i in overlap_idxs]] y_coords [b[1] for b in [all_boxes[i] for i in overlap_idxs]] x2_coords [b[2] for b in [all_boxes[i] for i in overlap_idxs]] y2_coords [b[3] for b in [all_boxes[i] for i in overlap_idxs]] fused_box [ min(x_coords), min(y_coords), max(x2_coords), max(y2_coords) ] fused.append((fused_box, best_score)) else: fused.append((best, best_score)) # remove processed boxes for i in sorted(overlap_idxs, reverseTrue): all_boxes.pop(i) all_scores.pop(i) return fused逻辑说明该函数不依赖 OpenCV 的 NMS 实现而是手动计算 IoU 并按置信度加权中心点。对飞机检测特别有效——当两架飞机机翼轻微重叠时NMS 会删掉低分框而加权融合能生成一个覆盖两机的合理大框后续再用cv2.minAreaRect提取旋转矩形适配斜停飞机。4.4 坐标识别精度验证用 OpenCV drawContours 标注真值 vs 预测def visualize_pred_vs_gt(img_path, pred_boxes, gt_boxes, save_path): img cv2.imread(img_path) # Draw ground truth (green) for box in gt_boxes: x1, y1, x2, y2 map(int, box) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, GT, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) # Draw prediction (red) for i, box in enumerate(pred_boxes): x1, y1, x2, y2 map(int, box) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, fPred{i}, (x1, y1-20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 1) cv2.imwrite(save_path, img) # 使用示例 visualize_pred_vs_gt( COCO_val2014_000000475710.jpg, abs_yolo, # 转换后的 YOLO 框 [[215,182,363,279]], # 手动标注的真值x_min,y_min,x_max,y_max debug_viz.jpg )效果生成debug_viz.jpg绿色框是人工标注真值红色框是模型预测。重点观察小飞机如远处塔台旁的直升机是否被框住——若真值框内无红框说明 recall 不足若红框远大于真值框说明 precision 低。这是比 mAP 更直观的坐标识别诊断法。5. 避坑指南飞机目标识别中 4 个血泪经验换来的硬核排错清单5.1 现象YOLOv5 训练 loss 突然爆炸从 1.2 跳到 15.0之后无法收敛原因Dockerfile中pip3 install -r requirements.txt安装了新版opencv-python4.8其cv2.resize默认插值方式从INTER_LINEAR改为INTER_AREA导致训练时图像缩放失真小飞机特征丢失。解决在yolov5/utils/datasets.py的LoadImagesAndLabels.__init__中强制指定插值self.img_size img_size self.interpolation cv2.INTER_LINEAR # 显式声明并在LoadImagesAndLabels.__getitem__的 resize 处改为img cv2.resize(img, (w, h), interpolationself.interpolation)5.2 现象Faster R-CNN 在 val 集上 AP0但 train 集 loss 正常下降原因instances_val2017.json中annotations的image_id与images列表索引不一致。例如images[0]是bus.jpg无标注但annotations[0]的image_id1导致 DataLoader 加载bus.jpg时找不到对应 annotation返回空 bboxAP 计算时除零。解决用以下脚本校验并修复import json with open(annotations/instances_val2017.json) as f: data json.load(f) img_ids [img[id] for img in data[images]] for ann in data[annotations]: if ann[image_id] not in img_ids: print(fInvalid image_id {ann[image_id]} in annotation {ann[id]}) # 修复确保每个 ann[image_id] 都在 img_ids 中否则删除该 ann5.3 现象YOLOv5 推理时 detect 出bus.jpg里的大巴车但 Faster R-CNN 对同一图返回空列表原因bus.jpg被放入images/val/但未在instances_val2017.json中声明正确YOLOv5 的val模式会强制预测所有图而 Faster R-CNN 的CocoEvaluator只处理有 annotation 的图。这本身不是 bug但若想让 Faster R-CNN 也输出bus.jpg的「无目标」结果需在train_faster_rcnn.py中修改 evaluator# 注释掉这一行 # coco_evaluator.update(output) # 改为 if len(output) 0: coco_evaluator.update(output) else: # 添加空检测结果避免 evaluator 报错 coco_evaluator.update([{boxes: torch.empty(0, 4), labels: torch.empty(0, dtypetorch.int64), scores: torch.empty(0)}])5.4 现象双模型融合后同一架飞机被框出两个极近似的框IoU0.95NMS 无法合并原因YOLOv5 和 Faster R-CNN 的 anchor 设计不同——YOLOv5 的yolov5s.yaml中anchors最小是10,13Faster R-CNN 的rpn_anchor_generator默认最小 anchor 是32导致对小飞机YOLOv5 输出多个高分小框Faster R-CNN 输出一个中等框二者中心点偏差 5 像素但 IoU 计算因浮点误差略低于阈值。解决在fuse_boxes函数中增加「中心点距离阈值」# 在 iou 计算前加 dist np.sqrt((cx1-cx2)**2 (cy1-cy2)**2) if dist 10: # 中心点距离 10 像素强制视为同一目标 iou_val 1.010 像素是经验值——对应 1280p 图像中约 0.5° 视场角足够覆盖飞机检测的定位抖动。6. 进阶技巧用 Faster R-CNN 的 ROI 特征图反哺 YOLOv5 的小目标检测6.1 为什么需要 ROI 特征图YOLOv5 的 neck 层对小飞机特征表达不足YOLOv5 的 PANet 结构在P3层stride8负责小目标但飞机在航拍图中常小于 20×20 像素P3特征图分辨率仅80×45输入 640×480单个 cell 覆盖 8×8 像素无法精确定位机头。而 Faster R-CNN 的 ROI Align 从P2层stride4提取 7×7 特征分辨率更高且 ROI 区域是动态裁剪能聚焦小目标局部纹理。6.2 特征蒸馏流程把 Faster R-CNN 的 ROI 特征注入 YOLOv5 的 Detect 层核心思路用 Faster R-CNN 的backbone fpn提取特征对 YOLOv5 的P3层做通道注意力加权# 修改 yolov5/models/yolo.py 的 DetectionModel.forward def forward(self, x): x self.backbone(x) # x is list: [P3, P4, P5] # Extract ROI features from Faster R-CNN backbone (same weights) with torch.no_grad(): frcnn_feats self.frcnn_backbone(x[0]) # x[0] is C3, feed to ResNet50 # Upsample frcnn_feats to P3 size and apply attention att_map F.interpolate(frcnn_feats, sizex[0].shape[-2:], modebilinear) att_map self.attention_head(att_map) # 1x1 conv → sigmoid x[0] x[0] * att_map x[0] # residual connection x self.neck(x) return self.head(x)参数说明self.frcnn_backbone是预加载的 Faster R-CNN ResNet50 权重不参与 YOLOv5 反向传播attention_head是nn.Sequential(nn.Conv2d(256, 1, 1), nn.Sigmoid())将 ROI 特征压缩为 1-channel 注意力图乘到 YOLOv5 的P3上。实测在COCO_val2014_000000232931.jpg含 3 架远距离飞机上小目标 recall 提升 12.3%。6.3 部署时的轻量化 trick用 ONNX TensorRT 加速双模型流水线YOLOv5 和 Faster R-CNN 分别导出 ONNX 后用 TensorRT 构建串联 pipeline# 伪代码TRT engine 串联 engine_yolo build_engine(yolov5s_airplane.onnx, fp16True) engine_frcnn build_engine(faster_rcnn.onnx, fp16True) def infer_pipeline(img): # Step 1: YOLOv5 coarse detection yolo_output engine_yolo.infer(img) # [N, 4] bbox [N] scores if len(yolo_output) 0: return [] # Step 2: Crop ROIs and feed to Faster R-CNN rois [] for box in yolo_output: x1, y1, x2, y2 map(int, box[:4]) roi img[y1:y2, x1:x2] # crop rois.append(cv2.resize(roi, (224, 224))) # FRCNN input size # Step 3: Batch infer FRCNN on all ROIs frcnn_output engine_frcnn.infer(np.array(rois)) # [N, 4] refined bbox return frcnn_output # 实测Jetson AGX Orin 上单图端到端耗时从 420msPyTorch→ 186msTRT关键点engine_frcnn的输入必须是224×224因为 Faster R-CNN 的 ROI Align 输入固定尺寸YOLOv5 的imgsz设为 640保证P3层有足够分辨率TRT 的fp16True对飞机检测无精度损失AP 变化 0.3%但速度提升 2.2×。从那以后我每次做小目标检测都强制走一遍「YOLOv5 初筛 → ROI Crop → Faster R-CNN 精修」的三步验证哪怕客户只要求单模型。因为飞机不会迁就你的模型——它要么停在阴影里要么侧身掠过镜头要么缩成像素点。而坐标识别的终极意义不是画出完美的框是让框里的数字经得起机场调度员指着屏幕问“这架是不是刚滑出跑道”希望帮到你。本文还有配套的精品资源点击获取
返回列表