news 2026/7/29 4:15:31

基于YOLOv5与行空板的轻量级红绿灯检测系统实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5与行空板的轻量级红绿灯检测系统实践

1. 项目缘起:为什么要在行空板上做红绿灯检测?

最近在折腾一个挺有意思的玩意儿——用行空板(UNIHIKER)做了一个红绿灯检测系统。可能有人会问,现在市面上成熟的ADAS(高级驾驶辅助系统)方案那么多,干嘛要自己用一块小小的开发板来“造轮子”?

这事儿得从我自己的实际需求说起。我手头有一些教育机器人、小型移动平台的项目,需要它们具备基础的“视觉”能力,比如识别前方的交通信号灯,然后做出“前进”或“停止”的决策。直接用商业级的ADAS模块,一来成本高,二来“黑盒”属性太强,对于教学、原型验证或者特定场景下的二次开发并不友好。我需要的是一个足够轻量、完全开源可控、并且能让我从数据采集、模型训练到部署推理全流程都摸一遍的方案。

行空板就进入了我的视野。它本质上是一块集成了屏幕、按键、多种传感器和GPIO接口的Python编程单片机,核心是双核A7芯片,运行着完整的Debian Linux系统。这意味着我可以在上面直接跑Python,调用OpenCV、PyTorch或TensorFlow Lite这些库,实现一个端到端的计算机视觉应用。用行空板做红绿灯检测,更像是一个“麻雀虽小,五脏俱全”的微型项目,它逼着我去思考:在资源(算力、内存)严格受限的边缘设备上,如何平衡检测的精度、速度和系统的实时性?

所以,这个项目的目的不是要做出一个媲美特斯拉的视觉系统,而是探索一条可行的技术路径:如何利用易得的硬件和开源工具栈,构建一个能实际跑起来的、轻量级红绿灯检测原型。这个过程涉及模型选型、数据准备、模型压缩、边缘部署等一系列环节,每一个环节都有不少坑要踩,也有不少经验可以分享。

2. 核心方案选型:YOLO vs. 传统视觉方法

确定了硬件平台(行空板)和目标(红绿灯检测)后,下一个关键决策是:用什么技术来实现检测?

传统计算机视觉方法,比如基于颜色空间(HSV)分割和形状匹配(圆形、矩形检测),是一个直观的起点。它的优点是逻辑简单、计算量极低,几乎不依赖外部数据。你可以写一段Python脚本,用OpenCV把图像从BGR转到HSV空间,设定红色和绿色的阈值范围,提取出可能是灯光的区域,再用轮廓查找和几何判断来确认。

注意:纯颜色+形状的方法在实验室可控光照下可能工作良好,但一到实际场景就“原形毕露”。黄昏、逆光、阴雨天气、LED灯珠的过曝、树叶的遮挡、远处红绿灯像素点过少……这些因素都会导致颜色阈值失效,产生大量误检或漏检。它的鲁棒性太差,无法作为一个可靠系统的基石。

因此,我毫不犹豫地选择了基于深度学习的目标检测方案。在众多模型中,YOLO(You Only Look Once)系列因其在速度和精度间的优秀平衡而成为边缘设备上的首选。它的核心思想是将目标检测视为一个回归问题,单次前向传播就能预测出图像中所有目标的边界框和类别,效率非常高。

对于行空板这样的边缘设备,我们需要的是YOLO的“瘦身”版本。主要有几个选择:

  1. YOLOv5s / YOLOv8n:这是Ultralytics官方维护的模型,结构清晰,生态完善。其中的-s(small) 或-n(nano) 版本参数量在700万到900万之间,相对轻量。
  2. YOLOv3-Tiny:更老但更经典的轻量级模型,结构简单,速度更快,但精度通常低于v5s/v8n。
  3. 专为移动端优化的模型:如MobileNet-SSD,但它的检测头设计不如YOLO高效,在兼顾多尺度目标(尤其是小目标如远处的红绿灯)时可能吃力。

