ARTICLE DETAIL

资讯详情

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

PyTorch Faster R-CNN飞机检测实战:数据清洗与训练调参指南

PyTorch Faster R-CNN飞机检测实战:数据清洗与训练调参指南 简介围绕“基于Pytorch搭建Faster R-CNN实现飞机目标检测”主题这份资源为计算机视觉与深度学习初学者提供了配套数据集和辅助代码适合正在学习目标检测算法、需要完成课程设计或复现经典模型的开发者。压缩包共106个文件大小13.18MB主体为99张JPEG格式的图片并附3个Python脚本、3个JSON标注文件及1个pyc文件可支撑从数据读取、标注解析到模型训练调参的完整流程。目前已有1796人浏览学习。借助该数据集读者能直接对照Faster R-CNN中区域提议网络RPN和RoI分类回归分支的实现逻辑进行实操无需额外收集图像同时可基于JSON标注快速验证模型预测效果理解目标检测中的边界框回归、置信度评估等关键环节。资源体积小巧、结构清晰适合作为算法原理学习与实践入门的快捷素材。1. 边框标注质量比网络结构更早决定 Faster R-CNN 飞机检测上限不少人在 Pytorch 里直接加载 Torchvision 的fasterrcnn_resnet50_fpn换上飞机图片就开始训练。第一轮 Loss 还能下降第二轮直接变大或者 mAP 一直卡在 0.1 附近。问题大多不在模型结构而在数据集侧RPN 计算 positive anchor 时要求真实框坐标在原图尺度成立如果 XML 里存的是归一化坐标又被二次缩放回归分支学到的就是一套错位映射。飞机目标检测的图像多来自航拍或监控目标尺度跨度大标注框包含机翼尖端还是只框住机身会直接影响 anchor 匹配难度。这里按“标注整理 → Dataset 编写 → 训练调参 → 验证修正”的顺序把基于 Pytorch 搭建 Faster R-CNN 做飞机检测时数据侧最常见的坑拆开讲。新手能照步骤跑通有经验的人也可以对照检查自己的数据流水线在哪一步丢了信息。2. 飞机检测数据集格式与 Faster R-CNN 加载约束Faster R-CNN 的数据接口比分类模型严格得多。images必须是归一化后的 Tensortargets必须是包含boxes、labels、image_id、area、iscrowd的字典。官方代码不会自动解析 PASCAL VOC也不会读取 YOLO 的 txt。所以无论原始数据来自 KITTI、DOTA 还是自己的标注工具都需要先转换成模型能直接消费的中间格式。2.1 为什么建议用 VOC/COCO 中间格式而不是直接存图片路径直接存图片路径加 txt 标注表面上看最省事但飞机数据集通常要为训练和验证分配不同子集还需要做水平翻转、随机裁剪等在线增强。VOC XML 或 COCO JSON 会把图像的宽高、通道、标注框、类别和 difficult 状态放在同一个结构里后续加载时不容易出现“图片读到了、框却对不上”的情况。另一个隐含约束是 Faster R-CNN 的 transform 会做等比缩放边界框坐标在缩放后会映射到新的尺寸格式里如果一开始就用 0 到 1 的归一化坐标再乘回原图大小时很容易因为四舍五入损失几个像素这些小偏移对飞机这类刚性目标尤其明显。VOC XML 在工程里的可读性最好COCO JSON 在分布式训练时序列化更紧凑。我的习惯是统一转成 VOC再在 Dataset 内部解析。下面是最小字段集。字段类型示例Faster R-CNN 中的用途image_pathstrairport_001.jpg读取原图xmin, ymin, xmax, ymaxint238, 512, 402, 624与 anchor 计算 IoUlabelstr/intairplane:1分类头输出索引image_width, image_heightint1920, 1080校验坐标越界difficultint0/1控制 RPN 是否忽略难例scene_typestrairport/aerial训练后分析类别混淆这里特别要留意difficult。VOC 里difficult1的样本表示遮挡或截断目标在分类任务里经常被舍弃但在 Faster R-CNN 训练中不应该简单丢弃。RPN 需要通过这些难例学习低质量候选框的回归全部删掉会让边界框预测变得过于保守。2.2 用 Python 清洗飞机标注中的重复框和越界框自己标的数据集经常出现两种情况同一个小飞机在相邻两帧里被标了两次或者标注框比飞机实际轮廓大出好几倍。Faster R-CNN 对这类噪声没有自愈能力smooth_l1_loss会把过大的框当作真值来优化导致预测框整体向外扩张。进入训练前最好先跑一遍清洗脚本规则包括删除宽高小于 10 像素的框、删除完全越界的框、把重叠面积超过 0.9 的重复框合并。import xml.etree.ElementTree as ET import os def collect_bad_annotations(annotation_dir, min_wh10): 扫描 VOC XML返回包含异常标注的文件名。 参数: annotation_dir: VOC XML 所在目录 min_wh: 小于该宽高的框视为误标注 bad_files [] for xml_name in os.listdir(annotation_dir): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(annotation_dir, xml_name)) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) objs root.findall(object) for obj in objs: box obj.find(bndbox) xmin int(float(box.find(xmin).text)) ymin int(float(box.find(ymin).text)) xmax int(float(box.find(xmax).text)) ymax int(float(box.find(ymax).text)) if xmax - xmin min_wh or ymax - ymin min_wh: bad_files.append(xml_name) break if xmin width or ymin height or xmax 0 or ymax 0: bad_files.append(xml_name) break return bad_files这段代码只做扫描不直接修改文件。运行后会得到一份问题文件清单再决定是人工复核还是用脚本自动修正。清洗时注意不要把所有难样本都去掉机翼遮挡、机身和背景颜色接近的样本恰恰是 RPN 训练中最有价值的负例来源。如果清洗逻辑一口气把这类框全删掉训练时 Loss 会下降得很快但验证集上的表现反而更差。2.3 从 KITTI 或 DOTA 样式标注转到 VOC 的要点飞机检测项目里经常遇到公开遥感数据集的标注格式。KITTI 的标签前几列是class truncated occluded alpha接着是 bbox 四个坐标。DOTA 则使用多边形点序列。转换成 VOC 时最容易出错的是点顺序和坐标原点不一致DOTA 的顶点顺序不稳定直接取min(x)和min(y)可能把一个旋转飞机框得过大。更稳妥的做法是先标注所有顶点计算最小外接水平矩形再检查矩形的长宽比是否和飞机形态吻合。转换脚本统一输出绝对像素坐标类别名固定为airplane。在多个数据集混合训练时类别编号一定要从全局classes.txt读取不要在代码里写死airplane1。3. 用 Pytorch 搭建 Faster R-CNN 飞机数据流水线与微调配置数据准备完成后接着要写 Dataloader 和训练入口。这里最关键的是Dataset的返回值必须兼容 Torchvision 检测头的内部逻辑否则训练流程会在跑完第一个 batch 后出现 insize mismatch或者 Loss 完全不变。3.1 自定义飞机 Dataset 时不能漏掉 area 和 iscrowd 字段Torchvision 的 RPN 和 RoI Head 都会读取targets字典里的字段。boxes和labels是最基本的area会参与在线难例挖掘iscrowd用于标记大面积杂乱目标。后两个字段漏掉时训练不会立刻报错但检测头在计算 Fast R-CNN Loss 时会少掉一部分候选框直观表现是每个 epoch 的 loss 值整体偏低而 mAP 涨得很慢。from torch.utils.data import Dataset import torch from PIL import Image import os class AirplaneDataset(Dataset): def __init__(self, root, ann_dir, img_dir, label_map, transformsNone): self.img_dir img_dir self.ann_dir ann_dir self.label_map label_map self.transforms transforms self.image_ids [f[:-4] for f in os.listdir(ann_dir) if f.endswith(.xml)] def __getitem__(self, idx): image_id self.image_ids[idx] image Image.open(os.path.join(self.img_dir, f{image_id}.jpg)).convert(RGB) boxes, labels, area, iscrowd self._parse_xml(image_id) target { boxes: torch.as_tensor(boxes, dtypetorch.float32), labels: torch.as_tensor(labels, dtypetorch.int64), image_id: torch.as_tensor([idx]), area: torch.as_tensor(area, dtypetorch.float32), iscrowd: torch.as_tensor(iscrowd, dtypetorch.uint8), } if self.transforms: image, target self.transforms(image, target) return image, target def __len__(self): return len(self.image_ids)area由(xmax-xmin) * (ymax-ymin)计算单位是像素。iscrowd全部置 0除非你确定某些标注包含多个紧邻的飞机目标且不想分别回归。image_id必须是包含一个元素的 Tensor不能是 Python int否则数据并行时索引会错乱。读取图片时统一convert(RGB)也可以避免灰度图像导致预训练权重通道不匹配。3.2 collate_fn 必须返回 list 而不是 stack检测任务的每个 batch 内图像大小不会完全一致Faster R-CNN 的 transform 虽然会统一缩放但缩放后宽高不一定相同。默认的torch.utils.data.DataLoader会尝试把图像 stack 成四维 Tensor检测任务里必须用自定义collate_fn阻止这个行为。from torch.utils.data import DataLoader def collate_fn(batch): images [item[0] for item in batch] targets [item[1] for item in batch] return images, targets train_loader DataLoader( dataset, batch_size4, shuffleTrue, num_workers4, collate_fncollate_fn, pin_memoryTrue )batch_size4是飞机检测中常用的显存平衡点。Faster R-CNN 每张图的显存消耗不固定大图会产生更多 anchor 和 proposal。如果用的是 12GB 显存显卡max_size1333时建议保持 batch_size 为 2 到 4。num_workers可以设置为 CPU 核心数的一半避免读取大型航拍图时 CPU 成为瓶颈。3.3 飞机小目标场景下的 anchor 与 transform 参数Faster R-CNN 默认的anchor_sizes覆盖 32 到 512 像素的尺度范围但这个范围对航拍飞机不一定合适。如果 1080p 图像里的飞机宽度集中在 20 到 60 像素默认 anchor 里的大部分尺度都会浪费在背景上。修改 anchor 前先统计数据集中所有标注框的宽高分布80% 的框宽度落在哪个区间就把anchor_sizes集中到哪个区间。参数默认值飞机目标建议调整原因rpn_pre_nms_top_n_train120006000遥感图目标稀疏降低冗余候选rpn_post_nms_top_n_train20001000减少后续 RoI Head 计算压力box_score_thresh0.050.3推理时过滤大量误检anchor_sizes((32,), (64,), (128,), (256,), (512,))((8,), (16,), (32,), (64,))匹配飞机小目标尺寸max_size13331664保留机翼边缘细节修改 anchor 后需要用脚本验证匹配率统计每个真值框与所有 anchor 的最大 IoU正常情况下至少要有 0.5 的框能超过 0.7 的 IoU。如果这个比例过低说明 anchor 尺度仍然偏大或偏小而不是训练轮数不够。4. 飞机目标检测训练中的 Loss 观察、评估指标与数据修正训练过程中打印 Loss 明细能快速判断数据集问题。Faster R-CNN 的 Loss 由四个部分组成每一部分异常对应的数据集原因不同。4.1 四部分 Loss 各自反映什么训练循环里通常只需要把四个 Loss 相加后反向传播但监控时要分别观察。outputs model(images, targets) loss sum(v for v in outputs.values()) loss.backward() optimizer.step() for key, value in outputs.items(): print(f{key}: {value.item():.4f}, end ) print()Loss 项数据侧含义异常表现loss_objectnessRPN 区分前景/背景的能力持续偏高时检查正负样本比例背景框太多loss_rpn_box_regRPN 预测框与真值框的回归误差偏高时优先怀疑坐标没有同步缩放loss_classifierRoI Head 的类别判断误差偏高时检查 label_map 是否写错loss_box_regRoI Head 的最终框回归误差偏高时检查飞机框是否包含太多背景如果loss_rpn_box_reg一直大于 1.0不要急着加正则化。先回看 transform 里是否对图像做了 resize 却没有同步更新boxes。另一个常见原因是 XML 里的xmax被写成了图像宽度导致正样本框无限偏右。这时模型只能靠回归分支把 anchor 往右推Loss 会下降到一个平台但预测框中心始终偏移。4.2 用按尺寸分开的 mAP 代替整体 mAP整体 mAP 在飞机这类多尺度目标上很容易掩盖问题。一架大飞机能贡献 0.9 的精度而二十架小飞机全部漏检时整体 mAP 可能仍在 0.5 以上。推荐使用torchmetrics.detection.mean_ap计算时把目标按面积划分成小目标小于 32x32、中目标32x32 到 96x96、大目标大于 96x96三组。from torchmetrics.detection import MeanAveragePrecision metric MeanAveragePrecision() metric.update(preds, targets) result metric.compute() print(result[map_small], result[map_medium], result[map_large])小目标 mAP 明显低于大目标时需要检查 FPN 是否有效利用了低层特征。Torchvision 的 FPN 默认包含 P2 层但训练时会给 P2 分配较小的权重如果飞机数据集里小目标占绝对多数可以在自定义BackboneWithFPN时提高 P2 的输出通道或降低 P2 的空间下采样倍数。这类改动要重新加载预训练权重中的兼容部分通常只训练几轮就能看出变化。4.3 用失败样本反向修正标注训练第二轮结束后把预测框与真值框 IoU 小于 0.5 的样本导出到 CSV。常见失败形态有三种框偏左、框包含机尾、两个相邻飞机被合并成一个框。飞机是刚性物体如果预测框始终比真值框大一圈基本可以判定标注时把飞机周围的地面纹理也框了进去。修正方式不是调整 NMS 阈值而是回到对应 XML把框的右边界向内收缩几个像素。import csv with open(hard_cases.csv, w, newline) as f: writer csv.writer(f) writer.writerow([image_id, gt_xmin, gt_ymin, gt_xmax, gt_ymax, pred_xmin, pred_ymin, pred_xmax, pred_ymax, iou]) for case in hard_cases: writer.writerow([case.image_id, *case.gt, *case.pred, round(case.iou, 4)])导出 CSV 后不要直接在表格里改坐标表格改完很难回写到 XML。更稳妥的做法是找到这些样本的图片用标注工具打开只调整失败的框。连续调整 20 到 50 个框后重新训练如果loss_box_reg明显下降说明数据修正方向正确。5. 用少量飞机样本快速验证数据质量与训练收敛的 3 个技巧训练一开始就上全部数据很难判断数据集和代码到底是谁先出错。下面三个技巧可以在一小时内筛掉大部分数据侧问题。5.1 先跑 10 张图的除错模式取数据集的 10 张图关闭数据增强只保留归一化和尺寸缩放训练 2 个 epoch。这一步不追求精度只看 Loss 是否稳定下降。如果第一个 batch 的loss_objectness就是 0.0说明标签没有进入模型去检查target里boxes的数量是否为 0。如果 Loss 完全不变优先确认model.train()和optimizer.zero_grad()是否在正确的顺序上。5.2 可视化 anchor 与真值框的匹配结果在训练脚本里插入一段绘图逻辑随机挑一张训练图把真值框、RPN 输出的 top-50 proposal、最终预测框画在同一张图上。如果真值框和 RPN 候选框完全重叠说明网络的结构部分没有问题。如果候选框全都停留在图像边缘那就是 anchor 尺度设置严重偏离目标尺寸。from torchvision.utils import draw_bounding_boxes from PIL import Image img_tensor (images[0] * 255).to(torch.uint8) vis draw_bounding_boxes( img_tensor, targets[0][boxes], labels[airplane] * len(targets[0][boxes]), width4 ) Image.fromarray(vis.permute(1, 2, 0).numpy()).save(debug_gt.png)对比真值框可视化图和最终预测结果图时关注飞机机翼尖端是否被截断。预测框经常把机翼和机身中间的凹陷区域包进去说明标注时把凹陷也当作飞机主体修正标注比调整 anchor 更有效。5.3 每 5 个 epoch 保存一次验证集拼接图训练过程中每 5 个 epoch 保存一张验证集拼接图图片命名带上 epoch 号。看完三组连续图片后如果检测框的位置在不同 epoch 之间不断左右跳动说明回归目标不稳定。先排除学习率过大再把数据集里的异常框数量和 Loss 波动对比。飞机检测中常见的一个现象是某一张图里有几十架小飞机且标注框互相重叠这种图会让loss_rpn_box_reg周期性升高单独拿出来做人眼检查比改模型更有效。最终稳定输出时验证图里的小飞机预测框应当紧贴机翼外沿不会出现大面积包含周围跑道的情况。本文还有配套的精品资源点击获取
返回列表