1. 项目概述为色觉障碍者点亮一盏“信号灯”你有没有想过每天过马路时那个决定我们“走”还是“停”的交通信号灯对一部分人来说可能是一个充满不确定性的模糊色块这不是一个遥远的故事而是全球数亿色觉障碍者常被称为色盲或色弱每天都要面对的日常挑战。红绿灯这个看似简单的三色系统却构成了他们独立出行中一个潜在的安全隐患。我身边就有这样的朋友他描述过那种在路口犹豫不决的焦虑远处的灯光是偏橙还是偏红是黄灯在闪烁还是绿灯在亮这种不确定性让每一次过马路都像一次小小的冒险。这个项目正是为了解决这个具体而微小的痛点为色觉障碍者设计一款能够辅助识别交通信号灯状态的便携设备。它不是一个复杂的医疗仪器也不是要改变现有的交通设施而是一个以人为本、聚焦于“信息转换”的辅助工具。其核心目标非常明确将色觉障碍者难以准确分辨的颜色信息红、黄、绿转化为他们能够清晰、快速、无歧义理解的其他感官信号比如声音提示、振动反馈或者明确的文字/图形显示。想象一下一位色觉障碍者佩戴或手持这个设备走在街头。当设备摄像头捕捉到前方的交通信号灯时它内部的处理器会实时分析灯光颜色和状态然后通过耳机告诉他“红灯请等待”或者通过手腕上的振动器发出“长振动-停止短振动-通行”的编码信号。这不仅仅是技术上的一个小创新更是科技向善、填补“感知鸿沟”的一次具体实践。它关乎尊严、独立与安全让科技真正服务于人的多样化需求。2. 核心设计思路与方案选型2.1 需求拆解不止于“识别颜色”在设计之初我们必须超越简单的“颜色识别”深入理解用户在实际场景中的核心需求实时性与低延迟交通场景瞬息万变设备的处理速度必须足够快从捕捉图像到给出反馈延迟应控制在毫秒级确保提示与信号灯变化基本同步。高准确性与抗干扰系统必须在复杂的城市环境中可靠工作。这意味着它需要能有效区分交通信号灯的红光与夕阳、汽车尾灯、广告牌区分绿灯与树叶反射的绿光、某些LED屏幕。准确率必须接近100%任何误判都可能带来安全风险。多状态识别不仅要识别红、黄、绿三种颜色还要识别状态常亮、闪烁特别是黄闪和红闪、箭头灯、倒计时数字灯等。不同的状态对应不同的通行规则。非视觉交互反馈既然用户存在色觉障碍反馈通道必须避开对颜色依赖的视觉显示除非结合文字。主流方案是听觉语音和触觉振动这两种方式不干扰用户观察路况且感知直接。便携与易用性设备需要小巧、轻便、续航持久佩戴或操作方式符合日常习惯如可夹在口袋、挂在脖子上、戴在手腕上。交互应极其简单最好是开机即用无需复杂设置。环境适应性能在白天强光、夜晚昏暗、雨雾天气等不同光照条件下稳定工作。基于以上需求一个典型的方案架构浮出水面“图像采集 - 信号灯检测与定位 - 颜色与状态识别 - 多模态反馈”的流水线。接下来我们需要为每个环节选择合适的技术路径。2.2 硬件平台选型平衡性能、功耗与成本硬件是项目的基石需要在计算能力、功耗、尺寸和成本之间找到最佳平衡点。核心处理器方案A专用微控制器MCU如ESP32系列。优点是功耗极低、成本低廉、开发简单内置Wi-Fi/蓝牙便于后续扩展。但纯MCU进行实时图像处理尤其是目标检测能力非常有限通常只能运行简单的颜色阈值算法在复杂场景下准确率堪忧。方案B嵌入式AI计算平台如树莓派Raspberry Pi搭配AI加速棒如Intel Neural Compute Stick 2、Jetson Nano、或高通骁龙开发板。这些平台拥有更强的通用计算能力或专用的神经网络处理单元NPU可以运行更复杂的深度学习模型实现高精度的信号灯检测与识别。这是目前的主流选择虽然功耗和成本高于MCU但带来的准确性提升是质的飞跃。我们的选择基于树莓派4B/CM4的方案。树莓派生态成熟社区支持强大有丰富的摄像头和外设支持。其CPU性能足以运行轻量化的深度学习模型。对于更极致的功耗和体积要求未来可迁移到Jetson Nano或地平线、瑞芯微等带NPU的国产AIoT平台。图像传感器普通RGB摄像头即可但建议选择低照度性能较好的型号如索尼IMX系列传感器。分辨率无需太高1080p足够高帧率如30fps以上对实时性更有帮助。需要考虑广角镜头以覆盖更宽的视野方便用户无需精确对准信号灯。一个重要考量是否需要加入红外IR或深度传感器对于极端天气浓雾或夜间红外可能有助于穿透。但考虑到成本和复杂度初期可以不加优先优化算法在可见光下的鲁棒性。反馈模块听觉反馈一个小型扬声器或更优的——骨传导耳机接口。骨传导耳机不堵塞耳道能让用户同时听到环境音和设备提示对出行安全至关重要。触觉反馈线性振动马达LRA。可以编码不同的振动模式如长短、节奏、强度来对应不同的信号灯状态直观且私密。视觉辅助反馈一个小型OLED屏幕用于显示文字“RED”、“GREEN”或简单图标。这对色觉障碍者仍有价值尤其是那些能分辨亮度但无法分辨色相的用户。电源大容量锂电池如5000mAh以上搭配高效的电源管理模块确保全天续航。支持USB-C充电。实操心得硬件选型的权衡在原型阶段强烈建议从树莓派开始。它的灵活性让你可以快速验证算法和交互逻辑。过早追求极致的低功耗和小型化如直接用MCU可能会在算法开发阶段遇到难以逾越的瓶颈。先让系统“跑起来”且“跑得准”再考虑如何让它“跑得更省、更小”。2.3 软件算法核心从“找颜色”到“找灯”这是项目的技术心脏。传统方法可能直接在全图像中搜索特定HSV颜色范围的像素块但这种方法极其脆弱容易受干扰。现代、可靠的方法是采用基于深度学习的目标检测模型分两步走交通信号灯检测让模型学会“什么是交通信号灯”不管它是什么颜色、什么状态。模型的任务是在图像中定位出信号灯的位置画出边界框。这解决了“在哪里”的问题。状态分类在检测到的信号灯区域ROI内再进行细粒度的分类是红灯、绿灯、黄灯是圆形还是箭头是否在闪烁这解决了“是什么”的问题。为什么选择深度学习而不是传统视觉鲁棒性深度学习模型能学习到信号灯的深层特征形状、结构、上下文环境而不仅仅是颜色。即使灯罩脏了、部分LED灯珠不亮、或者有强光反射模型依然有很高概率识别出来。上下文理解模型能利用周围环境信息如灯杆、安装位置辅助判断减少将远处红色广告牌误判为红灯的可能。端到端优化可以训练一个模型同时完成检测和分类提升效率。模型选型建议轻量化模型是关键考虑到要在树莓派上实时运行目标10fps必须选择计算量小的模型。推荐YOLOv5s / YOLOv8nYOLO系列是单阶段检测的标杆v5和v8的nano或small版本在精度和速度上取得了很好的平衡非常适合嵌入式部署。MobileNet-SSD或EfficientDet-Lite专为移动和嵌入式设备设计的网络效率极高。数据数据还是数据模型的性能取决于训练数据。你需要收集或创建一个包含各种场景白天、夜晚、雨天、雪天、不同角度、不同国家灯型下的交通信号灯图像数据集并精细标注。开源数据集如TT100K、DriveU可以作为一个起点但很可能需要针对你的具体使用环境进行补充和微调。3. 系统实现与核心环节拆解3.1 开发环境搭建与模型训练假设我们选择树莓派4B作为硬件平台使用YOLOv8进行开发。步骤1搭建训练环境在更强大的PC或云端进行# 创建Python虚拟环境 python -m venv traffic_light_env source traffic_light_env/bin/activate # Linux/macOS # traffic_light_env\Scripts\activate # Windows # 安装PyTorch (根据CUDA版本选择) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装Ultralytics YOLOv8 pip install ultralytics步骤2准备数据集将收集到的图片按YOLO格式组织。YOLO格式的标注文件.txt与图片同名内容如0 0.5 0.5 0.1 0.2 # 类别id0代表交通灯 中心点x 中心点y 框宽度 框高度均为归一化坐标你需要一个data.yaml文件来定义数据集path: /path/to/your/dataset train: images/train val: images/val # 类别列表 names: 0: traffic_light步骤3模型训练from ultralytics import YOLO # 加载一个预训练的YOLOv8n模型在COCO等大数据集上训练过有基础特征提取能力 model YOLO(yolov8n.pt) # 开始训练 results model.train( datadata.yaml, epochs100, imgsz640, batch16, device0, # 使用GPU projecttraffic_light_detection, nameexp1 )训练过程会输出损失曲线、精度mAP等指标。你需要关注模型在验证集上的表现防止过拟合。步骤4模型导出与优化训练完成后将模型导出为适合树莓派部署的格式。ONNX或TensorRT格式通常能获得更好的推理速度但PyTorch.pt文件也可以直接用于Ultralytics的推理。# 导出为ONNX格式 model.export(formatonnx)3.2 树莓派端部署与推理优化将训练好的模型如best.onnx和推理代码部署到树莓派。步骤1树莓派环境准备# 更新系统 sudo apt update sudo apt upgrade -y # 安装Python和必要库 sudo apt install python3-pip python3-venv # 安装OpenCV用于图像采集和预处理 sudo apt install python3-opencv # 安装ONNX Runtime用于运行ONNX模型比直接PyTorch轻量 pip install onnxruntime步骤2编写核心推理脚本import cv2 import onnxruntime as ort import numpy as np from collections import deque import time class TrafficLightDetector: def __init__(self, model_path, conf_threshold0.6): # 初始化ONNX Runtime会话 self.session ort.InferenceSession(model_path) self.input_name self.session.get_inputs()[0].name self.conf_threshold conf_threshold # 用于闪烁检测的帧缓存记录最近几帧的状态 self.state_history deque(maxlen10) self.current_state UNKNOWN def preprocess(self, image): 将摄像头图像预处理为模型输入格式 # 调整大小为模型输入尺寸如640x640 img_resized cv2.resize(image, (640, 640)) # 转换颜色通道 BGR - RGB img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) # 归一化到 [0, 1] 并转换为CHW格式 img_normalized img_rgb / 255.0 img_chw np.transpose(img_normalized, (2, 0, 1)) # 添加批次维度 img_batch np.expand_dims(img_chw, axis0).astype(np.float32) return img_batch def detect(self, frame): 执行检测 input_tensor self.preprocess(frame) outputs self.session.run(None, {self.input_name: input_tensor}) # 解析YOLO输出这里需要根据你的模型输出结构调整 # outputs[0] 通常是形状为 [1, num_boxes, 85] 的张量 predictions outputs[0][0] # 应用置信度阈值和非极大值抑制NMS过滤框 boxes self._postprocess(predictions, frame.shape) return boxes def _postprocess(self, predictions, orig_shape): 后处理过滤低置信度框映射坐标回原图 # 实现细节过滤置信度低于阈值的预测执行NMS将归一化坐标转换回原图坐标 # 此处省略具体实现可使用常规的NMS算法 processed_boxes [] # ... 处理逻辑 ... return processed_boxes def determine_state(self, roi_image): 在检测到的ROI内判断信号灯状态颜色、是否闪烁 # 方法1基于颜色阈值简单快速作为备用或初级方案 hsv cv2.cvtColor(roi_image, cv2.COLOR_BGR2HSV) # 定义红、绿、黄的大致HSV范围注意红色在HSV环的两端 red_lower1 np.array([0, 70, 50]) red_upper1 np.array([10, 255, 255]) red_lower2 np.array([170, 70, 50]) red_upper2 np.array([180, 255, 255]) green_lower np.array([40, 70, 50]) green_upper np.array([85, 255, 255]) yellow_lower np.array([20, 70, 50]) yellow_upper np.array([40, 255, 255]) # 创建掩膜并计算像素数量 red_mask cv2.inRange(hsv, red_lower1, red_upper1) cv2.inRange(hsv, red_lower2, red_upper2) green_mask cv2.inRange(hsv, green_lower, green_upper) yellow_mask cv2.inRange(hsv, yellow_lower, yellow_upper) red_pixels cv2.countNonZero(red_mask) green_pixels cv2.countNonZero(green_mask) yellow_pixels cv2.countNonZero(yellow_mask) # 方法2更优使用第二个轻量级CNN分类器对ROI进行分类 # 这里先展示阈值法的逻辑 total_pixels roi_image.shape[0] * roi_image.shape[1] threshold_ratio 0.05 # 当该颜色像素占比超过5%时认为有效 state UNKNOWN if red_pixels / total_pixels threshold_ratio: state RED elif green_pixels / total_pixels threshold_ratio: state GREEN elif yellow_pixels / total_pixels threshold_ratio: state YELLOW # 闪烁检测通过分析state_history中状态的变化频率 self.state_history.append(state) if len(self.state_history) self.state_history.maxlen: # 简单逻辑如果历史记录中频繁在某个状态和UNKNOWN/其他状态间切换则可能是闪烁 if state in [RED, YELLOW] and self.state_history.count(UNKNOWN) 3: state f{state}_FLASHING self.current_state state return state # 主循环 def main(): cap cv2.VideoCapture(0) # 打开摄像头 detector TrafficLightDetector(best.onnx) while True: ret, frame cap.read() if not ret: break boxes detector.detect(frame) for box in boxes: # box: [x1, y1, x2, y2, conf, cls] x1, y1, x2, y2 map(int, box[:4]) roi frame[y1:y2, x1:x2] if roi.size 0: continue state detector.determine_state(roi) # 根据state触发反馈 trigger_feedback(state) # 在图像上绘制框和状态用于调试 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, state, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,255,0), 2) # 显示调试窗口实际设备上可关闭以节省资源 cv2.imshow(Traffic Light Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() def trigger_feedback(state): 根据识别状态触发多模态反馈 feedback_map { RED: {voice: 红灯请等待, vibration: long_pulse, display: STOP}, GREEN: {voice: 绿灯可以通行, vibration: double_short_pulse, display: GO}, YELLOW: {voice: 黄灯请准备, vibration: short_pulse, display: CAUTION}, RED_FLASHING: {voice: 红灯闪烁确认安全后通行, vibration: rapid_pulse, display: FLASH RED}, YELLOW_FLASHING: {voice: 黄灯闪烁注意观察, vibration: rapid_pulse, display: FLASH YELLOW}, } feedback feedback_map.get(state, {voice: 状态未知, vibration: off, display: ???}) # 这里调用具体的硬件驱动 # 例如play_audio(feedback[voice]), set_vibration(feedback[vibration]), update_display(feedback[display]) print(f反馈: {feedback}) # 调试输出 if __name__ __main__: main()3.3 多模态反馈系统的实现反馈系统是用户直接交互的界面其稳定性和体验至关重要。听觉反馈实现语音合成TTS对于固定的几条提示语可以预先录制高质量的音频文件.wav格式在需要时直接播放。这比实时TTS延迟更低、音质更可控。可以使用pygame或pyaudio库播放音频。import pygame pygame.mixer.init() sound_red pygame.mixer.Sound(red.wav) def play_audio(state): if state RED: sound_red.play()骨传导耳机只需将音频输出连接到骨传导耳机即可。注意音量调节确保在嘈杂环境中也能听清同时避免音量过大。触觉反馈实现通过树莓派的GPIO控制一个振动马达驱动电路如使用MOSFET管。可以定义不同的PWM信号模式来驱动LRA马达产生不同的振动感觉。import RPi.GPIO as GPIO import time VIBRATION_PIN 18 GPIO.setmode(GPIO.BCM) GPIO.setup(VIBRATION_PIN, GPIO.OUT) pwm GPIO.PWM(VIBRATION_PIN, 200) # 200Hz频率 def vibrate(pattern): if pattern long_pulse: pwm.start(50) # 50%占空比 time.sleep(0.8) pwm.stop() elif pattern double_short_pulse: for _ in range(2): pwm.start(50) time.sleep(0.2) pwm.stop() time.sleep(0.1)视觉辅助反馈使用一个小型OLED屏幕如SSD1306驱动的0.96寸屏通过I2C接口连接树莓派。使用luma.oled库来显示文字或简单图形。from luma.core.interface.serial import i2c from luma.oled.device import ssd1306 from luma.core.render import canvas serial i2c(port1, address0x3C) device ssd1306(serial) with canvas(device) as draw: draw.text((10, 20), GO, fillwhite)注意事项反馈设计的用户体验避免信息过载反馈应简洁、明确。语音提示不宜过长振动模式应易于区分。优先级与冲突处理当信号灯状态快速变化时反馈系统应有去抖动机制避免语音重叠或振动混乱。可以设置一个最小状态保持时间如0.5秒才触发新反馈。可定制性允许用户选择偏好的反馈模式如仅振动、仅语音、或混合并调节音量/振动强度。4. 工程优化与挑战应对4.1 提升识别鲁棒性的实战技巧在实际街道测试中你会遇到无数训练数据里没有的 corner case。以下是一些提升系统鲁棒性的关键点动态曝光与白平衡交通信号灯在夜晚是极亮的光源在白天则是相对较暗的物体。固定参数的摄像头要么在夜晚过曝信号灯变成一片白色光晕要么在白天欠曝看不清灯框。解决方案是使用支持手动或自动曝光的摄像头库如picamera2库。实现一个简单的自动曝光策略检测图像整体亮度如果过亮则降低曝光值反之则增加。更高级的做法是在检测到疑似信号灯的区域后针对该区域进行点测光。# 使用picamera2的示例 from picamera2 import Picamera2 picam2 Picamera2() # 配置预览窗口并设置自动曝光模式 preview_config picam2.create_preview_configuration(main{size: (640, 480)}) picam2.configure(preview_config) picam2.start() # 可以在这里根据图像分析结果动态调整 controls # picam2.set_controls({ExposureTime: 10000, AnalogueGain: 1.0})多灯与远距离识别一个路口可能有多个信号灯组机动车、行人、左转。需要算法能同时检测多个目标并通过逻辑判断哪个是与用户相关的例如通过位置和角度推测。对于远距离小目标需要确保训练数据中包含足够多的小尺度信号灯样本并在模型设计时使用更适合小目标检测的 Neck 和 Head 结构如YOLOv8的 P5 模型。状态闪烁的可靠判断闪烁检测不能只靠单帧颜色分析。需要结合时序信息。一个稳健的方法是连续检测多帧如10帧约0.3秒。统计在这段时间内被判定为“红灯”或“黄灯”的帧数占总帧数的比例。如果比例处于一个中间范围例如30%-70%且状态在“亮”和“暗”或“未知”间有规律切换则判定为闪烁。如果比例很高90%则是常亮比例很低10%则是熄灭或其他干扰。4.2 功耗优化与续航提升要让设备真正“便携”续航是硬指标。硬件层面选择低功耗的核心板如树莓派 Zero 2 W但性能会下降需做模型深度优化。使用高效率的DC-DC降压模块为整个系统供电减少转换损耗。选择低功耗的摄像头模组和OLED屏幕。振动马达和扬声器只在需要时供电。软件与策略层面间歇性工作行人并非每时每刻都需要检测信号灯。可以加入一个简单的“激活”机制例如物理按钮用户快到路口时按一下设备开始工作30秒。运动传感器通过加速度计检测用户是否在行走行走时才启动核心识别模块。声音触发检测到十字路口特有的环境音如车流声变化时唤醒。动态频率调整在未检测到信号灯时降低检测频率如从30fps降至5fps。一旦检测到立刻恢复到高频率进行精确状态跟踪。CPU调频树莓派上可以使用cpufreq工具将CPU工作频率设置在满足实时性要求的最低档位。关闭不必要的服务和外设关闭HDMI输出、蓝牙如果不用于反馈、Wi-Fi如果不用于更新等。4.3 常见问题与排查实录在实际开发中你几乎一定会遇到以下问题问题1误检率高把红色汽车尾灯、广告牌也当成信号灯。排查与解决检查训练数据数据集中是否包含了足够多的负样本看起来像但不是信号灯的物体是否包含了夜间尾灯、红色招牌的图片调整模型置信度阈值提高conf_threshold如从0.5提高到0.7或0.8。但这可能会漏检一些远处的、不太清晰的信号灯。加入后处理逻辑利用信号灯的先验知识。例如真正的交通信号灯通常有特定的长宽比竖立的长方形且通常出现在图像的上半部分天空方向。可以在检测后根据框的宽高比和位置进行过滤。使用更复杂的模型如果轻量化模型能力不足考虑稍微大一点的模型如YOLOv8s并配合更强的硬件。问题2夜间或逆光环境下信号灯区域过曝颜色信息丢失。排查与解决启用摄像头的HDR或WDR模式如果支持。这能扩展动态范围让亮部不过曝暗部有细节。实现区域曝光如前所述针对检测到的候选区域进行曝光补偿。算法增强在颜色判断失效时可以启用备用方案。例如如果检测到一个高亮区域但颜色无法判断可以结合其形状圆形和闪烁频率如果是闪烁灯来综合推断。或者在过曝时尝试识别灯罩的黑色轮廓。问题3设备反应延迟明显感觉“慢半拍”。排查与解决性能分析使用Python的cProfile或line_profiler工具找出代码中的性能瓶颈。是图像预处理慢模型推理慢还是反馈输出慢优化推理将模型转换为TensorRT或OpenVINO格式并利用其针对硬件如树莓派的ARM CPU的优化。使用多线程一个线程专门负责图像采集和预处理另一个线程负责模型推理和反馈通过队列传递数据。降低输入分辨率尝试将模型输入尺寸从640x640降到480x480或320x320速度会显著提升但可能影响小目标检测精度需要权衡。简化反馈流程预加载音频文件到内存避免每次从SD卡读取。使用硬件PWM控制振动比软件模拟更高效。问题4振动反馈模式用户难以区分。排查与解决进行用户测试这是最重要的。找几位色觉障碍者或普通用户盲测几种不同的振动模式长短、节奏、强度组合记录他们的识别准确率和主观感受。差异化设计让不同状态的振动模式在节奏和强度上有显著区别。例如“红灯”用强烈、持续的长振动“绿灯”用两次轻快的短振动“黄灯”用一次中等长度的振动。避免只改变节奏而强度相同。提供学习模式在设备设置中让用户可以逐一体验并记忆每种振动模式对应的含义。这个项目从创意到实现是一个典型的嵌入式AI应用开发过程涉及硬件集成、计算机视觉、机器学习模型部署、交互设计等多个领域。它最大的价值不在于用了多么高深的技术而在于精准地定义了一个真实世界的痛点并用一套切实可行的技术方案去解决它。当你看到第一个原型机成功在路口识别出红灯并发出清晰的语音提示时那种成就感是无可比拟的。这不仅仅是一个毕业设计或业余项目它是一次让技术充满温度的实践。