简介:面向机场跑道异物碎片(FOD)检测的计算机视觉数据集,适合从事目标检测算法研究、模型训练与工程落地的开发者及学生使用,可用于构建多类别小目标检测任务,并借助光照与天气标注开展鲁棒性分析。资源包共2000个文件,以XML标注文件为主,对应YOLO与VOC两种标注格式,压缩包约367.52MB,便于直接接入主流检测框架进行训练与评估。数据集覆盖31个细粒度类别,包括电池、螺栓、垫圈、钳子、扳手、螺丝刀、锤子、金属片、软管、岩石、易拉罐、胶带、木块等典型机场异物,每个标注实例还进一步划分明亮、昏暗、黑暗三种光照级别以及干燥、潮湿两类天气条件,为复杂环境下的检测研究提供条件分层依据。目前已有1424人学习下载,适合需要多类别、多场景标注数据来验证模型泛化能力或开展对比实验的读者参考使用。
1. 机场跑道异物检测:一份 FOD-A 数据集能撑起什么样的检测任务
跑道上的螺丝钉、金属片、塑料杯,在起降速度下就是一颗子弹。FOD(Foreign Object Debris,外来物)检测这件事,难的不是模型结构,而是数据——真实机场不可能让你往跑道上撒钉子拍几千张图。FOD-A 这类机场异物数据集解决的正是这个冷启动问题:它把跑道、滑行道、停机坪等场景下的异物目标做成带标注的图像集合,让目标检测任务从“没数据可训”变成“有数据可调”。这份资源适合三类人:做安防/交通视觉落地的算法工程师、拿目标检测做课程设计或毕设的学生、以及需要快速验证检测 pipeline 的从业者。它不承诺 SOTA,但能让你在半天内跑通一条从数据加载到推理可视化的完整链路,先把“能不能检出”这件事验证掉,再谈精度优化。
2. 拆开数据集:FOD-A 的目录结构、标注格式与选型理由
拿到一个目标检测数据集,第一件事不是急着train.py,而是把目录翻一遍,搞清楚图像放哪、标注长什么样、类别怎么定义。FOD-A 的组织方式属于典型的“图像 + 单文件标注”结构,理解它决定了你后面写 DataLoader 时会不会翻车。
2.1 目录结构与文件组织
常见做法是图像集中放在一个目录下,标注用一个汇总文件(如 CSV 或 JSON)描述每张图的框信息,而不是每张图配一个 XML。这种组织方式对目标检测训练更友好——读取标注只需一次 IO,不用为每张图开文件。典型结构大致如下:
FOD-A/ ├── images/ # 原始图像,jpg/png ├── annotations/ # 标注汇总文件 │ └── annotations.csv └── README # 类别与字段说明先跑一遍统计,确认图像数量、尺寸分布和类别分布,这一步能提前暴露很多问题:
import os from PIL import Image from collections import Counter img_dir = "FOD-A/images" sizes, exts = Counter(), Counter() for name in os.listdir(img_dir): path = os.path.join(img_dir, name) if not os.path.isfile(path): continue exts[os.path.splitext(name)[1].lower()] += 1 with Image.open(path) as im: sizes[im.size] += 1 print("扩展名分布:", exts) print("尺寸种类数:", len(sizes)) print("最常见尺寸:", sizes.most_common(5))这段脚本的作用是摸清数据底细:扩展名分布告诉你有没有混入非图像文件;尺寸种类数如果很大,说明图像分辨率不统一,后面 resize 策略要格外小心;最常见尺寸则决定你 anchor 或输入尺寸的起点。参数上没什么可调的,重点是先看再改,别上来就写增强。
2.2 标注字段与坐标约定
标注文件里通常包含图像文件名、类别标签、边界框坐标。坐标是xyxy(左上右下)还是xywh(中心宽高),是像素值还是归一化值,这两点必须确认,否则训练 loss 会一路飘。用 pandas 快速看一眼:
import pandas as pd df = pd.read_csv("FOD-A/annotations/annotations.csv") print(df.columns.tolist()) print(df.head()) print("类别分布:\n", df["class"].value_counts()) print("框坐标范围:", df[["xmin","ymin","xmax","ymax"]].describe())逻辑说明:columns确认字段名,避免硬编码错列;value_counts看类别是否严重不均衡——FOD 场景里小目标多、某些类别样本极少是常态;describe看坐标最大值是否超过图像宽高,超过就说明坐标约定和你以为的不一样。如果坐标最大值在 0~1 之间,那是归一化坐标,转 YOLO 格式时不用再除图像尺寸;如果是几百上千的整数,就是像素坐标,转换时必须除以宽高。
2.3 为什么选它做检测基线
选 FOD-A 做基线而不是自己爬图,理由很实际:异物目标尺寸小、背景单一但纹理重复(跑道标线、沥青颗粒),这类数据对模型的考验集中在小目标召回和背景误检抑制上,正好是目标检测落地时最常翻车的两个点。用它调通一套 pipeline,换到工业质检、轨道巡检等场景时,数据格式转换和评估逻辑几乎可以平移。需要提醒的是,这类数据集的类别定义往往偏粗(金属、塑料、石块等大类),如果你的业务需要细分材质,得自己补标注,别指望开箱即用。
3. 从原始标注到可训练格式:转换脚本与四个边界坑
数据看明白了,下一步是把它转成训练框架认的格式。以 YOLO 系列为例,需要每张图一个.txt,每行class cx cy w h,全部归一化到 0~1。这一步看着简单,坑却集中爆发。
3.1 写一个可复用的转换脚本
import os import pandas as pd from PIL import Image df = pd.read_csv("FOD-A/annotations/annotations.csv") classes = sorted(df["class"].unique()) cls2id = {c: i for i, c in enumerate(classes)} out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for img_name, group in df.groupby("image"): img_path = os.path.join("FOD-A/images", img_name) if not os.path.exists(img_path): print("缺图,跳过:", img_name) continue w, h = Image.open(img_path).size lines = [] for _, r in group.iterrows(): # 像素坐标转归一化中心点格式 cx = (r["xmin"] + r["xmax"]) / 2.0 / w cy = (r["ymin"] + r["ymax"]) / 2.0 / h bw = (r["xmax"] - r["xmin"]) / w bh = (r["ymax"] - r["ymin"]) / h # 裁剪到 [0,1],防止越界框污染训练 cx, cy = min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh = min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f"{cls2id[r['class']]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, img_name.replace(".jpg", ".txt")), "w") as f: f.write("\n".join(lines)) print("类别映射:", cls2id)逻辑说明:groupby("image")保证一张图的所有框写进同一个 txt;cls2id用排序后的类别名生成稳定映射,避免每次运行 id 漂移;坐标裁剪是后悔药——原始标注里偶尔有框超出图像边界,不裁的话归一化后出现负值或大于 1,训练时 loss 会异常。参数上,.6f保留六位小数足够,别用科学计数法。
3.2 划分训练集与验证集
转换完别急着训,先按图像级别划分,防止同一张图的框被拆到两个集合造成数据泄漏:
import random random.seed(42) imgs = sorted(os.listdir("FOD-A/images")) random.shuffle(imgs) split = int(len(imgs) * 0.8) for name in imgs[:split]: open("train.txt", "a").write(f"images/{name}\n") for name in imgs[split:]: open("val.txt", "a").write(f"images/{name}\n")seed固定是为了可复现;按图像而非按框划分是硬要求,否则验证集指标虚高,上线就露馅。
3.3 四个边界坑
第一个坑:坐标约定误判。现象是训练 loss 一开始就很大且不下降,原因是把xywh当xyxy用了。解决方法是拿一张图把框画出来肉眼核对,别信字段名。
第二个坑:类别 id 不连续。现象是训练报index out of range,原因是某些类别在验证集里没出现,但模型输出维度按训练集类别数设的。解决方法是类别映射用全量数据生成,不要按子集生成。
第三个坑:小目标被 resize 抹掉。现象是训练正常但小异物召回极低,原因是输入尺寸缩得太狠,几个像素的目标直接消失。解决方法是输入尺寸不低于 640,或对小目标做切片训练。
第四个坑:文件名大小写与扩展名不一致。现象是部分图找不到标注,原因是 csv 里写.jpg、磁盘上是.JPG。解决方法是转换前统一小写比对。
4. 训练与评估:输入尺寸、增强策略和指标怎么看
格式就绪后进入训练。这一章不讲模型结构选型(YOLO 系、Faster R-CNN 都能跑),只讲和 FOD 数据强相关的参数决策,因为这些才是决定你这份数据集能不能出结果的关键。
4.1 输入尺寸与小目标召回
FOD 目标的像素面积普遍偏小,输入尺寸是第一优先级参数。经验值:640 起步,显存允许就上 960 或 1280。对比一下不同输入尺寸对召回的影响,用同一份验证集跑推理统计:
from ultralytics import YOLO for size in [640, 960]: model = YOLO("runs/detect/train/weights/best.pt") metrics = model.val(data="fod.yaml", imgsz=size, conf=0.25) print(f"imgsz={size} mAP50={metrics.box.map50:.4f} " f"mAP50-95={metrics.box.map:.4f}")逻辑说明:imgsz控制推理分辨率,conf是置信度阈值。重点看mAP50和小目标子集的召回,而不是只看总 mAP——总 mAP 被大目标拉高会掩盖小目标问题。如果 960 相比 640 提升明显,说明你的数据确实吃分辨率,训练时也该同步调大。
4.2 增强策略的取舍
跑道背景纹理重复,颜色增强(HSV 抖动)收益有限,几何增强(随机缩放、平移)对小目标帮助更大,但慎用 Mosaic:Mosaic 会把四张图拼一起,小目标被进一步缩小,FOD 场景下容易适得其反。常见做法是训练后期关闭 Mosaic,让模型在真实分布上收敛。翻转可以开,旋转角度别太大,异物没有固定朝向但过度旋转会引入不真实样本。
4.3 评估指标的正确读法
看三个数:precision、recall、以及按尺寸分桶的召回。FOD 业务里漏检代价远高于误检,所以阈值选择要偏召回。用混淆矩阵和 PR 曲线定位问题类别:
metrics = model.val(data="fod.yaml", imgsz=960, conf=0.1, plots=True) print(metrics.confusion_matrix.matrix)conf=0.1是为了看低阈值下的召回上限,plots=True会输出 PR 曲线和混淆矩阵图。如果某类异物大量被判成背景,说明该类样本不足或特征太弱,优先补数据而不是调模型。
5. 避坑与排查:五条血泪经验
5.1 验证集指标漂亮,实际推理一塌糊涂
现象:val 的 mAP 很高,拿几张新图推理却大量漏检。原因:验证集和训练集同源同分布,且划分时没按场景分层,模型记住了背景而非目标。解决:按场景(跑道/滑行道/停机坪)分层划分,并留一批完全没参与训练的图做最终验收。
5.2 训练中途 loss 变 NaN
现象:跑几十个 epoch 后 loss 突然 NaN。原因:标注里有零宽或零高的退化框,归一化后除零或产生极端值。解决:转换脚本里加过滤,bw <= 0 or bh <= 0的框直接丢弃并记录日志。
5.3 显存爆掉但 batch 已经很小
现象:batch 降到 2 还是 OOM。原因:输入尺寸调大后特征图显存占用是平方级增长,加上数据加载多进程缓存。解决:先降imgsz再降 batch,同时把 dataloader 的workers调小,用torch.cuda.empty_cache()排查是否有残留。
5.4 类别严重不均衡导致小类全漏
现象:样本多的类别召回正常,稀有类别几乎检不出。原因:损失被大类主导。解决:开启类别权重或 focal loss,同时对稀有类别做过采样;更稳妥的是先合并过细的类别,把任务做粗再逐步细化。
5.5 推理速度不达标
现象:精度够了但帧率上不去。原因:输入尺寸过大 + 后处理 NMS 阈值过松产生大量候选框。解决:导出 ONNX 或 TensorRT 加速,NMS 的iou阈值从 0.45 起调,别一味求召回把候选框堆爆。
6. 进阶技巧:用切片推理把小异物召回再拉一档
前面所有优化做完,如果小异物召回还是卡在瓶颈,别急着换模型,先试切片推理(SAHI 思路)。原理很直接:把大图切成有重叠的小块分别推理,再合并框,等效于在不增加显存的前提下放大了小目标。实现上不需要重训,直接套在已有权重上:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model = AutoDetectionModel.from_pretrained( model_type="yolov8", model_path="runs/detect/train/weights/best.pt", confidence_threshold=0.2, device="cuda:0", ) result = get_sliced_prediction( "test_fod.jpg", model, slice_height=512, slice_width=512, overlap_height_ratio=0.2, overlap_width_ratio=0.2, ) result.export_visuals(export_dir="vis/")参数说明:slice_height/width决定切片大小,太小会切碎目标、太大会失去放大意义,一般取输入尺寸的一半左右;overlap_*_ratio是切片重叠比例,0.2 是常用起点,重叠太小会在切片边界漏检,太大则推理耗时翻倍。合并阶段还要处理跨切片的重复框,SAHI 内部用 NMS 做,但阈值要单独调,别沿用全图推理的阈值。
这套方法在 FOD 场景的收益很直观:原本 640 输入下只有几个像素的螺丝钉,切片后变成几十像素,特征表达完全不一样。代价是推理耗时成倍增加,所以适合离线巡检或对实时性要求不高的场景;在线视频流要算清楚帧率和切片数的账。
我自己的习惯是:任何数据集在正式训之前,先抽 20 张图把标注画出来肉眼过一遍,再跑一次切片推理和全图推理的对比,两个结果差得离谱就说明数据或坐标有问题,先修数据再谈调参。这个流程帮我省过不止一次通宵重训。希望帮到你。
本文还有配套的精品资源,点击获取