RDK X5小车巡线缺少赛道检测功能包

求助大佬,目前在做小车巡线避障的部分内容,使用的是NodeHub上的开源包,并参考了一文带你完成20届智能车地瓜赛道备赛_一文带你完成地瓜机器人二十届只能车-CSDN博客这篇文章的思路,使用的是方案二:YOLOv5s 全功能检测(巡线+避障+扫码+车库),现在导入了racing_control和racing_obstacle_detection_yolo两个巡线的功能包,并参考文章进行了修改。

现在遇到的问题是官方的控制包 racing_control 必须要接收一个唯一的赛道中心点坐标,车子才会往前开,但是相关的功能包是使用的ResNet模型,我们这里使用的是YOLO全检测,请问哪里能找到相关的功能包、或者要怎么样进行修改呢

我来帮你分析一下这个问题,并搜索社区中是否有相关资源。

你好!看了你的问题,核心矛盾是:racing_control 控制包期望接收 ResNet 赛道检测输出的单一中心点坐标话题,但你使用的是 YOLOv5s 全功能检测,输出的是多类别检测结果,没有直接的赛道中心点话题。

解决方案

根据社区最新的备赛指南(一文带你完成 20 届智能车备赛),有两条路径:

方案 A:修改 racing_control 源码(推荐)

你需要修改控制包,让它能订阅 YOLO 的检测话题并从中提取赛道线信息。

步骤:

  1. 确认 YOLO 输出话题

    ros2 topic list | grep racing_obstacle
    # 通常是 /racing_obstacle_detection
    
  2. 修改 racing_control.cpp

    src/racing_control.cpp 中:

    // 原代码订阅赛道中心点
    // subscription_point_ = this->create_subscription<PointMsg>(
    //     "/racing_track_center_detection", ...);
    
    // 改为订阅 YOLO 检测结果
    subscription_yolo_ = this->create_subscription<TargetsMsg>(
        "/racing_obstacle_detection",
        rclcpp::QoS(10),
        std::bind(&RacingControlNode::yolo_callback, this, std::placeholders::_1)
    );
    
  3. 添加 YOLO 回调函数

    void yolo_callback(const TargetsMsg::SharedPtr msg) {
        // 从 YOLO 检测结果中筛选赛道线类别
        for (const auto& target : msg->targets) {
            if (target.type == "track_line") {  // 你的赛道线类别名
                // 计算中心点
                int center_x = target.rois[0].rect.x_offset + target.rois[0].rect.width / 2;
                int center_y = target.rois[0].rect.y_offset + target.rois[0].rect.height;
                
                // 调用原有的巡线控制逻辑
                LineFollowing(center_x, center_y);
                return;
            }
        }
        
        // 没检测到赛道线时的处理
        publishTwist(0.0, 0.0);
    }
    
  4. 重新编译

    cd ~/ros2_ws
    colcon build --packages-select racing_control --symlink-install
    source install/setup.bash
    

方案 B:添加中间转换节点

创建一个 ROS2 节点,将 YOLO 输出转换为 racing_control 期望的中心点格式。

节点示例:

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Point
from your_yolo_msg_type.msg import Targets  # 替换为实际的 YOLO 消息类型

class YOLOToCenterPoint(Node):
    def __init__(self):
        super().__init__('yolo_to_center_point')
        
        self.subscription = self.create_subscription(
            Targets,
            '/racing_obstacle_detection',
            self.yolo_callback,
            10
        )
        
        self.publisher = self.create_publisher(
            Point,
            '/racing_track_center_detection',
            10
        )
    
    def yolo_callback(self, msg):
        # 查找赛道线检测结果
        for target in msg.targets:
            if target.type == "track_line":
                point_msg = Point()
                point_msg.x = target.rois[0].rect.x_offset + target.rois[0].rect.width / 2
                point_msg.y = target.rois[0].rect.y_offset + target.rois[0].rect.height
                self.publisher.publish(point_msg)
                return

def main(args=None):
    rclpy.init(args=args)
    node = YOLOToCenterPoint()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

