基于深度学习的二轮电动车闯红灯检测系统
- 一、前言
- 二、二轮电动车数据集制备并搭建检测跟踪算法
- 2.1 数据集制备
- 2.2 检测算法选用及训练
- 3.3 跟踪算法选取
- 3.4 整体结构
- 3.4.1 视频处理函数
- 3.4.2 图像处理函数
- 3.4.3 二轮车测距函数
- 3.5 针对视频流不兼容的处理
- 三、结语&源码数据集获取
一、前言
现在对二轮电动车闯红灯的行为制止还停留在人工拦截和固定摄像头的方式,前者容易造成危险,后者太固定,因此我们采用手持摄像头平时视角的方式,自动抓取闯红灯的人员。
这种方法的实现不仅具有较大的实际意义,也具有较好的课题意义
先给出我们开发的闯红灯检测系统运行截图,左侧上传视频,右侧显示判断前后的视频,其中红色框内味判定的闯红灯的目标二轮电动车,蓝色框为正常行驶的电动车。
在视频中画出目标的运行轨迹,以更直观的对用户进行展现。
视频图像处理中,对整体进行一个灰度显示
方法处理后的视频(综上可以看出,其实判断相对来说还是比较准的 )
二、二轮电动车数据集制备并搭建检测跟踪算法
2.1 数据集制备
提前对数据进行标注,这里我们在交叉路口处拍摄视频并采用抽帧的方式得到约5000张含有二轮电动车目标的图像,并用矩形框进行标注,得到二轮电动车数据集。
使用labelimg进行标注,如图所示
2.2 检测算法选用及训练
检测采用我们的YOLOv10算法,它是一款端到端的实时性较强的目标检测算法,比较贴合我们需要完成的任务,所以选择此算法进行训练。其具体结构就不过多赘述了,如图所示
对我们收集到的数据集进行训练
训练效果如图所示:
可以看到mAP在95%左右,效果还是比较好的,适应性也较强
可以看到检测效果良好,由于收集制备的数据集包含夜晚、逆光、下雨天等场景,因此在这几类难检测的环境下算法的检测效果也相对较好。
3.3 跟踪算法选取
我们在交通场景中选用了经典的 ByteTrack 算法,并针对特定需求进行了改进,以更好地适应场景需求并降低 ID 丢失的情况。
ByteTrack 是在 2021 年 10 月首次公开的,并在 ECCV 2022 中斩获奖项。它凭借简单高效的设计理念,在当时击败了众多经过复杂改进的跟踪算法。尤其是在 MOT17 数据集上,ByteTrack 首次突破了 80 MOTA,并且在单张 V100 GPU 上实现了高达 30 FPS 的推理速度,展现了优异的性能。
核心思想
ByteTrack 的核心思想可以总结为以下几点:
- 区分高置信度与低置信度检测框
对不同置信度的检测框采取不同的处理策略。高置信度检测框优先用于更新跟踪目标,而低置信度检测框则被保留,用于后续可能的重新确认。
保留低置信度检测框 - 与传统 MOT 算法直接删除低置信度检测框不同,ByteTrack 选择保留这些检测框,从而在目标遮挡或置信度波动时能重新将其恢复为 confirm 状态。这种设计显著提升了算法对遮挡的鲁棒性,同时降低了 ID 切换 (ID Switch, IDSW) 的发生率。
遮挡处理 - 当目标逐渐被遮挡时,跟踪目标会与低置信度检测框匹配,保留其轨迹。
当目标重新显现时,跟踪目标则会优先与高置信度检测框匹配,从而实现目标的平滑恢复。
优势与注意事项
- ByteTrack 在解决遮挡问题上表现卓越,同时保持较低的 IDSW。即使目标由于遮挡导致检测置信度降低,算法依然能够保留其轨迹,并在目标重新出现时顺利恢复。
需要特别注意检测过程中的假阳性问题(即在无目标区域中生成的低置信度检测框),否则可能影响跟踪精度。
通过上述改进,ByteTrack 为我们的交通场景提供了更可靠的目标跟踪能力,尤其在复杂动态环境中表现出色。
Bytetrack原论文截取的图像
3.4 整体结构
3.4.1 视频处理函数
整体代码如下process_video函数所示:
def process_video(upload, output_path, model):parser = argparse.ArgumentParser("video processing with YOLO and ByteTrack")parser.add_argument("--track_thresh", default=0.5, type=float)parser.add_argument("--track_buffer", default=0.5, type=float)parser.add_argument("--mot20", default=False)parser.add_argument("--match_thresh", default=0.6, type=float)args = parser.parse_args()tracker = BYTETracker(args)cap = cv2.VideoCapture(upload)fps = int(cap.get(cv2.CAP_PROP_FPS))width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))fourcc = cv2.VideoWriter_fourcc(*'XVID') # 修改为兼容性更高的编码器out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))while cap.isOpened():ret, frame = cap.read()if not ret:breakframe, result, xyxy_scores = predict_and_detect(model, frame, classes=[0], conf=0.4)online_targets = tracker.update(xyxy_scores, frame, frame)result_img_with_ids = draw_ids_on_image(frame, online_targets)out.write(result_img_with_ids) # 确保写入的是处理后的帧cap.release()out.release()# 检查视频文件是否生成成功if os.path.exists(output_path):print(f"视频成功保存: {output_path}")else:print("视频保存失败")return Nonewith open(output_path, "rb") as f:return f.read()
其中,代码刚开始到tracker为跟踪器的定义,大家可以根据自己的应用场景自定义track_thresh、track_buffer、mot20、match_thresh四个值的大小,它们分别代表:
- track_thresh
作用:设定目标跟踪的置信度阈值。检测框的置信度分数高于此值,才会被作为潜在的跟踪目标。
默认值:0.5
意义:较高的阈值可以减少错误跟踪,但可能会漏掉一些置信度较低的目标;较低的阈值则可以捕捉更多目标,但可能引入更多误跟踪。 - track_buffer
作用:设置跟踪的缓冲时间,表示如果目标在短时间内未被检测到(即未匹配到新的检测框),是否保留其轨迹。
默认值:0.5
意义:值越大,允许跟踪目标“失踪”更长时间而不被丢弃;值越小,则目标更容易被认为是丢失。 - mot20
作用:是否启用针对 MOT20 数据集的特殊优化。MOT20 是多目标跟踪的一个数据集,其中场景非常密集且拥挤。
默认值:False
意义:启用此选项后,算法会根据 MOT20 的特点(如目标密集)调整一些内部参数或策略,优化跟踪效果。 - match_thresh
作用:设置目标与检测框之间的匹配阈值,通常与 IoU(交并比)或其他相似性度量相关。只有匹配分数高于该阈值的检测框,才会被认为是与现有目标相关联。
默认值:0.6
意义:值越高,匹配条件越严格,可能导致目标更容易断开;值越低,匹配会更宽松,但可能引入错误匹配。
parser = argparse.ArgumentParser("video processing with YOLO and ByteTrack")parser.add_argument("--track_thresh", default=0.5, type=float)parser.add_argument("--track_buffer", default=0.5, type=float)parser.add_argument("--mot20", default=False)parser.add_argument("--match_thresh", default=0.6, type=float)args = parser.parse_args()tracker = BYTETracker(args)
3.4.2 图像处理函数
可以看出,上文视频处理函数这一小节给出的大致是视频处理的方案,但是其实对于检测而言,我们都是把视频图像抽帧来进行图像检测的,所以上文函数中最重要的子函数其实是predict_and_detect,其具体代码如下所示:
def predict_and_detect(chosen_model, img, classes=[], conf=0.5, rectangle_thickness_ratio=0.007):"""检测目标并在图像上绘制检测结果"""img_height, img_width = img.shape[:2]rectangle_thickness = max(1, int(min(img_height, img_width) * rectangle_thickness_ratio))font_scale = min(img_height, img_width) / 800 # 根据分辨率调整字体大小text_thickness = max(1, rectangle_thickness // 2)xyxy_scores = np.empty((0, 5))results = chosen_model.predict(img, classes=classes, conf=conf)for result in results:for i, box in enumerate(result.boxes, start=1):LU_xy = (int(box.xyxy[0][0]), int(box.xyxy[0][1]))RD_xy = (int(box.xyxy[0][2]), int(box.xyxy[0][3]))# 计算目标中心点center_x = (LU_xy[0] + RD_xy[0]) // 2center_y = (LU_xy[1] + RD_xy[1]) // 2# 计算距离real_dis = predict_TWV_distance(LU_xy, RD_xy)# 添加文本框和距离信息text = f"dis={real_dis}"font = cv2.FONT_HERSHEY_SIMPLEXtext_color = (255, 255, 255)bg_color = (255, 0, 0)bg_color_warning = (0, 0, 255)(text_width, text_height), baseline = cv2.getTextSize(text, font, font_scale, text_thickness)text_position = (LU_xy[0], LU_xy[1] - 10)# 判断是否闯红灯:目标中心是否在上半部分if XXX(判定到闯红灯的具体逻辑):cv2.rectangle(img, LU_xy, RD_xy, (0, 0, 255), rectangle_thickness) # 红色框cv2.rectangle(img,(text_position[0], text_position[1] - text_height - baseline),(text_position[0] + text_width, text_position[1]),bg_color_warning,-1)cv2.putText(img, "Warning!!!", text_position, font, font_scale, text_color, text_thickness, cv2.LINE_AA)else: # 未闯红灯cv2.rectangle(img, LU_xy, RD_xy, (255, 0, 0), rectangle_thickness) # 蓝色框cv2.rectangle(img,(text_position[0], text_position[1] - text_height - baseline),(text_position[0] + text_width, text_position[1]),bg_color,-1)cv2.putText(img, text, (text_position[0], text_position[1] + 5), font, font_scale, text_color,text_thickness, cv2.LINE_AA)# 将xyxy和score添加到xyxy_scoresscore = float(box.conf[0]) # 置信度box_xyxy = [int(box.xyxy[0][0]), int(box.xyxy[0][1]), int(box.xyxy[0][2]), int(box.xyxy[0][3]), score]xyxy_scores = np.append(xyxy_scores, [box_xyxy], axis=0)return img, results, xyxy_scores
这份代码具体判断了目标是否闯红灯,并使用期对应的矩形框对各个目标进行框选,其中对每个目标显示各自的id,对闯红灯的目标用红色矩形框进行框选并在其上方标注warning!!!,对没有闯红灯的目标如图所示。
3.4.3 二轮车测距函数
大家可能看到视频及图像中给出了蓝色框距离相机目标的距离,这里其实没有具体使用到测出来的距离,后续会进一步基于获取到的距离值进行比较高级的任务处理。
3.5 针对视频流不兼容的处理
由于我们是对本地的图像使用代码进行处理,然后再保存到临时存储区域进行显示的,因此处理好的视频可能会存在于网页端播放器不兼容的问题,为了解决这个问题,博主也是在这里卡了很久,比较简单的处理方法是使用代码进行兼容性检测并进行处理,这里直接给出代码:
def ensure_browser_compatible(input_file, output_file):"""使用 moviepy 确保生成的视频与浏览器兼容"""try:clip = VideoFileClip(input_file)clip.write_videofile(output_file, codec="libx264", audio_codec="aac")clip.close()except Exception as e:print(f"Error processing video: {e}")
三、结语&源码数据集获取
总的来说,实现了二轮电动车闯红灯自动检测,后续还会更新新的内容,请持续关注!!!感谢
如果有需要源码请关注我的个人主页。