机器人学习!(二)ROS-基于Gazebo项目(2)2026/01/12

学习路线:

第一阶段:传统视觉跟踪 ✅ 已完成

第二阶段:深度学习目标检测(YOLO)

第三阶段:模型优化与加速(TensorRT)

第四阶段:Jetson硬件部署

第五阶段:完整机器人AI系统

项目一、基于Gazebo的ROS + OpenCV 实时颜色跟踪机器人

ROS与OpenCV集成

颜色检测算法实现

视觉伺服控制逻辑

Gazebo仿真环境配置

机器人硬件接口调用

复制代码
项目流程

循环开始
  ↓
获取图像 → 检测红色 → 找到最大区域
  ↓
计算目标中心 → 计算误差(error_x)
  ↓
计算角速度:angular.z = -error_x / 300
  ↓
计算线速度:linear.x = max_speed × (1 - 面积占比×5)
  ↓
限制速度范围 → 发布到 /cmd_vel
  ↓
显示图像和调试信息
  ↓
检查退出键


启动

启动 launch 文件
    ↓
启动 Gazebo + TurtleBot3(在空世界中)
    ↓
生成红色球体(在机器人前方2米,离地0.5米)
    ↓
启动你的跟踪节点(订阅摄像头数据)
    ↓
Gazebo摄像头发布图像 → 你的节点处理 → 发布控制指令 → 机器人移动



具体步骤:

第一步:创建一个简单ROS节点,用来显示摄像头图像

1创建视觉功能包

1.1功能包包含代码、配置文件、启动文件

2、具体操作

复制代码
# 进入工作空间源代码目录
cd ~/turtle_ws/src

# 创建功能包,注意依赖项多了roscpp(C++库)和image_transport
catkin_create_pkg robot_vision_cpp roscpp cv_bridge sensor_msgs image_transport

# 进入包目录
cd robot_vision_cpp

# C++源文件通常放在src目录下
mkdir src

# 在src目录下创建show_image.cpp
touch src/show_image.cpp

打开show_image.cpp

复制代码
// 包含必要的头文件
#include <ros/ros.h>                 // ROS C++核心库
#include <image_transport/image_transport.h> // 高效传输图像
#include <cv_bridge/cv_bridge.h>     // ROS和OpenCV之间的转换器
#include <opencv2/highgui/highgui.hpp> // OpenCV显示窗口功能
#include <opencv2/imgproc/imgproc.hpp> // OpenCV图像处理功能(后续会用)

// 定义一个类来管理我们的图像查看器
class ImageViewer
{
private:
    ros::NodeHandle nh_;                    // ROS节点句柄,与ROS系统通信
    image_transport::ImageTransport it_;    // 图像传输对象,高效处理图像
    image_transport::Subscriber image_sub_; // 图像订阅者
    
    // 回调函数:当收到新图像时自动调用
    void imageCallback(const sensor_msgs::ImageConstPtr& msg)
    {
        try
        {
            // 将ROS图像消息转换为OpenCV格式
            // 使用toCvCopy()创建图像的副本,这样我们可以安全地修改它
            // "bgr8"表示颜色编码:蓝-绿-红,每个通道8位
            cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, "bgr8");
            
            // 现在cv_ptr->image就是OpenCV的Mat对象,可以操作了
            
            // 显示图像
            cv::imshow("Camera View", cv_ptr->image);
            
            // 等待10毫秒,并检查是否按了'q'键
            // 如果不调用waitKey,窗口不会更新
            char key = (char)cv::waitKey(10);
            if (key == 'q' || key == 27) // 27是ESC键的ASCII码
            {
                ROS_INFO("用户按了退出键,关闭节点");
                ros::shutdown(); // 优雅地关闭ROS节点
            }
        }
        catch (cv_bridge::Exception& e)
        {
            // 如果转换失败,打印错误信息
            ROS_ERROR("cv_bridge异常: %s", e.what());
            return;
        }
    }

