限速标志识别与神经网络限速控制

本模块对应课程任务(2)——传感器感知及运动控制:MuJoCo 物理仿真中车载相机采集 前视图像,卷积神经网络(CNN)读取限速标志数值,控制器据此对差速小车实施闭环 限速运动控制。

与已有 CARLA 版交通标志识别模块的区别:carla_traffic_sign_recognition 与 carla_tsr_ros 使用 YOLOv8 检测 stop sign 类别(刹车/直行二值控制); 本模块识别限速数值本身(字符级 0-9 CNN → 任意两位数组合),并实现 连续速度闭环调节(限速值→目标速度→加速度限幅),感知与控制语义均不同。

功能

  • MuJoCo 圆形赛道差速小车巡航(刚体动力学推进,预瞄 P 控制横向,速度闭环纵向)
  • 车载前视相机(320x240,10 Hz):MuJoCo 离屏渲染标志牌,支持旋转/模糊/暗光/噪声退化
  • 感知流水线:HSV 红圈定位 → 圆内字符切分 → DigitCNN 字符识别 → 位值组合 → 滑窗防抖
  • 限速器:目标速度取巡航与限速的较小值,加速度限幅平滑减速,驶离视野后延时解除
  • 一键 launch 启动;预置 3 个场景(基准/强退化/密集牌序);离屏渲染帧可一键合成 GIF

系统架构

graph LR W[world_node
MuJoCo 动力学+相机渲染] -- "/camera/image_raw (Image)" --> D[detector_node
HSV定位+CNN识别] D -- "/speed_limit (Int32)" --> V[driver_node
预瞄P控制+限速器] V -- "/cmd_vel (Twist)" --> W W -- "/world/odom (Odometry)" --> V
话题 类型 发布者 说明
/camera/image_raw sensor_msgs/Image world 320x240 rgb8 前视图像,10 Hz
/world/odom nav_msgs/Odometry world 位姿与速度真值,20 Hz
/speed_limit std_msgs/Int32 detector 稳定限速值(km/h),-1 表示视野内无牌
/speed_limit_conf std_msgs/Float32 detector 该值平均置信度
/detector/debug sensor_msgs/Image detector 带定位框的调试图像
/cmd_vel geometry_msgs/Twist driver 速度控制指令

环境与安装

  • Ubuntu 20.04 + ROS 2 Humble、Python 3.8、PyTorch ≥ 2.0 (CPU)、OpenCV、Pillow、MuJoCo ≥ 3.0(无头环境经 OSMesa 软渲染)

    cd ~/ts_ws/src git clone <本仓库> # 或直接拷贝 src/ground/traffic_sign_speed_control cd ~/ts_ws pip3 install -r src/ground/traffic_sign_speed_control/requirements.txt colcon build --packages-select traffic_sign_speed_control source install/setup.bash

若 models/sign_cnn.pth 不存在,先训练(约 3 分钟,CPU):

python3 src/ground/traffic_sign_speed_control/scripts/train_signs.py

运行步骤

1. 一键启动

ros2 launch traffic_sign_speed_control traffic_sign_speed_control.launch.py
launch 参数 默认值 说明
render_mode file none 不渲染;file 离屏写帧(window 已弃用,按 file 处理)
frame_dir /tmp/ts_frames file 模式帧输出目录(PNG,用于合成 GIF)
scene default 预置场景:default / degraded / dense

启动后终端依次打印 world/detector/driver 的就绪日志。小车从角度 180° 处沿赛道 逆时针巡航,驶近"30"牌减速、驶离后恢复巡航,再驶近"15"牌再次减速,如下图:

运行画布

(左:世界画布——圆形赛道、限速牌、红色小车、HUD、相机画中画; 右下画中画:车载相机原始图像)

相机视角

2. 观察话题

ros2 topic echo /speed_limit          # 接近标志时输出 30 / 15,其余为 -1
ros2 topic echo /cmd_vel              # 限速生效时 linear.x 被压低

