简介:一套面向电车轨道与障碍物检测的目标检测项目,整合传统数字图像处理与YOLOv5深度学习算法,适合计算机相关专业学生、教师及开发者用于课程设计、毕业设计或算法学习。项目先采用边缘检测、透视变换、霍夫变换标注轨道并划定感兴趣区域,再借助YOLOv5模型对区域内障碍物进行实时识别与标注,白天和夜晚条件下均有较好效果,实测处理速度约为十七帧每秒。压缩包内共有六十四个文件,以配置文件、Python脚本、示例图片为主,同时包含Shell脚本、Markdown说明、Jupyter笔记本、Dockerfile等,便于环境搭建、模型配置与二次开发,整体压缩后大小仅约九百六十一千字节。已有111人学习下载。资料提供从轨道检测、区域提取到障碍物识别的完整代码管线,附有测试图片、模型配置文件和使用说明文档,代码均通过运行验证,可直接上手学习,也能够在此基础上扩展其他检测功能。
1. 轨道检测和障碍物识别,混搭不是炫技
电车轨道检测有个反直觉的事实:仅用YOLOv5端到端识别轨道,白天勉强能用,晚上几乎不可靠,因为轨道是细长线状目标,标注成本高且小目标特征弱。而传统数字图像处理里的边缘检测、霍夫变换却能稳稳锁定轨道线,代价是无法判断轨道上有什么。这套SJTU课程设计把两者串成一条流水线:先用Canny+霍夫变换求轨道线,再得到矩形ROI,最后把ROI交给YOLOv5做障碍物识别,输出帧率约17FPS。项目代码按python工程组织,含calImage、line、pipeline、detect、vedio等模块,适合做课设、毕设,也适合想搞懂目标检测预处理逻辑的工程师。它不是把两个模型堆在一起,而是让传统算法替深度模型做了一次空间注意力。
2. 从边缘检测到透视变换:轨道ROI提取与坐标系矫正
轨道检测的第一步不是跑网络,而是先搞清楚轨道在图像里的几何规律。电车轨道在画面中通常呈两条近似直线,交于灭点;只要把它们提取出来,就能框出火车驶来的方向区域。常见做法是Canny边缘检测 + 霍夫直线检测 + 透视变换。下面拆开讲。
2.1 Canny边缘检测与形态学闭运算的配合
Canny对光照敏感,但白天和夜晚的轨道边缘都有一个共同点:轨道与道床之间的灰度跳变是局部的。用Canny前先转灰度,再高斯模糊去掉细纹理。核心参数是threshold1和threshold2,过高会把夜间昏暗轨道过滤掉,过低会引入大量道砟边缘。我一般先设threshold1=50, threshold2=150,再按视频亮度上下浮动。
import cv2 import numpy as np def get_edges(frame): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) edges = cv2.Canny(blur, 50, 150) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 3)) closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) return closed这里Canny先给出二值边缘,再闭运算把断成一段段的轨道线连起来。闭运算的结构元用宽5高3的矩形,因为轨道在图像里是水平延展的曲线,宽度方向有轻微斜度;如果结构元太大,会把相邻两条轨道粘在一起,太小则夜间补不上断口。这个细节直接影响后续霍夫变换能不能检出完整直线。
2.2 霍夫直线检测与轨道线聚类
得到闭合边缘后,用cv2.HoughLinesP检测直线段。HoughLinesP是概率霍夫,比标准霍夫快,且输出线段端点。要特别注意minLineLength和maxLineGap两个参数:minLineLength设太短会把枕木阴影、路边护栏的短边缘也选成候选;设太长,夜间轨道边缘断续时可能检不出。实测中minLineLength设为80像素,maxLineGap设为20像素能兼顾白天和夜间。
lines = cv2.HoughLinesP(closed, rho=1, theta=np.pi/180, threshold=50, minLineLength=80, maxLineGap=20) left_lines, right_lines = [], [] if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] k = (y2 - y1) / (x2 - x1 + 1e-6) if k < -0.3: left_lines.append(line[0]) elif k > 0.3: right_lines.append(line[0])这里按斜率粗略分类:图像坐标系y轴向下,电车轨道向左远方延伸时斜率为负,向右延伸为正。之后对左右两组线段分别取最长的几条做最小二乘拟合,得到轨道直线方程。注意不能用平均斜率代替拟合,因为近处轨道在画面中占比大,远处占小,简单平均会高估远端的收敛角度。
2.3 透视变换矩阵计算与ROI掩膜生成
得到左右轨道直线后,可以计算它们在图像平面内的交点作为灭点,再结合画面底部的两个端点构成一个梯形。这个梯形是轨道区域的自然表示。但YOLOv5的输入是矩形,训练时多是正方形缩放;直接把梯形送给网络会拉伸目标比例。所以要做一个透视变换,把梯形矫正成矩形。
pts_src = np.float32([[x_left_bottom, y_bottom], [x_right_bottom, y_bottom], [x_right_top, y_top], [x_left_top, y_top]]) pts_dst = np.float32([[0, height], [width, height], [width, 0], [0, 0]]) M = cv2.getPerspectiveTransform(pts_src, pts_dst) bird_view = cv2.warpPerspective(frame, M, (width, height)) mask = np.zeros((height, width, 3), dtype=np.uint8) cv2.fillPoly(mask, [np.int32(pts_src)], (255, 255, 255))M就是透视变换矩阵。warpPerspective把原始图像投影到一个虚拟俯视角坐标系中,后续YOLOv5的推理既可以在这个鸟瞰图里做,也可以把矩形ROI反变换回原图做。注意pts_dst的width和height应当和YOLOv5输入尺寸对齐,否则后面坐标映射时要再做一次缩放,容易出错。mask的作用是限定障碍物检测的有效范围,把轨道区域外的电线杆、行人过滤掉。
3. 基于YOLOv5的障碍物识别:模型选型与检测流程
轨道区域框出来之后,真正的目标是识别轨道上的障碍物。项目选的是YOLOv5,这里不光是会用的问题,还要知道为什么在课设里选它而不是Faster R-CNN或SSD。
3.1 选择YOLOv5s作为基础模型的原因
YOLOv5s是yolov5网络结构中最小的标准版本,参数量大约7.2M,输入640x640时单帧推理在GTX1660上大约5ms,加上前后处理能到17FPS,正好满足本项目视频流的实时需求。而Faster R-CNN在同等精度下推理速度慢一个数量级,SSD对小型障碍物(如石块、锥桶)召回率低。更重要的是YOLOv5的工程配套完善:detect.py直接支持摄像头、视频和图片,训练脚本自带数据增强,配好环境和数据集就能跑,适合课程设计周期。
障碍物类别一般定义成person、bicycle、car、truck、cone、barrier等,因为电车轨道上的障碍物多是行人或停放的车辆。如果检测目标限定在轨道范围内,类别数不超过6个时,YOLOv5s在公开COCO预训练权重上微调即可,不需要从头训练。
3.2 detect.py推理链路与参数配置
项目中的detect.py基于YOLOv5官方源码,主要改动了两个地方:一是读取视频帧后先调用pipeline.py得到ROI mask;二是把检测结果框与mask做逻辑与操作,滤除区域外框。启动推理的命令常见做法是:
python detect.py --weights best.pt --source vedio.mp4 \ --img-size 640 --conf-thres 0.45 --iou-thres 0.45 \ --device 0 --project output --exist-ok参数含义:
- weights:训练好的权重文件,课程设计里一般用val集上mAP最高的权重,而非最后一轮权重。
- source:输入视频或图片路径,也可以设为0调用摄像头。
- conf-thres:置信度阈值,过滤低置信度框。白天可设0.45,夜间会适当降到0.35,因为夜间检测框得分整体偏低。
- iou-thres:NMS的IoU阈值,0.45是YOLOv5默认值,重叠目标多时改成0.3更保守。
- img-size:推理尺寸,640对速度与精度平衡最好,480能提升FPS但小障碍物容易漏。
3.3 白天/夜晚样本增强与训练细节
项目要求同时处理白天和夜晚两种环境,所以训练数据不能只用白天图像。YOLOv5自带的Mosaic增强把四张图拼成一张,能增强小目标与遮挡鲁棒性;但对夜晚样本,Mosaic反而可能让夜间目标被白天背景稀释。常见做法是在数据集中按1:1混入白天与夜晚帧,然后单独关闭夜间样本的RandomPerspective,或者对夜间图做一次等宽Gamma校正。
| 超参数 | 建议值 | 说明 |
|---|---|---|
| epochs | 150 | 微调时100-200都行,看验证损失是否收敛 |
| batch-size | 16 | 取决于显存,8G显存建议8 |
| lr0 | 0.01 | 预训练权重微调用0.01,随机初始化用0.1 |
| mosaic | 0.5 | 概率0.5,夜间数据占比高时调低 |
| fliplr | 0.25 | 轨道目标左/右翻转仍合理,但太高会破坏灭点方向 |
这里特别要提一句:yolov5训练自己的数据集时,一定要确认data.yaml里的nc和names与实际标注一致。项目中常见错误是标注类别索引从0开始,但训练时写了从1开始,导致所有标签偏移一位,检测框全部错位。检查方法很简单:训练前运行python train.py --data data.yaml --weights yolov5s.pt,观察第一个epoch的loss是否有明显下降,若震荡不降,多半是标签问题。
4. 轨道与障碍物结果融合:坐标映射与性能优化
模型单独跑通只是第一步。实际视频里,YOLOv5输出的是640x640坐标系的矩形框,必须与原始画面坐标对齐,同时还要受ROI约束。这个融合模块会决定检测结果看起来“准不准”。
4.1 ROI区域映射与置信度过滤
如果推理在原始图像上进行,那么检测框坐标就是原始坐标,直接用cv2.pointPolygonTest判断框底边中点是否落在ROI多边形内。如果推理在鸟瞰图里进行,则需要用透视变换矩阵的逆矩阵把检测框四个角点映射回原图,再做同一个判断。
pts_src = trailer_roi # 梯形ROI四个点,由line.py输出 def in_roi(box, roi_pts): cx = (box[0] + box[2]) / 2 cy = (box[1] + box[3]) / 2 return cv2.pointPolygonTest(roi_pts, (cx, cy), False) >= 0 filtered = [b for b in detections if in_roi(b, pts_src)]这里用框底边中心点而不是整个框来判断,是因为障碍物与轨道的接触点通常在地面附近,如果框的上半部分(如行人头部)在ROI外但脚在ROI内,应该保留;反过来,如果底边中心在ROI外,即便框的上边缘压线,也应该剔除。用框的中心点会误留很多路旁行人,实测漏检和误检都会上升。注意pointPolygonTest的第三个参数设置成False,表示只返回点在多边形内部还是外部,不加距离计算,速度最快。
4.2 帧间处理与FPS优化策略
17FPS来自几个取舍。第一是推理分辨率,本项目最终选择640但关闭了类别输出;第二是启用半精度推理,detect.py中加一句model.half(),在GTX 16系以上显卡上能把推理延迟降低约30%;第三是视频解码用cv2.VideoCapture直接读,不做逐帧缩放,而是只在送入模型前resize。这三个改动组合常见,效果稳定。
另一个更激进的方法是跳帧:每两帧只对一帧做YOLOv5检测,不做检测的帧直接沿用上一帧的检测结果,配合简单的KCF跟踪,能把总体帧率提高到25FPS。课程设计答辩时可以用这个方案展示“实时效果”,但要注意评估指标里要注明实际推理帧率是17FPS,跳帧属于后处理优化。
4.3 常见误检分析:路灯、信号灯与反光
夜间场景中,YOLOv5容易把亮度集中的路灯灯头、信号灯和轨道反光区域当作障碍物。原因很简单:conf阈值降低后,这些高对比目标与障碍物特征接近。处理方式不只有调阈值:
- 把ROI底边中心点的判断改成“地面接触点”,压线信息比框中心更可靠。
- 对检测类别做先验限制:比如轨道内出现“car”且位置固定不动,连续30帧没有位移,就降低其置信度并标记为静态物体,不作为紧急障碍物告警。
- 对反光带,可以在预处理里增加一个对比度归一化,让反光区域与障碍物的纹理差别更大。
注意不能只靠颜色过滤,因为夜间列车灯光会把轨道照射成亮黄色,与警告锥桶颜色接近。
5. 从课程设计到落地:验证指标与调参技巧
课设不能只停在“能跑”,还要让评估结果可复现。这里分享验证指标和调参的具体做法,这两项做扎实了,答辩才能从“效果较好”变成“结果可信”。
5.1 用mAP@0.5和漏检率评估检测效果
课程设计里不能只说“效果较好”,需要给量化指标。常见做法是在标注好的测试视频帧上统计mAP@0.5。这里给出一个轻量级评估脚本:
import torch from pathlib import Path from utils.metrics import ap_per_class def evaluate(dataloader, model, iou_thres=0.5): stats = [] for images, targets in dataloader: preds = model(images)[0] # 将preds转为(x1y1x2y2, conf, cls)格式 for batch_idx, pred in enumerate(preds): target = targets[targets[:, 0] == batch_idx] stats.append((pred.cpu(), target.cpu())) # 调用YOLOv5官方metrics计算AP p, r, ap, f1, _ = ap_per_class(*stats, plot=False, save_dir=Path('.')) return ap.mean(), r.mean(), p.mean()这个脚本直接用YOLOv5源码里的ap_per_class,省去自己实现IoU匹配。测试时要注意每类样本数量不能太少,至少每个类别超过50个真实框,否则mAP做出来不稳定。除了mAP@0.5,还要统计漏检率:针对视频里连续出现的同一障碍物,只要在连续10帧中有一帧没检测到,就算一次漏检。这个指标对轨道障碍物比mAP更敏感,因为行人一旦漏检一帧,后续跟踪就会丢失。
5.2 超参数调整优先级与踩坑记录
调参与其说按经验,不如按优先级排查。我一般会这样收敛:
- 先固定conf-thres=0.4,看无效检测框是太多还是太少:太多说明类别混淆严重,应回头检查数据集标注;太少则明显漏检,说明模型欠拟合或阈值不合适。
- 再看NMS的iou-thres:检测框重叠度高时降低到0.3,不要盲目用默认值。
- 最后才动模型超参数,比如学习率从0.01减到0.005,用余弦退火观察loss曲线是否平滑。
整个过程要记录每个版本在验证集上的mAP和漏检率,而不是只看demo视频。
踩坑记录中最常见的有几个:
- OpenCV版本影响透视变换结果:OpenCV 4.5以上对坐标精度有变化,课设中最好固定python=3.8, opencv-python=4.5.5.64。
- 中文路径问题:视频文件名或图片路径含中文时,cv2.VideoCapture会读取失败,detect.py报错找不到文件。把工程路径统一为英文小写。
- 权重文件路径:.pt文件是相对路径打包的,把best.pt移到其他目录后,要同步复制模型对应的yaml文件,否则会报yaml not found。
- 最容易被忽略的是eval期间的半精度开关:训练时用了model.half(),测试时如果忘记调成float32,会得到精度降低的框,特别是小目标上。
还有一个实用技巧:把pipeline.py中的轨道检测过程单独抽出来,输出一张可视化图,叠加Canny边缘和轨道直线。这样在答辩时可以清楚展示“数字图像处理”部分的中间结果,说明算法不黑盒。检测模型只在ROI中有意义,轨道线的质量决定了障碍物检测的上限。
本文还有配套的精品资源,点击获取