简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的机场跑道FOD(异物)监测系统完整项目,基于YOLOv8目标检测框架实现,可用于毕业设计、课程设计或大作业演示。资源包共97个文件,以70个Python源码文件为核心,辅以4个pt模型权重、5个xml配置、12个pyc编译文件及mp4演示视频等,压缩包约24.21MB,涵盖训练、推理、可视化界面与部署说明。项目已完整跑通,可生成核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果与标签分布图,并配有可视化页面与README说明,部署简单、上手即用。目前已有124人学习下载。读者可据此快速复现整套检测流程,理解YOLOv8在跑道异物监测中的落地方式,也可在源码基础上修改扩展,用于毕设答辩或项目初期立项演示。
1. 机场跑道 FOD 监测到底难在哪:从一条 3 厘米螺栓说起
一条 3 厘米长的螺栓掉在跑道接缝处,肉眼在 200 米外几乎看不见,但一架落地速度 250 km/h 的客机发动机吸入它,维修账单能到七位数。FOD(Foreign Object Debris,外来物碎片)监测要解决的就是这件事:在飞机起降前,把跑道上的金属件、碎石、塑料、纸箱、鸟尸这类东西找出来并定位。传统做法靠人工巡道,一小时一趟,中间的空窗期就是风险敞口。
这套《基于 YOLOv8 的机场跑道 FOD 监测系统》想干的事很直接:用摄像头拍跑道,用 YOLOv8 做目标检测,把 FOD 框出来,再配一个可视化界面和完整数据集,让毕设或课程设计能简单部署就跑起来。它适合三类人:要交毕设的学生、想快速验证 FOD 检测可行性的工程师、以及需要一套能改能扩的检测基线的人。核心词就三个——YOLOv8、FOD、监测系统,后面所有章节都围绕它们展开。
2. YOLOv8 做 FOD 检测的选型账:为什么不是 Faster R-CNN 或 YOLOv5
2.1 跑道场景对检测器的三个硬约束
跑道 FOD 检测和普通目标检测不一样,它有三个绕不开的约束。第一是目标极小,FOD 在画面里可能只占 20×20 像素,YOLOv8 的 P3 特征图(stride 8)是能覆盖这个尺度的最低层,再往下就丢了。第二是背景单调但干扰多,跑道是灰色混凝土,FOD 和接缝、轮胎印、阴影的对比度很低,模型容易把接缝当目标。第三是实时性,跑道监控通常多路摄像头,单路推理要压到 30 ms 以内,否则多路叠加就卡死。
YOLOv8 在这三点上的表现:anchor-free 解耦头对小目标回归更稳,C2f 模块比 YOLOv5 的 C3 多了一条梯度路径,小目标召回率实测能高 3 到 5 个百分点。Faster R-CNN 精度可能略高,但两阶段推理在 1080Ti 上单帧要 80 ms 以上,多路场景直接出局。YOLOv5 不是不能用,但 YOLOv8 的 ultralytics 库把训练、验证、导出、推理统一成一套 API,部署教程能少写一半。
2.2 环境配置:CPU 版和 GPU 版的分叉点
热词里有人搜「ubuntu20.04 搭建 yolov8 环境 cpu 版本」,也有人搜「gtx1660ti 跑 yolov8」,这两条路配置不一样。CPU 版适合先跑通流程、验证数据集,GPU 版才是训练和部署的正路。下面给一套在 Ubuntu 20.04 上从零到能推理的最小命令,Python 用 3.10,CUDA 用 11.8(GPU 版)。
# 1. 建虚拟环境,别用系统 Python,否则依赖冲突会让你怀疑人生 python3.10 -m venv fod_env source fod_env/bin/activate # 2. 装 PyTorch,CPU 版和 GPU 版二选一 # CPU 版(先跑通流程用) pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # GPU 版(CUDA 11.8,1660Ti 也能用) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 3. 装 ultralytics,YOLOv8 的官方库 pip install ultralytics # 4. 验证装好了没 yolo checks这段命令的逻辑:虚拟环境隔离依赖,PyTorch 按硬件选源,ultralytics 一条命令带出 YOLOv8 全部功能。参数上注意--index-url必须和你的 CUDA 版本匹配,装错了torch.cuda.is_available()会返回 False,训练时默默跑在 CPU 上,速度差 20 倍。yolo checks会打印环境摘要,重点看 CUDA 那一行是不是可用。
2.3 数据集怎么组织:FOD 标注的目录结构和 yaml 写法
标题里说「完整数据集」,但你要知道数据集怎么摆才能被 YOLOv8 吃进去。常见做法是 images 和 labels 平行放,train/val 分开,yaml 里写绝对路径或相对路径都行,但相对路径的基准是 ultralytics 的运行目录,容易踩坑,我一般写绝对路径。
# fod_dataset.yaml path: /home/user/fod_dataset # 数据集根目录 train: images/train # 训练集图片,相对 path val: images/val # 验证集图片 nc: 4 # 类别数,按你的数据集改 names: # 类别名,顺序必须和标注一致 0: metal # 金属件 1: stone # 碎石 2: plastic # 塑料 3: other # 其他标注格式是 YOLO 的 txt:每行class_id x_center y_center width height,全部归一化到 0 到 1。这里最容易翻车的是类别顺序,names 里 0 是 metal,标注文件里 0 也必须是 metal,错一位整个训练就废了。数据集划分建议 train:val = 8:2,FOD 这种小目标场景,验证集少于 200 张,mAP 波动会很大,看不出真实水平。
3. 训练自己的 FOD 数据集:参数怎么设、曲线怎么看
3.1 从预训练权重开始:一条命令跑通训练
YOLOv8 训练的核心就一条命令,但参数含义得说清楚,不然你调不动。下面这条是 FOD 场景的常用起点,基于 yolov8s(small)预训练权重,1660Ti 6G 显存能跑 batch 16。
yolo detect train \ model=yolov8s.pt \ # 预训练权重,别从 yaml 从零训 data=fod_dataset.yaml \ # 数据集配置 epochs=100 \ # 训练轮数,FOD 一般 80-150 imgsz=640 \ # 输入尺寸,小目标可上 1280 batch=16 \ # 批大小,显存不够就降 lr0=0.01 \ # 初始学习率 patience=20 \ # 20 轮没提升就早停 device=0 \ # GPU 编号,CPU 写 cpu project=runs/fod \ # 输出目录 name=exp1 # 实验名逻辑说明:model=yolov8s.pt是关键,预训练权重让模型带着通用特征来,FOD 数据集通常几千张,从零训根本收敛不了。imgsz=640是默认值,但 FOD 目标小,如果显存够,上 1280 能让小目标召回明显提升,代价是显存翻倍、速度减半。patience=20是后悔药,防止过拟合后白跑几十轮。lr0=0.01配合默认的余弦退火,前 3 轮 warmup,这是 ultralytics 的默认策略,一般不用改。
3.2 损失曲线和 mAP 曲线:哪条线不对劲就该停
训练跑起来后,runs/fod/exp1/下会有 results.csv 和一堆曲线图。热词里有人搜「yolov8 画损失函数曲线图」,其实 ultralytics 自动画了,你要会看。重点看三条:box_loss、cls_loss、mAP50。
box_loss 和 cls_loss 正常是前 10 轮快速下降,然后缓慢收敛。如果 box_loss 震荡不降,多半是学习率太大或标注框有问题。如果 cls_loss 降但 mAP 不涨,说明模型在背类别但定位不准,检查标注框是否贴合目标。mAP50 在 FOD 场景能到 0.85 以上算可用,0.7 以下要回头看数据集质量。验证集 mAP 涨、训练集 mAP 更高但差距拉大,就是过拟合,早停或加数据增强。
# 用 pandas 快速看 results.csv,比翻图快 import pandas as pd df = pd.read_csv('runs/fod/exp1/results.csv') df.columns = df.columns.str.strip() # 列名有空格,先去掉 # 看最后 10 轮的 mAP 和 loss print(df[['epoch', 'train/box_loss', 'metrics/mAP50(B)']].tail(10))这段代码的逻辑:results.csv 列名带空格,直接取列会报 KeyError,先 strip。看最后 10 轮能判断是否还在提升,如果 mAP 连续 10 轮不动,继续训就是浪费电。参数上metrics/mAP50(B)是 IoU=0.5 的 mAP,FOD 场景看这个就够,mAP50-95 更严格但小目标上普遍偏低,参考价值有限。
3.3 数据增强:FOD 场景该开哪些、该关哪些
YOLOv8 默认开了一堆增强,但 FOD 场景有些要关。默认的 mosaic 增强把 4 张图拼一起,对小目标有帮助,但 FOD 目标本来就小,拼完更小,建议保留但把mosaic=1.0降到 0.5。HSV 增强(色调、饱和度、亮度)要开,跑道光照变化大,这个能提升鲁棒性。翻转增强fliplr=0.5可以开,但flipud要关,跑道图上下翻转不符合物理场景,翻了反而引入噪声。
# 在训练命令里覆盖默认增强参数 yolo detect train model=yolov8s.pt data=fod_dataset.yaml epochs=100 \ mosaic=0.5 \ # 降低 mosaic 比例,保护小目标 fliplr=0.5 \ # 水平翻转,保留 flipud=0.0 \ # 关闭上下翻转 hsv_h=0.015 \ # 色调抖动,默认值 hsv_v=0.4 \ # 亮度抖动,跑道光照变化大,可加大 scale=0.5 # 缩放增强,小目标场景别太大参数说明:mosaic=0.5表示 50% 概率做 mosaic,比默认 1.0 温和。hsv_v=0.4比默认 0.4 持平,如果数据集光照单一可以加到 0.6。scale=0.5是随机缩放范围,FOD 目标小,缩放太狠会让目标消失,0.5 是保守值。这些参数没有绝对最优,跑两组对比看 mAP 就知道。
4. 可视化界面和部署:从权重文件到能点的系统
4.1 界面选型:Gradio 还是 PyQt,毕设场景怎么选
标题里说「可视化界面」,常见做法有两种:Gradio 做 Web 界面,PyQt 做桌面界面。毕设场景我推荐 Gradio,原因是部署简单,一条命令起服务,浏览器就能访问,答辩时不用装环境。PyQt 适合需要离线、需要复杂交互的场景,但打包成 exe 容易出玄学问题。
# app.py,Gradio 版 FOD 检测界面 import gradio as gr from ultralytics import YOLO from PIL import Image model = YOLO('runs/fod/exp1/weights/best.pt') # 训练好的权重 def detect(image): results = model(image, conf=0.25, iou=0.45) # 推理 annotated = results[0].plot() # 画框 return Image.fromarray(annotated[..., ::-1]) # BGR 转 RGB demo = gr.Interface( fn=detect, inputs=gr.Image(type='pil'), # 输入图片 outputs=gr.Image(type='pil'), # 输出带框图片 title='机场跑道 FOD 监测系统', description='上传跑道图片,自动检测异物' ) demo.launch(server_name='0.0.0.0', server_port=7860)逻辑说明:YOLO('best.pt')加载训练好的权重,conf=0.25是置信度阈值,低于这个的框不显示,FOD 场景建议 0.25 到 0.3,太低误报多,太高漏检。iou=0.45是 NMS 的 IoU 阈值,重叠框合并。results[0].plot()直接返回带框的 numpy 数组,格式是 BGR,转 RGB 才能被 PIL 正确显示。server_name='0.0.0.0'让局域网能访问,答辩时手机也能看。
4.2 模型导出:ONNX 和 TensorRT 怎么选
训练完的 .pt 权重推理要过 PyTorch,速度一般。部署到实际系统通常导出 ONNX 或 TensorRT。ONNX 通用性好,CPU 和 GPU 都能跑,TensorRT 在 NVIDIA 卡上最快但绑定硬件。热词里有人搜「rk3588 部署 yolov8」和「hi3516cv610 yolov8 模型转换」,那是嵌入式路线,导出格式又不一样,这里先说通用路线。
# 导出 ONNX,动态 batch,适合服务端 yolo export model=runs/fod/exp1/weights/best.pt format=onnx dynamic=True opset=12 # 导出 TensorRT,FP16 精度,1660Ti 上速度翻倍 yolo export model=runs/fod/exp1/weights/best.pt format=engine half=True device=0参数说明:dynamic=True让 ONNX 支持动态 batch 和动态尺寸,服务端多请求时有用。opset=12是 ONNX 算子集版本,太低不支持某些算子,太高有些推理引擎不认,12 是稳妥值。half=True是 FP16 量化,精度损失通常 1 个百分点以内,速度提升接近一倍,1660Ti 支持 FP16。导出后在 Python 里用YOLO('best.onnx')或YOLO('best.engine')直接推理,API 不变。
4.3 部署到实际监控流:从单图到视频流
单图检测跑通后,实际系统要处理视频流。常见做法是用 OpenCV 读 RTSP 流或本地视频,逐帧推理,画框后推流或显示。这里有个坑:逐帧推理如果模型慢,帧会堆积,延迟越来越大。解决办法是跳帧或开一个独立线程做推理,主线程只负责取帧和显示。
import cv2 from ultralytics import YOLO model = YOLO('best.engine') # 用 TensorRT 引擎,速度最快 cap = cv2.VideoCapture('rtsp://your_camera_stream') # 换成你的流地址 while True: ret, frame = cap.read() if not ret: break results = model(frame, conf=0.25, verbose=False) # verbose=False 关日志 annotated = results[0].plot() cv2.imshow('FOD Monitor', annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:verbose=False关掉每帧的日志打印,否则控制台刷屏。cv2.waitKey(1)是必须的,否则窗口不刷新。实际部署时imshow换成推流或存盘。如果帧率跟不上,在cap.read()后加跳帧逻辑,比如每 3 帧处理 1 帧,代价是可能漏掉快速出现的 FOD,需要根据场景权衡。
5. 避坑与排查:FOD 检测系统落地时最容易翻车的 5 个点
5.1 现象:训练 loss 正常降,但验证 mAP 一直是 0
原因:最常见的是标注文件和图片不对应,或者 yaml 里路径写错,模型实际在训空标签。其次是类别顺序错位,names 和标注的 class_id 对不上。
解决:先跑yolo detect val model=best.pt data=fod_dataset.yaml,看验证集能不能加载。再随机抽 10 张图,用yolo detect predict看有没有框。如果框乱飞,检查标注。路径问题用绝对路径最稳。
5.2 现象:小目标 FOD 漏检严重,大目标正常
原因:输入尺寸 640 时,20 像素的目标在 P3 特征图上只剩 2 到 3 个像素,特征太弱。或者数据集中小目标样本太少,模型没学到。
解决:把imgsz提到 1280,显存不够就降 batch。数据层面,用 mosaic 增强增加小目标出现频率,或者专门裁剪小目标区域做过采样。YOLOv8 的 P2 层(stride 4)可以加,但要改模型结构,属于改进范畴,先试前两个。
5.3 现象:推理时 GPU 利用率低,速度上不去
原因:数据预处理在 CPU 上成了瓶颈,或者用了 .pt 权重没导出 TensorRT。1660Ti 上 .pt 推理 640 尺寸大概 15 ms,TensorRT FP16 能到 7 ms。
解决:导出 TensorRT 引擎,half=True。如果还慢,检查cv2.VideoCapture的取帧是否阻塞,用独立线程取帧。另外verbose=False能省一点日志开销,量不大但积少成多。
5.4 现象:界面能跑,但上传大图就崩
原因:Gradio 默认对输入图片有尺寸限制,或者模型推理时显存爆了。大图 resize 到 1280 后显存占用翻倍。
解决:在 Gradio 的gr.Image里加image_mode='RGB'和尺寸限制,或者在detect函数里先 resize 到固定尺寸再推理。显存不够就降imgsz或换更小的模型(yolov8n)。
5.5 现象:换一台机器部署,报 CUDA 版本不匹配
原因:训练和部署的 CUDA 版本不一致,或者 PyTorch 版本和 CUDA 对不上。1660Ti 支持 CUDA 11.x 和 12.x,但 PyTorch 编译时绑定了特定版本。
解决:部署机器上用nvidia-smi看驱动支持的 CUDA 版本,然后装对应版本的 PyTorch。最稳的办法是用 Docker,把环境固化。如果目标机器没 GPU,导出 ONNX 用 CPU 推理,速度慢但能跑。
6. 把 FOD 检测做扎实的一个技巧:用置信度分层和尺寸过滤压误报
系统能跑之后,真正影响可用性的是误报。跑道上接缝、阴影、轮胎印被框成 FOD,监控员看多了就不信系统了。我一般加两层过滤:置信度分层和尺寸过滤。置信度高于 0.5 的直接报,0.25 到 0.5 之间的进二次确认队列,尺寸小于画面 0.1% 的框直接丢,因为真实 FOD 再小也有个下限。
def filter_detections(results, img_shape, conf_high=0.5, min_area_ratio=0.001): """分层过滤:高置信度直接留,低置信度看尺寸""" boxes = results[0].boxes h, w = img_shape[:2] img_area = h * w keep = [] for box in boxes: conf = float(box.conf) x1, y1, x2, y2 = box.xyxy[0].tolist() area_ratio = (x2 - x1) * (y2 - y1) / img_area if area_ratio < min_area_ratio: continue # 太小,丢 if conf >= conf_high: keep.append(box) # 高置信度直接留 elif conf >= 0.25: keep.append(box) # 低置信度但尺寸够,留待人工确认 return keep这段代码的逻辑:min_area_ratio=0.001表示框面积小于画面千分之一就丢,1080P 下约 2000 像素,对应 45×45 的框,比这小的 FOD 本来也看不清。conf_high=0.5是分层线,高置信度直接报,低置信度留给人工。参数要根据你的摄像头分辨率和 FOD 实际尺寸调,没有万能值。我习惯在验证集上跑一遍,看误报和漏检的平衡点,再定这两个数。
还有个习惯:每次改完参数,别只看 mAP,一定拿一段真实跑道视频跑一遍,肉眼看误报。mAP 涨了但误报多了,系统反而更难用。FOD 检测这事,召回率重要,但误报率决定监控员愿不愿意用。希望帮到你。
本文还有配套的精品资源,点击获取