简介:面向计算机相关专业毕设与课程设计的YOLOv8交通路口非机动车闯红灯识别完整包,将模型训练、实时检测与可视化界面集于一体,可直接部署并用于同类目标检测项目。压缩包共8个文件,含3个脚本、3个模型权重文件和2个说明文档,整体大小约15.91MB;训练脚本负责训练及指标曲线生成,检测脚本完成视频识别,界面脚本提供操作页面,配合说明文档即可快速部署运行。资源附带完整数据集,并能输出混淆矩阵、F1分数曲线、精确率-召回率曲线、标签分布图及验证集预测结果,适合答辩展示与效果对比。代码经测试通过,功能完整,既可直接用于非机动车闯红灯识别,也适合二次开发作为毕设、课设或项目演示。目前已有57人学习下载,对深度学习目标检测方向的学生和开发者具有实用价值。
1. 这个项目到底要解决什么:非机动车闯红灯识别的实际痛点
晚上值班的交警盯着十几个路口的监控画面,靠截屏去认定非机动车闯红灯,一晚上能挑出几十条就算高产。而路口每天实际发生的闯红灯数是这个数字的好几倍。这个标题里的项目,做的就是把这件重复劳动交给YOLOv8:用目标检测框出画面里的电动车和自行车,再结合红绿灯状态判断它是不是在红灯相位越过停止线。适合毕设或课程设计,因为它把训练模型、可视化界面、完整数据集、部署教程都打包了,拿到zip后不是从头搭环境,而是先跑通再替换成自己的数据。但能跑通不等于能落地,我拆开讲一遍,从数据到部署,把说明书不会写的坑都填上。
2. YOLOv8与非机动车闯红灯识别:为什么这个组合能落地
2.1 任务边界:检测“非机动车”和判断“闯红灯”是两件事
很多第一次做这个题目的人,以为训练一个YOLOv8模型就能直接输出“闯红灯”三个字。实际不是。YOLOv8只能回答“画面上哪里有非机动车”,它是一个目标检测器,输出的是类别、置信度和边界框。而闯红灯是一个事件判断,需要两个输入:一是非机动车的位置,二是当时的信号灯状态。常见做法是:在视频的固定区域画一条虚拟停止线,当检测框的底边中心点越过停止线,并且信号灯处于红灯相位,才判定为闯红灯。
所以拿到源码包后,先别急着训练,先看它的闯红灯判定逻辑在哪一层。如果是用检测结果加信号灯状态写的一个独立函数,那说明设计是对的;如果只是用检测框数量变化推断红灯,那数据一换就废。我一般会把判定逻辑单独写成一个类,输入当前帧的检测结果和信号灯状态,输出一个报警事件,这样后面调阈值和换信号源都方便。
这里要特别提醒:停止线位置不是随便画在画面中央的,得根据摄像头机位来标。正俯拍时,停止线就是路口白线在图像里的投影;侧向机位时,停止线是斜的。很多项目为了省事把停止线画成竖直或水平直线,导致车辆在直行车道被误判。我习惯在配置文件里定义四个点组成一个多边形,检测框底边中心点落在多边形内才算“越线”,这样斜向机位也能兜住。
2.2 YOLOv8在精度和部署上的优势
选YOLOv8而不是YOLOv5或Faster R-CNN,主要看三点。第一是anchor-free设计,不用手工调先验框尺寸,路口场景里非机动车形态从正侧面到俯拍差别很大,anchor-free在形变上更省心。第二是C2f模块在相同参数量下特征融合更充分,对体积小、占比小的非机动车(比如远处路口中央的电动车)检出率更高。第三是生态成熟,训练、导出ONNX、转TensorRT的命令都是标准化流程,网上“yolov8训练自己的数据集” “处理数据集用于yolov8训练”这类教程一搜一大把,遇到报错也好查。
如果你用的显卡是GTX1660ti这个档次,跑yolov8s在720p视频上能到30到40FPS;如果只有CPU,建议直接用yolov8n,输入尺寸降到416,否则没法实时。这也是标题里带部署教程的原因:检测模型只是第一步,真正花时间的是环境配置和界面集成。在ubuntu20.04上搭建yolov8环境cpu版本时,不要盲目装CUDA,先查显卡驱动,没有N卡就直接用CPU版torch,跑起来慢但能验证逻辑。很多人卡在这里一卡就是半天,其实按官方文档装CPU版本只要三步。
另外说一句权重下载的事。训练时会自动下载预训练权重,但国内网络经常失败。常见做法是手动下载yolov8s.pt放到项目根目录,再指定--weights yolov8s.pt。下载来源就是官方GitHub release,不要信第三方修改过的权重。版本必须和ultralytics库匹配,不然会报Unknown model之类的错,这个坑在毕设季特别常见。
2.3 拿到源码包第一件事:先读目录结构和README
打开zip后第一件事不是双击运行,而是把目录结构树列出来,找到关键位置:训练脚本、推理脚本、界面文件、权重文件、数据集目录、部署说明。用Linux的命令:
unzip "基于YOLOv8的交通路口非机动车闯红灯识别.zip" -d yolov8_traffic cd yolov8_traffic find . -maxdepth 2 -type f | sortfind比tree通用,Windows WSL里也能用。看目录的目的,是判断这个项目的运行入口是GUI还是命令行。常见结构是main.py或app.py负责可视化界面,detect.py或inference.py负责纯检测,weights/下放best.pt,dataset/下按YOLO格式组织。先翻一遍README,把依赖版本记下来,特别是PyQt5、ultralytics、onnxruntime这三类,版本不对会连锁报错。很多翻车都发生在这一步:直接跑python main.py,报ModuleNotFoundError: ultralytics,其实requirements.txt就在根目录。
我自己的习惯是先在项目里建一个虚拟环境,用python -m venv venv,再激活,最后按requirements安装。不要用全局环境,因为同时开好几个毕设项目,依赖打架是常事。装依赖时如果卡在pyqt5这种大包上,用清华源或阿里源加速,几十秒就完事。跑通第一遍后,再决定是否换成自己的数据继续训练。注意,很多压缩包里的数据集是别人的样本,直接用它训练的模型到你实际路口上不一定好使,后面会说怎么换数据。
3. 训练自己的闯红灯识别模型:从数据集到权重
3.1 数据集的组成:你需要多少张图、涵盖哪些场景
完整数据集一般已经标注好,但替换成自己路口的数据才是真训练。先说数量:如果只用“非机动车”一个类别,1000张左右能训练出能用的效果;要想在夜间和雨雾天不掉链子,至少2000张。建议场景分布:白天正常50%、夜间20%、黄昏/逆光15%、雨天/遮挡15%。如果数据集里全是固定机位的那一个路口,换一个路口会漏检,因为俯拍角度变了。
我见过不少人拿COCO数据集里所有带person的图来凑数,这不行。交通路口的非机动车是俯拍视角,COCO多为平拍,两者特征差距很大。正确做法是去实际路口录视频,每3到5秒抽一帧,挑出包含车辆的写脚本切出图片,再手工清洗掉模糊帧。抽帧脚本很容易写:
import cv2 cap = cv2.VideoCapture('cross_road.mp4') fps = cap.get(cv2.CAP_PROP_FPS) frame_interval = max(1, int(fps * 3)) # 每3秒一帧 idx = 0 out_id = 0 while True: ret, frame = cap.read() if not ret: break if idx % frame_interval == 0: cv2.imwrite(f'raw_images/{out_id:05d}.jpg', frame) out_id += 1 idx += 1 cap.release()抽出来的图会有一堆重复背景,用指尖上的图片去重工具或写个哈希去重脚本清理,留下构图有变化的。数量宁可少而精,不要堆大量相似图,否则训练集内部高度重复,验证集上看着mAP很高,现场一换场景就崩。
3.2 用LabelImg标注非机动车
如果自带数据集不够,或者想加自己的样本,用LabelImg最省事。安装和启动:
pip install labelImg labelImg images/ classes.txt在标注工具里打开图片,选择YOLO格式输出,画框时框住车辆的完整轮廓,不要只框车身。对电动车和自行车,我建议统一用一个类名non_motor,别拆成ebike和bike,因为很多图片里两者难以区分,类别越多误检越多。也有教程建议用LabelMe,但产出是JSON,还得转一次yolo格式,多一步转换就多一个出错环节,还是LabelImg直接出txt最顺。
生成的是txt文件,每行格式:class_id x_center y_center width height,坐标是相对值,范围0到1。注意检查有没有负坐标或大于1的坐标,后面训练会报错。我在标注时习惯把车身完整框进去,包括骑行者,但不要框到其他人。如果画面里行人非常多,而你的目标只有非机动车,建议额外加一个person类别,哪怕不用于报警也要标出来,这样模型能学到“什么是行人,什么不是非机动车”,误检率会明显下降。
3.3 数据集划分与YOLOv8目录组织
YOLOv8官方要求用数据yaml文件指向训练、验证图片目录。目录结构:
dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/所有图片和对应txt必须同名。划分脚本:
import os, random, shutil random.seed(42) src_images = 'raw_images' src_labels = 'raw_labels' train_img = 'dataset/images/train' val_img = 'dataset/images/val' train_lbl = 'dataset/labels/train' val_lbl = 'dataset/labels/val' names = [f[:-4] for f in os.listdir(src_images) if f.endswith('.jpg')] random.shuffle(names) split = int(len(names) * 0.8) for name in names[:split]: shutil.copy(f'{src_images}/{name}.jpg', f'{train_img}/{name}.jpg') shutil.copy(f'{src_labels}/{name}.txt', f'{train_lbl}/{name}.txt') for name in names[split:]: shutil.copy(f'{src_images}/{name}.jpg', f'{val_img}/{name}.jpg') shutil.copy(f'{src_labels}/{name}.txt', f'{val_lbl}/{name}.txt')随机种子固定,保证每次划分一致。8:2是保守比例,样本少时建议9:1,验证集至少50张。注意不要有图片在训练和验证都出现,否则mAP虚高。shutil.copy不改文件名,但前提是原文件名始终一一对应;如果有个jpg没有同名txt,脚本会抛错,建议加个判断跳过这种坏样本。
划分完还要写dataset.yaml:
path: dataset train: images/train val: images/val nc: 1 names: ['non_motor']path用相对路径时,以执行训练命令的当前目录为基准。如果你把dataset.yaml放在项目根目录,path: dataset就行。别把train和val写成绝对路径,否则换台机器又要改。
3.4 训练命令与关键参数
进入项目目录,先装依赖:
pip install -r requirements.txt用ultralytics新版训练:
yolo train data=dataset.yaml model=yolov8s.pt epochs=100 batch=16 imgsz=640 device=0如果是旧项目里的train.py,常见是:
python train.py --data dataset.yaml --weights yolov8s.pt --epochs 100 --batch 16 --imgsz 640我一般会先跑10个epoch验证数据和模型能正常工作,再拉长到100。前面数据没问题的话,10个epoch就能看到loss在降。关键参数:
epochs:100起步,数据集小的话50就能收敛。batch:16在GTX1660ti的6G显存上跑yolov8s没问题,显存不足降到8,并开启梯度累积。imgsz:640是速度和精度平衡点,如果摄像头拉得远,框很小,可以试960但训练时间变长。patience:默认10,超过10个epoch val loss不降就早停,防止过拟合。device:CPU写cpu,但别指望一夜训练完,小数据集也要几小时。
预训练权重yolov8s.pt会自动从官方地址下载,如果下载失败,手动下载后放到当前目录。我个人喜欢用yolov8m在中等数据集上做对比,但毕设交作业用s就够,m训练时间翻倍,精度只高1到2个点。如果你电脑是纯CPU,老实选n,训练时间能少一半,而且跑界面时不至于卡成幻灯片。
3.5 训练过程监控与loss曲线解读
训练结束后看权重目录下的runs/detect/train。用tensorboard看曲线:
pip install tensorboard tensorboard --logdir runs/detect浏览器打开http://localhost:6006就能看到损失函数曲线图。这个标签也是很常见的长尾词:yolov8画损失函数曲线图怎么做?其实不用额外写代码,训练日志里自动保存了。重点看train/box_loss和val/box_loss。正常情况下两者同步下降,如果train loss继续降而val loss反弹,就是过拟合,提前停止或在data.yaml里加augment。还有一个常见问题:loss降到0.02就不再降了,但mAP只有0.3,这通常不是训练问题,而是标注质量差,比如框不贴合目标或类别标签标错。建议用yolo val跑一次验证集,把预测结果和标签画出来对比:
yolo predict model=runs/weights/best.pt source=val_one.jpg save_txt=True画出来的框如果偏大偏小,就去修标注,而不是加训练轮数。训练完还会生成confusion_matrix.png,如果里面non_motor和background的混淆严重,说明负样本不够,去补充完全不包含电动车的路口背景图。这个细节很多人忽略,但比调学习率有用得多。
4. 可视化界面与部署:让模型在路口“跑起来”
4.1 用PyQt5搭一个带视频预览的检测界面
标题里带可视化界面,一般就是用PyQt5实现一个窗口:左边是视频播放区,右边是检测结果列表和报警日志。最小框架示例:
import sys from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import QTimer import cv2 from ultralytics import YOLO class MainWindow(QWidget): def __init__(self): super().__init__() self.label = QLabel() layout = QVBoxLayout() layout.addWidget(self.label) self.setLayout(layout) self.model = YOLO('weights/best.pt') self.cap = cv2.VideoCapture(0) self.timer = QTimer() self.timer.timeout.connect(self.update_frame) self.timer.start(30) # 约33ms一帧 def update_frame(self): ret, frame = self.cap.read() if not ret: return results = self.model(frame) annotated = results[0].plot() # BGR转RGB并显示 rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape qimg = QImage(rgb.data, w, h, ch*w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg)) app = QApplication(sys.argv) win = MainWindow() win.show() sys.exit(app.exec_())这个例子把推理放在QTimer回调里,对30FPS的视频会卡,后面改进。注意cv2.VideoCapture(0)读取的是摄像头索引,读视频文件改成路径。界面布局别做太花哨,毕设答辩时能稳定跑比特效重要。我在界面上放了三个区域:视频画布、报警滚动列表、一个“开始/停止”按钮。报警列表用QListWidget,每当检测到越线就把时间戳和置信度加进去,这个列表就是你的演示数据。
4.2 红绿灯状态判定:从图像颜色到信号机数据
这部分是闯红灯识别的核心。最简单的做法是截取画面中信号灯区域,用颜色阈值判断红绿。示例:
def get_light_state(frame, roi): # roi是信号灯区域,比如 [x1, y1, x2, y2] crop = frame[roi[1]:roi[3], roi[0]:roi[2]] # 转HSV,提取红色阈值 hsv = cv2.cvtColor(crop, cv2.COLOR_BGR2HSV) mask_red = cv2.inRange(hsv, (0, 100, 100), (10, 255, 255)) + \ cv2.inRange(hsv, (160, 100, 100), (180, 255, 255)) mask_green = cv2.inRange(hsv, (35, 100, 100), (85, 255, 255)) red_area = cv2.countNonZero(mask_red) green_area = cv2.countNonZero(mask_green) if red_area > 20 and red_area > green_area * 2: return 'red' elif green_area > 20 and green_area > red_area * 2: return 'green' return 'unknown'红色在HSV有两种范围,所以要组合。面积阈值20是因为画面中信号灯区域可能很小;阈值设大了,远处红灯会漏判。更好的做法是接入信号机输出的RS-485数据,但毕设一般没有硬件条件,用颜色阈值足够。要注意信号灯区域可能因摄像头移动而漂移,所以ROI要跟随固定参考物,或者直接让摄像头固定机位。实际项目中,红绿灯有可能是LED数字倒计时,那颜色判断会被数字干扰,我遇到的情况是把ROI缩小到信号灯圆形灯盘的中心区域,避开数字。
4.3 摄像头/视频流接入:线程与帧率控制
不能把检测放在UI线程里,否则界面会卡死。用QThread读帧+推理,把结果通过信号发给主线程:
from PyQt5.QtCore import QThread, pyqtSignal import cv2 from ultralytics import YOLO class DetectThread(QThread): result_signal = pyqtSignal(dict) def __init__(self, source): super().__init__() self.source = source self.model = YOLO('weights/best.pt') self.stop = False def run(self): cap = cv2.VideoCapture(self.source) while not self.stop: ret, frame = cap.read() if not ret: break results = self.model(frame) frame_annotated = results[0].plot() self.result_signal.emit({'frame': frame_annotated, 'boxes': results[0].boxes.data}) cap.release()主线程里连接信号,更新QLabel。帧率控制靠QThread里加time.sleep(0.03),或者丢弃积压帧。如果视频流卡顿,优先检查是不是摄像头分辨率设太高,把cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)和高度720设上。网络摄像头断流后cap.read()会一直返回False,要加重连逻辑,比如连续10次False就cap.release()再重新打开。
我自己的经验是不要用queue.Queue无脑塞帧,一旦推理慢,队列会积压成千上万frame,内存直接涨上去。改成一个只保留最新帧的缓存即可:
class LatestFrameBuffer: def __init__(self): self.frame = None def set(self, frame): self.frame = frame def get(self): return self.frame这样处理线程每次拿到的是最新帧,旧帧自动被覆盖,界面既不延迟,内存也稳定。
4.4 导出模型与TensorRT加速(可选)
如果部署到GPU服务器或边缘盒子,把PyTorch模型导出成ONNX再转TensorRT:
yolo export model=weights/best.pt format=onnx yolo export model=weights/best.pt format=engine device=0导出engine需要本机有TensorRT和CUDA。很多板端部署(比如瑞芯微RK3588)不支持直接跑engine,需要转成RKNN,流程是ONNX -> RKNN-Toolkit2 -> .rknn。这个不建议在毕设里碰,除非题目明确要求。更实际的做法是:用ONNX Runtime跑CPU版模型,对普通PC部署足够。注意导出时固定imgsz,后面推理的输入尺寸必须一致,否则会报形状错误。
在Windows上打包exe时,ultralytics的包很大,PyInstaller打包出来经常超过500MB。一个降低体积的方法是不打包torch,改用onnxruntime,但界面代码里的模型加载方式要换成onnxruntime的Session。这个取舍看你的需求:如果只是现场演示,源码跑就行;如果要交给老师一个双击就能开的exe,那得提前一星期处理打包依赖,别拖到最后一天。
5. 避坑:非机动车闯红灯识别最常见的5个翻车点
5.1 现象:行人被识别成非机动车,误报率高
这个坑几乎每个做交通识别的都会踩到。原因很直接:训练数据里把推车行人、背对摄像头的行人标成了非机动车;或者非机动车类别缺少行人负样本。解决:首先清理标注,把行人框剔除;然后在数据集中加入person类别,即使不报警也要训练,让模型学会区分。我在项目里还加了后处理:检测框的长宽比和面积不符合非机动车特征的直接丢弃。非机动车框一般长宽比在0.3到1.5,车身像素面积占画面0.01以上,用这个过滤能压掉一部分误检。
boxes = results[0].boxes.data.cpu().numpy() filtered = [] for box in boxes: x1, y1, x2, y2, conf, cls = box w = x2 - x1 h = y2 - y1 if 0.3 < w/h < 1.5 and conf > 0.35: filtered.append(box)这个后处理是“宁可漏检不要误报”的逻辑。如果你发现正常行驶的车辆也被过滤掉了,把长宽比范围放宽到0.2到2.0。另外,conf > 0.35要根据实际效果调,白天可以设0.4,夜间设0.2。我见过有人把conf设到0.7来压误报,结果画面里的电动车一个都检测不出来,那就本末倒置了。
5.2 现象:绿灯时也报警,判断逻辑串了
原因:信号灯颜色判断和检测结果处理不同步。比如视频是实时流,检测线程在t帧取信号灯状态,但显示的是上一帧的结果;或者信号灯区域被树枝、车体遮挡,颜色阈值永远判成红色。解决:把信号灯状态封装成带时间戳的对象,检测结果也带时间戳,比较两者时间差小于0.5秒才可用;信号灯连续10帧判定为同一颜色才更新状态,避免单帧闪烁。
class LightState: def __init__(self): self.state = 'unknown' self.frame_cnt = 0 def update(self, detected): self.frame_cnt = self.frame_cnt + 1 if detected == self.state else 1 self.state = detected if self.frame_cnt > 10 else self.state另一个容易忽略的点:视频文件本身是录好的,没有实时信号灯状态。如果你拿一段历史视频做测试,必须手动标注视频里的红灯时间段,或者用颜色阈值判断每一帧。而颜色阈值判断在视频压缩后可能偏色,红灯变成了橙红色。解决办法是调HSV范围时用视频里真实过曝的几帧做校准,不要用网上找的“标准”红绿灯截图。
5.3 现象:训练时loss不降或直接爆显存
loss不降先检查数据集标签格式。常见问题是坐标归一化后出现大于1的值,因为标注时用了绝对坐标没转换。写一段脚本遍历labels验证:
import os for root, dirs, files in os.walk('dataset/labels'): for f in files: with open(os.path.join(root, f)) as fp: for line in fp: parts = line.split() if len(parts) != 5: print(f, '字段数不对') else: x, y, w, h = map(float, parts[1:]) if x > 1 or y > 1 or w > 1 or h > 1 or x < 0 or y < 0: print(f, '坐标越界')我自己有一次查了一晚上,最后发现是LabelImg在YOLO模式下输出的坐标是0-1的相对值,但有人把它导成PascalVOC格式的绝对像素坐标再存成txt,导致盒子坐标全乱。所以标注前先确认输出格式,别中途切换。
爆显存解法:batch从16降到8,imgsz从640降到512,或者换yolov8n。如果还爆,开启混合精度训练,在训练命令里加amp=True。GTX1660ti只有6G显存,别用yolov8l。如果你用的包是旧版train.py,amp可能是个开关参数,一定要确认版本。还有一条:减少workers值,Windows下workers一多就会报DataLoader错误,直接设workers=0最省心。
5.4 现象:可视化界面卡死,视频掉帧
原因:推理放在主线程,UI每帧都要等推理结束才能刷新。解决:用QThread,见上一章代码;同时控制队列长度,如果队列积压超过2帧就直接丢弃最旧帧,保证实时。我在界面里插了一个跳帧策略:检测耗时超过100ms时,显示原视频但报警仍按每帧检测,这样界面不卡,报警也不漏。具体做法是检测线程只做模型推理,把结果帧放到一个最新帧缓存,UI的QTimer只从缓存取最新帧显示,这样UI刷新频率和检测频率解耦。
还有一种卡死是OpenCV的cv2.waitKey(1)和PyQt的app.processEvents()混用。如果你在代码里还留着旧项目里的cv2.imshow,它会创建另一个窗口,和Qt事件循环打架。排查方法很简单:把cv2.waitKey全部注释,只保留Qt的Timer。界面卡死时看一眼CPU占用:如果主线程100%,多半是推理在主线程;如果检测线程100%,说明模型太大,需要换成更小的权重。
5.5 现象:部署到CPU机器上检测速度只有1-2FPS
原因:模型是yolov8s以上,CPU推理本来就慢。解决:换成yolov8n,输入尺寸降到416;用ONNX Runtime的CPU推理比PyTorch CPU快20%左右;检测间隔改每2帧检测一次,中间帧用上一帧的框位置,前提是场景静止或车辆移动不大。这个方法对闯红灯识别足够,因为我们只需要知道车辆是否越线,不需要精确到每一帧。
在CPU机器上还要注意线程数设置。onnxruntime可以设置intra_op_num_threads,默认会吃满所有核,但有时候多线程反而慢。我习惯先用4线程测试,再对比8线程,取最快。PyTorch CPU则有torch.set_num_threads(4)。另外,如果模型输入是640,在CPU上单帧推理可能200ms,降到416后能压到100ms以内。你的界面如果要做实时预览,建议把“预览分辨率”和“检测分辨率”分开:预览显示1280x720,但送进模型之前缩放到416,这样画面清晰度不影响检测速度。
6. 从“能跑”到“跑得稳”:验证方法与进阶技巧
6.1 用一段带红绿灯变化的视频做端到端验证
准备一段至少2分钟、包含红灯转绿灯再转红的视频。手动记录每个闯红灯事件的时刻,跑系统,把报警时刻列表对比。我常用一个简单表:真实事件数、系统报出数、其中真阳性、误报数,算召回和精确率。如果召回低,调低conf_thres;如果误报多,调高。这个验证必须在固定机位下做,不要中途拉镜头,否则停止线会漂移。
6.2 调节conf_thres与iou_thres:阈值到底怎么设
conf_thres默认0.25,如果误报多调到0.4到0.5,如果漏检多降到0.1到0.15。iou_thres用于NMS,一般保持0.45。给出推荐表:
| 场景 | conf_thres | iou_thres | 备注 |
|---|---|---|---|
| 白天正常 | 0.4 | 0.45 | 宁可少报别乱报 |
| 夜间 | 0.2 | 0.45 | 低置信目标多,降阈值 |
| 远距离小目标 | 0.15 | 0.5 | 框太小置信低 |
这个表不是玄学,是根据实际路灯下多曝光状态的统计经验。调节时每次改0.05,跑一段固定视频看结果,别一次跳到0.5。我在最终提交的版本里把conf_thres做成了界面上的滑块,这样答辩时能现场演示调节效果,比硬编码更出彩。
6.3 进阶:引入ByteTrack做跨帧目标跟踪
单帧检测只能判断“有非机动车越过停止线”,没法判断“同一辆车是不是持续闯红灯”,加上跟踪能锁ID。ByteTrack是目前在YOLOv8社区里用得最多的方案。集成思路是把检测框送入tracker,tracker输出ID和跟踪框,然后只对“穿过停止线且ID首次出现”的目标报警。伪代码:
from bytetrack import ByteTrack tracker = ByteTrack() for frame in video: boxes = detector.detect(frame) # 返回xyxy, conf, cls tracks = tracker.update(boxes) for t in tracks: tid, x1, y1, x2, y2, conf = t if is_cross_stopline(x1, y1, x2, y2) and light_state == 'red': if tid not in reported_ids: report(tid) reported_ids.add(tid)注意:跟踪会让报警延迟几帧,因为需要积累几个帧的关联。不要对每一个越线帧都报警,否则同一个人会被报5次。报警去重用reported_ids集合,但集合会无限增长,我设了一个上限,超过500就清空一次,避免内存占用。
6.4 把识别结果写成报警日志和统计表
毕设答辩时,“只显示框”没有说服力,要把报警记录导出来。我喜欢用JSONL,一行一个事件:
import json, time def log_event(tid, conf): event = { 'time': time.strftime('%Y-%m-%d %H:%M:%S'), 'track_id': tid, 'confidence': round(float(conf), 2), 'cross_stopline': True, 'light': 'red' } with open('alerts.jsonl', 'a', encoding='utf-8') as f: f.write(json.dumps(event, ensure_ascii=False) + '\n')后续可以用pandas读进来统计小时级报警量,画个柱状图。我自己的教训是先把统计表做出来再回头调算法,因为只看检测框你永远觉得模型很准,一看到误报率数字,才知道哪里要改。这个习惯帮我省了至少一周返工时间。希望帮到你。
本文还有配套的精品资源,点击获取