我的选择是YOLOv5s。原因如下:

  • 社区与工具链成熟:Ultralytics提供的训练和导出工具链非常完善,从数据标注格式(YOLO格式)到模型训练、验证、导出为ONNX或TorchScript,都有现成的脚本,极大降低了开发门槛。
  • 精度与速度的平衡:在自制的红绿灯数据集上实测,YOLOv5s的mAP@0.5能达到95%以上,而在行空板CPU上推理一张640x640的图片,耗时大约在800-1200毫秒。虽然达不到实时(30FPS),但对于教育机器人或低速移动平台每秒1-2次的检测频率需求,是可以接受的。
  • 易于优化:YOLOv5的模型结构清晰,后续如果需要进一步压缩(如通道剪枝、量化),有比较多的参考资料和实践案例。

确定了主干模型,接下来就要解决两个核心问题:数据和部署。

3. 数据集的“脏活累活”:采集、标注与增强

“垃圾进,垃圾出”在机器学习领域是铁律。没有一个好的数据集,再精巧的模型也无用武之地。红绿灯检测的数据集准备,是项目中最耗时但也最关键的“脏活累活”。

3.1 数据采集

我的数据来源主要有三个:

  1. 公开数据集:如Bosch Small Traffic Lights Dataset、DriveU Traffic Light Dataset。这些数据集专业、标注质量高,但场景可能偏海外,与国内常见的红绿灯样式(如倒计时读秒、横向排列)有差异。
  2. 行车记录仪视频:从网上找一些国内城市道路的行车记录仪视频,用OpenCV按帧抽取图片。这是获取真实、多样场景的最有效方式。
  3. 实拍:拿着行空板(或手机)到路口实地拍摄。这部分数据量不大,但能针对性地补充一些特殊角度、光照条件的数据,对于提升模型在最终部署环境下的泛化能力很有帮助。

总共收集了大约3000张包含红绿灯的图片。这个数量对于YOLOv5s这样的模型做迁移学习来说,是一个比较理想的起点。

3.2 数据标注

我使用LabelImgRoboflow进行标注。标注时需注意:

  • 标注格式:统一使用YOLO格式(归一化的中心点x, y,宽度w,高度h)。
  • 类别定义:最初我只定义了red,green两类。但在实际标注中发现,红绿灯有圆形、箭头形、行人信号等多种形态,且存在“红灯亮”、“绿灯亮”和“全灭”三种状态。为了提高模型的实用性,我最终定义了6个类别:red,green,yellow,left_arrow_red,left_arrow_green,pedestrian_green。这样模型不仅能识别颜色,还能识别部分语义。
  • 框体精度:框要紧贴灯壳,但不必过于精确到每个灯珠。对于远处很小的红绿灯,框可以适当放大,确保包含整个发光区域。

3.3 数据增强(Data Augmentation)

这是提升模型鲁棒性的“魔法”。我直接在YOLOv5的训练配置文件中启用了其内置的强大增强功能。以下是一些针对红绿灯场景特别重要的增强策略及其原因:

增强方法目的与原理对红绿灯检测的针对性作用
Mosaic将四张图片随机拼接成一张。让模型在一张图中看到更多不同尺度、背景的红绿灯,极大提升对小目标(远处红绿灯)的检测能力。
随机仿射变换(旋转、缩放、平移)模拟摄像头视角的微小变化。红绿灯在图像中的位置和大小会因车辆行驶而不断变化,此增强让模型对此不敏感。
色彩抖动(HSV调整)随机调整图像的色调(H)、饱和度(S)、明度(V)。至关重要!模拟不同天气(雾、雨)、不同时间(黄昏、夜晚)和不同摄像头白平衡下的颜色变化,防止模型只认死板的RGB值。
添加噪声在图像上添加高斯噪声或椒盐噪声。模拟图像传感器在低光照下的噪点,提升模型在夜间或隧道等环境下的稳定性。

data/hyps/hyp.scratch-low.yaml配置文件中,我调整了增强参数,比如适度提高了HSV增强的强度,以确保模型不会过度依赖绝对颜色。

4. 模型训练与优化:在云端“炼丹”