public:
    // 构造函数:初始化节点
    ImageViewer() : it_(nh_)
    {
        // 打印启动信息
        ROS_INFO("🟡 正在启动C++图像查看节点...");
        
        // 创建图像订阅者
        // 参数1:订阅的话题名
        // 参数2:队列大小(缓存的消息数)
        // 参数3:回调函数,使用boost::bind绑定到当前对象的imageCallback方法
        // 参数4:this指针,告诉ROS回调函数属于哪个对象
        image_sub_ = it_.subscribe("/camera/image_raw", 1, 
                                   &ImageViewer::imageCallback, this);
        
        // 创建OpenCV显示窗口
        cv::namedWindow("Camera View", cv::WINDOW_AUTOSIZE);
        
        ROS_INFO("🟢 节点已启动,等待图像数据...");
        ROS_INFO("   话题: /camera/image_raw");
        ROS_INFO("   按'q'或ESC键退出");
    }
    
    // 析构函数:节点关闭时自动调用
    ~ImageViewer()
    {
        // 关闭OpenCV窗口
        cv::destroyWindow("Camera View");
        ROS_INFO("节点关闭");
    }
};

// 主函数:程序入口
int main(int argc, char** argv)
{
    // 初始化ROS节点
    // 参数1:命令行参数个数
    // 参数2:命令行参数数组
    // 参数3:节点名,会显示在rosnode list中
    ros::init(argc, argv, "image_viewer_cpp");
    
    // 创建ImageViewer对象
    ImageViewer viewer;
    
    // ros::spin()进入循环,等待回调函数被调用
    // 它会一直运行,直到收到关闭信号(如Ctrl+C或ros::shutdown())
    ros::spin();
    
    return 0;
}

3、配置编译系统

CMakelist.txt

复制代码
## 依赖项设置(通常在文件开头)
find_package(catkin REQUIRED COMPONENTS
  roscpp
  cv_bridge
  sensor_msgs
  image_transport
)

## 找到OpenCV(需要单独声明)
find_package(OpenCV REQUIRED)

## 包含目录设置
include_directories(
  ${catkin_INCLUDE_DIRS}
  ${OpenCV_INCLUDE_DIRS}
)

## 声明我们要生成的可执行文件
## 格式:add_executable(可执行文件名 源代码文件)
add_executable(show_image_cpp src/show_image.cpp)

## 链接库:告诉编译器需要哪些库文件
target_link_libraries(show_image_cpp
  ${catkin_LIBRARIES}
  ${OpenCV_LIBRARIES}
)

package.xml

复制代码
<!-- 编译时依赖 -->
<build_depend>roscpp</build_depend>
<build_depend>cv_bridge</build_depend>
<build_depend>sensor_msgs</build_depend>
<build_depend>image_transport</build_depend>

<!-- 运行时依赖 -->
<exec_depend>roscpp</exec_depend>
<exec_depend>cv_bridge</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<exec_depend>image_transport</exec_depend>

4、编译测试

复制代码
# 回到工作空间根目录
cd ~/turtle_ws

# 编译整个工作空间
catkin_make

# 如果只想编译这个包,可以指定
# catkin_make --pkg robot_vision_cpp

测试图像源(生成图像源 代表 虚拟摄像头 获取到的画面)

复制代码
# 1. 创建脚本文件
mkdir -p ~/ros_test
cd ~/ros_test

# 2. 创建Python脚本
cat > virtual_cam.py << 'EOF'
#!/usr/bin/env python3
import rospy
import numpy as np
import cv2
from sensor_msgs.msg import Image
from cv_bridge import CvBridge

rospy.init_node('virtual_camera')
pub = rospy.Publisher('/camera/image_raw', Image, queue_size=10)
bridge = CvBridge()
rate = rospy.Rate(30)  # 30帧/秒

width, height = 640, 480
print("✅ 虚拟摄像头已启动")
print("📡 发布话题: /camera/image_raw")
print("🖼️  分辨率: 640x480")

