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