3. 合成演示 GIF

ros2 run traffic_sign_speed_control main demo     # 需要 ffmpeg;frames/ 与 GIF 输出到当前工作目录

4. 完整演示

演示动图

指令序列:巡航 1.25 m/s(45 km/h 缩比)→ 识别"30"→ 减速至 0.83 m/s → 驶离恢复 1.25 m/s → 识别"15"→ 减速至 0.42 m/s → 恢复巡航,基准场景限速区内零超速。

原理与公式

1. 差速小车刚体动力学

小车在 MuJoCo 中建模为刚体:底盘 + 两驱动轮(hinge 关节)+ 万向轮,/cmd_vel 的 (v, ω) 按差速关系换算为左右轮目标转速,由速度执行器(velocity actuator) 跟踪,物理以 2 ms 子步 implicitfast 积分推进(每 50 Hz 控制周期 10 个子步):

其中 b 为半轮距(0.095 m),r 为驱动轮半径(0.035 m)。轮地摩擦产生实际 纵向速度与偏航角速度,里程计由 free joint 位姿差分得到。

2. 预瞄 P 控制(横向)

设赛道圆心 C、半径 R。取车头前方弧长 L_d 处的预瞄点,计算预瞄点航向与当前 航向的误差 e_θ(角度归一化到 (-π, π]),转角指令为:

3. 限速器与加速度限幅(纵向)

本平台为 1:10 缩比模型(赛道半径 2.5 m):速度按几何相似缩放 v_sim = n / 36(n 为牌面 km/h 标注),牌面数值沿用真实交通标注。设巡航速度 v_c、感知限速 n,纵向控制为:

减速取 a = 1.6 m/s²,加速取 a = 1.0 m/s²,保证速度曲线连续可跟踪。

4. MuJoCo 车载相机

相机刚体安装于车头(高 0.2 m,水平视场 90°,分辨率 320x240),由 MuJoCo 离屏渲染器逐帧渲染。标志牌为边长 l_s = 0.6 m 的带纹理几何体,牌面中心 距地 0.35 m;距离 d、水平偏角 α 时,针孔投影近似给出牌面中心的图像横坐标 u 与牌面边长 s(像素):

渲染以 2 倍分辨率超采样后 INTER_AREA 降采样回 320x240,避免远处牌面数字 笔画在缩采样中糊化;视距 4.5 m 外认为不可见(真值区间计算用)。

5. HSV 红色分割与圆形度

HSV 空间中红色分布在 H 两端,掩膜取 (0 ≤ H ≤ 8) 与 (172 ≤ H ≤ 180) 两区间并集 (S ≥ 90,V ≥ 60)。对每个连通域以圆形度筛选标志牌(A 为面积、P 为周长):

再用最小外接圆半径 r ∈ [15, 110] px 过滤。

6. 字符 CNN(DigitCNN)

网络结构:Conv(1→16,3×3)-ReLU-MaxPool2 → Conv(16→32,3×3)-ReLU-MaxPool2 → Dropout(0.25) → FC(2048→128)-ReLU → FC(128→10), 输入 32×32 灰度图。推理取 softmax 最大概率类别及置信度:

两位数牌面按位组合 value = 10·d1 + d2,单字符牌 value = d1。任一字符置信度 低于 0.60 时整帧判无效。

7. 滑窗防抖(多数票)

检测值序列进入长度 N = 5 的滑动窗口,稳定值取窗口内票数最多的非负读数 (≥ 2 票,同票数时置信度均值决胜),上锁与改判同规则;仅当整窗均为无牌(-1) 时才解除。相比"连续 N 帧一致",多数票容忍退化场景下的散乱误读与掉帧, 同时防止单帧误读翻转已生效的限速。

算法流程

感知流水线