frame_count = 0
while not rospy.is_shutdown():
    # 创建蓝色渐变背景
    img = np.zeros((height, width, 3), dtype=np.uint8)
    
    # 水平渐变:左边蓝色,右边绿色
    for col in range(width):
        blue_intensity = int((1.0 - col/width) * 255)
        green_intensity = int((col/width) * 255)
        img[:, col, 0] = blue_intensity    # 蓝色通道
        img[:, col, 1] = green_intensity   # 绿色通道
    
    # 添加一个移动的红色圆形
    center_x = int(width/2 + 100 * np.sin(frame_count * 0.1))
    center_y = int(height/2 + 80 * np.cos(frame_count * 0.07))
    cv2.circle(img, (center_x, center_y), 30, (0, 0, 255), -1)  # 红色实心圆
    
    # 添加一个移动的黄色矩形
    rect_x = int(100 + 50 * np.sin(frame_count * 0.05))
    rect_y = int(100 + 40 * np.cos(frame_count * 0.08))
    cv2.rectangle(img, (rect_x, rect_y), (rect_x+60, rect_y+40), (0, 255, 255), 2)  # 黄色边框
    
    # 添加说明文字
    cv2.putText(img, 'ROS Virtual Camera', (30, 30), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
    cv2.putText(img, 'Red circle and yellow rectangle move', (30, 70),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
    cv2.putText(img, f'Frame: {frame_count}', (30, 100),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200, 200, 200), 1)
    
    # 转换为ROS消息
    msg = bridge.cv2_to_imgmsg(img, encoding='bgr8')
    msg.header.stamp = rospy.Time.now()
    msg.header.frame_id = 'virtual_camera'
    pub.publish(msg)
    
    frame_count += 1
    rate.sleep()

cv2.destroyAllWindows()
EOF

# 3. 设置执行权限
chmod +x virtual_cam.py

验证调试

复制代码
roscore

cd ~/ros_test
python3 virtual_cam.py

# 列出所有话题,应该看到 /camera/image_raw
rostopic list

# 查看图像消息的发布频率
rostopic hz /camera/image_raw

cd ~/turtle_ws
source devel/setup.bash
rosrun robot_vision_cpp show_image_cpp

第二步、检测图像中的红色物体

  1. 找到图像中所有红色的区域

  2. 用绿色框标记出来

  3. 在终端显示红色物体的位置和大小

    cd ~/turtle_ws/src/robot_vision_cpp/src
    touch color_detector.cpp

    // 颜色检测节点 - 检测图像中的红色物体
    #include <ros/ros.h>
    #include <image_transport/image_transport.h>
    #include <cv_bridge/cv_bridge.h>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp> // 新增:图像处理功能

    class ColorDetector
    {
    private:
    ros::NodeHandle nh_;
    image_transport::ImageTransport it_;
    image_transport::Subscriber image_sub_;

    复制代码
     // 新增:用于显示处理结果的窗口
     const std::string ORIGINAL_WINDOW = "Original Image";
     const std::string MASK_WINDOW = "Red Mask";
     const std::string RESULT_WINDOW = "Detection Result";
     
     // 颜色范围定义(HSV颜色空间)
     // 注意:OpenCV中H的范围是0-179(不是0-360)
     cv::Scalar lower_red1 = cv::Scalar(0, 100, 100);    // 红色范围1(H: 0-10)
     cv::Scalar upper_red1 = cv::Scalar(10, 255, 255);
     cv::Scalar lower_red2 = cv::Scalar(160, 100, 100);  // 红色范围2(H: 160-180)
     cv::Scalar upper_red2 = cv::Scalar(180, 255, 255);
     
     // 图像回调函数
     void imageCallback(const sensor_msgs::ImageConstPtr& msg)
     {
         ROS_INFO_ONCE("开始处理图像数据...");
         
         try
         {
             // 1. 转换图像格式:ROS消息 -> OpenCV图像
             cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, "bgr8");
             cv::Mat original = cv_ptr->image;  // 原始图像
             
             // 2. 转换到HSV颜色空间(对光照变化更鲁棒)
             cv::Mat hsv_image;
             cv::cvtColor(original, hsv_image, cv::COLOR_BGR2HSV);
             
             // 3. 创建红色掩码(mask)
             cv::Mat mask1, mask2, red_mask;
             
             // 检测红色范围1(0-10度)
             cv::inRange(hsv_image, lower_red1, upper_red1, mask1);
             
             // 检测红色范围2(160-180度)  
             cv::inRange(hsv_image, lower_red2, upper_red2, mask2);
             
             // 合并两个范围的掩码
             cv::bitwise_or(mask1, mask2, red_mask);
             
             // 4. 形态学操作:去除噪声,填充空洞
             cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
             cv::morphologyEx(red_mask, red_mask, cv::MORPH_OPEN, kernel);   // 开运算:去噪
             cv::morphologyEx(red_mask, red_mask, cv::MORPH_CLOSE, kernel);  // 闭运算:填充
             
             // 5. 寻找红色区域的轮廓
             std::vector<std::vector<cv::Point>> contours;
             cv::findContours(red_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
             
             // 6. 在原始图像上标记检测结果
             cv::Mat result = original.clone();
             
             // 如果没有找到红色区域
             if (contours.empty()) {
                 cv::putText(result, "No red object detected", cv::Point(30, 50),
                            cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 0, 255), 2);
             } 
             else {
                 // 遍历所有轮廓
                 for (size_t i = 0; i < contours.size(); i++) {
                     // 计算轮廓的面积,忽略太小的噪点
                     double area = cv::contourArea(contours[i]);
                     if (area < 500) continue;  // 面积小于500像素的忽略
                     
                     // 获取轮廓的外接矩形
                     cv::Rect boundRect = cv::boundingRect(contours[i]);
                     
                     // 绘制绿色矩形框
                     cv::rectangle(result, boundRect, cv::Scalar(0, 255, 0), 2);
                     
                     // 在框上方显示面积信息
                     std::string text = "Red Area: " + std::to_string((int)area);
                     cv::putText(result, text, 
                                cv::Point(boundRect.x, boundRect.y - 5),
                                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);
                     
                     // 在终端输出检测信息
                     ROS_INFO("检测到红色物体 %zu: 位置(%d, %d), 大小(%dx%d), 面积%.0f像素", 
                             i+1, boundRect.x, boundRect.y, 
                             boundRect.width, boundRect.height, area);
                 }
                 
                 // 显示检测到的物体数量
                 std::string count_text = "Red objects: " + std::to_string(contours.size());
                 cv::putText(result, count_text, cv::Point(30, 50),
                            cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 255, 0), 2);
             }
             
             // 7. 显示所有窗口
             cv::imshow(ORIGINAL_WINDOW, original);
             cv::imshow(MASK_WINDOW, red_mask);
             cv::imshow(RESULT_WINDOW, result);
             
             // 8. 检查按键
             char key = (char)cv::waitKey(10);
             if (key == 'q' || key == 27) {
                 ROS_INFO("用户按了退出键,关闭节点");
                 ros::shutdown();
             }
         }
         catch (cv_bridge::Exception& e)
         {
             ROS_ERROR("cv_bridge异常: %s", e.what());
         }
     }

    public:
    ColorDetector() : it_(nh_)
    {
    ROS_INFO("🟡 启动红色物体检测节点...");

    复制代码
         // 订阅图像话题
         image_sub_ = it_.subscribe("/camera/image_raw", 1, 
                                    &ColorDetector::imageCallback, this);
         
         // 创建显示窗口
         cv::namedWindow(ORIGINAL_WINDOW, cv::WINDOW_AUTOSIZE);
         cv::namedWindow(MASK_WINDOW, cv::WINDOW_AUTOSIZE);
         cv::namedWindow(RESULT_WINDOW, cv::WINDOW_AUTOSIZE);
         
         ROS_INFO("🟢 节点已启动");
         ROS_INFO("   正在监听话题: /camera/image_raw");
         ROS_INFO("   显示3个窗口:");
         ROS_INFO("     1. Original Image - 原始图像");
         ROS_INFO("     2. Red Mask - 红色区域掩码(白色=红色区域)");
         ROS_INFO("     3. Detection Result - 检测结果(绿色框标记)");
         ROS_INFO("   按'q'或ESC键退出");
     }
     
     ~ColorDetector()
     {
         cv::destroyWindow(ORIGINAL_WINDOW);
         cv::destroyWindow(MASK_WINDOW);
         cv::destroyWindow(RESULT_WINDOW);
         ROS_INFO("节点关闭");
     }

    };

    int main(int argc, char** argv)
    {
    ros::init(argc, argv, "color_detector");
    ColorDetector detector;
    ros::spin();
    return 0;
    }