方案 C:使用官方 ResNet 赛道检测包

如果 YOLO 训练赛道线效果不好,可以同时运行两个检测包

  • YOLOv5s:负责避障 + 二维码
  • ResNet18:专门负责赛道检测

这样可以直接使用官方的 racing_control 无需修改。

# 启动 ResNet 赛道检测
ros2 launch racing_track_detection_resnet racing_track_detection_resnet.launch.py

# 启动 YOLO 避障检测
ros2 launch racing_obstacle_detection_yolo racing_obstacle_detection_yolo.launch.py

# 启动控制包(会收到 ResNet 的中心点)
ros2 launch racing_control racing_control.launch.py

关键检查点

  1. 确认 YOLO 训练数据包含赛道线类别

    • 检查 obstacles.list 文件是否有赛道线类别
    • 如果没有,需要重新训练模型
  2. 查看实际输出的话题格式

    ros2 topic echo /racing_obstacle_detection --once
    
  3. 确认控制包订阅的话题名称

    grep -r "create_subscription" src/racing_control/
    

推荐路径

新手建议:先用方案 C(双模型),快速验证系统能跑起来,再逐步过渡到方案 A(单模型优化)。

如果还有具体问题,可以把你的:

  • YOLO 检测话题的实际输出格式
  • racing_control 的源码订阅部分
  • 训练数据的类别列表

发出来,我可以帮你更精确地修改代码。

参考方案1吧,比较合理

看到社区管理员 Marcelo6151 也推荐方案 C(双模型方案),这确实是最稳妥的选择。我来帮你细化落地步骤:

:bullseye: 推荐方案:ResNet 巡线 + YOLO 避障

为什么选方案 C?

对比项 方案 A (改控制包) 方案 C (双模型)
开发难度 高 (需改 C++ 源码) 低 (直接用现成包)
稳定性 需自行调试 社区验证过
巡线精度 依赖 YOLO 训练质量 ResNet 专为巡线优化
上手时间 2-3 天 2-3 小时

:package: 快速部署步骤

Step 1: 获取赛道检测包

cd ~/ros2_ws/src

# RDK X5 版本
git clone https://github.com/wunuo1/racing_track_detection_resnet.git -b feature-x5

# 如果 git 失败,从网页下载 zip 上传

Step 2: 编译

cd ~/ros2_ws
source /opt/tros/humble/setup.bash
colcon build --packages-select racing_track_detection_resnet racing_control --symlink-install
source install/setup.bash

Step 3: 启动顺序(3 个终端)

# Terminal 1: 底盘 + 摄像头
ros2 launch origincar_bringup origincar_bringup.launch.py

# Terminal 2: 赛道检测 (ResNet) + 障碍物检测 (YOLO)
ros2 launch racing_track_detection_resnet racing_track_detection_resnet.launch.py
ros2 launch racing_obstacle_detection_yolo racing_obstacle_detection_yolo.launch.py

# Terminal 3: 控制包
ros2 launch racing_control racing_control.launch.py

Step 4: 验证话题

# 确认赛道中心点话题有输出
ros2 topic echo /racing_track_center_detection --once

# 确认障碍物检测有输出
ros2 topic echo /racing_obstacle_detection --once

# 查看话题频率
ros2 topic hz /racing_track_center_detection

:warning: 常见问题排查

问题 检查点 解决方案
车不走 /racing_track_center_detection 无数据 检查 ResNet 模型路径配置
帧率低 YOLO 推理慢 检查模型是否转为 .bin 格式
Web 不显示检测框 web_node 未启动 检查 launch 文件是否包含 web_node

:books: 相关资源


建议:先用方案 C 让车跑起来,后续有时间再尝试方案 A 做单模型优化。有问题可以把具体的报错日志发出来。

各位佬,黄色区域有什么好的想法吗,opencv感觉太难调了

你好!关于黄色区域检测,OpenCV 调参困难是常见问题(光照敏感、颜色阈值难定)。结合社区经验和智能车竞赛场景,给你 3 个更优方案:


