AGV路径规划实战:如何用Python+ROS实现激光SLAM导航(附避障代码)
如果你是一位机器人开发者或自动化工程师,正面对着一台AGV小车,思考如何让它真正“聪明”地动起来,那么这篇文章就是为你准备的。我们不再空谈理论,而是直接切入实战,用Python和ROS(机器人操作系统)这两个强大的工具,一步步搭建一个具备激光SLAM导航和实时避障能力的AGV系统。从零开始的环境配置,到核心算法的代码实现,再到决定成败的参数调优,我们将手把手带你走完整个流程。你会发现,让小车在复杂环境中自主规划路径并安全行驶,并非遥不可及的学术课题,而是一系列可以拆解、实现和调试的具体工程任务。
1. 环境搭建与ROS基础配置
在开始编写任何一行导航代码之前,一个稳定、配置正确的开发环境是成功的基石。ROS虽然功能强大,但其依赖复杂,版本管理严格,稍有不慎就会陷入“依赖地狱”。因此,我们首先需要建立一个可复现的、隔离的开发环境。
1.1 选择合适的ROS版本与系统环境
ROS的版本与Ubuntu操作系统版本是强绑定的。对于生产级或长期维护的项目,我强烈建议选择ROS Noetic Ninjemys,它对应Ubuntu 20.04 LTS,拥有长期支持,社区资源丰富,稳定性极高。如果你追求更新的功能,也可以考虑ROS 2 Humble,但ROS 1在工业界和学术界积累的成熟工具链(如rviz、gmapping、move_base)目前仍具有巨大优势。
为了避免污染主机系统,使用Docker容器是一个绝佳的选择。它能确保所有开发者、所有部署环境的一致性。下面是一个用于AGV开发的Dockerfile示例,它集成了ROS、必要的导航包以及一些常用的开发工具。
# 使用ROS Noetic的完整桌面版作为基础镜像
FROM osrf/ros:noetic-desktop-full
# 设置非交互式安装以避免提示
ENV DEBIAN_FRONTEND=noninteractive
# 更新软件源并安装必要的工具和依赖
RUN apt-get update && apt-get install -y \
git \
wget \
nano \
python3-pip \
python3-catkin-tools \
ros-noetic-navigation \
ros-noetic-slam-gmapping \
ros-noetic-hector-slam \
ros-noetic-teb-local-planner \
ros-noetic-amcl \
&& rm -rf /var/lib/apt/lists/*
# 创建一个catkin工作空间
RUN mkdir -p /catkin_ws/src
WORKDIR /catkin_ws
# 初始化工作空间
RUN /bin/bash -c "source /opt/ros/noetic/setup.bash && catkin_make"
# 将工作空间的setup.bash添加到bashrc中,方便使用
RUN echo "source /catkin_ws/devel/setup.bash" >> ~/.bashrc
# 设置容器启动命令
CMD ["/bin/bash"]
使用 docker build -t agv_dev . 构建镜像,然后用 docker run -it --net=host --env="DISPLAY" --volume="$HOME/.Xauthority:/root/.Xauthority:rw" agv_dev 启动一个带图形界面的容器。这样,我们就拥有了一个纯净、可移植的ROS开发环境。
1.2 创建ROS工作空间与功能包
进入容器后,我们开始创建项目专属的工作空间和功能包。结构清晰的代码组织是大型项目管理的生命线。
# 确保在容器内的 /catkin_ws 目录下
cd /catkin_ws/src
# 创建一个名为agv_navigation的功能包,依赖roscpp, rospy, std_msgs, sensor_msgs, geometry_msgs, nav_msgs, tf
catkin_create_pkg agv_navigation roscpp rospy std_msgs sensor_msgs geometry_msgs nav_msgs tf
# 返回工作空间根目录并编译
cd ..
catkin_make
source devel/setup.bash
提示:每次打开新的终端窗口,都需要执行
source devel/setup.bash来使能当前工作空间的环境变量。你可以将其添加到~/.bashrc文件中实现自动加载。
至此,我们的开发环境已经就绪。接下来,我们将进入传感器数据处理的环节,这是SLAM和导航的“眼睛”。
2. 激光雷达数据接入与预处理
激光雷达(LiDAR)是AGV感知环境的核心传感器。它提供周围环境的点云数据,但原始数据通常包含噪声、无效点(如过近或过远的点)以及来自玻璃等特殊材质的干扰。直接使用原始数据进行SLAM或避障,效果往往不佳。因此,数据预处理是必不可少的一步。
2.1 编写激光雷达数据订阅与发布节点
我们创建一个Python节点,订阅原始的激光扫描话题(通常是 /scan),对其进行滤波处理,然后发布一个干净的数据到新话题上。这里我们使用ROS的 laser_filters 包是一个更工程化的选择,但为了理解原理,我们先手动实现一个简单的距离和角度过滤器。
在 /catkin_ws/src/agv_navigation/scripts/ 目录下创建 laser_filter.py:
#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import LaserScan
class LaserFilter:
def __init__(self):
# 初始化节点
rospy.init_node('laser_filter_node', anonymous=True)
# 创建发布者和订阅者
self.filtered_pub = rospy.Publisher('/filtered_scan', LaserScan, queue_size=10)
self.raw_sub = rospy.Subscriber('/scan', LaserScan, self.scan_callback)
# 配置过滤参数(可从参数服务器动态加载)
self.min_range = rospy.get_param('~min_range', 0.1) # 最小有效距离,单位:米
self.max_range = rospy.get_param('~max_range', 10.0) # 最大有效距离,单位:米
self.min_angle = rospy.get_param('~min_angle', -3.14) # 最小有效角度,单位:弧度
self.max_angle = rospy.get_param('~max_angle', 3.14) # 最大有效角度,单位:弧度
rospy.loginfo("激光雷达过滤器节点已启动。")
def scan_callback(self, raw_scan):
# 创建新的LaserScan消息,复制头部信息等
filtered_scan = LaserScan()
filtered_scan.header = raw_scan.header
filtered_scan.angle_min = max(raw_scan.angle_min, self.min_angle)
filtered_scan.angle_max = min(raw_scan.angle_max, self.max_angle)
filtered_scan.angle_increment = raw_scan.angle_increment
filtered_scan.time_increment = raw_scan.time_increment
filtered_scan.scan_time = raw_scan.scan_time
filtered_scan.range_min = self.min_range
filtered_scan.range_max = self.max_range
# 计算有效的角度索引范围
start_idx = int((self.min_angle - raw_scan.angle_min) / raw_scan.angle_increment)
end_idx = int((self.max_angle - raw_scan.angle_min) / raw_scan.angle_increment)
# 过滤距离数据
filtered_ranges = []
filtered_intensities = [] if raw_scan.intensities else None
for i in range(start_idx, end_idx):
r = raw_scan.ranges[i]
# 应用距离过滤
if self.min_range <= r <= self.max_range:
filtered_ranges.append(r)
if filtered_intensities is not None:
filtered_intensities

&spm=1001.2101.3001.5002&articleId=152314008&d=1&t=3&u=8e135249c03e4be4ae18ec85d6b1b44c)
336

被折叠的 条评论
为什么被折叠?