修改CMakelist.txt

复制代码
# 在现有内容后添加

# 添加新的可执行文件
add_executable(color_detector src/color_detector.cpp)

# 链接同样的库
target_link_libraries(color_detector
  ${catkin_LIBRARIES}
  ${OpenCV_LIBRARIES}
)

核心概念总结

概念 作用 类比
HSV颜色空间 分离颜色和亮度,对光照变化更鲁棒 像把"红色"从"亮红色"和"暗红色"中抽象出来
掩码(Mask) 二值图像,标记感兴趣区域 像用红色荧光笔在照片上标出所有红色物体
形态学操作 去除噪声,连接断裂区域 像用橡皮擦掉小污点,用笔连接断线
轮廓检测 找到白色区域的边界 像沿着荧光笔标记的边缘画线
外接矩形 用矩形框住检测到的物体 像给标记的区域画个绿色方框

第三步、让机器人自动跟踪检测到的红色物体

1,创建视觉跟踪节点

目标:

复制代码
如果红色物体在图像左侧 → 机器人左转
如果红色物体在图像右侧 → 机器人右转
如果红色物体在图像中心 → 机器人直行
复制代码
cd ~/turtle_ws/src/robot_vision_cpp/src
touch red_tracker.cpp

// 红色物体跟踪节点
#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <geometry_msgs/Twist.h>  // 新增:速度控制消息

