简介本资源是一套基于Python与OpenCV实现的交通路口红绿灯智能识别与控制系统源码面向计算机视觉初学者、嵌入式AI实践者及高校课程设计学生解决真实场景下交通灯状态感知与逻辑响应的核心问题。压缩包共34个文件含3个核心Python脚本main.py、sql.py、video.py、3个JPEG/PNG图像样本、3个HTML/CSS/JS前端页面支持简易可视化、4个XML配置或标注文件以及README.md、requirements.txt等工程支撑文件整体1.35MB轻量易部署适配树莓派等边缘设备开发。已有127人学习下载资源结构清晰含模拟路口目录、Web交互模块admin.html/index.html和视频流处理主逻辑提供从图像采集、HSV颜色空间转换、自适应阈值分割、轮廓检测到红绿灯状态判别与控制输出的完整技术链路附带使用说明与依赖清单便于快速复现与二次开发。1. 这不是交通信号灯的“模拟器”而是一套可部署的视觉感知逻辑调度闭环系统很多人看到“红绿灯控制系统”第一反应是这不就是个定时切换的 PLC 逻辑但本项目标题里的关键词——Python、OpenCV、交通路口——已经划出了完全不同的技术边界它不依赖物理控制器或硬件接口而是用摄像头实时捕获路口视频流通过图像识别判断红绿灯状态红/黄/绿再结合预设规则如车流密度、行人等待时长动态调整各方向通行时长。本质是视觉感知驱动的轻量级自适应信号调度原型。它解决的不是“怎么让灯亮”而是“在没有地磁/雷达等传感器的情况下仅靠普通摄像头如何让红绿灯‘看懂’当前路口状态并做出合理响应”。适合嵌入式边缘设备如树莓派USB摄像头、教学实验平台或城市微循环路口的低成本智能化改造验证。对初学者它是 OpenCV 图像处理状态机设计的综合实战对有经验者它暴露了光照变化、遮挡、多灯组干扰等真实场景下的鲁棒性瓶颈——这些恰恰是工业级系统必须跨越的门槛。2. 用 OpenCV 实现红绿灯状态识别从 ROI 提取到 HSV 阈值分割的完整链路红绿灯识别不是简单的颜色分类而是需要在复杂背景天空、广告牌、车辆反光中稳定定位灯组并准确区分红、黄、绿三色。OpenCV 提供了成熟的图像处理工具链但关键在于流程设计与参数调优。2.1 定义感兴趣区域ROI并提取灯组位置直接在整帧图像上做颜色检测效率低且易受干扰。实际做法是先通过几何约束缩小搜索范围。典型路口红绿灯呈垂直或水平排列灯体为圆形尺寸相对固定。我们利用这一先验知识用形态学操作粗略定位灯组区域import cv2 import numpy as np def detect_traffic_light_roi(frame): # 转灰度并高斯模糊降噪 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5, 5), 0) # Canny 边缘检测 形态学闭运算连接断裂边缘 edges cv2.Canny(blurred, 50, 150) kernel np.ones((3,3), np.uint8) closed_edges cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) # 查找轮廓筛选近似圆形的轮廓灯体 contours, _ cv2.findContours(closed_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) rois [] for cnt in contours: area cv2.contourArea(cnt) if area 100: # 过滤过小噪声 continue perimeter cv2.arcLength(cnt, True) circularity 4 * np.pi * area / (perimeter * perimeter) if perimeter 0 else 0 # 圆形度接近 1.0 且面积适中认为是灯体候选 if 0.7 circularity 1.2: (x, y), radius cv2.minEnclosingCircle(cnt) rois.append((int(x), int(y), int(radius))) # 聚类将空间距离近的灯体归为同一灯组例如垂直排列的红黄绿 if len(rois) 3: # 简单按 Y 坐标聚类垂直灯组或 X 坐标水平灯组 rois.sort(keylambda r: r[1]) # 按 Y 排序 group [rois[0]] for i in range(1, len(rois)): dy abs(rois[i][1] - rois[i-1][1]) if dy 50: # 同一组内灯间距小于 50 像素 group.append(rois[i]) else: if len(group) 3: # 至少 3 个灯才认为是有效灯组 return group group [rois[i]] return []提示此函数返回的是(x, y, radius)元组列表代表每个灯在图像中的中心坐标和半径。它不保证顺序红/黄/绿后续需通过颜色判定。实际部署中应结合路口地理信息如灯杆朝向预先标定 ROI 区域避免每帧都做全图搜索大幅提升性能。2.2 HSV 颜色空间分割与状态判定RGB 空间下红、黄、绿在不同光照下色值漂移严重。HSV色调 Hue、饱和度 Saturation、明度 Value能更好分离颜色信息。OpenCV 的cv2.cvtColor支持 RGB 到 HSV 的转换关键在于设定合理的 HSV 阈值范围def classify_light_color(frame, roi_list): # 取 ROI 中心区域避免边缘模糊进行颜色分析 h_values, s_values, v_values [], [], [] for (cx, cy, r) in roi_list: # 提取以 (cx,cy) 为中心、半径 r//2 的圆形区域 mask np.zeros(frame.shape[:2], dtypenp.uint8) cv2.circle(mask, (cx, cy), r//2, 255, -1) hsv_roi cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) hsv_roi_masked cv2.bitwise_and(hsv_roi, hsv_roi, maskmask) # 计算该区域 HSV 均值更鲁棒于局部噪声 mean_hsv cv2.mean(hsv_roi_masked, maskmask)[:3] h_values.append(mean_hsv[0]) s_values.append(mean_hsv[1]) v_values.append(mean_hsv[2]) # 根据 HSV 均值判定颜色典型阈值需根据实际摄像头校准 states [] for h, s, v in zip(h_values, s_values, v_values): # 饱和度和明度需足够排除灰白/暗色干扰 if s 50 or v 50: states.append(unknown) continue # Hue 在 0-180 范围OpenCV 特殊约定 if (h 0 and h 10) or (h 170 and h 180): # 红色环状跨 0 度 states.append(red) elif h 20 and h 40: # 黄色 states.append(yellow) elif h 45 and h 90: # 绿色 states.append(green) else: states.append(unknown) return states # 示例调用 # frame cv2.imread(traffic.jpg) # rois detect_traffic_light_roi(frame) # colors classify_light_color(frame, rois) # print(Detected states:, colors) # [red, yellow, green] 或类似注意HSV 阈值不是固定值。h值受白平衡影响极大强烈建议在目标部署环境白天/夜晚/阴天下采集多组样本用cv2.inRange和滑动条实时调试。例如使用cv2.createTrackbar创建 HSV 调节窗口观察inRange(hsv, lower_red, upper_red)的掩膜效果。常见误区是直接套用网络上的“标准值”导致夜间红灯因白平衡偏移被误判为橙色或粉色。2.3 处理多灯组与方向映射一个路口通常有多个灯组东西向、南北向每个灯组包含红黄绿三灯。仅识别出颜色不够还需知道该颜色属于哪个方向。本项目采用空间位置映射法预先在配置文件中定义各灯组的 ROI 坐标范围如east_roi [(100,200), (150,250)]运行时将检测到的灯中心点(cx, cy)归入最近的 ROI 区域。代码结构如下# config.py DIRECTION_ROIS { north: {center: (320, 100), radius: 80}, # 假设北向灯在图像顶部中央 south: {center: (320, 450), radius: 80}, east: {center: (600, 280), radius: 80}, west: {center: ( 40, 280), radius: 80} } def assign_direction_to_lights(roi_list): assignments {} for (cx, cy, r) in roi_list: min_dist float(inf) assigned_dir unknown for direction, roi_info in DIRECTION_ROIS.items(): dist np.sqrt((cx - roi_info[center][0])**2 (cy - roi_info[center][1])**2) if dist min_dist and dist roi_info[radius]: min_dist dist assigned_dir direction # 将灯按方向分组 if assigned_dir not in assignments: assignments[assigned_dir] [] assignments[assigned_dir].append((cx, cy, r)) return assignments此方法简单可靠避免了复杂的多目标跟踪。其前提是安装摄像头视角固定且灯组位置在图像中相对稳定。若路口有施工或临时灯杆需提供手动校准接口。参数说明典型值调试建议Canny低阈值控制边缘检测灵敏度30~60光照强时调高避免过多噪声边morphologyEx核大小连接断裂边缘(3,3) 或 (5,5)灯体较大时用更大核HSVs下限过滤低饱和度灰白区域40~60雾霾天需降低否则绿灯变灰被过滤HSVv下限过滤过暗区域30~50夜间需大幅降低但会引入阴影噪声ROI 聚类dy阈值同一灯组内灯间距容忍度40~70 像素高分辨率摄像头可适当增大3. 构建状态机与动态调度逻辑从静态识别到闭环控制识别出红绿灯状态只是第一步。真正的“控制系统”体现在如何根据识别结果结合交通流信息本项目中简化为车流量统计生成下一周期的信号配时方案。这是一个典型的有限状态机FSM问题但加入了外部输入车流的反馈调节。3.1 定义核心状态与转移条件本系统定义 4 个主状态覆盖标准四相位路口东西直行、东西左转、南北直行、南北左转。每个状态对应一组灯组的显示组合状态名东西向灯南北向灯触发条件EAST_WEST_GREEN绿黄红 → 绿亮红黄绿 → 红亮初始状态或东西向车流 南北向车流×1.5NORTH_SOUTH_GREEN红亮绿亮南北向车流 东西向车流×1.5EAST_WEST_LEFT左转绿箭头若支持红亮东西向左转车流积压NORTH_SOUTH_LEFT红亮左转绿箭头南北向左转车流积压注意本项目源码中LEFT相位常被简化为直行相位的延伸即直行绿灯后附加短时左转绿灯因多数国产灯无独立左转箭头。状态转移不依赖绝对时间而由车流密度变化率驱动。例如当EAST_WEST_GREEN状态下东西向车流持续 3 秒低于阈值且南北向车流上升则触发向NORTH_SOUTH_GREEN的转移。3.2 车流量统计基于背景减除的简易计数没有地磁线圈如何获取车流本项目采用 OpenCV 的cv2.createBackgroundSubtractorMOG2进行运动目标检测再通过 ROI 统计进入/离开特定区域的运动像素块数量# 初始化背景减除器需在启动时学习背景 bg_subtractor cv2.createBackgroundSubtractorMOG2(history500, varThreshold50, detectShadowsTrue) # 定义东西向车流统计 ROI例如画面底部 1/4 区域 east_west_roi (0, frame.shape[0]//2, frame.shape[1], frame.shape[0]//2) def count_vehicles_in_roi(frame, bg_subtractor, roi): x, y, w, h roi roi_frame frame[y:yh, x:xw] # 应用背景减除 fg_mask bg_subtractor.apply(roi_frame) # 形态学去噪 kernel np.ones((5,5), np.uint8) fg_mask cv2.morphologyEx(fg_mask, cv2.MORPH_CLOSE, kernel) fg_mask cv2.morphologyEx(fg_mask, cv2.MORPH_OPEN, kernel) # 查找连通域每个连通域视为一辆车 contours, _ cv2.findContours(fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) vehicle_count 0 for cnt in contours: area cv2.contourArea(cnt) if area 500: # 过滤小噪声如树叶晃动 vehicle_count 1 # 可选绘制轮廓用于调试 # cv2.drawContours(roi_frame, [cnt], -1, (0,255,0), 2) return vehicle_count # 主循环中调用 # east_west_flow count_vehicles_in_roi(frame, bg_subtractor, east_west_roi) # north_south_flow count_vehicles_in_roi(frame, bg_subtractor, north_south_roi)提示MOG2的history参数决定背景模型更新速度。值越大背景越“老”对缓慢变化如云移动鲁棒但对新出现的静止物体如停在路口的故障车适应慢。生产环境建议设为 300~500。varThreshold控制像素方差容忍度值越小越敏感但也越易受噪声影响。3.3 动态配时算法基于车流比的自适应时长计算传统固定配时如东西 30 秒、南北 30 秒无法应对潮汐车流。本项目实现一个简化的自适应算法基础周期为 90 秒各相位时长按实时车流比例分配但设置上下限防止极端情况def calculate_phase_duration(east_west_flow, north_south_flow, base_cycle90): total_flow east_west_flow north_south_flow if total_flow 0: return {EAST_WEST_GREEN: 45, NORTH_SOUTH_GREEN: 45} # 平均分配 # 计算比例但限制最小占比为 20%最大为 80% ew_ratio max(0.2, min(0.8, east_west_flow / total_flow)) ns_ratio 1 - ew_ratio ew_duration int(base_cycle * ew_ratio) ns_duration base_cycle - ew_duration # 确保最小绿灯时间如 15 秒和最大红灯时间如 70 秒 ew_duration max(15, min(70, ew_duration)) ns_duration max(15, min(70, ns_duration)) return {EAST_WEST_GREEN: ew_duration, NORTH_SOUTH_GREEN: ns_duration} # 示例当 east_west_flow12, north_south_flow3 时 # ratio 12/(123)0.8 - ew_duration 90*0.872秒ns_duration18秒 # 符合上限无需裁剪此算法虽简单但已能体现“车多放行久、车少快速切”的核心思想。进阶版本可加入历史数据平滑如加权移动平均、行人过街请求通过额外 ROI 检测人形等输入。4. 部署与性能优化在树莓派 4B 上实现实时15 FPS运行源码.zip 的价值不仅在于算法更在于它能否在资源受限的边缘设备上稳定运行。树莓派 4B4GB RAM是本项目的理想验证平台但默认 OpenCV 编译往往未启用硬件加速导致 CPU 占用过高。4.1 树莓派专用 OpenCV 编译与安装官方pip install opencv-python安装的是通用版未针对 ARM 架构优化。必须从源码编译并启用NEON和VFPV3指令集# 更新系统 sudo apt update sudo apt upgrade -y # 安装编译依赖 sudo apt install -y build-essential cmake git pkg-config libjpeg-dev \ libtiff-dev libjasper-dev libpng-dev libavcodec-dev libavformat-dev \ libswscale-dev libv4l-dev libxvidcore-dev libx264-dev libfontconfig1-dev \ libcairo2-dev libgdk-pixbuf2.0-dev libpango1.0-dev libgtk2.0-dev \ libgtk-3-dev libatlas-base-dev gfortran python3-dev python3-pip # 下载 OpenCV 4.5.5与热词中 4.5.2 兼容且修复了若干 ARM bug cd ~ wget -O opencv.zip https://github.com/opencv/opencv/archive/refs/tags/4.5.5.zip unzip opencv.zip cd opencv-4.5.5 # 创建构建目录并配置关键启用 NEON、禁用不需要的模块 mkdir build cd build cmake -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D OPENCV_EXTRA_MODULES_PATH~/opencv_contrib-4.5.5/modules \ -D ENABLE_NEONON \ -D ENABLE_VFPV3ON \ -D BUILD_TESTSOFF \ -D BUILD_PERF_TESTSOFF \ -D BUILD_opencv_python2OFF \ -D BUILD_opencv_python3ON \ -D PYTHON3_EXECUTABLE/usr/bin/python3 \ -D PYTHON3_PACKAGES_PATH/usr/lib/python3/dist-packages \ -D PYTHON3_LIBRARY/usr/lib/arm-linux-gnueabihf/libpython3.9.so \ -D PYTHON3_INCLUDE_DIR/usr/include/python3.9 .. # 编译使用 4 线程约 2 小时 make -j4 sudo make install sudo ldconfig提示编译前务必确认python3 --version输出为3.9.x树莓派 OS 默认否则PYTHON3_LIBRARY路径会错误。若使用pip3 install opencv-python-headless请先sudo apt remove python3-opencv避免冲突。4.2 关键性能瓶颈与绕过方案即使编译优化纯 Python OpenCV 在树莓派上仍面临两大瓶颈I/O 延迟USB 摄像头帧读取和CPU 解码压力H.264 视频流。解决方案是绕过 OpenCV 的cv2.VideoCapture直接使用picamera2树莓派官方库或v4l2工具链# 替代方案使用 picamera2 获取更高效帧需树莓派 Camera Module from picamera2 import Picamera2 import time picam2 Picamera2() config picam2.create_preview_configuration(main{size: (640, 480)}) picam2.configure(config) picam2.start() # 主循环中用 picam2.capture_array() 替代 cap.read() # 效率提升约 40%且延迟更低 while True: frame picam2.capture_array() # ... 处理逻辑 if cv2.waitKey(1) ord(q): break picam2.stop()对于 USB 摄像头使用v4l2-ctl工具强制设置格式避免 OpenCV 自动协商导致的性能损失# 查看设备支持格式 v4l2-ctl -d /dev/video0 --list-formats-ext # 设置为 MJPEG 格式比 YUYV 解码快得多 v4l2-ctl -d /dev/video0 --set-fmt-videowidth640,height480,pixelformatMJPG v4l2-ctl -d /dev/video0 --set-ctrlfocus_auto0 v4l2-ctl -d /dev/video0 --set-ctrlfocus_absolute50然后在 Python 中指定解码器cap cv2.VideoCapture(/dev/video0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M, J, P, G)) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)4.3 内存与 CPU 监控脚本部署后必须持续监控资源占用以下脚本可集成到主程序中当 CPU 80% 或内存 90% 时自动降级如跳过非关键处理、降低帧率import psutil import time def check_system_resources(): cpu_percent psutil.cpu_percent(interval1) memory psutil.virtual_memory() memory_percent memory.percent if cpu_percent 80 or memory_percent 90: print(fWarning: High load! CPU{cpu_percent}%, Mem{memory_percent}%) # 触发降级策略例如将处理帧率从 30FPS 降至 15FPS return False return True # 主循环中 while True: if not check_system_resources(): time.sleep(0.067) # 15FPS 间隔 continue ret, frame cap.read() if not ret: break # 正常处理...5. 验证与调试技巧用 OpenCV 的cv2.imshow实现可视化诊断流水线调试红绿灯识别系统最有效的方式不是打印日志而是实时可视化每一处理步骤的中间结果。OpenCV 的cv2.imshow是黄金工具但需合理组织窗口布局避免信息过载。5.1 分层窗口布局与快捷键控制将处理流程拆分为 5 个关键阶段每个阶段一个独立窗口并绑定快捷键实现动态开关窗口名显示内容快捷键用途Original原始帧o对照基准ROI_DetectROI 检测结果画出圆圈r验证灯定位是否准确HSV_MaskHSV 颜色掩膜红/黄/绿分别用不同颜色h调试 HSV 阈值Flow_Mask车流检测掩膜运动区域f确认车流 ROI 是否覆盖有效车道Final_State最终状态叠加如在原图上用大字标出 EAST_WEST_GREENs系统输出验证# 在主循环中添加 if show_original: cv2.imshow(Original, frame) if show_roi: roi_vis frame.copy() for (cx, cy, r) in rois: cv2.circle(roi_vis, (cx, cy), r, (0,255,0), 2) cv2.imshow(ROI_Detect, roi_vis) if show_hsv: # 创建 HSV 掩膜可视化红红色黄黄色绿绿色 hsv_vis np.zeros_like(frame) for i, color in enumerate(colors): if color red: cv2.circle(hsv_vis, (rois[i][0], rois[i][1]), rois[i][2], (0,0,255), -1) elif color yellow: cv2.circle(hsv_vis, (rois[i][0], rois[i][1]), rois[i][2], (0,255,255), -1) elif color green: cv2.circle(hsv_vis, (rois[i][0], rois[i][1]), rois[i][2], (0,255,0), -1) cv2.imshow(HSV_Mask, hsv_vis) # ... 其他窗口同理 # 键盘监听 key cv2.waitKey(1) 0xFF if key ord(o): show_original not show_original elif key ord(r): show_roi not show_roi elif key ord(h): show_hsv not show_hsv elif key ord(f): show_flow not show_flow elif key ord(s): show_state not show_state elif key ord(q): break注意cv2.imshow在树莓派 GUI 桌面环境下运行正常但若通过 SSH 远程连接需启用 X11 转发ssh -X piraspberrypi或改用cv2.imwrite保存关键帧到文件系统再通过 Samba 共享查看。5.2 光照鲁棒性测试构建标准化测试集真实路口最大的挑战是光照变化。必须建立一套涵盖不同时间段的测试视频集day_clear.mp4正午晴天高对比度day_cloudy.mp4阴天低饱和度dusk.mp4黄昏红灯易过曝night.mp4夜间仅靠路灯绿灯易被误判为白色对每个视频人工标注每帧的正确状态红/黄/绿/灭然后运行系统用以下脚本计算准确率def evaluate_video(video_path, ground_truth_csv): cap cv2.VideoCapture(video_path) gt_data pd.read_csv(ground_truth_csv) # CSV 格式frame_id,state correct 0 total 0 frame_id 0 while cap.isOpened(): ret, frame cap.read() if not ret: break # 运行识别流程 rois detect_traffic_light_roi(frame) if len(rois) 3: colors classify_light_color(frame, rois) # 假设第一个 ROI 是红灯取其状态 pred_state colors[0] if colors else unknown # 获取该帧真实状态 true_state gt_data.loc[gt_data[frame_id] frame_id, state].values[0] if pred_state true_state: correct 1 total 1 frame_id 1 cap.release() accuracy correct / total if total 0 else 0 print(fAccuracy on {video_path}: {accuracy:.3f}) return accuracy # 批量测试 videos [day_clear.mp4, day_cloudy.mp4, dusk.mp4, night.mp4] for v in videos: evaluate_video(v, f{v.split(.)[0]}_gt.csv)此测试集是优化 HSV 阈值、调整 ROI 检测参数的唯一依据。没有它任何“调参”都是空中楼阁。5.3 日志与状态持久化记录决策依据而非仅结果系统上线后仅记录“当前状态”毫无价值。必须记录为什么做出这个决策以便事后复盘。关键日志字段包括字段示例值说明timestamp2023-10-05T14:22:35.123ZISO8601 时间戳detected_states{north: red, south: red, east: green, west: green}各方向识别结果vehicle_counts{east_west: 8, north_south: 2}车流统计值calculated_durations{EAST_WEST_GREEN: 65, NORTH_SOUTH_GREEN: 25}本次配时计算结果cpu_usage72.3当前 CPU 百分比memory_usage68.1当前内存百分比使用 Python 的logging模块将日志写入滚动文件每日一个文件并确保vehicle_counts和calculated_durations字段存在这是分析调度合理性如是否过度偏向某方向的唯一数据源。本文还有配套的精品资源点击获取
