机器人学习!(二)ROS-基于Gazebo项目-YOLO(3)2026/01/13

项目二、基于ROS+YOLO的目标检测与跟踪

第一步、环境配置

安装conda并换源

复制代码
# 1. 下载Miniconda(国内镜像,速度快)
cd ~
wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh

# 2. 安装(静默安装到用户目录)
bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda

# 3. 初始化conda(会修改.bashrc)
$HOME/miniconda/bin/conda init bash

# 4. 使配置立即生效
source ~/.bashrc

# 5. 验证安装
conda --version

# 配置conda使用清华源(下载速度快)
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --set show_channel_urls yes

# 可选:移除默认源(避免混合使用)
conda config --remove channels defaults

创建专用环境

复制代码
# 创建Python 3.8环境(与ROS Noetic兼容)
conda create -n ros_yolo python=3.8 -y

# 激活环境
conda activate ros_yolo

# 验证Python版本
python --version
# 应该显示 Python 3.8.x

创建空间过程中,若出现如下,表示要接受服务条款:

复制代码
CondaToSNonInteractiveError: Terms of Service have not been accepted for the following channels. Please accept or remove them before proceeding:
    - https://repo.anaconda.com/pkgs/main
    - https://repo.anaconda.com/pkgs/r
。。。。
之类的
-----------------------------------------------------------------------------------
# 接受主通道的服务条款
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r

# 如果你也配置了其他通道,可能需要接受
conda tos accept --override-channels --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/

关键:安装PyTorch的 python3.8 兼容版本,及yolo 、opencv及依赖性

复制代码
使用conda安装
conda install pytorch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 cpuonly -c pytorch -y

# 验证安装
python -c "import torch; print(f'PyTorch版本: {torch.__version__}')"

# 1. 首先安装ROS Python包(必须用pip)
pip install rospkg catkin_pkg empy

# 2. 安装YOLO
pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple

# 3. 安装OpenCV和其他依赖
pip install opencv-python numpy scipy matplotlib pillow

# 4. 可选:安装其他有用的包
pip install pandas seaborn tqdm defusedxml rospkg catkin_pkg lxml netifaces gnupg pycryptodomex

测试安装是否成功

复制代码
touch ~/test_hybrid_env.py

conda activate ros_yolo
python ~/test_hybrid_env.py


test_hybrid_env.py内容如下:

#!/usr/bin/env python3
print("=" * 60)
print("混合环境测试 (conda PyTorch + pip 其他包)")
print("=" * 60)

import sys
print(f"Python: {sys.version[:50]}")
print(f"路径: {sys.executable}")

# 测试conda安装的PyTorch
try:
    import torch
    print(f"✓ PyTorch: {torch.__version__} (conda安装)")
except Exception as e:
    print(f"✗ PyTorch: {e}")

# 测试pip安装的包
packages = [
    ("ultralytics", "YOLO"),
    ("rospkg", "ROS"),
    ("cv2", "OpenCV"),
    ("numpy", "NumPy"),
]

for import_name, display_name in packages:
    try:
        if import_name == "cv2":
            import cv2
            version = cv2.__version__
        elif import_name == "ultralytics":
            from ultralytics import YOLO
            version = "导入成功"
        else:
            exec(f"import {import_name}")
            exec(f"version = {import_name}.__version__")
        print(f"✓ {display_name}: {version}")
    except Exception as e:
        print(f"✗ {display_name}: {e}")

print("=" * 60)

第二步、开始运行YOLO

创建python功能包与节点(第一次运行会自动下载YOLOv8)

复制代码
# 激活conda环境(如果还没激活)
conda activate ros_yolo

#新建功能包
catkin_create_pkg robot_vision rospy std_msgs sensor_msgs geometry_msgs image_transport cv_bridge dynamic_reconfigure

# 进入工作空间
cd ~/turtle_ws/src/robot_vision

# 创建scripts目录(如果不存在)
mkdir -p scripts

# 创建YOLO检测节点
cat > scripts/yolo_detector.py << 'EOF'
#!/usr/bin/env python3
"""
YOLOv8实时目标检测与机器人跟踪节点
使用ROS Noetic + Python 3.8 + PyTorch 1.13.1
"""

import rospy
import cv2
import numpy as np
from sensor_msgs.msg import Image
from geometry_msgs.msg import Twist
from cv_bridge import CvBridge
from ultralytics import YOLO
import time