class RedTracker
{
private:
    ros::NodeHandle nh_;
    image_transport::ImageTransport it_;
    image_transport::Subscriber image_sub_;
    
    // 新增:速度发布者
    ros::Publisher cmd_vel_pub_;
    
    // 窗口名称
    const std::string WINDOW_NAME = "Red Tracker";
    
    // 颜色范围(和之前一样)
    cv::Scalar lower_red1 = cv::Scalar(0, 100, 100);
    cv::Scalar upper_red1 = cv::Scalar(10, 255, 255);
    cv::Scalar lower_red2 = cv::Scalar(160, 100, 100);
    cv::Scalar upper_red2 = cv::Scalar(180, 255, 255);
    
    // 控制参数
    double max_linear_speed = 0.2;   // 最大线速度 m/s
    double max_angular_speed = 1.0;  // 最大角速度 rad/s
    int image_center_x = 320;        // 图像中心X坐标(640x480)
    
    void imageCallback(const sensor_msgs::ImageConstPtr& msg)
    {
        try
        {
            // 1. 转换图像
            cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, "bgr8");
            cv::Mat original = cv_ptr->image;
            cv::Mat hsv_image;
            cv::cvtColor(original, hsv_image, cv::COLOR_BGR2HSV);
            
            // 2. 创建红色掩码
            cv::Mat mask1, mask2, red_mask;
            cv::inRange(hsv_image, lower_red1, upper_red1, mask1);
            cv::inRange(hsv_image, lower_red2, upper_red2, mask2);
            cv::bitwise_or(mask1, mask2, red_mask);
            
            // 3. 形态学处理
            cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
            cv::morphologyEx(red_mask, red_mask, cv::MORPH_OPEN, kernel);
            cv::morphologyEx(red_mask, red_mask, cv::MORPH_CLOSE, kernel);
            
            // 4. 寻找轮廓
            std::vector<std::vector<cv::Point>> contours;
            cv::findContours(red_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
            
            // 5. 初始化控制指令
            geometry_msgs::Twist cmd_vel;
            cmd_vel.linear.x = 0.0;
            cmd_vel.angular.z = 0.0;
            
            // 6. 如果没有检测到红色物体,停止
            if (contours.empty()) {
                ROS_WARN_THROTTLE(1, "没有检测到红色物体,机器人停止");
                cmd_vel_pub_.publish(cmd_vel);
                
                // 显示提示
                cv::putText(original, "No red object - STOP", cv::Point(30, 50),
                           cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 0, 255), 2);
                cv::imshow(WINDOW_NAME, original);
                cv::waitKey(10);
                return;
            }
            
            // 7. 找到最大的红色区域
            double max_area = 0;
            cv::Rect target_rect;
            
            for (size_t i = 0; i < contours.size(); i++) {
                double area = cv::contourArea(contours[i]);
                if (area > max_area && area > 500) {
                    max_area = area;
                    target_rect = cv::boundingRect(contours[i]);
                }
            }
            
            // 8. 如果找到有效目标,计算控制指令
            if (max_area > 0) {
                // 计算目标的中心点
                int target_center_x = target_rect.x + target_rect.width / 2;
                int target_center_y = target_rect.y + target_rect.height / 2;
                
                // 绘制目标框和中心点
                cv::rectangle(original, target_rect, cv::Scalar(0, 255, 0), 2);
                cv::circle(original, cv::Point(target_center_x, target_center_y), 
                          5, cv::Scalar(0, 255, 255), -1);  // 黄色中心点
                
                // 绘制图像中心线
                cv::line(original, cv::Point(image_center_x, 0), 
                        cv::Point(image_center_x, 480), cv::Scalar(255, 0, 0), 2);
                
                // 9. 【核心控制逻辑】计算误差并生成控制指令
                //    误差 = 目标中心X - 图像中心X
                double error_x = target_center_x - image_center_x;
                
                // 比例控制:角速度与误差成正比
                cmd_vel.angular.z = -error_x / 500.0;  // 比例系数1/500
                
                // 线速度:目标越大(越近),速度越慢
                double normalized_area = max_area / (640.0 * 480.0);  // 面积占比
                cmd_vel.linear.x = max_linear_speed * (1.0 - normalized_area * 5.0);
                cmd_vel.linear.x = std::max(0.0, cmd_vel.linear.x);  // 不小于0
                
                // 限制角速度范围
                if (cmd_vel.angular.z > max_angular_speed)
                    cmd_vel.angular.z = max_angular_speed;
                if (cmd_vel.angular.z < -max_angular_speed)
                    cmd_vel.angular.z = -max_angular_speed;
                
                // 10. 显示信息
                std::string info = "Target: (" + std::to_string(target_center_x) + 
                                  ", " + std::to_string(target_center_y) + ")";
                cv::putText(original, info, cv::Point(30, 30),
                           cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(255, 255, 255), 2);
                
                std::string control = "Control: lin=" + std::to_string(cmd_vel.linear.x).substr(0,4) +
                                     " ang=" + std::to_string(cmd_vel.angular.z).substr(0,4);
                cv::putText(original, control, cv::Point(30, 60),
                           cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(255, 255, 255), 2);
                
                std::string error_str = "Error: " + std::to_string((int)error_x);
                cv::putText(original, error_str, cv::Point(30, 90),
                           cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(255, 255, 255), 2);
                
                ROS_INFO("目标位置: (%d, %d), 误差: %.1f, 控制: lin=%.2f, ang=%.2f",
                        target_center_x, target_center_y, error_x, 
                        cmd_vel.linear.x, cmd_vel.angular.z);
            }
            
            // 11. 发布控制指令
            cmd_vel_pub_.publish(cmd_vel);
            
            // 12. 显示图像
            cv::imshow(WINDOW_NAME, original);
            
            // 检查退出键
            char key = (char)cv::waitKey(10);
            if (key == 'q' || key == 27) {
                // 停止机器人后退出
                cmd_vel.linear.x = 0;
                cmd_vel.angular.z = 0;
                cmd_vel_pub_.publish(cmd_vel);
                ROS_INFO("停止机器人并退出");
                ros::shutdown();
            }
        }
        catch (cv_bridge::Exception& e)
        {
            ROS_ERROR("cv_bridge异常: %s", e.what());
        }
    }