:bullseye: 推荐方案对比

方案 难度 稳定性 推荐指数
方案 A:YOLO 检测黄色区域 :star::star: :star::star::star::star::star: :fire::fire::fire::fire::fire:
方案 B:OpenCV HSV + 自适应阈值 :star::star::star::star: :star::star::star: :star::star::star:
方案 C:语义分割模型 :star::star::star: :star::star::star::star: :star::star::star::star:

:white_check_mark: 方案 A:用 YOLO 检测黄色区域(强烈推荐)

核心思路:把黄色区域当成一个目标类别,用 YOLO 直接检测,避免 OpenCV 调参。

步骤 1:训练数据准备

# 在 YOLOv5s 训练数据中添加黄色区域类别
# obstacles.list 添加一行:
construction_cone
qr_code
park
yellow_zone  # 新增黄色区域类别

标注工具:使用 LabelImg 标注黄色区域为 yellow_zone

步骤 2:重新训练模型

# 使用社区推荐的训练流程
# 参考:https://forum.d-robotics.cc/t/topic/28624
python train.py --img 640 --batch 16 --epochs 100 --data yellow_zone.yaml --weights yolov5s.pt

步骤 3:部署到 RDK X5

# 模型转换为.bin 格式
hb_mapper model --model-type yolov5 --march x5 --output yellow_zone.bin

步骤 4:代码集成

racing_obstacle_detection_yolo 节点中:

void yolo_callback(const TargetsMsg::SharedPtr msg) {
    for (const auto& target : msg->targets) {
        if (target.type == "yellow_zone") {
            // 获取黄色区域位置
            int center_x = target.rois[0].rect.x_offset + target.rois[0].rect.width / 2;
            int bottom_y = target.rois[0].rect.y_offset + target.rois[0].rect.height;
            
            // 根据黄色区域位置调整控制策略
            handleYellowZone(center_x, bottom_y);
            return;
        }
    }
}

优势

  • :white_check_mark: 不受光照影响
  • :white_check_mark: 无需调 HSV 阈值
  • :white_check_mark: 与避障/巡线共用一个模型
  • :white_check_mark: 社区已有成熟流程

:wrench: 方案 B:OpenCV HSV 优化方案(如果必须用 OpenCV)

如果坚持用 OpenCV,给你抗光照干扰的调参方法

关键代码

import cv2
import numpy as np

