1. 环境准备与ROS安装
在开始构建仿真小车之前,我们需要先搭建好开发环境。ROS(Robot Operating System)是目前机器人开发最流行的框架之一,它提供了硬件抽象、设备驱动、库函数、可视化工具等丰富功能。我推荐使用Ubuntu 20.04 LTS系统配合ROS Noetic版本,这是目前最稳定的组合。
安装ROS其实没有想象中那么复杂。首先确保你的系统已经更新到最新状态:
sudo apt update && sudo apt upgrade -y然后添加ROS软件源和密钥:
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654接下来安装完整版的ROS桌面环境:
sudo apt update sudo apt install ros-noetic-desktop-full安装完成后,记得初始化rosdep并设置环境变量:
sudo rosdep init rosdep update echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc为了后续开发方便,我建议再安装一些常用工具:
sudo apt install python3-rosinstall python3-rosinstall-generator python3-wstool build-essential2. 创建ROS工作空间与Gazebo环境配置
有了ROS基础环境后,我们需要创建一个专门的工作空间来开发我们的仿真小车。我习惯在home目录下创建catkin_ws工作空间:
mkdir -p ~/catkin_ws/src cd ~/catkin_ws/ catkin_make这个命令会生成标准的ROS工作空间结构。记得把工作空间的环境变量也加入bashrc:
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc source ~/.bashrcGazebo是ROS默认的仿真环境,但为了确保所有依赖都安装完整,建议单独安装Gazebo组件:
sudo apt install gazebo11 libgazebo11-dev ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control安装完成后可以测试Gazebo是否正常工作:
gazebo如果能看到空白的仿真环境界面,说明安装成功。在实际项目中,我遇到过Gazebo启动黑屏的问题,通常是因为显卡驱动不兼容导致的,可以尝试添加--verbose参数查看详细错误信息。
3. 构建基础小车模型
现在我们来创建仿真小车的URDF模型。URDF(Unified Robot Description Format)是ROS中描述机器人模型的XML格式文件。在catkin_ws/src目录下创建一个新的功能包:
cd ~/catkin_ws/src catkin_create_pkg my_robot rospy tf gazebo_ros在my_robot包中创建urdf目录,并新建一个robot.urdf文件。基础的小车模型可以这样定义:
<robot name="my_robot"> <link name="base_link"> <visual> <geometry> <box size="0.3 0.2 0.1"/> </geometry> </visual> <collision> <geometry> <box size="0.3 0.2 0.1"/> </geometry> </collision> <inertial> <mass value="5"/> <inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.1"/> </inertial> </link> <joint name="left_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="left_wheel"/> <origin xyz="0 0.15 0" rpy="1.5707 0 0"/> <axis xyz="0 1 0"/> </joint> <link name="left_wheel"> <visual> <geometry> <cylinder length="0.05" radius="0.05"/> </geometry> </visual> </link> <!-- 右轮定义类似,位置改为xyz="0 -0.15 0" --> </robot>这个模型定义了一个长方体车身和两个圆柱形轮子。为了让小车能在Gazebo中运动,我们还需要添加Gazebo特定的插件:
<gazebo> <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so"> <commandTopic>cmd_vel</commandTopic> <odometryTopic>odom</odometryTopic> <odometryFrame>odom</odometryFrame> <robotBaseFrame>base_link</robotBaseFrame> <publishWheelTF>true</publishWheelTF> <wheelSeparation>0.3</wheelSeparation> <wheelDiameter>0.1</wheelDiameter> <publishWheelJointState>true</publishWheelJointState> </plugin> </gazebo>4. 集成摄像头传感器
视觉感知是机器人环境交互的重要部分。我们在小车上添加一个摄像头传感器。在URDF中继续添加:
<link name="camera_link"> <visual> <geometry> <box size="0.05 0.05 0.05"/> </geometry> </visual> <inertial> <mass value="0.1"/> <inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001"/> </inertial> </link> <joint name="camera_joint" type="fixed"> <parent link="base_link"/> <child link="camera_link"/> <origin xyz="0.15 0 0.1" rpy="0 0 0"/> </joint> <gazebo reference="camera_link"> <sensor type="camera" name="camera1"> <update_rate>30.0</update_rate> <camera name="head"> <horizontal_fov>1.3962634</horizontal_fov> <image> <width>640</width> <height>480</height> <format>R8G8B8</format> </image> <clip> <near>0.02</near> <far>300</far> </clip> </camera> <plugin name="camera_controller" filename="libgazebo_ros_camera.so"> <alwaysOn>true</alwaysOn> <updateRate>0.0</updateRate> <cameraName>camera</cameraName> <imageTopicName>image_raw</imageTopicName> <cameraInfoTopicName>camera_info</cameraInfoTopicName> <frameName>camera_link</frameName> <hackBaseline>0.07</hackBaseline> <distortionK1>0.0</distortionK1> <distortionK2>0.0</distortionK2> <distortionK3>0.0</distortionK3> <distortionT1>0.0</distortionT1> <distortionT2>0.0</distortionT2> </plugin> </sensor> </gazebo>这段配置定义了一个640x480分辨率的摄像头,固定在车体前部。安装完成后,可以通过以下命令测试摄像头:
roslaunch my_robot display.launch rostopic list # 应该能看到/camera/image_raw话题5. 实现键盘控制
为了让小车动起来,我们需要创建一个键盘控制节点。在my_robot包中创建scripts目录,并添加teleop.py文件:
#!/usr/bin/env python3 import rospy from geometry_msgs.msg import Twist import sys, select, termios, tty msg = """ Control Your Robot! --------------------------- Moving around: u i o j k l m , . q/z : increase/decrease max speeds by 10% w/x : increase/decrease only linear speed by 10% e/c : increase/decrease only angular speed by 10% space key, k : force stop anything else : stop smoothly CTRL-C to quit """ moveBindings = { 'i':(1,0), 'o':(1,-1), 'j':(0,1), 'l':(0,-1), 'u':(1,1), ',':(-1,0), '.':(-1,1), 'm':(-1,-1), } speedBindings={ 'q':(1.1,1.1), 'z':(.9,.9), 'w':(1.1,1), 'x':(.9,1), 'e':(1,1.1), 'c':(1,.9), } def getKey(): tty.setraw(sys.stdin.fileno()) rlist, _, _ = select.select([sys.stdin], [], [], 0.1) if rlist: key = sys.stdin.read(1) else: key = '' termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings) return key def vels(speed,turn): return "currently:\tspeed %s\tturn %s " % (speed,turn) if __name__=="__main__": settings = termios.tcgetattr(sys.stdin) rospy.init_node('teleop_twist_keyboard') pub = rospy.Publisher('/cmd_vel', Twist, queue_size = 1) speed = rospy.get_param("~speed", 0.5) turn = rospy.get_param("~turn", 1.0) x = 0 th = 0 status = 0 try: print(msg) print(vels(speed,turn)) while(1): key = getKey() if key in moveBindings.keys(): x = moveBindings[key][0] th = moveBindings[key][1] elif key in speedBindings.keys(): speed = speed * speedBindings[key][0] turn = turn * speedBindings[key][1] print(vels(speed,turn)) else: x = 0 th = 0 if (key == '\x03'): break twist = Twist() twist.linear.x = x*speed; twist.linear.y = 0; twist.linear.z = 0 twist.angular.x = 0; twist.angular.y = 0; twist.angular.z = th*turn pub.publish(twist) except Exception as e: print(e) finally: twist = Twist() twist.linear.x = 0; twist.linear.y = 0; twist.linear.z = 0 twist.angular.x = 0; twist.angular.y = 0; twist.angular.z = 0 pub.publish(twist) termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)记得给脚本添加执行权限:
chmod +x ~/catkin_ws/src/my_robot/scripts/teleop.py现在你可以通过键盘控制小车在Gazebo环境中移动了。启动仿真环境:
roslaunch my_robot display.launch然后在另一个终端运行控制脚本:
rosrun my_robot teleop.py6. 集成YOLO目标检测
YOLO是目前最流行的实时目标检测算法之一。我们将使用Darknet_ros包来集成YOLOv3。首先安装依赖:
sudo apt install ros-noetic-vision-msgs ros-noetic-image-transport ros-noetic-cv-bridge然后下载并编译darknet_ros:
cd ~/catkin_ws/src git clone --recursive https://github.com/leggedrobotics/darknet_ros.git cd ~/catkin_ws catkin_make -DCMAKE_BUILD_TYPE=Release下载预训练的YOLO权重文件:
cd ~/catkin_ws/src/darknet_ros/darknet_ros/yolo_network_config/weights/ wget https://pjreddie.com/media/files/yolov3.weights配置darknet_ros订阅我们仿真摄像头的图像话题。编辑~/catkin_ws/src/darknet_ros/darknet_ros/config/ros.yaml:
camera_reading: topic: /camera/image_raw queue_size: 1现在可以启动YOLO检测节点了:
roslaunch darknet_ros darknet_ros.launch检测结果会发布在/darknet_ros/bounding_boxes话题。为了可视化检测结果,我们可以使用image_view:
rosrun image_view image_view image:=/darknet_ros/detection_image7. 完整系统集成与测试
现在我们已经有了所有组件,让我们创建一个启动文件来一次性启动整个系统。在my_robot/launch目录下创建simulation.launch:
<launch> <!-- 加载机器人模型 --> <param name="robot_description" command="$(find xacro)/xacro '$(find my_robot)/urdf/robot.urdf'" /> <!-- 启动Gazebo --> <include file="$(find gazebo_ros)/launch/empty_world.launch"> <arg name="world_name" value="worlds/empty.world"/> <arg name="paused" value="false"/> <arg name="use_sim_time" value="true"/> <arg name="gui" value="true"/> <arg name="headless" value="false"/> <arg name="debug" value="false"/> </include> <!-- 在Gazebo中生成机器人 --> <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" args="-param robot_description -urdf -model my_robot" /> <!-- 发布关节状态 --> <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" /> <!-- 启动键盘控制 --> <node name="teleop" pkg="my_robot" type="teleop.py" output="screen"/> <!-- 启动YOLO检测 --> <include file="$(find darknet_ros)/launch/darknet_ros.launch" /> </launch>现在只需一个命令就能启动整个系统:
roslaunch my_robot simulation.launch在实际测试中,我发现Gazebo仿真环境和YOLO检测对计算资源要求较高。如果你的电脑性能不足,可以尝试降低Gazebo的图像质量或使用更小的YOLO模型(如tiny YOLO)。