public:
    RedTracker() : it_(nh_)
    {
        ROS_INFO("🟡 启动红色物体跟踪节点...");
        
        // 订阅图像
        image_sub_ = it_.subscribe("/camera/image_raw", 1, 
                                   &RedTracker::imageCallback, this);
        
        // 初始化速度发布者(重要:控制TurtleBot的话题)
        cmd_vel_pub_ = nh_.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
        
        // 创建显示窗口
        cv::namedWindow(WINDOW_NAME, cv::WINDOW_AUTOSIZE);
        
        ROS_INFO("🟢 跟踪节点已启动");
        ROS_INFO("   订阅图像: /camera/image_raw");
        ROS_INFO("   发布控制: /cmd_vel");
        ROS_INFO("   按'q'或ESC键退出");
        ROS_INFO("");
        ROS_INFO("控制逻辑说明:");
        ROS_INFO("  1. 蓝色竖线: 图像中心线");
        ROS_INFO("  2. 绿色框: 检测到的红色物体");
        ROS_INFO("  3. 黄点: 物体中心点");
        ROS_INFO("  4. 误差 = 黄点X坐标 - 蓝线X坐标");
        ROS_INFO("  5. 误差>0: 物体在右侧 → 机器人右转(负角速度)");
        ROS_INFO("  6. 误差<0: 物体在左侧 → 机器人左转(正角速度)");
    }
    
    ~RedTracker()
    {
        cv::destroyWindow(WINDOW_NAME);
        
        // 节点关闭前停止机器人
        geometry_msgs::Twist stop_cmd;
        stop_cmd.linear.x = 0;
        stop_cmd.angular.z = 0;
        cmd_vel_pub_.publish(stop_cmd);
        
        ROS_INFO("跟踪节点关闭,机器人已停止");
    }
};

int main(int argc, char** argv)
{
    ros::init(argc, argv, "red_tracker");
    RedTracker tracker;
    ros::spin();
    return 0;
}