行空板的算力不足以支持模型训练,这一步需要在有GPU的电脑或云端服务器上完成。我使用的是Google Colab的免费GPU资源。

4.1 训练环境搭建在Colab笔记本中,克隆YOLOv5官方仓库,安装依赖。

!git clone https://github.com/ultralytics/yolov5 %cd yolov5 !pip install -r requirements.txt

然后将准备好的数据集(包含imageslabels文件夹,以及定义数据路径的data.yaml文件)上传到Colab环境。

4.2 关键训练配置创建data/traffic_light.yaml文件定义数据集:

# 类别数和类别名 nc: 6 names: ['red', 'green', 'yellow', 'left_arrow_red', 'left_arrow_green', 'pedestrian_green'] # 训练和验证图像的路径(Colab中的路径) train: /content/dataset/images/train/ val: /content/dataset/images/val/

开始训练,关键参数如下:

!python train.py \ --img 640 \ # 训练图像尺寸 --batch 16 \ # 批次大小,根据GPU内存调整 --epochs 100 \ # 训练轮数 --data data/traffic_light.yaml \ # 数据配置文件 --cfg models/yolov5s.yaml \ # 模型结构配置文件 --weights yolov5s.pt \ # 预训练权重,加速收敛 --name traffic_light_s \ # 本次训练任务名称 --hyp data/hyps/hyp.scratch-low.yaml \ # 使用轻量化的超参配置 --cache \ # 缓存图像到内存,加速训练 --device 0 # 使用GPU 0

4.3 训练过程监控与调优训练开始后,利用TensorBoard或YOLOv5自带的日志可以监控关键指标:

  • 损失函数(box_loss, obj_loss, cls_loss):观察其是否平稳下降。如果cls_loss(分类损失)居高不下,可能是类别定义不清或样本不均衡。
  • 精度指标(mAP@0.5):这是核心指标。关注其在验证集上的表现。如果训练集mAP很高但验证集很低,可能是过拟合,需要增加数据增强强度或使用早停(Early Stopping)。
  • 验证集预测结果:定期查看模型在验证集图片上的预测效果,直观判断问题所在。例如,发现模型总是漏检远处的灯,就需要回头检查数据集中小目标样本是否充足,或者考虑在Mosaic增强中提高小目标的出现概率。

我训练了大约100轮,最终在验证集上的mAP@0.5达到了96.2%,满足要求。

5. 模型部署与加速:让模型在行空板上“跑起来”

训练好的.pt模型文件不能直接在行空板上运行,需要经过转换和优化。

5.1 模型格式转换:PyTorch -> ONNX -> TensorFlow Lite行空板原生对TensorFlow Lite支持较好,且TFLite运行时内存占用更少。转换路径如下:

  1. PyTorch 转 ONNX:使用YOLOv5自带的export.py脚本。
    !python export.py --weights runs/train/traffic_light_s/weights/best.pt --include onnx --img 640 --dynamic
    得到best.onnx文件。--dynamic参数允许输入动态尺寸,但为简化部署,我固定为640x640。
  2. ONNX 转 TensorFlow Lite:这里有个坑。不能直接用onnx-tf转换YOLOv5 v6.0+的模型,因为其包含的Focus切片操作在转换时容易出错。更稳定的方法是先转成TensorFlow SavedModel,再转TFLite。
    # 安装 onnx-tf (可能需要特定版本) !pip install onnx-tf # 1. ONNX 转 TensorFlow SavedModel import onnx from onnx_tf.backend import prepare onnx_model = onnx.load('best.onnx') tf_rep = prepare(onnx_model) tf_rep.export_graph('best_savedmodel') # 2. SavedModel 转 TensorFlow Lite import tensorflow as tf converter = tf.lite.TFLiteConverter.from_saved_model('best_savedmodel') converter.optimizations = [tf.lite.Optimize.DEFAULT] # 应用默认优化(量化) tflite_model = converter.convert() with open('best_float16.tflite', 'wb') as f: f.write(tflite_model)
    我选择了Optimize.DEFAULT,它通常会执行权重量化(如float16量化),能在几乎不损失精度的情况下减小模型体积、提升推理速度。

