
简介这份资源面向计算机视觉初学者与智能交通方向开发者提供一套基于Python与OpenCV、调用预训练YOLOv3权重实现红绿灯检测的完整工程。项目源自实习期间的实际需求可对图片或视频流中的交通信号灯进行实时识别是理解目标检测落地流程、衔接自动驾驶与智能交通场景的实用案例。压缩包共444个文件约10.77MB以309个py源码为主体辅以dll、pyd等运行依赖以及cfg模型配置、jpg与png测试图、txt与xml说明文件并附带少量exe与bat脚本目录结构接近可直接运行的工程形态。已有938人学习下载说明其在同类入门项目中具备一定参考价值。读者可借此掌握加载yolov3.weights、处理输入数据、执行预测与结果可视化的完整链路省去自行训练模型的耗时环节并从中体会预训练权重快速部署的思路为后续深入目标检测或交通信号识别打下基础。1. 红绿灯识别项目拆解从 yolov3.weights 到 OpenCV 实时框选路口红灯还剩几秒摄像头画面里那团模糊的红色光斑到底算不算信号灯这个问题我在一个园区路口项目里被折磨了整整两周。用 Python OpenCV 做红绿灯识别核心思路其实很朴素拿 YOLOv3 做目标检测把画面里的红绿灯框出来再根据框内颜色判断红或绿。标题里的yolov3.weights就是那个训练好的权重文件配合yolov3.cfg和coco.names三个文件就能在本地跑起来。这套方案适合谁适合已经会装 Python、能跑通 OpenCV 读摄像头但还没做过完整检测项目的同学。它不追求工业级精度胜在链路短、依赖少、一台普通笔记本就能出结果。下面我按自己踩过的顺序把环境、加载、推理、调参和避坑一次讲透。2. 环境与模型文件把 OpenCV 和 YOLOv3 权重凑齐2.1 为什么选 OpenCV DNN 而不是自己编译 Darknet很多人一搜 YOLOv3 就掉进编译 Darknet 的坑里opencv cmake编译步骤这类词也跟着冒出来。我的血泪经验是如果只是做红绿灯识别这种单类目标检测完全没必要碰 Darknet 源码。OpenCV 从 4.x 开始自带dnn模块能直接读 YOLOv3 的 cfg 和 weights省掉 CUDA 编译、Makefile 报错、contourarea ()未定义标识符这些玄学问题。你只需要保证opencv-python版本不低于 4.2用 pip 一条命令就能装好。选它的理由很直接API 稳定、文档多、和 Python 生态无缝衔接出问题也好搜。2.2 三个文件放哪、怎么校验YOLOv3 跑红绿灯需要三个文件yolov3.weights权重约 240MB、yolov3.cfg网络结构、coco.names80 类名称。COCO 数据集里本身就有traffic light这一类所以不用自己训练就能直接检测红绿灯。目录我一般这样摆project/ ├── yolov3.weights ├── yolov3.cfg ├── coco.names ├── detect_traffic_light.py └── test.jpg下载后先校验文件完整性权重文件如果下了一半OpenCV 加载时会直接抛错。用下面这段代码确认三个文件都能被读到import os files [yolov3.weights, yolov3.cfg, coco.names] for f in files: if not os.path.exists(f): print(f缺失: {f}) else: size os.path.getsize(f) / 1024 / 1024 print(f{f} 存在, 大小 {size:.1f} MB)逻辑说明这段不涉及模型推理只做文件存在性和大小检查。参数上yolov3.weights正常在 230MB 到 250MB 之间明显偏小说明没下完yolov3.cfg约 8KB 到 10KBcoco.names是纯文本80 行。如果coco.names行数不对后面类别索引会错位红绿灯可能被标成别的类。2.3 安装 OpenCV 时最容易翻车的两个点第一个坑是modulenotfounderror: no named opencv。这通常不是没装而是装到了另一个 Python 解释器里。VS Code 里vscode python环境配置没选对解释器时终端 pip 装的和编辑器跑的不是同一个。解决方法是先python -c import sys; print(sys.executable)看当前解释器路径再用python -m pip install opencv-python装保证装到同一个环境。第二个坑是版本冲突某些老教程让你装opencv-contrib-python如果你同时装了opencv-python两个包会互相覆盖。红绿灯识别用不到 contrib 的额外模块只装opencv-python就够。python -m pip install opencv-python4.8.0.76 python -c import cv2; print(cv2.__version__)参数说明版本号我锁在 4.8 附近太老的 4.2 以下对 YOLOv3 的dnn支持不完整太新的偶尔有 API 变动。打印出版本号且不报错环境就算通了。3. 用 OpenCV DNN 加载 yolov3.weights 并跑通第一帧3.1 加载网络与读取类别名OpenCV 加载 YOLOv3 只有两行核心代码但参数顺序不能错。readNetFromDarknet第一个参数是 cfg第二个是 weights写反了会报解析失败。import cv2 import numpy as np net cv2.dnn.readNetFromDarknet(yolov3.cfg, yolov3.weights) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) with open(coco.names, r) as f: classes [line.strip() for line in f.readlines()] layer_names net.getLayerNames() output_layers [layer_names[i - 1] for i in net.getUnconnectedOutLayers()] print(输出层:, output_layers)逻辑说明readNetFromDarknet把权重和结构读进内存setPreferableBackend和setPreferableTarget指定用 CPU 推理如果你有支持 CUDA 的显卡可以改成DNN_TARGET_CUDA但需要 OpenCV 编译时带 CUDA 支持普通 pip 包不带。getUnconnectedOutLayers拿到的是 YOLOv3 的三个输出层索引注意 OpenCV 返回的是从 1 开始的下标所以要减 1。参数上classes列表里traffic light的索引是 9后面过滤时要用到。3.2 预处理blobFromImage 的四个关键参数YOLOv3 对输入有固定要求416x416 或 608x608、归一化到 0 到 1、RGB 顺序。blobFromImage一次搞定。img cv2.imread(test.jpg) h, w img.shape[:2] blob cv2.dnn.blobFromImage( img, 1/255.0, (416, 416), swapRBTrue, cropFalse ) net.setInput(blob) outs net.forward(output_layers) print(输出层数量:, len(outs))逻辑说明1/255.0是缩放因子把像素值压到 0 到 1(416, 416)是网络输入尺寸越大越准但越慢swapRBTrue因为 OpenCV 读进来是 BGRYOLOv3 要 RGBcropFalse表示不裁剪直接缩放。net.forward返回三个数组分别对应三个尺度的检测输出。参数上如果你显卡弱416 够用要检测远处的小红绿灯可以提到 608但帧率会掉一半左右。3.3 解析输出置信度阈值和 NMS 怎么设三个输出层每个检测框有 85 个值前 4 个是中心点和宽高第 5 个是物体置信度后 80 个是类别概率。红绿灯识别只需要看第 9 类。conf_threshold 0.5 nms_threshold 0.4 boxes, confidences, class_ids [], [], [] for out in outs: for detection in out: scores detection[5:] class_id np.argmax(scores) confidence scores[class_id] if confidence conf_threshold and class_id 9: cx, cy, bw, bh (detection[0:4] * np.array([w, h, w, h])).astype(int) x int(cx - bw / 2) y int(cy - bh / 2) boxes.append([x, y, int(bw), int(bh)]) confidences.append(float(confidence)) class_ids.append(class_id) indices cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold) print(红绿灯候选框:, len(boxes), NMS 后:, len(indices))逻辑说明detection[0:4]是归一化坐标乘以[w, h, w, h]还原到原图尺寸。class_id 9是 COCO 里traffic light的索引写错就什么都检不到。NMSBoxes做非极大值抑制去掉重叠框。参数上conf_threshold设 0.5 比较稳低于 0.3 会冒出大量误检nms_threshold设 0.4太小会把相邻的两个红绿灯合并成一个太大则同一个灯留下多个框。这两个值我调了不下十次0.5 和 0.4 是路口场景的甜点区。4. 红绿状态判定从检测框到颜色分类4.1 裁出灯区域再做 HSV 阈值YOLOv3 只告诉你“这是红绿灯”不告诉你红还是绿。常见做法是把框内区域裁出来转 HSV用颜色阈值判断。def judge_color(frame, box): x, y, bw, bh box roi frame[y:ybh, x:xbw] if roi.size 0: return unknown hsv cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) red1 cv2.inRange(hsv, (0, 100, 100), (10, 255, 255)) red2 cv2.inRange(hsv, (160, 100, 100), (180, 255, 255)) red cv2.countNonZero(red1) cv2.countNonZero(red2) green cv2.countNonZero(cv2.inRange(hsv, (40, 100, 100), (85, 255, 255))) if red green and red 20: return red elif green red and green 20: return green return unknown逻辑说明红色在 HSV 里跨了 0 和 180 两端所以要两个区间相加。countNonZero统计命中像素数谁多算谁。参数上(0, 100, 100)里的 100 是饱和度和明度下限太低会把灰白色也算进去20是最小像素阈值防止噪点误判。如果画面偏暗可以把明度下限降到 60。4.2 把框和状态画回原图检测完不画出来等于白跑。OpenCV 的rectangle和putText足够用。for i in indices: x, y, bw, bh boxes[i] color judge_color(img, boxes[i]) label ftraffic light: {color} cv2.rectangle(img, (x, y), (xbw, ybh), (0, 255, 0), 2) cv2.putText(img, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(result.jpg, img)逻辑说明indices是 NMS 后的框索引遍历画框。putText的坐标(x, y-10)让文字在框上方避免压住灯。参数上线宽 2、字体缩放 0.6 在 1080p 画面上清晰可读。如果处理视频流把imwrite换成imshow加waitKey(1)即可。4.3 视频流场景的帧率与跳帧策略红绿灯识别如果跑在实时视频上CPU 推理 YOLOv3 一帧大概 200 到 500 毫秒直接逐帧跑会卡成幻灯片。我一般用跳帧每 3 帧检测一次中间帧沿用上一次结果。cap cv2.VideoCapture(0) frame_count 0 last_boxes [] while True: ret, frame cap.read() if not ret: break frame_count 1 if frame_count % 3 0: last_boxes detect(frame) for box in last_boxes: draw(frame, box) cv2.imshow(traffic light, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明frame_count % 3控制检测频率last_boxes缓存上一次结果。参数上跳帧数根据机器性能调CPU 弱就 5有 GPU 可以 1。注意waitKey(1)不能省否则窗口不刷新。这套逻辑在opencv python拉流中断的场景下也适用拉流断了要加重连判断但那是另一个话题。5. 避坑与排查红绿灯识别最常见的 5 个翻车现场5.1 一个框都检不到输出层全是空现象代码跑通不报错但boxes长度为 0。原因通常是class_id 9写错或者conf_threshold设太高。COCO 里traffic light确实是第 9 类从 0 开始数但有些coco.names文件行序不同。解决打印classes.index(traffic light)确认索引再把conf_threshold降到 0.3 试一次。如果还是零检查blobFromImage的swapRB是不是漏了。5.2 同一个红绿灯被框了三四次现象NMS 后同一个灯还有多个重叠框。原因是nms_threshold设太大或者框的宽高算错。解决把nms_threshold从 0.6 降到 0.4同时确认detection[0:4]乘的是[w, h, w, h]而不是[w, h, bw, bh]。这个顺序写反框会跑到画面外。5.3 颜色判断永远返回 unknown现象框检到了但judge_color总返回 unknown。原因是 ROI 裁出来是空区域或者 HSV 阈值不匹配当前光照。解决先cv2.imwrite把 ROI 存下来看一眼确认裁的位置对再根据实际画面调 HSV 上下限。夜间场景红色饱和度低可以把(0, 100, 100)改成(0, 60, 60)。5.4 加载权重时报 Unsupported activation 或解析失败现象readNetFromDarknet抛异常。原因是yolov3.cfg和yolov3.weights版本不配套或者文件下载不完整。解决重新下载配套的三个文件校验 weights 大小不要混用 YOLOv3 和 YOLOv3-tiny 的 cfg 与 weights。5.5 CPU 推理太慢视频卡顿现象每帧几百毫秒画面延迟严重。原因是 YOLOv3 全量模型在 CPU 上本来就重。解决换yolov3-tiny.weights和yolov3-tiny.cfg速度能快三到五倍代价是远处小灯检不到或者把输入尺寸从 608 降到 416再不行就跳帧用第 4.3 节的策略。6. 进阶技巧用面积比和位置先验过滤误检跑通基础版之后你会发现画面里广告牌、车尾灯、红色招牌都可能被当成红绿灯。我最后收敛误检靠的是两个先验面积比和位置。红绿灯在画面里通常不会占太大面积也不会出现在画面最底部。def filter_by_prior(box, img_w, img_h): x, y, bw, bh box area_ratio (bw * bh) / (img_w * img_h) center_y_ratio (y bh / 2) / img_h if area_ratio 0.15: return False if center_y_ratio 0.9: return False return True逻辑说明area_ratio超过 0.15 的框基本是误检红绿灯没这么大center_y_ratio超过 0.9 说明框在画面最底部通常是地面反光或车灯。参数上这两个阈值要根据你的摄像头安装高度调我用的路口摄像头角度偏高0.15 和 0.9 比较合适。把这两个条件加进第 3.3 节的循环里误检能少一半以上。再进一步如果你要判断红绿灯的朝向或者读倒计时那就得引入更细的模型或者 OCRYOLOv3 本身做不到。我的习惯是先用这套方案把检测链路跑稳确认帧率、误检率、颜色判断准确率都能接受再考虑要不要换模型。很多项目死在第一步还没跑通就想着上大模型。这套 Python OpenCV YOLOv3 的组合胜在能让你一个下午就看到框和颜色剩下的优化才有方向。希望帮到你。本文还有配套的精品资源点击获取