第四步、在Gazebo中仿真,将视觉跟踪系统部署到TurtleBot3中

1。在世界文件中添加红色球体

复制代码
cd ~/turtle_ws/src/robot_vision_cpp
mkdir launch
touch launch/red_tracking_sim.launch


<launch>
    <!-- 红色物体跟踪仿真系统 -->
    
    <!-- 设置TurtleBot3型号 -->
    <arg name="model" default="waffle_pi" />
    
    <!-- 启动Gazebo空世界 -->
    <include file="$(find turtlebot3_gazebo)/launch/turtlebot3_empty_world.launch">
        <arg name="model" value="$(arg model)" />
        <!-- 注意:参数名可能是world_file而不是world_name -->
        <!-- 我们先注释掉,用默认空世界 -->
        <!-- <arg name="world_file" value="$(find turtlebot3_gazebo)/worlds/empty.world" /> -->
    </include>
    
    <!-- 在Gazebo中生成红色球体(作为跟踪目标) -->
    <node name="spawn_red_ball" pkg="gazebo_ros" type="spawn_model" 
          args="-file $(find turtlebot3_gazebo)/models/red_ball/model.sdf
                -sdf
                -model red_ball
                -x 2.0
                -y 0.0
                -z 0.5"
          output="screen" />
    
    <!-- 启动你的红色跟踪节点 -->
    <node name="red_tracker" pkg="robot_vision_cpp" type="red_tracker"
          output="screen">
        <!-- 重映射话题:仿真摄像头的话题名不同 -->
        <remap from="/camera/image_raw" to="/camera/rgb/image_raw" />
    </node>
    
    <!-- 可选:启动RViz观察 -->
    <!-- <node name="rviz" pkg="rviz" type="rviz"
          args="-d $(find turtlebot3_gazebo)/rviz/turtlebot3_gazebo_model.rviz" /> -->
    
</launch>

启动Gazebo,并若无红色球体,则手动添加红色方块

复制代码
# 终端1
export TURTLEBOT3_MODEL=waffle_pi
roslaunch turtlebot3_gazebo turtlebot3_empty_world.launch

# 1. 先删除现有模型(如果存在)
rosservice call /gazebo/delete_model '{model_name: red_ball}'

# 2. 重新生成一个更明显的红色立方体(不是球体)
echo '<?xml version="1.0"?>
<sdf version="1.6">
<model name="red_box">
<pose>1.5 0.0 0.5 0 0 0</pose>
<link name="link">
<visual name="visual">
<geometry><box><size>0.6 0.6 0.6</size></box></geometry>
<material>
  <ambient>1 0 0 1</ambient>
  <diffuse>1 0 0 1</diffuse>
  <emissive>0.5 0 0 1</emissive>  <!-- 自发光,更明显 -->
</material>
</visual>
<collision name="collision">
<geometry><box><size>0.6 0.6 0.6</size></box></geometry>
</collision>
</link>
</model>
</sdf>' | rosrun gazebo_ros spawn_model -sdf -stdin -model red_box

运行跟踪节点

复制代码
# 终端3
# 先测试摄像头是否工作
rosrun image_view image_view image:=/camera/rgb/image_raw

# 终端4
cd ~/turtle_ws
source devel/setup.bash
export TURTLEBOT3_MODEL=waffle_pi

# 运行跟踪节点
rosrun robot_vision_cpp red_tracker /camera/image_raw:=/camera/rgb/image_raw

手动移动方块,机器人会跟随移动,小项目已完成。

相关推荐
Freshman小白2 小时前
《智能制造系统》网课答案
学习·答案·网课答案
副露のmagic3 小时前
更弱智的算法学习 day34
python·学习
写点什么呢3 小时前
AD21安装激活
学习
wdfk_prog4 小时前
[Linux]学习笔记系列 -- bits
linux·笔记·学习
求梦8204 小时前
JVM学习
jvm·学习
星火开发设计4 小时前
C++ multiset 全面解析与实战指南
开发语言·数据结构·c++·学习·set·知识
am心4 小时前
学习笔记-菜品接口-菜品分页查询
笔记·学习
丝斯20114 小时前
AI学习笔记整理(44)——大规模预训练模型数据处理管道Pipeline
人工智能·笔记·学习
藦卡机器人5 小时前
安徽做包装机器人机械臂的品牌有哪些?
机器人