graph TD A[相机图像 320x240] --> B[HSV 红色分割] B --> C{圆形度≥0.6 且 15≤r≤110?} C -- 否 --> Z[发布 -1] C -- 是 --> D[圆内暗色连通域字符切分] D --> E{1~2 个字符?} E -- 否 --> Z E -- 是 --> F[DigitCNN 逐字符分类] F --> G{置信度≥0.60?} G -- 否 --> Z G -- 是 --> H[位值组合 v=10d1+d2] H --> I[滑窗多数票防抖 N=5] I --> J[发布 /speed_limit]

限速状态机

stateDiagram-v2 [*] --> CRUISE CRUISE --> LIMITED: 检测到限速牌 n km/h LIMITED --> LIMITED: 持续可见(目标 min(v_c, n/36)) LIMITED --> CRUISE: 驶离视野且超过保持时间 1.2 s CRUISE --> CRUISE: 无牌 / 目标 v_c

代码结构

文件 类型 职责
traffic_sign_speed_control/world_node.py 节点 构建 MuJoCo 世界、50 Hz 控制周期 mj_step 推进、里程计/相机话题发布、俯视画布渲染与帧输出
traffic_sign_speed_control/mujoco_scene.py 库 运行时生成 MJCF(赛道纹理/限速牌/差速小车/相机)、物理推进与相机离屏渲染
traffic_sign_speed_control/detector_node.py 节点 HSV 定位 → 字符切分 → CNN 推理 → 滑窗防抖,发布 /speed_limit
traffic_sign_speed_control/driver_node.py 节点 限速状态机与加速度限幅、预瞄 P 横向控制,发布 /cmd_vel
traffic_sign_speed_control/scene.py 库 3 个预置场景定义,支持 /world/scene_config 运行时热切换
traffic_sign_speed_control/sign_render.py 库 标志牌纹理绘制(仿真材质与训练数据共用)、字符合成与增广、字符归一化
traffic_sign_speed_control/model.py 库 DigitCNN 网络定义与推理预处理
scripts/train_signs.py 脚本 合成数据集生成与 CNN 训练(约 3 分钟,CPU)
scripts/evaluate_detector.py 脚本 检测器退化矩阵评测(6 种条件 × 10 类 × 100 张)
scripts/run_scenes.py 脚本 多场景端到端评测(进程组管理,指标统计与画图)
traffic_sign_speed_control/main.py / launch/ 入口 ros2 run ... main CLI(train/evaluate/scenes/demo)与三节点一键启动

设计要点:

  • 手工构造 sensor_msgs/Image(rgb8,step = width*3),零 cv_bridge 依赖;
  • 训练与推理共用 canonicalize_char() 字符归一化(暗像素紧裁剪 → 方形白底 → 32×32),消除"训练字符带白边 / 相机字符满框"的分布不匹配;
  • 牌面以程序化纹理贴附 MuJoCo 几何体,同一套 draw_limit_sign() 同时生成 仿真材质与训练数据,保证两域字形一致;
  • 车载相机 2 倍超采样渲染后 INTER_AREA 降采样,避免远处牌面笔画缩采样糊化; 牌面与立杆设为无碰撞(contype=0),不干扰小车动力学;
  • run_scenes.py 以进程组管理三节点栈(start_new_session + killpg),保证多场景 连续评测无残留进程。

训练与数据

训练数据完全由程序合成(无需外部数据集):0-9 每类 400 张,DejaVu/Liberation 粗体渲染 + 随机缩放平移 + 部分样本叠加红圈边缘模拟裁剪,再施加与相机相同的 退化增广(旋转 ±18°、模糊 σ≤1.2、亮度扰动、噪声)。划分 80/10/10, Adam(lr=1e-3)、batch 128、15 epoch,CPU 训练约 3 分钟。

样本网格

训练曲线

混淆矩阵

测试集(400 张)准确率 99.5%(混淆矩阵仅 2 处错误:4→6、5→8),验证集 99.25% (合成同分布数据,下节退化矩阵给出更严格的逐条件泛化结果)。

多场景测试与性能评价

