项目二、基于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
接下来进行排查