class YOLOTracker:
    def __init__(self):
        # 初始化ROS节点
        rospy.init_node('yolo_tracker', anonymous=True)
        
        # 创建CV桥接器
        self.bridge = CvBridge()
        
        # 加载YOLOv8模型(第一次运行会自动下载)
        rospy.loginfo("正在加载YOLOv8模型...")
        try:
            self.model = YOLO('yolov8n.pt')  # nano版本,最小最快
            rospy.loginfo("✓ YOLOv8模型加载成功!")
        except Exception as e:
            rospy.logerr(f"模型加载失败: {e}")
            rospy.signal_shutdown("模型加载失败")
        
        # 订阅摄像头话题
        self.image_sub = rospy.Subscriber(
            "/camera/rgb/image_raw", 
            Image, 
            self.image_callback,
            queue_size=1
        )
        
        # 发布控制指令
        self.cmd_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=10)
        
        # 目标类别(COCO数据集的前10个类别)
        self.target_classes = {
            0: "person",    # 人
            1: "bicycle",   # 自行车
            2: "car",       # 汽车
            3: "motorcycle", # 摩托车
            5: "bus",       # 巴士
            7: "truck",     # 卡车
            9: "traffic light", # 交通灯
            11: "stop sign", # 停止标志
            15: "cat",      # 猫
            16: "dog"       # 狗
        }
        
        # 控制参数
        self.target_width_ratio = 0.25  # 目标理想宽度比例
        self.max_linear_speed = 0.15    # 最大线速度
        self.max_angular_speed = 0.4    # 最大角速度
        
        # 性能统计
        self.frame_count = 0
        self.detection_count = 0
        self.start_time = time.time()
        
        rospy.loginfo("YOLO跟踪器初始化完成!")
        rospy.loginfo(f"跟踪目标: {list(self.target_classes.values())[:5]}...")
    
    def image_callback(self, msg):
        """处理摄像头图像"""
        self.frame_count += 1
        
        try:
            # 转换ROS图像为OpenCV格式
            cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8")
        except Exception as e:
            rospy.logerr(f"图像转换错误: {e}")
            return
        
        # 运行YOLO检测
        start_time = time.time()
        results = self.model(cv_image, conf=0.5, verbose=False)
        inference_time = time.time() - start_time
        
        # 处理检测结果
        display_image, cmd_vel = self.process_detections(cv_image, results)
        
        # 发布控制指令
        self.cmd_pub.publish(cmd_vel)
        
        # 显示性能信息
        fps = 1.0 / inference_time if inference_time > 0 else 0
        self.display_info(display_image, fps, inference_time)
        
        # 显示图像
        cv2.imshow("YOLOv8 Detection - TurtleBot3", display_image)
        
        # 按'q'退出
        if cv2.waitKey(1) & 0xFF == ord('q'):
            rospy.signal_shutdown("用户按下q键")
    
    def process_detections(self, image, results):
        """处理检测结果并生成控制指令"""
        display = image.copy()
        height, width = display.shape[:2]
        
        # 初始化控制指令
        cmd_vel = Twist()
        
        # 如果没有检测结果,缓慢旋转搜索
        if results[0].boxes is None or len(results[0].boxes) == 0:
            cmd_vel.angular.z = 0.3
            return display, cmd_vel
        
        # 提取检测框
        boxes = results[0].boxes.cpu().numpy()
        
        # 寻找最佳跟踪目标
        best_target = None
        min_center_distance = float('inf')
        image_center = (width // 2, height // 2)
        
        for box in boxes:
            class_id = int(box.cls[0])
            confidence = float(box.conf[0])
            
            # 只关注我们定义的目标类别
            if class_id in self.target_classes:
                x1, y1, x2, y2 = map(int, box.xyxy[0])
                center_x = (x1 + x2) // 2
                center_y = (y1 + y2) // 2
                
                # 计算到图像中心的距离
                distance = np.sqrt((center_x - image_center[0])**2 + 
                                  (center_y - image_center[1])**2)
                
                # 选择最接近中心的目标
                if distance < min_center_distance:
                    min_center_distance = distance
                    best_target = {
                        'bbox': (x1, y1, x2, y2),
                        'center': (center_x, center_y),
                        'class_id': class_id,
                        'class_name': self.target_classes[class_id],
                        'confidence': confidence
                    }
                    self.detection_count += 1
        
        # 绘制所有检测框
        for box in boxes:
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            class_id = int(box.cls[0])
            confidence = float(box.conf[0])
            
            # 颜色:绿色为普通检测,红色为跟踪目标
            if best_target and (x1, y1, x2, y2) == best_target['bbox']:
                color = (0, 0, 255)  # 红色
                thickness = 3
            else:
                color = (0, 255, 0)  # 绿色
                thickness = 1
            
            # 绘制边界框
            cv2.rectangle(display, (x1, y1), (x2, y2), color, thickness)
            
            # 显示标签(只显示高置信度或跟踪目标)
            if confidence > 0.7 or (best_target and (x1, y1, x2, y2) == best_target['bbox']):
                label = f"{self.target_classes.get(class_id, f'class_{class_id}')} {confidence:.2f}"
                cv2.putText(display, label, (x1, y1-10),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
        
        # 如果有跟踪目标,绘制额外信息和生成控制
        if best_target:
            x1, y1, x2, y2 = best_target['bbox']
            center_x, center_y = best_target['center']
            
            # 绘制十字线
            cv2.line(display, (center_x, 0), (center_x, height), (0, 255, 255), 1)
            cv2.line(display, (0, center_y), (width, center_y), (0, 255, 255), 1)
            
            # 绘制中心点
            cv2.circle(display, (center_x, center_y), 8, (0, 255, 255), -1)
            
            # 计算控制指令
            cmd_vel = self.calculate_control(best_target, width, height)
            
            # 显示跟踪状态
            status = f"TRACKING: {best_target['class_name']}"
            cv2.putText(display, status, (10, 30),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
        
        return display, cmd_vel
    
    def calculate_control(self, target, img_width, img_height):
        """根据目标位置计算控制指令"""
        cmd = Twist()
        
        x1, y1, x2, y2 = target['bbox']
        center_x, _ = target['center']
        
        # 计算水平误差
        error_x = center_x - img_width // 2
        
        # 计算目标大小
        target_width = x2 - x1
        target_height = y2 - y1
        target_area = target_width * target_height
        image_area = img_width * img_height
        area_ratio = target_area / image_area
        
        # 角速度控制(比例控制)
        cmd.angular.z = -0.002 * error_x
        cmd.angular.z = max(-self.max_angular_speed, 
                           min(self.max_angular_speed, cmd.angular.z))
        
        # 线速度控制(基于目标大小)
        if area_ratio < self.target_width_ratio * 0.7:
            # 目标太小,前进
            cmd.linear.x = self.max_linear_speed * 0.7
        elif area_ratio > self.target_width_ratio * 1.3:
            # 目标太大,后退
            cmd.linear.x = -self.max_linear_speed * 0.3
        else:
            # 大小合适,停止
            cmd.linear.x = 0.0
        
        # 日志信息
        rospy.loginfo(f"目标: {target['class_name']:12s} | "
                     f"误差: {error_x:4d} | "
                     f"角速度: {cmd.angular.z:6.3f} | "
                     f"线速度: {cmd.linear.x:6.3f} | "
                     f"大小: {area_ratio:.3f}")
        
        return cmd
    
    def display_info(self, image, fps, inference_time):
        """在图像上显示性能信息"""
        height, width = image.shape[:2]
        
        # 绘制图像中心点
        cv2.circle(image, (width//2, height//2), 5, (255, 0, 0), -1)
        
        # 显示性能统计
        info_lines = [
            f"FPS: {fps:.1f}",
            f"推理时间: {inference_time*1000:.1f}ms",
            f"帧数: {self.frame_count}",
            f"检测数: {self.detection_count}",
            f"运行时间: {time.time()-self.start_time:.0f}s"
        ]
        
        for i, line in enumerate(info_lines):
            cv2.putText(image, line, (width - 200, 30 + i*25),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    
    def shutdown(self):
        """节点关闭时的清理工作"""
        cv2.destroyAllWindows()
        
        # 输出统计信息
        total_time = time.time() - self.start_time
        rospy.loginfo("=" * 50)
        rospy.loginfo("YOLO跟踪器关闭")
        rospy.loginfo(f"总帧数: {self.frame_count}")
        rospy.loginfo(f"总检测数: {self.detection_count}")
        rospy.loginfo(f"总运行时间: {total_time:.1f}秒")
        rospy.loginfo(f"平均FPS: {self.frame_count/total_time:.1f}")
        rospy.loginfo("=" * 50)

if __name__ == '__main__':
    try:
        tracker = YOLOTracker()
        rospy.on_shutdown(tracker.shutdown)
        rospy.spin()
    except rospy.ROSInterruptException:
        pass
    finally:
        cv2.destroyAllWindows()
EOF

设置权限:

复制代码
chmod +x ~/turtle_ws/src/robot_vision/scripts/yolo_detector.py

创建启动文件

复制代码
# 创建launch目录
mkdir -p ~/turtle_ws/src/robot_vision/launch

# 创建启动文件
cat > ~/turtle_ws/src/robot_vision/launch/yolo_tracking.launch << 'EOF'
<?xml version="1.0"?>
<launch>
  <!-- YOLOv8目标检测与跟踪 -->
  
  <!-- 设置TurtleBot3型号 -->
  <arg name="model" default="waffle_pi" />
  
  <!-- 启动Gazebo仿真(使用house世界,有更多物体) -->
  <include file="$(find turtlebot3_gazebo)/launch/turtlebot3_house.launch">
    <arg name="model" value="$(arg model)" />
  </include>
  
  <!-- 启动YOLO检测节点 -->
  <node name="yolo_detector" 
        pkg="robot_vision" 
        type="yolo_detector.py" 
        output="screen"
        respawn="true">
    
    <!-- 重映射摄像头话题 -->
    <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>
EOF

修改CMakelist.txt文件,新增

复制代码
catkin_install_python(PROGRAMS scripts/yolo_detector.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

install(DIRECTORY launch
  DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)

创建一键启动:

复制代码
touch ~/start_yolo_robot.sh

start_yolo_robot.sh 内容如下:
#!/bin/bash
echo "=== 启动YOLO机器人跟踪系统 ==="
echo ""

# 1. 激活conda环境
echo "1. 激活conda环境..."
source ~/miniconda/etc/profile.d/conda.sh
conda activate ros_yolo

# 2. 设置ROS环境
echo "2. 设置ROS环境..."
source /opt/ros/noetic/setup.bash
source ~/turtle_ws/devel/setup.bash

# 3. 设置机器人型号
echo "3. 设置机器人型号..."
export TURTLEBOT3_MODEL=waffle_pi

# 4. 显示环境信息
echo ""
echo "环境信息:"
echo "  Python: $(python --version)"
echo "  PyTorch: $(python -c 'import torch; print(torch.__version__)')"
echo "  ROS: $(rosversion -d)"
echo ""

# 5. 启动系统
echo "5. 启动YOLO机器人跟踪系统..."
echo "正在启动Gazebo仿真,请稍候..."
roslaunch robot_vision yolo_tracking.launch

echo ""
echo "=== 系统已启动 ==="

暂时一启动就会报错:

ERROR\] \[1768323879.415398, 3.882000\]: 图像转换错误: /lib/libgdal.so.26: undefined symbol: TIFFReadRGBATileExt, version LIBTIFF_4.0 接下来进行排查

相关推荐
im_AMBER2 小时前
Leetcode 103 反转链表 II
数据结构·c++·笔记·学习·算法·leetcode
bst@微胖子2 小时前
HuggingFace项目实战之使用Trainer执行训练
人工智能·机器学习
d0ublεU0x002 小时前
注意力机制与transformer
人工智能·深度学习·transformer
凤希AI伴侣2 小时前
凤希AI提出:FXPA2P - 当P2P技术遇上AI,重新定义数据与服务的边界
人工智能·凤希ai伴侣
腾迹2 小时前
2026年企业微信SCRM系统服务推荐:微盛·企微管家的AI私域增长方案
大数据·人工智能
xiangshi_yan2 小时前
内核学习之路【3/100】-内存管理
学习
崇山峻岭之间2 小时前
Matlab学习记录34
学习
寰宇视讯2 小时前
脑科技走进日常 消费级应用开启新蓝海,安全与普惠成关键
人工智能·科技·安全
云卓SKYDROID2 小时前
无人机电机模块选型与技术要点
人工智能·无人机·遥控器·高科技·云卓科技