简介这份资源面向计算机视觉入门者与智能交通方向开发者提供一套基于Python与OpenCV的红绿灯检测完整实现可用于自动驾驶感知、交通监控等场景的算法学习与二次开发。包内共12个文件以6张png与1张jpg示例图片、2个Python脚本、2份md说明文档为主另含1个pyc缓存文件压缩包约343KB体积轻便便于快速运行与调试。核心脚本围绕颜色空间转换、轮廓与形状检测、红绿灯状态判定等环节展开配套图片覆盖红、绿、黄及箭头指示灯等多种信号形态可直接用于验证检测效果。目前已有4037人学习下载说明该方案在同类教程中具备一定参考价值。读者可借此理解从图像预处理到目标识别的完整链路掌握HSV阈值分割、圆形与箭头灯区分等实用技巧并在此基础上扩展实时视频流处理或深度学习端到端检测适合作为课程设计、毕业项目或算法练手的起步素材。1. 从一张红灯照片说起这个 OpenCV 红绿灯检测包到底能跑出什么手头这个红绿灯检测.zip解压完第一眼看到的是detectColor.py、TLState.py两个脚本加上images目录里一堆red.jpg、green.png、yellow.png、flashingyellow.png、red_greenarrow.png、redarrow.png、red_yellowarrow.png。这不是一个训练模型的工程而是一套基于颜色阈值和状态机的轻量级红绿灯识别方案走的是 OpenCV 传统图像处理路线不依赖 GPU、不依赖深度学习框架Python 装好 OpenCV 就能跑。它解决的是「给定一张红绿灯图片或一帧视频画面判断当前是红灯、绿灯、黄灯还是箭头灯」这类问题。适合做交通监控原型、自动驾驶感知模块的入门验证、课程设计或者你手头有个摄像头想快速搭个信号灯状态识别 demo。不适合直接上量产车规级场景原因后面会讲。先把这套代码的骨架拆开看再决定要不要往自己的项目里搬。2. 颜色阈值法为什么还能打HSV 空间与 TLState 状态机拆解2.1 从 BGR 到 HSV红绿灯检测的颜色空间选择OpenCV 读进来的图片默认是 BGR 三通道直接拿 BGR 做颜色判断会翻车。原因很直白BGR 三个分量都受光照强度影响同一盏红灯白天拍出来 R 值可能是 220傍晚拍出来可能只有 140你没法用一个固定阈值同时覆盖。HSV 把色相H、饱和度S、明度V拆开色相基本只跟「这是什么颜色」有关跟亮不亮关系不大这才是颜色检测该待的空间。红色在 HSV 里有个经典坑H 通道是 0 到 179 的环形红色横跨 0 和 179 两端所以红色阈值必须写两段。绿色和黄色就正常一段搞定。这套代码里detectColor.py大概率就是干这件事的我按常见写法还原一下核心逻辑import cv2 import numpy as np def detect_color(image_path): img cv2.imread(image_path) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 红色分两段0-10 和 170-179 mask_red1 cv2.inRange(hsv, (0, 100, 100), (10, 255, 255)) mask_red2 cv2.inRange(hsv, (170, 100, 100), (179, 255, 255)) mask_red cv2.bitwise_or(mask_red1, mask_red2) # 绿色H 大约 40-85 mask_green cv2.inRange(hsv, (40, 80, 80), (85, 255, 255)) # 黄色H 大约 20-35 mask_yellow cv2.inRange(hsv, (20, 100, 100), (35, 255, 255)) # 统计各颜色像素占比占比最大的判为当前灯色 results {} for name, mask in [(red, mask_red), (green, mask_green), (yellow, mask_yellow)]: ratio cv2.countNonZero(mask) / (img.shape[0] * img.shape[1]) results[name] ratio return max(results, keyresults.get)逻辑说明cv2.inRange做的是逐像素阈值判断落在范围内的置 255否则置 0输出一张二值掩膜。红色用bitwise_or把两段合并成一张完整掩膜。最后用countNonZero统计每张掩膜的白点数量除以总像素数得到占比占比最高的颜色就是判定结果。参数说明(0, 100, 100)里的三个数分别是 H、S、V 的下限。S 下限设 100 是为了滤掉灰白色的干扰区域V 下限设 100 是为了滤掉暗部噪声。这两个值调低会让检测更敏感但误检增多调高则相反。绿色和黄色的 S、V 下限可以适当放宽到 80因为这两种灯在实际画面里往往没有红色那么饱和。2.2 TLState.py 在做什么从单帧颜色到灯态序列单张图片判颜色只是第一步真实场景里你需要的是「当前信号灯处于什么状态」。TLState.py这个文件名暗示它是一个状态管理模块常见做法是把连续多帧的颜色判定结果做平滑和状态迁移。为什么需要这层因为单帧检测会抖——摄像头噪点、反光、遮挡都可能让某一帧突然判成黄色如果直接输出给下游控制逻辑就会抽风。一个典型的状态机实现思路是这样的from collections import deque class TLState: def __init__(self, window10, min_stable6): self.history deque(maxlenwindow) self.min_stable min_stable self.current_state unknown def update(self, color): self.history.append(color) if len(self.history) self.history.maxlen: return self.current_state # 统计窗口内出现次数最多的颜色 counts {} for c in self.history: counts[c] counts.get(c, 0) 1 top_color, top_count max(counts.items(), keylambda x: x[1]) # 超过稳定阈值才切换状态 if top_count self.min_stable: self.current_state top_color return self.current_state逻辑说明维护一个长度为window的滑动窗口每来一帧颜色结果就压进去。窗口满了之后统计众数只有当某个颜色出现次数达到min_stable才真正切换状态。这样偶发的单帧误判不会影响输出。参数说明window10意味着看最近 10 帧按 30fps 算大约 0.33 秒的观察期。min_stable6意味着 10 帧里至少 6 帧一致才认。这两个参数要根据你的帧率和场景动态程度调帧率低就减小 window场景变化快就减小 min_stable但太小会失去平滑效果。2.3 箭头灯怎么处理轮廓形状过滤的补充手段images目录里有redarrow.png、red_greenarrow.png、red_yellowarrow.png说明这个包考虑了箭头灯。纯颜色阈值分不出圆形灯和箭头灯得靠形状。常见做法是在颜色掩膜基础上做轮廓检测用cv2.findContours拿到每个色块的轮廓再算面积、宽高比、凸包缺陷等特征来区分。contours, _ cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area cv2.contourArea(cnt) if area 50: # 滤掉噪点 continue x, y, w, h cv2.boundingRect(cnt) aspect_ratio w / float(h) # 圆形灯宽高比接近 1箭头灯通常偏离 1 if 0.8 aspect_ratio 1.2: shape circle else: shape arrow逻辑说明findContours在二值掩膜上找连通区域边界。contourArea算面积用来滤噪。boundingRect拿外接矩形宽高比是区分圆形和箭头最省事的特征。参数说明面积阈值 50 是经验值图片分辨率高就往上调。宽高比 0.8 到 1.2 算圆形是宽松判断实际箭头灯可能更细长可以收紧到 0.9 到 1.1。注意findContours在 OpenCV 3.x 和 4.x 里返回值个数不同3.x 返回三个值4.x 返回两个代码里用contours, _ 是 4.x 写法跑 3.x 会报ValueError。3. 把 detectColor.py 跑起来环境、命令与参数调优3.1 环境准备Python 与 OpenCV 安装的版本坑这套代码是 Python 3.6 时代的东西__pycache__里躺着detectColor.cpython-36.pyc说明作者当年用的是 3.6。现在你机器上大概率是 3.8 到 3.11直接跑一般没问题但 OpenCV 版本要留意。cv2.findContours的返回值差异就是 3.x 和 4.x 的分水岭装的时候直接上 4.x 省事pip install opencv-python4.8.1.78 pip install numpy逻辑说明opencv-python是预编译 wheel 包装完直接import cv2就能用不需要自己编译。numpy是 OpenCV 的硬依赖虽然 pip 会自动带上但显式写出来避免版本冲突。参数说明4.8.1.78是 4.x 里比较稳的一个版本支持 Python 3.7 到 3.11。如果你用的是 Apple Silicon 或者需要 CUDA 加速换opencv-python-headless或自己编译但红绿灯检测这种量级用不上 CUDA。装完验证一下import cv2 print(cv2.__version__)如果报ModuleNotFoundError: No module named opencv九成是 pip 装到了另一个 Python 环境里用python -m pip install opencv-python确保装到当前解释器。3.2 跑通单张图片检测从 red.jpg 到 green.png环境好了之后先拿images里的静态图验证。假设detectColor.py接受命令行参数传入图片路径常见调用方式python detectColor.py images/red.jpg python detectColor.py images/green.png python detectColor.py images/redarrow.png如果脚本没写命令行入口直接在代码里改路径也行。跑通之后你应该看到终端输出类似Detected: red或者弹出一张带标注框的窗口。这一步的目的是确认颜色阈值对你的图片有效。如果red.jpg判成了 yellow说明红色阈值范围偏窄或者图片本身偏色需要调 H 范围。调参的时候别瞎试用 OpenCV 的 trackbar 做交互式调试最快import cv2 import numpy as np def nothing(x): pass cv2.namedWindow(trackbar) cv2.createTrackbar(H_low, trackbar, 0, 179, nothing) cv2.createTrackbar(H_high, trackbar, 10, 179, nothing) cv2.createTrackbar(S_low, trackbar, 100, 255, nothing) cv2.createTrackbar(V_low, trackbar, 100, 255, nothing) img cv2.imread(images/red.jpg) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) while True: h_low cv2.getTrackbarPos(H_low, trackbar) h_high cv2.getTrackbarPos(H_high, trackbar) s_low cv2.getTrackbarPos(S_low, trackbar) v_low cv2.getTrackbarPos(V_low, trackbar) mask cv2.inRange(hsv, (h_low, s_low, v_low), (h_high, 255, 255)) cv2.imshow(mask, mask) if cv2.waitKey(1) 0xFF 27: break cv2.destroyAllWindows()逻辑说明拖动滑块实时看掩膜变化找到能把红灯区域完整抠出来、同时背景全黑的那组参数记下来写回detectColor.py。参数说明H 范围从 0 到 10 是红色第一段如果红灯偏紫就往上扩到 15偏橙就往下收到 5。S 和 V 的下限根据背景复杂度调背景越花越要调高。3.3 接视频流从单帧到实时检测的改造点静态图跑通后把cv2.imread换成cv2.VideoCapture就能接摄像头或视频文件cap cv2.VideoCapture(0) # 0 是默认摄像头也可以传视频文件路径 tl TLState(window10, min_stable6) while True: ret, frame cap.read() if not ret: break color detect_color_frame(frame) # 把单图检测逻辑抽成帧处理函数 state tl.update(color) cv2.putText(frame, state, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(traffic light, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()逻辑说明VideoCapture(0)打开默认摄像头传文件路径则读视频。每帧调颜色检测结果喂给TLState做平滑再把状态画到画面上。参数说明waitKey(1)里的 1 是毫秒等待控制循环帧率太小 CPU 跑满太大画面卡顿。摄像头分辨率高的话检测前先cv2.resize到 640 宽能显著降负载。4. 避坑与排查颜色阈值法最容易翻车的五个地方4.1 现象白天能识别傍晚全判成 unknown原因HSV 的 V 通道下限设太高傍晚光照弱红灯的 V 值掉到阈值以下掩膜全黑。解决把 V 下限从 100 降到 50 甚至 30同时把 S 下限适当提高来补偿噪声。更好的做法是用自适应阈值根据画面整体亮度动态调 V 下限。4.2 现象红色和黄色分不清红灯判成黄灯原因红色 H 范围上界设到了 15黄色 H 下界设到了 15两者在边界重叠。解决红色第一段收到 0 到 8黄色从 20 开始中间留出 9 到 19 的缓冲带。如果实际场景里橙黄色灯多缓冲带还要加宽。4.3 现象findContours 报 ValueError: not enough values to unpack原因OpenCV 3.x 的findContours返回三个值(image, contours, hierarchy)4.x 返回两个(contours, hierarchy)。代码按 4.x 写的跑在 3.x 上就炸。解决统一用 4.x或者写兼容代码contours cv2.findContours(...)[-2]取倒数第二个永远拿到轮廓列表。4.4 现象视频流跑几秒后卡死或报拉流中断原因VideoCapture读网络流或高分辨率摄像头时缓冲区积压导致延迟越来越大。解决设置cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)把缓冲降到最小或者开独立线程读帧、主线程处理避免读写互相阻塞。4.5 现象箭头灯和圆形灯混判状态频繁跳变原因形状过滤的宽高比阈值太宽松箭头灯旋转角度不同宽高比变化大有时落进圆形区间。解决不要只靠宽高比加上cv2.matchShapes做轮廓匹配或者用cv2.minAreaRect拿最小外接矩形的角度信息辅助判断。另外TLState的min_stable调高到 7 或 8让状态切换更迟钝。5. 从能跑到能用多帧投票、ROI 裁剪与阈值自适应这套代码跑通静态图不难难的是让它在真实视频里稳定输出。我自己的习惯是加三层保险。第一层是 ROI 裁剪红绿灯在画面里的位置通常固定先用cv2.selectROI框出信号灯大致区域只在这个区域里做颜色检测背景干扰直接砍掉一大半。第二层是多帧投票TLState那个滑动窗口就是干这个的但窗口大小和稳定阈值要根据帧率算——30fps 下 window15、min_stable10 是比较稳的组合相当于半秒观察期。第三层是阈值自适应每隔几十帧统计一次 ROI 内的 V 通道均值按均值比例动态调整 V 下限这样白天傍晚都能跟得上。验证方法也简单拿手机拍几段不同时段、不同角度的红绿灯视频丢给脚本跑统计状态切换的时间点和实际灯色变化的时间点差多少。差在 0.5 秒以内算合格超过 1 秒就要回头查是窗口太大还是阈值漂了。有个细节容易忽略cv2.inRange的输入必须是 HSV 图如果你在循环里忘了cvtColor掩膜会全黑但代码不报错这种黑匣子式的静默失败最耗时间。从那以后我每次改完颜色检测逻辑都强制先拿一张已知灯色的图跑一遍确认输出不是空的再往下接视频。希望帮到你。本文还有配套的精品资源点击获取