
简介这份资源面向计算机视觉入门者、智能交通方向的学生及需要快速验证红绿灯检测方案的开发者提供一套基于Python与OpenCV、调用预训练YOLOv3权重实现红绿灯识别的完整工程。项目可直接对图片或视频流中的交通信号灯进行检测与状态判别是自动驾驶感知、智能交通系统等场景的典型实践案例无需自行训练模型即可上手推理。压缩包共444个文件约10.77MB以309个py源码文件为主体辅以57个dll、27个pyd等运行依赖以及cfg模型配置、weights权重、jpg与png测试素材和少量bat、md说明文件构成可直接运行的目录结构。目前已有938人学习下载。读者可从中掌握加载yolov3.weights、处理输入数据、执行预测并可视化结果的完整流程理解多尺度预测在小目标检测上的优势并借鉴实习项目中从模型调用到结果呈现的工程组织方式为后续目标检测任务提供可复用的参考。1. 红绿灯识别项目拆包YOLOv3 权重加 OpenCV 到底能跑出什么路口监控视频里红绿灯在画面中往往只占几十个像素背景还有车灯、广告牌、霓虹灯干扰传统颜色阈值法一到傍晚就集体翻车。这个资源包给的是一个用 Python OpenCV 加载 YOLOv3 预训练权重做红绿灯检测的完整工程核心文件是yolov3.weights加配套的.cfg和.names代码侧负责读视频流、前向推理、画框输出。它解决的是「不想自己标注训练、又想快速验证红绿灯检测可行性」这个诉求适合做智能交通原型、课程设计、实习交付的从业者。包里还带了python37.dll、tcl86t.dll、sqlite3.dll这类运行时依赖说明作者是把一个可独立运行的 Python 3.7 环境一起打包了拿到手不用从零配环境这是它比纯代码仓库更省事的地方。2. 环境与依赖为什么包里塞了 python37.dll 和一串 dll2.1 从文件清单反推运行环境先看包里的文件构成这不是一个纯源码目录而是一个带嵌入式运行时的工程。activate.bat、deactivate.bat、pyvenv.cfg、sysconfig.cfg是 Python 虚拟环境的标志文件python37.dll说明解释器版本锁在 3.7libcrypto-1_1-x64.dll是 OpenSSL 的运行时tcl86t.dll、tk86t.dll对应 Tkinter 图形界面sqlite3.dll是标准库依赖。作者把这些 dll 平铺在目录里通常意味着代码里用了相对路径加载或者干脆是把 Python 安装目录的关键文件拷了出来。这种打包方式的好处是省去「python安装教程」里那一堆勾选项坏处是路径一旦挪动就可能找不到 dll。我一般会先确认目录结构再决定是直接用这个环境还是重建一个干净的。# 先看目录里到底有什么确认权重和配置文件是否齐全 ls -la # 重点找这三个yolov3.weights、yolov3.cfg、coco.names 或 traffic.names find . -name *.weights -o -name *.cfg -o -name *.names逻辑说明第一步列目录看整体结构第二步用 find 精确定位权重、配置、类别名三类文件。参数上-o是逻辑或三个条件任意命中都会输出。如果yolov3.weights不在这个包就是残缺的后面所有推理都无从谈起。2.2 依赖安装与 OpenCV 版本选择如果不想用包里自带的运行时自己重建环境更可控。Python 3.7 是这套代码的舒适区新版 Python 在加载老权重时偶尔会有兼容性提示。OpenCV 建议用opencv-python而不是opencv-contrib-python除非你要用 SIFT 这类专利算法红绿灯检测用不到。# 创建独立环境避免污染系统 Python python -m venv tl_env # Windows 下激活 tl_env\Scripts\activate # 安装核心依赖numpy 是 OpenCV 的硬依赖 pip install opencv-python4.5.5.64 numpy1.21.6逻辑说明venv建隔离环境激活后 pip 装的包只影响这个目录。OpenCV 锁 4.5.5 是因为这个版本对cv2.dnn模块的 YOLO 加载支持稳定太新的版本在readNetFromDarknet上偶发报错。numpy 锁 1.21 是为了和 Python 3.7 的 ABI 匹配装太新会提示ModuleNotFoundError: No module named opencv之外的二进制不兼容错误。提示如果 pip 安装 OpenCV 时卡在下载换国内镜像源即可命令后加-i https://pypi.tuna.tsinghua.edu.cn/simple。2.3 权重文件的放置与校验yolov3.weights是 Darknet 格式的权重官方完整版约 237MB。这个包里的权重如果是针对红绿灯微调过的体积可能不同。放置位置要和代码里的路径一致常见做法是放在脚本同级目录或weights/子目录。import os # 校验权重文件是否存在且大小合理 weight_path yolov3.weights if not os.path.exists(weight_path): raise FileNotFoundError(f权重文件缺失: {weight_path}) size_mb os.path.getsize(weight_path) / (1024 * 1024) print(f权重文件大小: {size_mb:.1f} MB) # 完整 yolov3 约 237MB明显偏小说明是裁剪版或下载不完整逻辑说明os.path.getsize返回字节数除以 1024 两次得到 MB。判断标准是完整 YOLOv3 权重在 236 到 238MB 之间如果只有几十 MB要么是 tiny 版本要么是断点续传没下完。这一步能提前拦住「模型加载后检测全是乱框」这类玄学问题。3. 推理流程从 readNetFromDarknet 到画框输出3.1 加载模型与获取输出层OpenCV 的 dnn 模块加载 Darknet 模型只要两行但输出层的获取方式很多人写错。YOLOv3 有三个尺度输出层名不是固定的yolo_82这种得用getUnconnectedOutLayersNames动态取。import cv2 import numpy as np # 加载 Darknet 模型和权重 net cv2.dnn.readNetFromDarknet(yolov3.cfg, yolov3.weights) # 用 CPU 推理有 CUDA 可换 setPreferableBackend net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 动态获取输出层名称避免硬编码层名 layer_names net.getLayerNames() output_layers [layer_names[i - 1] for i in net.getUnconnectedOutLayers()] print(输出层:, output_layers)逻辑说明readNetFromDarknet第一个参数是网络结构 cfg第二个是权重。getUnconnectedOutLayers返回的是层索引OpenCV 里索引从 1 开始所以取layer_names[i-1]。硬编码yolo_82在换 cfg 后会直接报错动态获取是更稳的写法。setPreferableTarget设 CPU 是保底方案有 NVIDIA 显卡且装了 CUDA 版 OpenCV 可以改成DNN_TARGET_CUDA速度差好几倍。3.2 预处理与单帧推理YOLOv3 要求输入是 416x416 或 608x608 的 blob用blobFromImage做归一化和缩放。红绿灯是小目标输入尺寸建议用 608416 容易漏检远处信号灯。def detect_frame(frame, net, output_layers, conf_threshold0.5, nms_threshold0.4): height, width frame.shape[:2] # 构建 blob1/255 归一化swapRB 因为 OpenCV 读的是 BGR blob cv2.dnn.blobFromImage(frame, 1/255.0, (608, 608), swapRBTrue, cropFalse) net.setInput(blob) outputs net.forward(output_layers) boxes, confidences, class_ids [], [], [] for output in outputs: for detection in output: scores detection[5:] class_id np.argmax(scores) confidence scores[class_id] if confidence conf_threshold: # 中心点转左上角坐标 center_x int(detection[0] * width) center_y int(detection[1] * height) w int(detection[2] * width) h int(detection[3] * height) x int(center_x - w / 2) y int(center_y - h / 2) boxes.append([x, y, w, h]) confidences.append(float(confidence)) class_ids.append(class_id) # NMS 去重 indices cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold) return boxes, confidences, class_ids, indices逻辑说明blobFromImage的1/255.0是归一化系数swapRBTrue把 BGR 转成模型训练时的 RGB。detection[0:4]是归一化的中心点和宽高乘回原图尺寸才能画框。detection[5:]是各类别得分前 5 个是框坐标和 objectness。NMSBoxes做非极大值抑制nms_threshold越小去重越狠0.4 是常用值。红绿灯场景下如果两个灯挨得近这个值调太大会把其中一个框吃掉。3.3 视频流读取与结果绘制视频检测和单图检测的区别在于循环读帧以及要考虑拉流中断。本地文件用VideoCapture直接读网络流要处理断线重连。cap cv2.VideoCapture(traffic.mp4) # 获取视频帧率用于控制写入速度 fps cap.get(cv2.CAP_PROP_FPS) writer cv2.VideoWriter(output.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (608, 608)) while cap.isOpened(): ret, frame cap.read() if not ret: break boxes, confidences, class_ids, indices detect_frame(frame, net, output_layers) if len(indices) 0: for i in indices.flatten(): x, y, w, h boxes[i] label fid:{class_ids[i]} {confidences[i]:.2f} cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow(Traffic Light, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() writer.release() cv2.destroyAllWindows()逻辑说明indices.flatten()是因为 OpenCV 不同版本返回的 NMS 结果维度不一致有的返回二维数组flatten 后统一处理。VideoWriter的尺寸要和帧尺寸一致不一致会导致写入失败或画面拉伸。waitKey(1)里的 1 是毫秒等待值越大播放越慢调试时可以调大方便观察。4. 避坑与排查红绿灯检测最容易翻车的五个点4.1 检测框满天飞但没一个在灯上现象视频里画了几十个框位置全在车灯、路牌、天空上。原因通常是类别名文件不匹配或者权重根本不是红绿灯微调版而是 COCO 通用权重。COCO 的 80 类里没有 traffic light 之外的交通信号细分且通用权重对小目标召回本来就弱。解决确认.names文件里是否有 traffic light 或红绿灯相关类别没有的话这个权重只能做通用检测需要自己微调。4.2 报错 ModuleNotFoundError: No module named cv2现象脚本一跑就提示找不到 cv2。原因有两种一是没装 opencv-python二是装了但当前 Python 解释器和 pip 不是同一个。常见于系统里同时有多个 Python 版本。解决用python -c import sys; print(sys.executable)确认解释器路径再用python -m pip install opencv-python确保装到同一个环境。4.3 权重加载卡住或内存暴涨现象readNetFromDarknet执行后程序无响应内存占用飙升。原因是权重文件损坏或 cfg 与 weights 不配套。YOLOv3 的 cfg 和 weights 必须严格对应拿 tiny 的 cfg 配完整权重会直接崩。解决核对 cfg 里的[net]段width、height、channels是否和权重匹配重新下载配套文件。4.4 视频拉流中断后程序退出现象处理网络摄像头或 RTSP 流时网络抖动一下程序就报错退出。原因是cap.read()返回 False 后没有重连逻辑。解决在循环里加重试计数连续失败超过阈值再退出中间加time.sleep等待重连。import time fail_count 0 while True: ret, frame cap.read() if not ret: fail_count 1 if fail_count 30: print(连续读取失败退出) break time.sleep(0.1) continue fail_count 0 # 正常处理帧逻辑说明fail_count累计连续失败次数成功一次就清零。阈值 30 配合 0.1 秒等待大约容忍 3 秒的网络抖动。这个逻辑对本地文件没影响对网络流是保命的。4.5 检测速度慢到无法实时现象CPU 推理 608 输入每帧要几百毫秒视频卡成幻灯片。原因是 YOLOv3 完整版在 CPU 上本来就慢。解决换 416 输入、改用 yolov3-tiny 权重、或者装 CUDA 版 OpenCV 走 GPU。如果只是验证流程抽帧检测也能接受不必每帧都跑。5. 进阶技巧用置信度分层和 ROI 裁剪把误检压下去跑通基础流程后真正影响可用性的是误检率。红绿灯检测有个天然优势信号灯位置相对固定不会满画面乱跑。我一般会加两层过滤第一层是 ROI 裁剪只保留画面上半部分或车道正前方区域第二层是置信度分层高置信度直接输出中等置信度结合颜色直方图二次确认。def filter_by_roi(boxes, frame_shape, roi_ratio0.6): 只保留画面上方 roi_ratio 区域的检测框 height frame_shape[0] keep [] for i, (x, y, w, h) in enumerate(boxes): # 框中心点在上方区域才保留 if (y h / 2) height * roi_ratio: keep.append(i) return keep def refine_by_color(frame, box): 对候选框做颜色判断红绿灯只有红黄绿三种主色 x, y, w, h box roi frame[y:yh, x:xw] if roi.size 0: return None hsv cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) # 红色范围HSV 里红色跨 0 和 180 mask_red cv2.inRange(hsv, (0, 100, 100), (10, 255, 255)) | \ cv2.inRange(hsv, (170, 100, 100), (180, 255, 255)) mask_green cv2.inRange(hsv, (40, 100, 100), (80, 255, 255)) red_ratio mask_red.sum() / 255 / roi.shape[0] / roi.shape[1] green_ratio mask_green.sum() / 255 / roi.shape[0] / roi.shape[1] if red_ratio 0.3: return red elif green_ratio 0.3: return green return None逻辑说明filter_by_roi用框中心点的纵坐标判断是否在画面上方roi_ratio0.6表示保留上方 60% 区域这个值根据摄像头安装高度调整。refine_by_color把候选框转 HSV红色在 HSV 里跨 0 和 180 两段所以要取并集inRange后除以 255 再除以像素总数得到颜色占比。阈值 0.3 是经验值低于这个说明框里没有明显信号灯颜色判为误检。这套组合下来误检能压掉一大半。参数上 ROI 比例和颜色占比阈值需要按实际场景标定没有万能值。我自己的习惯是每换一个摄像头位置先跑一段视频把这两个参数调一遍再上正式流程。注意颜色判断依赖光照夜间和白天的 HSV 阈值要分开设一套参数走天下必然翻车。从那以后我每次拿到新的检测权重都强制先跑一段已知结果的视频做回归确认框的位置和类别都对得上再往业务里接。希望帮到你。本文还有配套的精品资源点击获取