def detect_yellow_zone(frame):
    # 1. 转换到 HSV 空间
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 2. 黄色 HSV 范围(需根据实际光照微调)
    # 推荐先用这个范围测试,再微调
    lower_yellow = np.array([20, 100, 100])
    upper_yellow = np.array([35, 255, 255])
    
    # 3. 创建掩码
    mask = cv2.inRange(hsv, lower_yellow, upper_yellow)
    
    # 4. 形态学操作去噪
    kernel = np.ones((5, 5), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    
    # 5. 查找轮廓
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    if contours:
        # 取最大轮廓
        largest_contour = max(contours, key=cv2.contourArea)
        area = cv2.contourArea(largest_contour)
        
        if area > 1000:  # 面积阈值过滤
            # 计算中心点
            M = cv2.moments(largest_contour)
            if M["m00"] != 0:
                cx = int(M["m10"] / M["m00"])
                cy = int(M["m01"] / M["m00"])
                return (cx, cy), largest_contour
    
    return None, None

:magnifying_glass_tilted_left: 快速调参技巧

  1. 使用 trackbar 实时调试
def nothing(x):
    pass

cv2.namedWindow('HSV Tuning')
cv2.createTrackbar('H_min', 'HSV Tuning', 20, 180, nothing)
cv2.createTrackbar('H_max', 'HSV Tuning', 35, 180, nothing)
cv2.createTrackbar('S_min', 'HSV Tuning', 100, 255, nothing)
cv2.createTrackbar('S_max', 'HSV Tuning', 255, 255, nothing)
cv2.createTrackbar('V_min', 'HSV Tuning', 100, 255, nothing)
cv2.createTrackbar('V_max', 'HSV Tuning', 255, 255, nothing)

while True:
    h_min = cv2.getTrackbarPos('H_min', 'HSV Tuning')
    h_max = cv2.getTrackbarPos('H_max', 'HSV Tuning')
    # ... 获取其他参数
    
    mask = cv2.inRange(hsv, (h_min, s_min, v_min), (h_max, s_max, v_max))
    cv2.imshow('Mask', mask)
  1. 光照自适应
# 动态调整 V 阈值
v_avg = np.mean(hsv[:, :, 2])
v_min = max(50, v_avg - 50)
v_max = min(255, v_avg + 50)

:bar_chart: 方案 C:语义分割(适合复杂场景)

如果黄色区域形状不规则,考虑用语义分割模型:


:light_bulb: 实战建议

  1. 优先方案 A(YOLO 检测)

    • 你已经在使用 YOLOv5s 全功能检测
    • 只需增加一个类别,无需额外部署
    • 社区有大量 YOLO 训练部署经验
  2. 如果必须用 OpenCV

    • 先用 trackbar 工具找到最佳 HSV 范围
    • 加入形态学操作去噪
    • 考虑光照补偿算法
  3. 调试技巧

    # 在 Web 界面实时查看掩码效果
    # 修改 websocket.launch.py 订阅 mask 话题
    

:books: 相关资源


建议:既然你已经在用 YOLOv5s 做全功能检测,直接在训练数据里加上黄色区域类别是最快的方案,避免 OpenCV 调参的坑。

需要具体的 YOLO 训练配置或 OpenCV 调试代码可以继续问我!

那在黄色区域转一圈结束后如何保证他接下来从通道离开而不是继续转圈

好问题!这是典型的状态机设计问题。核心是要有任务完成判定 + 状态切换逻辑。


:bullseye: 解决方案:有限状态机 (FSM)

状态设计

enum TaskState {
    STATE_LINE_FOLLOWING,    // 巡线状态
    STATE_YELLOW_ZONE_ENTER, // 进入黄色区域
    STATE_YELLOW_ZONE_ROTATE,// 黄色区域转圈
    STATE_YELLOW_ZONE_EXIT,  // 退出黄色区域
    STATE_TASK_COMPLETE      // 任务完成
};

:memo: 核心代码实现

1. 状态变量定义

// racing_control.cpp 中添加
TaskState current_state_ = STATE_LINE_FOLLOWING;
double rotate_start_time_ = 0.0;
double rotate_duration_ = 3.0;  // 转圈时长(秒)
int rotate_count_ = 0;
int target_rotate_count_ = 1;   // 目标转圈次数
bool yellow_zone_detected_ = false;
int yellow_zone_frame_count_ = 0;  // 黄色区域连续检测帧数

2. 状态机主逻辑

void timer_callback() {
    switch (current_state_) {
        case STATE_LINE_FOLLOWING:
            handleLineFollowing();
            break;
            
        case STATE_YELLOW_ZONE_ENTER:
            handleYellowZoneEnter();
            break;
            
        case STATE_YELLOW_ZONE_ROTATE:
            handleYellowZoneRotate();
            break;
            
        case STATE_YELLOW_ZONE_EXIT:
            handleYellowZoneExit();
            break;
            
        case STATE_TASK_COMPLETE:
            publishTwist(0.0, 0.0);  // 停车
            break;
    }
}

3. 黄色区域检测回调

void yolo_callback(const TargetsMsg::SharedPtr msg) {
    bool yellow_detected = false;
    
    for (const auto& target : msg->targets) {
        if (target.type == "yellow_zone") {
            yellow_detected = true;
            break;
        }
    }
    
    // 状态切换逻辑
    if (current_state_ == STATE_LINE_FOLLOWING) {
        if (yellow_detected) {
            yellow_zone_frame_count_++;
            // 连续检测到 5 帧才确认进入,避免误触发
            if (yellow_zone_frame_count_ >= 5) {
                current_state_ = STATE_YELLOW_ZONE_ENTER;
                yellow_zone_frame_count_ = 0;
                RCLCPP_INFO(this->get_logger(), "进入黄色区域!");
            }
        } else {
            yellow_zone_frame_count_ = 0;  // 重置计数
        }
    } 
    else if (current_state_ == STATE_YELLOW_ZONE_EXIT) {
        // 退出黄色区域后,检测到赛道线则恢复巡线
        if (!yellow_detected && track_line_detected_) {
            current_state_ = STATE_LINE_FOLLOWING;
            RCLCPP_INFO(this->get_logger(), "退出黄色区域,恢复巡线");
        }
    }
}

4. 各状态处理函数

// 进入黄色区域 - 减速并定位中心
void handleYellowZoneEnter() {
    publishTwist(0.15, 0.0);  // 低速前进
    
    // 可以根据黄色区域位置调整车身姿态
    // 这里简化处理,直接进入转圈状态
    current_state_ = STATE_YELLOW_ZONE_ROTATE;
    rotate_start_time_ = this->now().seconds();
    RCLCPP_INFO(this->get_logger(), "开始转圈");
}

// 转圈控制
void handleYellowZoneRotate() {
    double elapsed = this->now().seconds() - rotate_start_time_;
    
    if (elapsed >= rotate_duration_) {
        // 转圈完成,进入退出状态
        current_state_ = STATE_YELLOW_ZONE_EXIT;
        RCLCPP_INFO(this->get_logger(), "转圈完成,准备退出");
    } else {
        // 持续旋转
        publishTwist(0.0, 1.5);  // 原地旋转
    }
}

// 退出黄色区域
void handleYellowZoneExit() {
    publishTwist(0.3, 0.0);  // 低速直线前进
    
    // 退出逻辑在 yolo_callback 中处理
    // 检测到不再在黄色区域内且有赛道线时,切换回巡线状态
}

// 正常巡线
void handleLineFollowing() {
    if (track_line_detected_) {
        LineFollowing(track_center_);
    } else {
        publishTwist(0.2, 0.0);  // 没检测到线时低速直行
    }
}

:gear: 关键参数配置

racing_control.launch.py 中添加:

parameters=[
    # 黄色区域任务参数
    {"yellow_zone_rotate_duration": 3.0},    # 转圈时长(秒)
    {"yellow_zone_confirm_frames": 5},       # 确认进入所需连续帧数
    {"yellow_zone_exit_speed": 0.3},         # 退出速度
    {"yellow_zone_rotate_speed": 1.5},       # 转圈角速度
]

:magnifying_glass_tilted_left: 调试技巧

1. 添加状态日志

RCLCPP_INFO(this->get_logger(), 
    "当前状态:%d, 转圈耗时:%.2f, 黄色区域检测:%d",
    current_state_, elapsed, yellow_zone_detected_);

2. 监控话题

# 查看状态切换日志
ros2 topic echo /rosout | grep "黄色区域"

# 手动测试状态切换
ros2 param set /racing_control current_state 3  # 强制切换到退出状态

3. Web 界面显示当前状态

修改 websocket 节点,发布当前状态到 /task_state 话题,在上位机显示。


:bar_chart: 状态切换流程图

巡线状态 ──[检测到黄色区域 5 帧]──> 进入状态
                                    │
                                    ↓
                              转圈状态 ──[达到设定时间]──> 退出状态
                                    │                        │
                                    │                        ↓
                                    └───────────────────[检测到赛道线]──> 巡线状态

:light_bulb: 进阶优化

问题 解决方案
转圈角度不精确 加 IMU 角速度积分,达到 360° 停止
退出后找不到赛道 退出后先直行 1 秒再搜索赛道线
误触发黄色区域 增加连续检测帧数阈值或面积阈值
需要转多圈 添加 rotate_count_ 计数器

总结:关键是状态机 + 退出条件判定,不要只在检测回调里写逻辑。把任务拆解成明确的状态,每个状态有清晰的进入/退出条件,这样就不会陷入死循环了。

有问题可以把你的状态机代码发出来帮你看看!