简介:本资源是一套基于Python与OpenCV实现的交通路口红绿灯智能控制系统高分毕业设计源码,面向计算机、自动化及智能交通方向的本科生,解决真实路口信号识别与状态联动控制问题,适用于毕业设计、课程设计及期末大作业场景。压缩包共33个文件,含3个核心Python脚本(main.py、sql.py、video.py)、3个JPEG/3个PNG图像资源、2个HTML前端页面、3个CSS/2个JS样式与交互文件、4个XML配置或标注文件,以及README.md、requirements.txt等工程支撑文档,整体仅1.25MB,轻量易部署。已有162人学习下载。项目代码全程手写并附详尽中文注释,含模拟路口场景、Web管理界面与视频流实时识别模块,经严格调试可直接运行;配套PD文档与使用说明清晰,结构分层合理(web/、static/、pycache/等),兼顾功能完整性与教学可读性,是入门CV实战与嵌入式视觉控制的优质参考范例。
1. 这不是“模拟红绿灯动画”,而是一套可实测响应真实图像输入的交通信号控制逻辑闭环
很多同学拿到“红绿灯控制系统”毕设题目,第一反应是用 tkinter 或 PyGame 画三个圆、写个定时器切换颜色——那只是状态演示,不是“控制系统”。真正能拿高分的毕设,必须体现“感知-决策-输出”闭环:用 OpenCV 实时采集路口视频流(或预录视频),识别红绿灯色块/箭头/倒计时数字,结合车流密度、行人等待时长等视觉特征,动态调整各方向灯色持续时间,并将控制指令映射为可验证的输出信号(如串口指令、GPIO电平、或可视化状态日志)。本方案不依赖硬件控制器,但所有模块均按嵌入式部署前的标准设计:图像预处理抗光照变化、HSV阈值鲁棒性调参、多ROI区域独立检测、状态机防误触发、帧率自适应降载机制。适合计算机、自动化、智能交通方向本科生,代码结构清晰、注释完整、参数可调、测试路径明确,已通过 OpenCV 4.5+ 和 Python 3.8~3.11 全版本验证。
2. 用 OpenCV 在本地跑通红绿灯状态识别的最小命令链:从视频读取到 HSV 色域分割
2.1 为什么必须用 HSV 而非 RGB 做红绿灯颜色识别?
RGB 空间中,红色在不同光照下会偏橙、偏紫、甚至发灰,直接比较 R/G/B 通道值极易误判。HSV 将颜色信息(Hue)、饱和度(Saturation)、明度(Value)解耦,其中 Hue 通道对光照变化不敏感——红灯对应 H≈0° 或 H≈180°,绿灯 H≈60°,黄灯 H≈30°。OpenCV 的cv2.cvtColor(img, cv2.COLOR_BGR2HSV)输出 H 通道范围为 [0,179](注意:不是 [0,360]),S 和 V 均为 [0,255]。实际调试中发现,单纯靠 Hue 阈值仍会受强反光干扰,因此必须联合 S>80(排除灰白噪点)和 V>50(排除暗区误检)构成三维掩膜。
提示:不要用网上流传的固定 HSV 阈值(如 red_lower = [0,100,100]),每个摄像头白平衡、镜头镀膜、环境色温都不同。必须用
cv2.createTrackbar动态调试,保存最优参数到 config.py。
2.2 三步构建可复用的红绿灯 ROI 检测模块
2.2.1 定义物理位置固定的检测区域(Region of Interest)
真实路口摄像头视野稳定,红绿灯位置相对固定。避免全图遍历降低效率,应预先划分四个 ROI:左转灯、直行灯、右转灯、人行道灯。用cv2.rectangle()可视化调试,坐标以(x, y, w, h)形式存入字典:
# config.py ROI_COORDINATES = { "left_turn": (120, 80, 60, 60), # x,y,width,height "straight": (220, 80, 60, 60), "right_turn":(320, 80, 60, 60), "pedestrian":(420, 200, 40, 40) }2.2.2 对每个 ROI 执行 HSV 掩膜 + 形态学去噪
import cv2 import numpy as np def detect_light_color(roi_img, hsv_lower, hsv_upper): hsv = cv2.cvtColor(roi_img, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv, hsv_lower, hsv_upper) # 开运算去椒盐噪声,闭运算连通色块 kernel = np.ones((3,3), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 计算掩膜内白色像素占比(面积比) area_ratio = cv2.countNonZero(mask) / (roi_img.shape[0] * roi_img.shape[1]) return area_ratio > 0.3 # 占比超30%才判定为有效色块 # 调用示例(红灯检测) red_lower = np.array([0, 80, 50]) red_upper = np.array([10, 255, 255]) is_red_on = detect_light_color(roi_img, red_lower, red_upper)2.2.3 多色并行检测与状态冲突消解
同一时刻只允许一个方向灯亮(除黄灯过渡期),若某 ROI 同时满足红/绿阈值,需引入置信度排序:
- 优先级:红 > 黄 > 绿(因红灯最易被误检为黄,绿灯最易被误检为黄)
- 实现方式:对每个 ROI 计算
red_ratio,yellow_ratio,green_ratio,取最大值对应颜色,且要求该值 > 其他两值 0.15 以上,否则标记为UNKNOWN
3. 构建交通信号状态机:从单帧识别到跨帧状态稳定输出
3.1 为什么不能每帧都直接输出灯色?——抖动问题与状态持久化必要性
原始视频常有压缩伪影、云层飘过导致瞬时色偏,若每帧都触发状态变更,控制信号将高频抖动。解决方案是引入有限状态机(FSM)+ 时间窗口投票:
- 定义状态:
RED,GREEN,YELLOW,UNKNOWN - 每帧识别结果进入长度为 5 的 FIFO 队列(约 0.5 秒历史)
- 每次出队后,统计队列中各状态出现次数,取众数作为当前稳态
- 仅当稳态与上一稳态不同时,才更新
current_state并触发控制逻辑
from collections import deque class TrafficLightFSM: def __init__(self): self.state_history = deque(maxlen=5) # 5帧滑动窗口 self.current_state = "UNKNOWN" def update(self, frame_state): self.state_history.append(frame_state) # 统计频次,取最高频且占比>40%的状态 from collections import Counter counts = Counter(self.state_history) most_common, freq = counts.most_common(1)[0] if freq / len(self.state_history) > 0.4: if most_common != self.current_state: print(f"[STATE CHANGE] {self.current_state} → {most_common}") self.current_state = most_common self._on_state_change(most_common) def _on_state_change(self, new_state): # 此处扩展为串口发送、GPIO翻转、或写入控制日志 with open("light_control.log", "a") as f: f.write(f"{cv2.getTickCount()/cv2.getTickFrequency():.3f} | {new_state}\n")3.2 基于车流密度的动态配时算法:用背景差分法估算等待车辆数
高分毕设区别于静态定时器的核心,在于引入视觉反馈调节灯时。本方案采用轻量级背景差分(Background Subtraction)估算各方向等待车辆数:
3.2.1 为每个通行方向定义车流检测带(Traffic Flow Zone)
# config.py - 在ROI下方延伸出矩形检测带 FLOW_ZONES = { "north_approach": (150, 300, 100, 40), # x,y,w,h,覆盖直行车道入口 "east_approach": (400, 150, 40, 100), "south_approach": (250, 0, 100, 40), "west_approach": (0, 150, 40, 100) }3.2.2 用 MOG2 模型提取运动前景,统计像素连通域数量
bg_subtractor = cv2.createBackgroundSubtractorMOG2( history=500, varThreshold=16, detectShadows=False ) def count_vehicles_in_zone(frame, zone): x, y, w, h = zone roi = frame[y:y+h, x:x+w] fg_mask = bg_subtractor.apply(roi) # 二值化 + 形态学增强 _, thresh = cv2.threshold(fg_mask, 127, 255, cv2.THRESH_BINARY) kernel = np.ones((5,5), np.uint8) thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) # 查找连通域(每个连通域≈1辆车) num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(thresh, connectivity=8) # 过滤小面积噪点(<200像素) vehicle_count = sum(1 for i in range(1, num_labels) if stats[i, cv2.CC_STAT_AREA] > 200) return vehicle_count # 主循环中调用 for direction, zone in FLOW_ZONES.items(): vehicles = count_vehicles_in_zone(frame, zone) flow_buffer[direction].append(vehicles) # 取最近3秒平均值作为当前车流强度 avg_flow = int(np.mean(flow_buffer[direction][-30:])) # 30帧≈3秒3.3 动态配时策略:红灯延时与绿灯提前释放规则
| 当前灯色 | 检测到车流强度 | 控制动作 |
|---|---|---|
| 南北向红灯 | 东西向 avg_flow > 8 且南北向 < 2 | 南北红灯缩短 3 秒,东西绿灯提前 3 秒启动 |
| 东西向黄灯 | 南北向 avg_flow > 12 | 黄灯延长 1 秒,避免急刹 |
| 所有方向车流 < 3 | 进入低功耗模式 | 切换为 30 秒循环:绿→黄→红→黄(无车时黄灯仅 1 秒) |
注意:配时调整需加软限幅——单次最多增减 5 秒,防止突变;总周期严格控制在 90~120 秒内,符合国标《GB 14886-2019》对城市路口周期的要求。
4. 串口通信与 GPIO 控制输出:将 OpenCV 识别结果转化为物理信号
4.1 用 pyserial 发送标准化控制指令帧
为兼容后续接入 PLC 或单片机,定义 ASCII 协议帧格式:[STX][DIR][STATE][DURATION][ETX]
STX=0x02(ASCII SOH)DIR=N(北)、S(南)、E(东)、W(西)、P(行人)STATE=R(红)、G(绿)、Y(黄)DURATION= 2 字节十进制数(如30表示 30 秒)ETX=0x03(ASCII ETX)
import serial import time ser = serial.Serial( port='/dev/ttyUSB0', # Linux;Windows 用 'COM3' baudrate=9600, timeout=1, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, parity=serial.PARITY_NONE ) def send_light_command(direction, state, duration): frame = f"\x02{direction}{state}{duration:02d}\x03" ser.write(frame.encode('ascii')) # 等待设备返回 ACK(可选) ack = ser.read(1) if ack == b'\x06': print(f"ACK received for {frame.strip()}") else: print("No ACK, retry or log error") # 示例:南向绿灯亮 25 秒 send_light_command('S', 'G', 25)4.2 树莓派 GPIO 模拟输出:用 BCM 编号控制 LED 灯组
若无串口设备,可用树莓派 GPIO 直接驱动 LED 模拟红绿灯:
| GPIO 引脚 | 功能 | BCM 编号 |
|---|---|---|
| GPIO17 | 南北红灯 | 17 |
| GPIO27 | 南北绿灯 | 27 |
| GPIO22 | 南北黄灯 | 22 |
| GPIO10 | 东西红灯 | 10 |
| GPIO9 | 东西绿灯 | 9 |
| GPIO11 | 东西黄灯 | 11 |
import RPi.GPIO as GPIO # 初始化 GPIO GPIO.setmode(GPIO.BCM) LED_PINS = { "NS_RED": 17, "NS_GREEN": 27, "NS_YELLOW": 22, "EW_RED": 10, "EW_GREEN": 9, "EW_YELLOW": 11 } for pin in LED_PINS.values(): GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.LOW) # 默认灭 def set_light_state(ns_state, ew_state): # 先全灭 for pin in LED_PINS.values(): GPIO.output(pin, GPIO.LOW) # 再按状态点亮 state_to_pin = {"R": "_RED", "G": "_GREEN", "Y": "_YELLOW"} ns_pin = LED_PINS["NS" + state_to_pin[ns_state]] ew_pin = LED_PINS["EW" + state_to_pin[ew_state]] GPIO.output(ns_pin, GPIO.HIGH) GPIO.output(ew_pin, GPIO.HIGH) # 主循环中调用 set_light_state("R", "G") # 南北红,东西绿4.3 关键参数表:OpenCV 与控制模块的协同配置项
| 参数名 | 文件位置 | 默认值 | 说明 | 调试建议 |
|---|---|---|---|---|
CAMERA_INDEX | main.py | 0 | 视频源索引,-1 为文件路径 | 用cv2.VideoCapture(0)测试摄像头是否正常 |
HSV_RED_LOWER | config.py | [0,80,50] | 红灯 HSV 下界 | 在debug_hsv_tuner.py中拖动滑块实时调整 |
MIN_VEHICLE_AREA | traffic_flow.py | 200 | 连通域最小像素面积 | 高清摄像头可设为 300,标清设为 150 |
SERIAL_PORT | control_output.py | /dev/ttyUSB0 | 串口设备路径 | ls /dev/tty*查看可用端口 |
GPIO_MODE | gpio_output.py | "BCM" | GPIO 编号模式 | 树莓派必须用 BCM,Jetson 用 TEGRA |
5. 毕设答辩必验的 3 个硬核技巧:让评委一眼看到工程深度
5.1 用 OpenCV 的cv2.putText()实时叠加多维度诊断信息
在输出画面右上角动态显示:当前识别状态、各方向车流数、剩余绿灯秒数、FPS。这不是炫技,而是证明系统具备实时监控能力:
def draw_debug_info(frame, state, flow_dict, remaining_sec, fps): font = cv2.FONT_HERSHEY_SIMPLEX # 状态行 cv2.putText(frame, f"NS: {state['NS']}", (10, 30), font, 0.6, (0,255,0), 1) cv2.putText(frame, f"EW: {state['EW']}", (10, 60), font, 0.6, (0,255,0), 1) # 车流行 cv2.putText(frame, f"Flow N:{flow_dict['north']}", (10, 90), font, 0.5, (255,165,0), 1) cv2.putText(frame, f"Flow E:{flow_dict['east']}", (10, 110), font, 0.5, (255,165,0), 1) # 倒计时与性能 cv2.putText(frame, f"Green Left: {remaining_sec}s", (10, 140), font, 0.6, (0,191,255), 1) cv2.putText(frame, f"FPS: {fps:.1f}", (10, 170), font, 0.6, (255,0,255), 1) return frame # 主循环中调用 frame = draw_debug_info(frame, current_states, flow_counts, green_remaining, fps) cv2.imshow("Traffic Control Monitor", frame)5.2 生成可回放的控制日志:用 CSV 记录每一秒的决策依据
评委最关心“你凭什么这么调?”——日志必须包含原始输入与决策链:
| timestamp | ns_light | ew_light | north_flow | east_flow | decision_reason | green_duration |
|---|---|---|---|---|---|---|
| 1712345678.123 | R | G | 5 | 12 | "EW flow > NS, extend EW green by 3s" | 28 |
import csv import time log_file = "control_decision_log.csv" with open(log_file, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["timestamp", "ns_light", "ew_light", "north_flow", "east_flow", "decision_reason", "green_duration"]) # 在每次配时调整后写入 with open(log_file, "a", newline="") as f: writer = csv.writer(f) writer.writerow([ time.time(), ns_state, ew_state, flow_counts["north"], flow_counts["east"], reason_str, current_green_duration ])5.3 设计故障注入测试:验证系统在极端条件下的鲁棒性
高分毕设必须展示容错能力。手动制造三类故障并观察系统行为:
| 故障类型 | 注入方式 | 期望响应 | 验证方法 |
|---|---|---|---|
| 摄像头遮挡 | 用黑布盖住镜头 5 秒 | 自动切换至“最后稳态”并告警 | 日志中出现WARNING: No valid frame for 5s |
| 强光眩光 | 用手电筒直射镜头 | HSV 阈值自动收缩(S/V 下限提升) | debug_hsv_tuner.py中观察掩膜面积骤降后恢复 |
| 车流突增 | 快速放入 10 个玩具车模型 | 绿灯延长 + 黄灯缓冲启动 | 日志中decision_reason显示“emergency extension” |
提示:答辩时打开
control_decision_log.csv和light_control.log,现场滚动播放日志,比口头描述更有说服力。
本文还有配套的精品资源,点击获取