AGV路径规划实战:如何用Python+ROS实现激光SLAM导航(附避障代码)

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值