5.2 行空板环境部署

  1. 传输文件:将转换好的best_float16.tflite文件、类别标签文件classes.txt以及一个用于预处理和后处理的Python脚本inference.py通过U盘或SCP传到行空板上。
  2. 安装依赖:在行空板的终端里,安装必要的Python库。
    pip install opencv-python-headless numpy tflite-runtime
    注意:安装opencv-python-headless而不是完整版,可以节省空间。tflite-runtime是TensorFlow Lite的独立运行时,比安装完整的TensorFlow更轻量。
  3. 编写推理脚本:inference.py是核心,其工作流程如下:
    import cv2 import numpy as np import tflite_runtime.interpreter as tflite # 1. 加载TFLite模型 interpreter = tflite.Interpreter(model_path='best_float16.tflite') interpreter.allocate_tensors() # 2. 获取输入输出详情 input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() input_shape = input_details[0]['shape'] # 通常是 [1, 640, 640, 3] # 3. 图像预处理函数 def preprocess(img): # 缩放到模型输入尺寸 img_resized = cv2.resize(img, (input_shape[2], input_shape[1])) # BGR转RGB,归一化到[0,1] img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) / 255.0 # 调整维度为 [1, H, W, C] 并转为float32 img_input = img_rgb.astype(np.float32)[np.newaxis, ...] return img_input # 4. 后处理函数 (解析YOLO输出) def postprocess(outputs, img_shape, conf_threshold=0.5, iou_threshold=0.45): # 这里需要根据你的模型输出结构进行解析。 # YOLOv5 TFLite输出通常是 [1, 25200, 85] 或类似格式。 # 需要实现非极大值抑制(NMS)来过滤重叠框。 # 这是一个简化示例,实际需要完整的解码和NMS。 detections = [] # ... (解码逻辑,包含中心点、宽高转成左上右下坐标,过滤低置信度框) # ... (使用NMS算法,例如通过numpy实现或调用cv2.dnn.NMSBoxes) return detections # 返回格式: [x1, y1, x2, y2, conf, class_id] # 5. 主循环 cap = cv2.VideoCapture(0) # 打开行空板摄像头 while True: ret, frame = cap.read() if not ret: break # 预处理 input_data = preprocess(frame) # 推理 interpreter.set_tensor(input_details[0]['index'], input_data) interpreter.invoke() # 获取输出 outputs = interpreter.get_tensor(output_details[0]['index']) # 后处理 dets = postprocess(outputs, frame.shape[:2]) # 可视化 for det in dets: x1, y1, x2, y2, conf, cls_id = map(int, det[:4]), det[4], int(det[5]) label = f'{classes[cls_id]} {conf:.2f}' cv2.rectangle(frame, (x1, y1), (x2, y2), (0,255,0), 2) cv2.putText(frame, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) # 显示结果(行空板有屏幕) cv2.imshow('Traffic Light Detection', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

6. 性能实测与瓶颈分析

将上述脚本在行空板上运行,就可以看到实时检测效果了。但“跑起来”和“跑得好”是两回事。我进行了一系列性能测试,并分析了瓶颈。

6.1 关键性能指标

  • 推理速度:使用640x640输入,在行空板CPU上,单次推理耗时约900-1200毫秒。即FPS在0.8~1.1之间。
  • 内存占用:运行推理脚本后,通过htop观察,Python进程内存占用约250MB。行空板总内存为512MB,尚有余量。
  • 检测精度:在白天光照良好的室外测试,对20米内的红绿灯检测准确率很高,几乎无漏检。但对更远的、像素面积小于20x20的红绿灯,偶尔会出现漏检。对于箭头灯和行人灯的识别,在近距离下效果不错。

