基于行空板与K210的红绿灯检测系统:从模型训练到嵌入式部署全流程
1. 项目概述:当行空板遇上红绿灯检测
最近在捣鼓一个挺有意思的项目,用行空板做了一个红绿灯检测系统。听起来是不是有点“大材小用”?一块主打AI和物联网的教育板子,去识别红绿灯?但恰恰是这种跨界组合,让我发现了很多在纯软件仿真或高端硬件平台上体会不到的乐趣和挑战。这个项目的核心,就是利用行空板内置的K210 AI芯片和摄像头,在真实的路口环境中,实时识别出交通信号灯的状态(红灯、绿灯、黄灯),并可以通过板载的屏幕显示结果,或者通过网络将状态信息发送出去。
这不仅仅是一个简单的图像分类任务。在真实世界做检测,你得考虑光照变化(清晨、正午、黄昏、夜晚)、天气影响(雨雾天气灯光会晕开)、遮挡问题(被树枝或部分车辆遮挡),以及最重要的——实时性。行空板的算力有限,如何在资源受限的条件下,跑出一个又快又准的模型,才是这个项目真正的“硬核”所在。它非常适合对嵌入式AI、边缘计算感兴趣的朋友,无论是学生用于课程设计、科创比赛,还是开发者做轻量级物联网应用原型验证,都能从中获得从数据采集、模型训练到部署落地的完整经验。
2. 系统整体设计与核心思路拆解
2.1 为什么选择行空板?
市面上能做AI视觉的开发板很多,树莓派加个USB摄像头跑OpenCV是常见方案。但我选择行空板,主要基于几个关键考量:
All-in-One的集成度:行空板直接把K210 AI芯片、摄像头、LCD屏幕、Wi-Fi/蓝牙、多种传感器和GPIO口做到了一块板子上。这意味着我们不需要额外连接摄像头、屏幕,省去了大量的硬件接线和驱动调试工作,让开发者能更专注于算法和应用逻辑本身。对于红绿灯检测这种需要“眼睛”(摄像头)、“大脑”(AI芯片)和“脸面”(屏幕显示)的应用,开箱即用的体验非常好。
低功耗与实时性:K210芯片是一款专为边缘AI设计的芯片,主打低功耗和实时推理。相比在树莓派上运行通用的深度学习框架(如TensorFlow Lite),K210通过其KPU(神经网络处理器)对模型进行硬件加速,推理速度更快,功耗更低。这对于需要长时间在线、可能由电池供电的户外或移动检测场景(比如安装在小型巡检车上)来说,是一个巨大优势。
开发环境友好:行空板配套的Mind+或Python编程环境,对初学者和快速原型开发非常友好。特别是其封装的AI模块,让我们可以用很少的代码就调用摄像头、加载模型、进行推理,大大降低了嵌入式AI的门槛。
2.2 红绿灯检测的技术路线选择
红绿灯检测本质上是一个特定的目标检测任务。我们有几个技术路线可选:
- 传统图像处理(颜色分割+形状识别):在HSV或YCrCb颜色空间分割出红色、绿色、黄色区域,然后通过轮廓查找、形状匹配(圆形)来判断。这种方法计算量小,在光照恒定、背景简单的环境下效果尚可。但致命缺点是鲁棒性极差,傍晚天空的红色、绿色广告牌、车尾灯等都极易造成误检,且无法处理灯光点亮(圆形区域为黑色)和熄灭(圆形区域为灰色)的状态。
- 基于深度学习的目标检测:这是当前的主流和更可靠的方法。我们训练一个目标检测模型(如YOLO、SSD的轻量级变种),直接让模型学会从图像中定位(Bounding Box)并识别(Classification)出“红灯”、“绿灯”、“黄灯”以及“非灯”或“熄灭”状态。这种方法能更好地学习红绿灯在各种复杂场景下的特征,抗干扰能力强。
在这个项目中,我毫不犹豫地选择了深度学习路线。虽然模型训练需要数据和工作量,但一旦完成,系统的可靠性和泛化能力是传统方法无法比拟的。我们的目标是在行空板有限的算力上,部署一个足够轻量、足够快的目标检测模型。
2.3 系统工作流程设计
整个系统的运行流程可以清晰地分为几个阶段:
- 图像采集:行空板上的摄像头模块周期性(例如每秒5-10帧)捕获前方道路图像。
- 预处理:对采集到的图像进行缩放、归一化等操作,调整到模型需要的输入尺寸(如224x224)。
- AI推理:预处理后的图像数据送入K210的KPU,加载好的目标检测模型进行前向推理,输出预测结果(包含边界框坐标、类别置信度和类别标签)。
- 后处理:对模型的原始输出进行过滤,例如应用非极大值抑制(NMS)去除重叠的冗余框,只保留置信度高于阈值(如0.6)的检测结果。
- 结果输出与决策:
- 本地显示:在行空板的LCD屏幕上,实时绘制摄像头画面,并用矩形框和文字标出检测到的红绿灯及其状态。
- 状态判断:对于一个路口可能存在的多个灯(如直行灯、左转灯),系统可以设定规则,例如只关注图像中特定区域(ROI)的灯,或根据框的大小、位置判断哪个是主信号灯。
- 网络上报:通过Wi-Fi,将当前检测到的主信号灯状态(“红”、“绿”、“黄”、“未知”)以JSON格式(如
{“status”: “red”, “confidence”: 0.95})发送到指定的服务器或物联网平台,供上层应用(如车辆控制模拟、交通数据统计)使用。 - 本地交互:可以通过板载按钮,切换检测模式、拍照保存困难样本用于后续模型优化等。
3. 模型训练与优化:打造行空板专属的“火眼金睛”
3.1 数据采集与标注:一切的基础
模型的性能上限很大程度上由数据决定。对于红绿灯检测,我们需要大量包含各种红绿灯的图像。
- 数据来源:
- 公开数据集:如BDD100K、TT100K等自动驾驶数据集包含红绿灯标注,但数据场景、红绿灯样式可能与本地有差异。
- 网络爬取:从搜索引擎、视频网站截取相关图片,需注意版权。
- 实地拍摄(推荐):这是获取最匹配数据的方式。拿着行空板(或手机),在你希望系统部署的环境附近(如小区门口、学校周边路口),在不同时间(早中晚)、不同天气(晴雨阴)、不同角度拍摄红绿灯照片。重点拍摄一些困难样本:部分遮挡的、逆光的、远处模糊的、与相似颜色物体并存的。
- 数据标注:使用标注工具(如LabelImg、Makesense.ai)对图片中的每一个红绿灯进行标注。关键点:类别不要只设“traffic_light”,而应细分为“red_light”, “green_light”, “yellow_light”。如果还想区分灯是亮还是灭,可以进一步增加“red_light_on”, “red_light_off”等类别。标注框应紧密包围整个发光区域(圆形或箭头形)。
- 数据增强:为了提升模型鲁棒性,必须对训练数据进行增强。包括:随机亮度、对比度调整(模拟不同光照)、添加高斯噪声(模拟传感器噪声)、随机缩放平移(模拟距离和角度变化)。注意:谨慎使用颜色空间变换(如色相调整),这可能会改变红绿灯的本质颜色特征,导致模型学习到错误信息。
实操心得:数据标注是体力活,但至关重要。建议初期至少准备500-1000张高质量标注图片。一个技巧是,可以先用一个在公开数据集上预训练的模型对你的实地拍摄图片进行初步检测,然后人工修正错误的标注框和标签,这能大大提升标注效率。
3.2 模型选型与训练:在精度与速度间寻找平衡
行空板K210的KPU对模型有特定支持和要求(如层类型、量化方式)。通常,我们需要选择或设计一个非常轻量化的目标检测网络。
- 模型选择:
- YOLO系列:YOLOv5n, YOLOv8n 是非常流行的轻量级选择,社区支持好,易于训练和导出。
- 专为边缘设备设计的模型:如MobileNet-SSD, PeleeNet, NanoDet。这些模型参数量更少,速度更快。
- 行空板社区模型:关注行空板官方或社区是否提供了针对K210优化过的目标检测模型架构,这通常是最省事的起点。
- 训练框架:在PC端,使用PyTorch或TensorFlow/Keras进行模型训练。训练时,将数据集按比例(如8:1:1)划分为训练集、验证集和测试集。
- 训练技巧:
- 迁移学习:使用在COCO或VOC等大型数据集上预训练的模型权重作为起点,进行微调(Fine-tuning)。这能加速收敛并提升最终性能。
- 输入尺寸:为了速度,尽量使用较小的输入尺寸,如224x224或320x320。这需要在训练和推理时保持一致。
- 损失函数:关注边界框回归损失和分类损失的变化,确保模型同时在学习定位和分类。
3.3 模型转换与量化:让模型在K210上飞起来
训练好的PyTorch(.pt)或TensorFlow(.h5)模型不能直接在K210上运行,必须经过转换和量化。
- 模型导出:将训练好的模型导出为ONNX格式,这是一个通用的中间表示格式。
- 模型转换:使用K210芯片厂商提供的工具链(如NNCase),将ONNX模型转换为K210支持的KMODEL格式。这个过程中,工具会对模型图进行优化,合并一些操作,并准备进行量化。
- 模型量化:这是关键一步。训练时的模型通常是FP32(浮点数)精度的,占用的内存和计算量都很大。量化是将权重和激活值从FP32转换为INT8(8位整数)的过程,能大幅减少模型体积、提升推理速度、降低功耗。NNCase工具会使用一部分校准数据(可以从训练集中抽取)来统计激活值的分布,确定量化的尺度参数。
- 模型测试:转换量化后,务必在PC端用模拟器或工具对生成的.kmodel文件进行推理测试,使用测试集图片验证其精度是否相比原始浮点模型有可接受的下降(通常会有1-3个百分点的轻微损失)。如果损失太大,可能需要调整量化策略或使用更多校准数据。
注意事项:量化过程是不可逆的,且对模型最终性能影响巨大。务必确保校准数据具有代表性(覆盖各种场景)。如果发现量化后模型在某个特定场景(如夜间)下性能骤降,就需要在校准数据中增加该类场景的样本。
4. 行空板端程序开发与部署
4.1 开发环境搭建
行空板的编程非常简便。我主要使用Mind+图形化编程软件(适合初学者)和Python代码编程(适合更灵活的控制)。
- 连接行空板:通过USB-C数据线将行空板连接至电脑。电脑会自动识别出一个U盘(用于传输文件)和一个串口(用于通信)。
- 安装驱动与软件:根据行空板官网指引,安装USB驱动和Mind+软件。在Mind+中,选择“行空板”主板,连接串口。
- Python环境:行空板内置了MicroPython,并封装了丰富的硬件库。我们后续的代码主要基于这些库进行开发。
4.2 核心代码模块解析
下面我们分解一下行空板端Python程序的核心模块。
# 导入必要的库 import sensor, image, time, lcd, tf, uos, gc from machine import UART import json import network import socket # 1. 硬件初始化 def hardware_init(): lcd.init() # 初始化屏幕 sensor.reset() # 复位摄像头 sensor.set_pixformat(sensor.RGB565) # 设置像素格式 sensor.set_framesize(sensor.QVGA) # 设置图像大小 (320x240) sensor.skip_frames(time = 2000) # 等待摄像头稳定 clock = time.clock() return clock # 2. 加载AI模型 def load_model(model_path): try: net = tf.load(model_path) # 加载.kmodel模型 labels = ['red', 'green', 'yellow', 'unknown'] # 模型对应的标签 return net, labels except Exception as e: print("模型加载失败:", e) return None, None # 3. 图像预处理与推理 def detect_traffic_light(img, net, labels, confidence_threshold=0.6): # 将图像缩放到模型输入尺寸,例如224x224 img_resized = img.resize(224, 224) # 进行推理 objects = net.detect(img_resized) detections = [] for obj in objects: if obj.score() > confidence_threshold: # 获取边界框在原图(QVGA)上的坐标,需要将224尺寸的坐标映射回320x240 x, y, w, h = obj.rect() # 坐标映射计算(此处为简化示例,实际需按比例计算) x_orig = int(x * 320 / 224) y_orig = int(y * 240 / 224) w_orig = int(w * 320 / 224) h_orig = int(h * 240 / 224) label = labels[obj.classid()] detections.append({ 'label': label, 'bbox': (x_orig, y_orig, w_orig, h_orig), 'score': obj.score() }) return detections # 4. 主循环 def main(): clock = hardware_init() net, labels = load_model('/flash/traffic_light.kmodel') # 模型文件放在板子flash中 if net is None: lcd.draw_string(10, 10, "Model Load Fail!", lcd.RED, lcd.BLACK) return # 初始化网络连接(可选,用于上报结果) # wifi_connect() while(True): clock.tick() img = sensor.snapshot() # 捕获一帧图像 detections = detect_traffic_light(img, net, labels) # 在LCD上显示原图 lcd.display(img) # 在图像和LCD上绘制检测结果 current_status = "unknown" for det in detections: x, y, w, h = det['bbox'] label = det['label'] score = det['score'] # 绘制矩形框 img.draw_rectangle(x, y, w, h, color=(255, 0, 0), thickness=2) # 绘制标签文本 text = "{}:{:.2f}".format(label, score) img.draw_string(x, y-10, text, color=(255, 255, 255), scale=1) # 简单的状态决策逻辑:取置信度最高的灯作为当前状态 if score > 0.8: # 设置一个较高的阈值用于状态决策 current_status = label # 在屏幕固定位置显示当前判断的红绿灯状态 lcd.draw_string(10, 10, "Status: " + current_status, lcd.GREEN, lcd.BLACK) # 显示帧率 lcd.draw_string(10, 30, "FPS:{:.2f}".format(clock.fps()), lcd.WHITE, lcd.BLACK) # 通过网络上报状态(示例) # if current_status != "unknown": # send_status(current_status) # 必要的内存管理和延迟 gc.collect() # time.sleep_ms(50) # 控制循环频率 if __name__ == '__main__': main()代码关键点解析:
- 坐标映射:模型在224x224的图上进行推理,但我们的摄像头采集和LCD显示是320x240(QVGA)。因此,检测出的边界框坐标必须按比例映射回原图坐标,否则绘制的位置会错乱。这是新手常忽略的一个细节。
- 双阈值策略:代码中出现了两个置信度阈值。一个用于过滤原始检测结果(
confidence_threshold=0.6),保证显示的结果有一定可信度。另一个更高的阈值(score > 0.8)用于最终的状态决策,确保系统输出的“红灯/绿灯”状态非常可靠,避免在置信度不高的情况下频繁误报。这种策略在实践中很有效。 - 内存管理:在嵌入式设备上连续进行图像采集和AI推理,容易产生内存碎片。定期调用
gc.collect()进行垃圾回收,有助于保持系统长时间稳定运行。 - 帧率控制:通过
clock.tick()和clock.fps()可以监控推理帧率。如果帧率过低(如低于2 FPS),会影响实时性。可以通过降低图像采集分辨率、简化模型、优化代码逻辑来提升帧率。
4.3 网络功能扩展(可选)
如果需要将红绿灯状态上报到服务器,可以添加Wi-Fi连接和Socket或HTTP通信代码。
def wifi_connect(ssid, password): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') wlan.connect(ssid, password) while not wlan.isconnected(): time.sleep(1) print('network config:', wlan.ifconfig()) def send_status(status): # 使用Socket或HTTP POST发送数据 # 示例:构建一个简单的TCP Socket客户端 addr = ('192.168.1.100', 8080) # 服务器地址和端口 try: client = socket.socket() client.connect(addr) data = json.dumps({'device_id': 'xingkong_01', 'light_status': status}) client.send(data.encode()) client.close() except Exception as e: print("Send failed:", e)5. 系统调试与性能优化实战
5.1 常见问题与排查技巧
在部署过程中,你几乎一定会遇到下面这些问题:
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 模型加载失败 | 1. 模型文件路径错误或不存在。 2. 模型文件损坏。 3. 模型格式不正确(不是.kmodel)。 | 1. 使用uos.listdir('/flash')确认文件存在。2. 重新转换并传输模型文件。 3. 确保使用正确的工具链转换模型。 |
| 推理结果为空或完全错误 | 1. 图像预处理不一致(训练和推理时的归一化方式、通道顺序不同)。 2. 量化失败导致模型精度丧失严重。 3. 标签顺序与模型输出不匹配。 | 1. 检查训练时数据增强管道和推理前预处理代码,确保一致(如都是RGB顺序,都除以255)。 2. 在PC端用NNCase工具测试量化后模型精度。增加校准数据的多样性和数量。 3. 核对模型输出层的类别索引与 labels列表的顺序是否对应。 |
| 帧率(FPS)过低 | 1. 模型过于复杂。 2. 图像分辨率设置过高。 3. 代码中存在耗时操作(如频繁的文件读写、网络请求)。 4. 没有进行有效的内存回收。 | 1. 换用更轻量的模型(如从YOLOv5s换为YOLOv5n)。 2. 尝试将 sensor.set_framesize设为更低分辨率(如QQVGA160x120),但要注意检测精度可能下降。3. 将网络上报等非实时操作改为异步或降低频率。 4. 在循环中适当位置加入 gc.collect()。 |
| 检测框位置偏移 | 未进行正确的坐标映射。模型在缩放后的图像上检测,但框画在了原始尺寸图像上。 | 仔细检查并实现detect_traffic_light函数中的坐标映射计算逻辑。可以画一个测试图(如在固定位置放一个色块)来验证映射是否正确。 |
| 夜间或逆光检测效果差 | 1. 训练数据中缺乏此类场景。 2. 摄像头在低光照下噪点多,图像质量差。 3. 红绿灯过曝变成白色光斑。 | 1. 补充夜间、逆光数据重新训练模型。 2. 尝试调整摄像头参数: sensor.set_contrast,sensor.set_brightness,sensor.set_saturation,或启用自动增益控制。3. 在图像预处理中,可以尝试使用直方图均衡化或CLAHE来增强对比度。 |
| 误检率高(将车尾灯、广告牌等识别为红绿灯) | 1. 训练数据中负样本(非红绿灯但颜色形状相似的物体)不足。 2. 置信度阈值设置过低。 | 1. 在数据集中主动加入车尾灯、红色绿色广告牌、路灯等“困难负样本”图片,并确保它们被正确标注为背景或“未知”类。 2. 逐步提高 confidence_threshold,直到误检减少到可接受范围,同时观察漏检是否显著增加,寻找平衡点。 |
5.2 性能优化进阶技巧
当基本功能实现后,可以尝试以下优化来提升系统整体表现:
- 区域兴趣(ROI)检测:如果摄像头安装位置固定,红绿灯在画面中出现的大致区域也是固定的。可以在调用模型检测前,先使用
img.crop()截取这个ROI区域,只对这部分图像进行推理。这能显著减少输入数据量,提升帧率。 - 多尺度检测与融合:对于距离不同的红绿灯(近处大、远处小),单一尺度的模型可能检测不全。可以尝试将图像缩放到不同尺寸,分别进行检测,然后合并结果。但这会增加计算量,需在行空板上测试是否可行。
- 状态滤波与去抖:直接输出每一帧的检测结果会导致状态在边界帧频繁跳动(如红-绿-红)。可以引入一个简单的状态机或滤波器,例如:连续3帧都检测到“绿灯”,才将系统状态切换为“绿灯”;一旦切换,需要连续5帧检测到其他状态才允许再次切换。这能有效输出稳定的状态信号。
- 模型集成:如果存储空间允许,可以训练两个模型:一个轻量级模型用于快速初筛(高召回率),另一个更精确的模型只对初筛出的候选区域进行细分类(高精确度)。这种级联结构可以在速度和精度间取得更好平衡。
6. 项目扩展与应用场景思考
完成基础的红绿灯状态检测后,这个项目还有很大的扩展空间:
- 多灯识别与逻辑判断:扩展模型,使其能同时识别并区分“直行红绿灯”、“左转红绿灯”、“人行道红绿灯”。并编写简单的交通规则逻辑,例如“当直行为绿灯且左转为红灯时,输出可直行信号”。
- 倒计时数字识别:很多红绿灯带有倒计时显示器。可以增加一个OCR(光学字符识别)模块,在检测到红绿灯区域后,再对该区域进行数字识别,读取倒计时秒数。这需要额外的数字识别模型,对行空板的算力是更大挑战。
- 与执行机构联动:将行空板的GPIO口利用起来。例如,检测到绿灯时,点亮一个绿色LED;检测到红灯时,点亮红色LED并控制一个舵机模拟栏杆落下。这可以做成一个微型的智能路口演示模型。
- 车路协同模拟:结合另一块行空板或单片机模拟车辆端。路口检测板通过Wi-Fi或蓝牙将红绿灯状态广播出去,“车辆”板子接收后,根据状态决定“加速”、“减速”或“停车”,并在小车上实现。这构成了一个简单的车路协同(V2I)演示系统。
- 数据收集与模型迭代:在行空板程序中加入一个“困难样本捕获”模式。当用户按下某个按钮,或者系统自身检测到置信度很低(如0.3~0.6之间)的目标时,自动保存当前图片。定期收集这些“难例”,用于后续优化模型,形成闭环迭代。
这个项目从视觉AI算法到嵌入式部署,从硬件调优到软件逻辑,涉及的知识点非常综合。调试过程中,你可能需要反复在数据、模型、代码、硬件参数之间权衡。当看到行空板的小屏幕上,实时框出路口的红绿灯并准确标出状态时,那种把抽象算法变成具体感知能力的成就感,正是嵌入式AI开发的魅力所在。