指标定义

  • 识别延迟:牌进入相机视野真值时刻 → 防抖后 /speed_limit 首次等于牌值的时延;
  • 达标时间:进入视野后,速度持续 1.0 s 满足误差不超过 max(0.05, 5%·v_lim) 的最短时间;
  • 稳态误差:视野区间最后 2 s 内 |v − v_lim| 均值;
  • 超速占比:达标之后区间内 v > v_lim + 容差的采样占比。

检测器退化矩阵

对 0-9 每个数字在 6 种退化条件下各生成 100 张合成样本(与训练增广同源但 参数独立随机),字符级准确率:

退化矩阵

(数值由 scripts/evaluate_detector.py 评测生成)

场景端到端结果

每个场景以 render_mode:=none 运行真实 ROS 系统 55 s(约 3–4 圈),注入场景后 从小车复位起统计指标(由 scripts/run_scenes.py 生成场景 JSON 与速度曲线图):

场景 标志序列 退化 检出率 平均识别延迟 平均达标时间 平均稳态误差
default 30 → 15 无 100% (2/2) 0.68 s 1.33 s 0.010 m/s
degraded 30 → 15 旋转12°/模糊1.4/暗光/噪声 100% (2/2) 1.40 s 2.95 s 0.012 m/s
dense 30 → 15 → 20 轻度 100% (3/3) 0.39 s 1.66 s 0.012 m/s

分解到逐牌(检出后能否在可见区间内把速度稳住):

场景 牌 延迟 (s) 达标时间 (s) 稳态误差 (m/s) 超速占比
default 30 0.90 1.65 0.012 0%
default 15 0.45 1.00 0.008 0%
degraded 30 1.15 2.00 0.012 0%
degraded 15 1.66 3.90 0.012 0%
dense 30 0.00 2.83 0.012 0%
dense 15 0.81 1.35 0.013 0%
dense 20 0.35 0.80 0.012 0%

结果显示:感知全部命中(7/7 牌、三场景检出率均 100%),且全部牌在可见区间内 完成限速整定,限速区内零超速;强退化场景仅使识别延迟增加约 0.5–1.2 s、达标时间 相应延长(degraded-15 为 3.90 s),稳态误差保持在 0.012 m/s 量级。

调参方向

degraded-15 与 dense-30 的达标时间仍偏长(误读抑制与起步加速挤占减速窗口), 可通过预瞄提前减速进一步压缩。

三场景速度跟踪曲线:

default 场景速度曲线

degraded 场景速度曲线

dense 场景速度曲线

结果复现:

python3 src/ground/traffic_sign_speed_control/scripts/evaluate_detector.py
python3 src/ground/traffic_sign_speed_control/scripts/run_scenes.py --duration 55

端到端延迟

感知单帧耗时(ROI 定位 + 字符切分 + CNN 推理)实测约 2.5 ms(CPU i5 级虚拟机 6 核,20 帧均值),相机 10 Hz 下感知链路不构成瓶颈;从牌面进入视野到限速生效的 端到端延迟主要由多数票防抖(5 帧窗口 ≈ 0.2–0.5 s)、小尺寸牌面的识别置信度积累与 控制周期(0.05 s)共同决定。

已知限制

  • MuJoCo 场景为程序化材质与光照渲染,未覆盖真实相机的畸变、曝光与运动模糊;
  • 限速作用域为"牌面可见区间",未建模真实交规中区段限速的延续语义 (可通过增加"解除限速"牌类扩展);
  • 字符分割对极端透视形变(牌面斜视 > 45°)鲁棒性有限,牌面朝向已按来车方向 预瞄摆放以缓解;
  • 无 GPU 环境依赖 OSMesa 软件渲染,渲染帧率受 CPU 限制;
  • 训练与测试均基于同一程序化渲染管线,未验证真实相机实拍图像。

大模型使用声明

使用 deep seek 辅助开发:ROS 2 节点与感知/控制代码的编写与调试、训练与评测 脚本、教程文档整理;实验方案、参数选择与全部验证结论经作者实际运行确认。

相关模块