6.2 主要瓶颈与优化思路

  1. CPU算力是最大瓶颈:行空板的双核A7 CPU主频较低,进行密集的浮点运算(即使是float16)速度依然不够快。1秒1帧的速度,对于需要快速反应的移动机器人来说太慢了。
  2. 模型输入尺寸:640x640对于红绿灯小目标检测是必要的,但这也增加了计算量。可以尝试训练一个专门针对小目标优化的、输入尺寸为320x320的模型,速度会显著提升,但需要重新评估对小目标的检测精度。
  3. 后处理耗时:在Python中实现NMS(非极大值抑制)是另一个耗时点。如果使用TensorFlow Lite的Delegate(委托)机制,将NMS等操作转移到更高效的底层实现,可以节省一些时间。但对于行空板,可用的Delegate(如GPU、DSP)支持有限。
  4. 摄像头数据流:cv2.VideoCapture读帧和cv2.imshow显示本身也有开销。可以考虑使用多线程,一个线程专门负责抓取摄像头帧并放入队列,另一个线程负责推理和显示,避免I/O等待阻塞推理。

6.3 一个实用的优化尝试:模型量化前面我们用Optimize.DEFAULT做了float16量化。更激进的方法是整型量化(INT8)。这需要代表性的校准数据集来统计激活值的范围。

# 在转换时加入INT8量化 converter = tf.lite.TFLiteConverter.from_saved_model('best_savedmodel') converter.optimizations = [tf.lite.Optimize.DEFAULT] converter.representative_dataset = representative_dataset_gen # 一个生成校准数据的函数 converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type = tf.uint8 # 输入也转为uint8 converter.inference_output_type = tf.uint8 tflite_int8_model = converter.convert()

INT8量化后,模型体积可减小至原来的1/4,推理速度也能有显著提升(在支持INT8加速的硬件上)。但在行空板的CPU上,速度提升可能不如体积减少那么明显,且精度可能会有轻微损失(1-2个mAP点),需要仔细权衡。

7. 系统集成与场景应用思考

一个完整的“红绿灯检测系统”不仅仅是模型推理。在行空板上,我们可以将其与其他功能模块集成,形成一个闭环。

7.1 状态判断与决策逻辑模型输出的是“一个红色的圆形”或“一个绿色的左箭头”。我们需要根据业务逻辑将其转化为“可通行”或“不可通行”的指令。

def get_traffic_command(detections, frame_center_x): """ 根据检测结果生成控制指令。 简单逻辑:找到图像中心水平线附近置信度最高的红/绿灯。 """ relevant_dets = [] for det in detections: x1, y1, x2, y2, conf, cls_id = det box_center_x = (x1 + x2) / 2 # 假设我们只关心图像下半部分(地平线以上)的信号灯 if y2 < frame.shape[0] * 0.7: if cls_id in [0, 3]: # red, left_arrow_red relevant_dets.append(('STOP', conf, box_center_x)) elif cls_id in [1, 4, 5]: # green, left_arrow_green, pedestrian_green relevant_dets.append(('GO', conf, box_center_x)) if not relevant_dets: return 'UNKNOWN' # 选择置信度最高的指令,如果存在多个,可以选择离图像中心最近的那个 relevant_dets.sort(key=lambda x: x[1], reverse=True) # 按置信度降序 return relevant_dets[0][0]

这个逻辑非常初级。更复杂的系统可能需要跟踪红绿灯状态(识别黄灯、判断闪烁),并结合车辆所在车道(通过图像分割或先验知识)来决定关注哪一组信号灯。

7.2 与执行机构联动行空板有GPIO引脚。我们可以将决策指令(‘GO’/‘STOP’)转化为电平信号,控制一个LED灯(模拟刹车灯/通行灯),或者通过串口/UART发送指令给下位机(如STM32),进而控制电机的启停。

import gpiod # 初始化GPIO chip = gpiod.Chip('gpiochip0') line = chip.get_line(18) # 假设GPIO18连接绿色LED line.request(consumer='traffic_light', type=gpiod.LINE_REQ_DIR_OUT) while True: command = get_traffic_command(current_detections, center_x) if command == 'GO': line.set_value(1) # 点亮绿灯 # 同时可以通过PWM控制电机转速 elif command == 'STOP': line.set_value(0) # 熄灭绿灯,或点亮红灯 # 发送停止指令

