FOD-A机场异物数据集实战:从数据转换到YOLO训练全指南
简介面向目标检测与计算机视觉研究者的FOD-A机场异物数据集聚焦机场跑道常见异物碎片FOD检测为缺乏公开标准数据的外物入侵检测任务提供训练与评测基础也可支撑民航、机场场务等工业场景的智能巡检方案落地。资源包为7z压缩包共2000个XML标注文件包体约367.52MB标注格式兼容YOLO与VOC可直接用于YOLO、Faster R-CNN等常见检测框架节省大量数据预处理时间。数据覆盖电池、螺栓、螺母、钳子、螺丝刀、易拉罐、木块等31个常见异物类别并为每个实例标注明亮/昏暗/黑暗三档光照和干燥/潮湿两类天气条件便于研究算法在复杂环境下的泛化能力也为多环境迁移学习、域自适应等方向提供数据基础。目前已有1422人学习下载适合从事智慧机场、安全巡检及目标检测算法开发的学生、研究人员和工程师参考使用。1. 先说结论FOD-A 这类机场异物数据集到底能帮你解决什么凌晨的跑道上一颗掉落的螺丝钉、一块轮胎碎皮一旦被发动机吸进去轻则航班取消重则酿成事故。传统靠人工巡检一小时走几公里眼睛盯着水泥地面漏检是常态。所以这两年机场地面保障团队开始转向视觉检测用摄像头或巡检车采集道面图像让模型自动标出可能造成风险的异物。FOD-A 机场异物数据集就是专门为这个任务准备的图像数据——它解决的并不是“能不能检测出飞机”这种通用目标而是“跑道上一小块黑色橡胶碎片和阴影怎么区分”这种极端贴近地气的难题。这份数据集最值得关注的地方在于它把目标检测里最难啃的几个点全占了目标像素少、背景纹理复杂、光照变化剧烈而且类别往往只有几类但每类的误检代价都很高。如果你正在做工业缺陷检测、小目标检测或者在做机场、港口、仓库的智能巡检项目这套数据集的标注思路和训练配置非常有参考价值。我下面会从数据集结构、格式转换、训练参数到踩坑记录把整条链路拆开给你一条能直接照做的路线。2. 读懂 FOD-A 数据集图像构成、标注格式和类别体系2.1 FOD 检测任务为什么不能直接用通用目标检测数据集很多人第一反应是拿 COCO 或者常见的目标检测数据集来预训练然后微调不就行了但实际做一轮就发现效果很差。原因不在模型而在数据分布。通用目标检测数据集里的物体比如人、车、猫狗普遍占图像的 10% 以上甚至更多。而跑道上的 FOD通常是一块硬币大小的金属片、一小节螺栓、一小片轮胎橡胶在 1080p 图像里可能只占 30 到 50 个像素。这类目标在目标检测里有个专门叫法小目标。小目标对模型而言特征是弱的稍微有一点卷积下采样信息就丢了。再加上跑道背景是水泥地面上面有标线、裂缝、修补痕迹、轮胎印纹理复杂程度不输给自然图像。一个原本在通用数据集上表现很好的模型遇到这种“大背景 小异物”的分布漏检率会很高。所以FOD-A 这类数据集的意义就是让你在接近真实场景的数据上做训练和验证而不是拿着自然图像里的模型硬套。这也是为什么很多落地项目宁可自己标注几千张跑道图也不愿意用通用预训练模型直接上线。数据分布决定模型上限模型结构决定逼近上限的效率。这句话在 FOD 检测上体现得格外明显。2.2 FOD-A 数据集的常见目录结构和标注格式以我见过的 FOD 检测数据组织方式FOD-A 这种命名的数据集通常不会只丢一堆图片给你而是带着完整的标注文件。常见的目录结构大致是这样fod-a-dataset/ ├── images/ │ ├── train/ │ │ ├── R001_0001.jpg │ │ ├── R001_0002.jpg │ │ └── ... │ └── val/ │ ├── R002_0001.jpg │ └── ... ├── annotations/ │ ├── train/ │ │ ├── R001_0001.xml │ │ ├── R001_0002.xml │ │ └── ... │ └── val/ │ ├── R002_0001.xml │ └── ... ├── classes.txt └── README.md这份结构里images存原始图像annotations存和图像同名的标注文件。classes.txt是类别清单每行一个类别名。很多采集设备会按巡检路段编号命名比如R001_0001表示第 1 段路的第 1 帧。这个命名习惯看着不起眼实际很有用因为后面划分训练集和验证集时你可以按路段来切而不是随机切单张图避免同一段路上同一块异物的连续帧同时出现在训练集和验证集里。标注格式方面FOD 数据集常见两种一种是 PASCAL VOC 的 XML 格式一种是 YOLO 的 TXT 格式。XML 格式适合人工检查结构清晰TXT 格式直接喂给检测框架训练效率高。下面是我经常看到的两种标注样例。XML 标注annotation foldertrain/folder filenameR001_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namemetal_fragment/name bndbox xmin1280/xmin ymin520/ymin xmax1310/xmax ymax545/ymax /bndbox /object object namerubber_piece/name bndbox xmin900/xmin ymin700/ymin xmax925/xmax ymax722/ymax /bndbox /object /annotation这段 XML 里最关键的是bndbox也就是目标边界框。注意xmax - xmin算出来只有 30 像素ymax - ymin只有 25 像素这就是典型的小目标。如果你看到的数据集里大多数框都小于 32×32那训练策略必须往小目标方向倾斜不能按普通目标检测的默认配置走。YOLO 标注格式0 0.6667 0.4935 0.0156 0.0231 1 0.4688 0.6481 0.0130 0.0204YOLO 格式每一行表示一个目标分别是类别编号、归一化后的中心点 x、中心点 y、归一化后的宽度、归一化后的高度。比如第一行类别 0 对应metal_fragment中心点在图像横向 66.67% 的位置竖向 49.35% 的位置宽约 15.6 除以 1000也就是 30 像素左右。这种格式的好处是无论图像缩放到多大框的比例不变适合训练框架直接读取。坏处是归一化坐标一眼看不出真实像素大小检查边界框是否准需要反算回去。2.3 类别平衡小目标、遮挡和背景复杂性分析拿到数据后不要急着训练先做类别和尺寸统计。这一步能帮你避开很多玄学调参。我一般会写一个简单的统计脚本把每个类别的框数量、框面积分布、高宽比都跑出来。FOD 数据集里最常见的问题是类别极度不平衡金属类可能有几千个框而织物类可能只有几十个框。小目标问题同样突出。如果你把框的面积占图像总面积的比例画成直方图会发现大多数点集中在 0.01% 到 0.1% 之间。对应到 1920×1080 图像就是 20 到 200 像素的物体。这种分布下默认的锚框anchor或者查询嵌入很可能不够用需要调整特征金字塔的层数或者提高输入分辨率。背景复杂性则体现在“负样本”上。跑道标线、轮胎印、水渍、阴影这些都不是 FOD但在图像里和真正的 FOD 长得很像。数据集的标注质量很大程度上取决于负样本是否覆盖到位。一个能用的 FOD 数据集除了标出正样本还会特意包含大量没有目标的纯背景图像用来压低误检率。所以查看数据时别忘了数一数有没有“空标注”的图像。如果训练集里全是正样本模型上线后会频繁把标线当异物那时悔之晚矣。3. 把 FOD-A 数据集转成可训练格式从 VOC 到 YOLO 的完整脚本3.1 先做数据体检统计类别、尺寸和坏图拿到数据集第一步不是写训练代码而是把数据从头到尾检查一遍。坏图、黑图、标注和图像尺寸对不上的情况在真实数据集里太常见了。我习惯用一段脚本先做基础体检。import os import cv2 import xml.etree.ElementTree as ET from collections import Counter img_dir fod-a-dataset/images/train ann_dir fod-a-dataset/annotations/train stats Counter() bad_images [] for name in os.listdir(img_dir): img_path os.path.join(img_dir, name) ann_path os.path.join(ann_dir, name.replace(.jpg, .xml)) img cv2.imread(img_path) if img is None: bad_images.append(name) continue h, w img.shape[:2] tree ET.parse(ann_path) root tree.getroot() for obj in root.findall(object): cls obj.find(name).text stats[cls] 1 bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 框超出图像边界的直接记录 if xmin 0 or ymin 0 or xmax w or ymax h: print(fout_of_bounds: {name}) print(类别数量统计:, stats) print(坏图数量:, len(bad_images), bad_images[:5])这段脚本做了三件事读取图像检查是否能正常解码解析 XML 统计每个类别的目标数量检查边界框是否超出图像范围。坏图可能是损坏的 JPEG也可能是大小为 0 的空文件。边界框越界是最容易忽略的问题很多训练框架会在数据加载时报错或者默默裁剪掉最后模型学到的框全是歪的。参数说明img_dir和ann_dir要按你的实际路径改name.replace(.jpg, .xml)假定了图像是 jpg 格式如果数据集中是 png改成.png即可。3.2 转换标注格式的脚本如果数据集给的是 VOC XML而你的训练框架需要 YOLO TXT转换脚本是少不了的。下面是我常用的一段带详细注释。import os import xml.etree.ElementTree as ET # 类别清单必须和后续训练配置保持一致 class_names [metal_fragment, rubber_piece, plastic_fragment, stone] def voc_to_yolo(xml_path, out_path, class_names): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_names: print(f未知类别 {cls_name} 已跳过) continue cls_id class_names.index(cls_name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 防御性处理把越界坐标拉回图像范围内 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) box_w xmax - xmin box_h ymax - ymin if box_w 0 or box_h 0: continue # 归一化到 0~1 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w_norm box_w / img_w h_norm box_h / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) # 批量转换 xml_dir fod-a-dataset/annotations/train out_dir fod-a-dataset/labels/train os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) out_name xml_name.replace(.xml, .txt) voc_to_yolo(xml_path, os.path.join(out_dir, out_name), class_names)这段代码有几个关键设计。第一是类别顺序class_names的索引就是 YOLO 格式里的类别编号如果后续训练配置里的类别列表顺序不一致模型训练会完全错乱。第二是越界坐标裁剪标注人员手滑导致框超出图像边界很常见不处理的话训练时 loss 会突然变成 nan。第三是过滤掉宽高小于等于 0 的无效框这类框通常是标注工具误操作产生的。参数说明class_names必须和数据集自带的classes.txt保持一致如果你的数据集类别比我这里多直接按顺序追加即可。3.3 划分训练验证集时的三个注意点数据划分看起来简单但在 FOD 场景里随便切会带来严重的评估失真。第一按视频帧来源划分不要按单张图随机划分。FOD 数据很多来自巡检视频抽帧同一段视频里同一个异物会出现几十帧。如果随机切同一异物的不同帧可能同时进了训练集和验证集模型等于提前“见过”验证目标指标虚高上线后立刻现原形。正确做法是先对图像文件名按路段前缀分组再按组划分。比如R001开头的进训练集R002开头的进验证集。第二保证验证集包含各种光照和天气。FOD 数据集通常不止白天晴天还有黄昏、夜晚、雨后、逆光。如果验证集只挑肉眼最清晰的图模型在夜间场景的漏检率会被掩盖。划分前先看一眼文件路径或文件名里有没有场景标记有的话按场景分层采样没有的话至少随机抽 20% 的图保证分布不至于太偏。第三保留一整块“测试集”不动。很多人把数据切成训练和验证就开训了最后连测试集都没有只能拿验证集顶替。但验证集在训练过程中会反复参与模型选择调参多了同样会过拟合。我习惯切出一块 5% 左右的图像单独放在test目录里整个调参阶段不碰它最后用一版固定权重做最终评估。这样得到的 mAP 才有说服力。没有这块数据你做的所有指标都只能叫“验证集表现”不能叫“模型真实水平”。4. 用 YOLO 系模型训练 FOD 检测模型参数配置和训练命令4.1 模型选型和环境准备FOD 检测属于典型的小目标检测任务。模型结构上我一般会用 YOLO 系的检测模型原因是它在精度和速度之间平衡得比较好部署到巡检车上或监控摄像头边上都有可用的推理方案。两阶段检测器精度可能更高但推理速度慢而且对于 FOD 这种“小目标 实时巡检”场景性价比不高。环境准备不复杂核心是保证 CUDA 版本和深度学习框架匹配。训练前先用一个小数据集跑通流程比如每类只放 20 张图训 1 个 epoch确认数据加载、反向传播、权重保存都没有问题再上全量数据。这一步能省下很多排查时间。我见过有人直接全量训练跑了两小时后才发现数据路径写错白白浪费算力。4.2 一份适合小目标检测的配置文件下面的配置不是某个程序的特定格式而是通用的关键参数清单。你可以在自己用的检测框架里找到对应字段按这个思路去改。# fod_det_config.yaml # 数据集路径 path: ./fod-a-dataset train: images/train val: images/val test: images/test # 类别 nc: 4 names: [metal_fragment, rubber_piece, plastic_fragment, stone] # 训练超参数 batch_size: 16 epochs: 200 imgsz: 960 lr0: 0.01 lrf: 0.01 mosaic: 0.5 fliplr: 0.5 scale: 0.3 hsv_h: 0.015 hsv_s: 0.5 hsv_v: 0.4imgsz我直接设成了 960而不是默认的 640。这不是随手写的。FOD 目标太小640 的输入意味着很多 20 像素的小物体下采样几次就没了特征。提高到 960 后原本 30 像素的异物在特征图上能多保留一些信息。代价是训练速度和显存占用增加如果你的显卡显存不足可以先用 640 跑通再逐步往上提。mosaic: 0.5表示 50% 概率启用四图拼接增强。对小目标检测mosaic 能显著增加目标尺寸多样性但对 FOD 这种背景纹理复杂的场景mosaic 可能让模型学到拼接缝的伪影。所以我没有设成默认的 1.0而是降到 0.5保留一半概率使用原始背景。hsv增强参数对 FOD 很重要。跑道光照变化大同一块金属在阳光下和阴影里色差明显。适度增强色相、饱和度和明度可以让模型对光线变化更鲁棒。但幅度不能太大否则异物和背景的边界会被抹掉。上面这套数值是我在类似数据上调过的适合白天黑夜混合的场景。4.3 训练命令与日志监控数据准备好后训练命令本身很直接。我这里用你本地的检测框架入口来示意。python train_det.py --cfg fod_det_config.yaml --weights pretrain.pt --device 0把train_det.py换成你实际使用的入口脚本。--weights pretrain.pt是预训练权重建议用在大规模自然图像上预训练的权重它能提供底层特征提取能力尤其是边缘和纹理特征这对小目标检测很有帮助。如果你的硬件实在带不动也可以从零训练但收敛速度和最终精度都会差不少。训练过程中不要只看终端打印的 loss。我一般每 5 个 epoch 记录一次验证集 mAP50 和 mAP50-95并且关注验证集 loss 和训练集 loss 的差距。如果训练 loss 一直降验证 loss 在某个点开始反弹说明过拟合了此时应该考虑增加增强强度或提前停止。显存占用是个硬约束。imgsz: 960加上batch_size: 16可能一张卡不够。如果遇到 out of memory可以先把 batch_size 降到 8或者开启梯度累积。尽量不要直接降分辨率那会抵消 FOD 小目标检测的收益。我通常优先调整 batch size而不是降分辨率因为分辨率对小目标的影响远大于 batch size 的影响。训练完成后检查一下run目录下保存的results.csv里面每一轮迭代的指标都有。很多人训练完只看最后几行忽略了中间的变化趋势。比如 mAP 在 120 epoch 后就不再增长那 200 epoch 就是浪费算力如果 200 epoch 还在涨说明预算不够应该加长训练。5. FOD-A 数据集训练的避坑记录5 个最容易翻车的地方5.1 现象loss 降到很低但 mAP 只有零点几原因数据划分出了问题或者数据集中大量标注框是错的。有一次我拿一份 FOD 数据训练训练 loss 降到 0.02 以下验证 mAP 却只有 0.3。排查后发现验证集里混进了训练集同源的图像但实际是把同一路段的不同帧切开了看起来不重其实重叠严重。另一个可能是标注框中心点偏离真实目标模型学到的框全偏了。解决先重新按路段分组划分训练集和验证集再人工抽查 50 张图像的标注框确认框是否紧贴目标边缘。如果标注框普遍偏大或偏小直接放弃这份标注联系标注方重新返工。对 FOD 这种小目标几个像素的偏差都会导致 IoU 大幅下降从而拉低 mAP。5.2 现象验证集上漏检率高的全是小目标原因输入分辨率和特征金字塔的设计不匹配小目标。默认配置通常针对 32×32 以上的目标设计而 FOD 数据集中大量目标只有 20×20 甚至更小。模型在多层下采样后这些小目标在深层特征图上已经丢失了位置信息。解决把输入分辨率从 640 提到 960观察 mAP 变化。同时增大训练时随机裁剪的拉伸范围让模型在小目标上见到更多样本。如果用了多尺度训练把尺度范围从 0.5~1.5 改成 0.8~1.8给大图上的小目标更多机会。5.3 现象训练时数据加载卡住进度条长时间不动原因数据集里有损坏的图像或者标注文件找不到对应的图像。很多数据集的图像和标注文件名大小写不一致比如R001_0001.JPG对应R001_0001.xml在 Windows 上没毛病在 Linux 上就会按大小写区分导致文件匹配失败。解决用前面第 3.1 节的体检脚本把所有图像跑一遍检查是否有解码失败。再把标注文件名统一成小写或者统一成和图像完全相同的后缀。训练前先跑一个 epoch 作为冒烟测试不要直接启动长训练。这一步很快但能挡掉 90% 的数据加载问题。5.4 现象误检把跑道标线当成异物验证集 precision 惨不忍睹原因跑道上白色或黄色标线在图像里是强边缘和某些小异物比如白色塑料片的视觉特征非常接近。模型如果没在训练阶段见过足够的纯背景负样本就会把标线、警示线、甚至阴影全部当成目标。解决在训练数据中加入纯背景图像。这类图像没有目标标注文件为空训练时模型会学会抑制这些区域的输出。通常我会把纯背景图像的比例控制在 20% 到 30%。如果数据集本身没有纯背景图可以从巡检视频里截取没有异物的帧。这一步对 FOD 落地价值极大能显著降低误报率。5.5 现象换了夜晚图像后模型完全失效白天指标再好也没用原因模型学的是颜色加纹理的联合特征而夜视摄像机下异物和背景的对比度分布和白天完全不同。白天训练的模型到了夜晚特征分布的偏移量太大检测头输出的置信度会普遍跌到阈值以下。解决训练数据必须覆盖多个光照时段至少包括白天、黄昏、夜晚三类。如果数据集里没有夜晚图像我一般会先做离线增强把部分图像的亮度降低并加噪声模拟夜视效果。增强只能应急真正解决还是要采集更多夜间数据。另外推理时针对白天和夜晚各保留一套阈值不要用同一个置信度阈值同时处理两种场景效果会好很多。6. 从跑通到能用的进阶用测试集做 error analysis再决定要不要加大输入分辨率6.1 用混淆矩阵定位误检来源训练完拿到一个不错的 mAP先别急着上线。把之前留出的测试集跑一遍输出混淆矩阵。很多检测框架会自动生成如果没有你也可以统计所有预测框和你手工标注的真实框之间的匹配关系分成四类正确检测、漏检、误检、类别错误。对这四类分别计数你会发现问题的真实重心。我做过一次 FOD 检测的 error analysis发现最大的问题不是漏检而是误检——模型把跑道上的接缝处阴影当成了金属碎片。只看 mAP 根本看不出这一点必须看混淆矩阵。6.2 一个实用技巧把输入分辨率从 640 提到 960观察指标变化不要凭感觉决定最终部署分辨率。在测试集上做两轮实验第一轮imgsz: 640第二轮imgsz: 960其余参数完全一致。对比漏检率和误检率的变化。对于 FOD 数据通常分辨率提升后漏检率明显下降但误检率也可能小幅上升因为细节多了模型会把更多纹理误判为目标。此时结合 6.1 的混淆矩阵决定是否需要在后处理里增加一个“面积阈值”滤掉小于多少个像素的预测框。我在实际项目中一般会把极小框过滤掉因为真正的 FOD 在巡检车视角下至少会占据一定像素小于阈值的预测框基本都是噪声。6.3 养成习惯把每次训练的权重和训练参数一起存档训练模型时除了保存权重文件我还会把当前训练用的配置文件、数据划分脚本、标注类别清单一起复制到同一份目录下。原因很简单模型翻车时你得知道这个模型是怎么来的。有一次我们业务方反馈一批误报排查后发现用的是一周前某次实验的权重那次实验改过类别顺序导致推理时类别错位。从那以后我坚持每个实验一个目录权重和配置锁死绝不混用。这个习惯在多人协作时尤其重要它比任何文档都可靠。FOD 检测这件事数据比模型更值钱。把数据吃透把避坑点都趟过一遍再用 error analysis 驱动下一轮迭代模型才能真正从“能跑”变成“能用”。希望这些记录能帮你在自己的数据集上少走几步弯路。本文还有配套的精品资源点击获取