7.3 项目扩展方向这个原型系统可以沿多个方向深化:

  • 多任务学习:让模型同时检测红绿灯、车道线和行人,共享主干网络特征,提升整体效率。
  • 时序信息利用:连续帧检测结果可以用于状态跟踪和滤波,减少单帧误检的影响,并识别出“绿灯刚亮”或“红灯即将结束”等状态。
  • 模型轻量化再进一步:尝试使用神经网络架构搜索(NAS)或知识蒸馏(Knowledge Distillation)技术,获得一个专为行空板定制的、更小更快的模型。
  • 系统优化:使用C++重写核心推理和后处理代码,并利用ARM NEON指令集进行加速,有望将推理时间压缩到200毫秒以内。

通过这个项目,我深刻体会到在边缘设备上部署AI应用的挑战与乐趣。它要求你在算法精度、计算资源、功耗和实时性之间不断做出权衡。行空板作为一个功能丰富的教学和原型平台,为这类探索提供了绝佳的起点。虽然最终的性能指标无法与专业硬件相比,但整个从数据到部署的完整流程走通,所带来的经验和对底层细节的理解,是只看论文或调用API无法比拟的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/29 4:12:21

Python质因数分解算法:从试除法到工程化优化的完整指南

1. 项目概述&#xff1a;从一道经典题看编程基本功“将一个正整数分解质因数”&#xff0c;这几乎是每个学习编程的人都会遇到的经典练习题。乍一看&#xff0c;它像是一个纯粹的数学问题&#xff0c;但当你真正用代码去实现时&#xff0c;你会发现它远不止是数学公式的翻译。它…

作者头像 李华
网站建设 2026/7/29 4:12:17

深入解析西门子S7协议报文:从TPKT/COTP到数据读写实战

1. 从一次“黑盒”调试说起&#xff1a;为什么我们需要理解S7协议报文几年前&#xff0c;我接手一个老旧产线的数据采集项目。现场有一台西门子S7-300 PLC&#xff0c;负责控制几个关键阀门的开度。客户的需求很简单&#xff1a;把阀门的实时开度值&#xff08;一个浮点数&…

作者头像 李华
网站建设 2026/7/29 4:11:46

深入解析STM32定时器从模式:原理、实战与高级应用

1. 项目概述&#xff1a;为什么需要深入理解定时器从模式&#xff1f;如果你用过STM32的定时器&#xff0c;大概率是从“主模式”开始的&#xff1a;配置一个ARR&#xff08;自动重装载值&#xff09;和一个PSC&#xff08;预分频器&#xff09;&#xff0c;然后启动定时器&…

作者头像 李华
网站建设 2026/7/29 4:10:09

Python实战网格交易策略:从核心原理到实盘部署的完整指南

1. 网格策略&#xff1a;一个被误解的“懒人”交易工具如果你在量化交易圈子里待过一阵子&#xff0c;大概率听过“网格策略”这个名字。很多人把它简单理解成“在震荡行情里自动低买高卖的程序”&#xff0c;甚至戏称为“懒人捡钱神器”。我最初也是这么想的&#xff0c;直到自…

作者头像 李华
网站建设 2026/7/29 4:06:14

基于Dify与RAG技术构建游戏智能助手实战指南

这次我们来看一个基于 Dify 和 RAG 技术构建专属游戏智能助手的实战项目。这个项目不是简单的概念介绍&#xff0c;而是从零开始搭建一个能实际回答三角洲特种部队游戏问题的知识库系统&#xff0c;重点解决游戏攻略、武器数据、任务指引等具体问题。Dify 作为一个开源的大模型…

作者头像 李华
网站建设 2026/7/29 4:03:53

卡尔曼滤波与扩展卡尔曼滤波:从原理到工程实践详解

1. 从“猜”到“算”&#xff1a;为什么我们需要卡尔曼滤波如果你做过机器人、无人机或者任何需要融合传感器数据的项目&#xff0c;大概率听过卡尔曼滤波这个名字。它听起来很高深&#xff0c;一堆矩阵公式让人望而却步。但它的核心思想&#xff0c;其实非常朴素&#xff1a;如…

作者头像 李华