从零搭建人跟随机器人:视觉感知、PID控制与ROS实战

📅 2026/8/19 7:43:49
从零搭建人跟随机器人:视觉感知、PID控制与ROS实战
1. 项目概述从“跟随”到“智能伙伴”的机器人构想“Can you make a Human Following Robot?” 这个问题乍一听像是科幻电影里的场景但今天它已经是一个完全可以通过现有技术实现的、充满趣味与实用价值的DIY项目。简单来说人跟随机器人就是一个能够自主识别特定目标通常是某个人并实时调整自身运动轨迹与目标保持预设距离和相对位置的移动平台。它不像扫地机器人那样漫无目的地游荡而是像一个忠实的“小尾巴”你走到哪它就跟到哪。这个项目的核心价值远不止于“好玩”。在仓储物流中它可以作为自动搬运小车跟随拣货员运送货物在零售场景它可以成为智能购物车在博物馆或展厅它可以作为导览机器人跟随参观者甚至在家庭环境中它可以帮你搬运重物或跟随拍摄。其技术栈融合了计算机视觉、传感器融合、运动控制和嵌入式系统是一个绝佳的跨学科实践项目能让你深入理解从感知到决策再到执行的完整机器人控制闭环。我花了几个月时间从零搭建了一个原型机期间踩过无数坑也收获了大量一线经验。本文将完全基于我的实战经历拆解如何一步步实现一个稳定、可靠的人跟随机器人。我不会只讲理论而是会聚焦于方案选型的权衡、具体组件的调试、代码中的魔鬼细节以及那些只有亲手做过才会知道的“坑”。无论你是机器人爱好者、嵌入式开发者还是对AI应用感兴趣的学生都能从中找到可以直接“抄作业”的实操指南。2. 核心方案选型与设计思路拆解实现人跟随首要问题是“如何看见并认出要跟的人”。这直接决定了整个系统的架构和复杂度。主流方案有三条技术路径各有优劣选择哪一种取决于你的预算、技术栈和性能要求。2.1 感知方案对比视觉、激光与融合感知方案一纯视觉方案RGB摄像头这是最直观、信息量最丰富的方案。通过一个普通的USB摄像头或树莓派摄像头利用深度学习模型如YOLO、SSD或传统图像处理如HOG特征SVM来检测人体。它的优势是成本极低一个摄像头几十元到几百元且能获取丰富的颜色、纹理信息便于进行更精细的身份识别比如跟随穿特定颜色衣服的人。然而其致命弱点是对光照变化极其敏感在暗光或逆光下性能骤降并且单纯从2D图像中难以精确获取目标的距离信息深度信息虽然可以通过目标在图像中的大小变化来粗略估算但精度和实时性都较差。方案二激光雷达方案2D LiDAR激光雷达通过发射激光束并测量反射时间来获取周围环境的距离信息生成一个二维的“点云”图。通过聚类算法我们可以从点云中识别出类似人腿部的两个圆柱形物体。这种方案的优点是测距精度极高厘米级、不受光照影响、响应速度快。一个常见的低成本选择是RPLIDAR A1。但它的缺点也很明显首先它只能识别“腿”的形状如果目标静止站立或腿部被遮挡比如站在桌子后面就容易跟丢其次它无法区分不同的人在人多的地方可能会跟错目标。方案三深度视觉方案RGB-D摄像头这是目前综合性能最好的方案代表产品如英特尔Realsense D435i、奥比中光Astra系列。这类摄像头同时输出彩色图像和深度图像。你可以用彩色图像做高精度的人体检测和识别同时用深度图像直接读取目标胸口或身体中轴点的三维坐标X, Y, Z。它结合了前两者的优点既有了视觉的丰富信息又有了精确的距离感知。缺点是成本较高数百到上千元并且部分型号在室外强光下深度信息可能失效。我的选择与理由在我的项目中我选择了方案三英特尔Realsense D435i。原因在于我希望机器人不仅仅能“跟随”未来还能扩展更多交互功能如手势识别这就需要丰富的视觉信息。同时精确的深度信息对于实现平滑、稳定的跟随控制至关重要。虽然成本高了一些但它省去了后期融合多传感器数据的巨大麻烦让开发更聚焦于核心算法。对于预算有限的初学者我建议可以从方案一纯视觉入手先实现基本的检测和粗略跟随理解整个流程再考虑升级。2.2 机器人底盘与运动模型选择感知到目标后机器人需要移动。这就涉及到底盘的选择。常见的有两轮差速驱动如TurtleBot、四轮麦克纳姆轮全向驱动、以及四轮汽车结构阿克曼转向。两轮差速驱动这是最经典、最经济的机器人底盘。通过控制左右两个轮子的速度差来实现前进、后退和转弯。结构简单数学模型清晰非常适合算法学习和验证。我的原型机就基于这种底盘。它的缺点是在高速或地面不平时可能不够稳定。麦克纳姆轮全向驱动可以实现平面内任意方向的平移和旋转运动非常灵活。在需要横向移动的狭窄空间里优势明显。但缺点是结构复杂、成本高、对地面平整度要求高且运动噪音较大。阿克曼转向类似汽车适用于速度较快的场景但转向半径大不适合室内小空间灵活跟随。对于室内人跟随场景运动速度要求不高通常与人步行速度相当但要求灵活转向。两轮差速底盘在成本、复杂度和性能上取得了最佳平衡是绝大多数入门和中级项目的首选。2.3 控制系统架构设计整个系统的软件架构我采用了经典的ROS机器人操作系统。ROS不是一个真正的操作系统而是一个分布式通信框架它提供了节点Node、话题Topic、服务Service等概念能让你像搭积木一样组合不同的功能模块。我的架构设计如下感知节点运行在主机如Intel NUC或高性能树莓派4B上订阅Realsense摄像头发布的彩色和深度图像话题。使用OpenCV和深度学习模型处理图像计算出目标人体在摄像头坐标系下的三维坐标(x, y, z)然后通过坐标变换将其转换到机器人底盘的中心坐标系下发布为一个包含目标位置的消息到/target_pose话题。控制节点订阅/target_pose话题获取目标相对于机器人的位置。根据这个位置信息采用比例-积分-微分控制器计算出机器人左右轮应有的速度指令(linear_velocity, angular_velocity)并发布到/cmd_vel话题。底盘驱动节点订阅/cmd_vel话题将速度指令通过串口或CAN总线发送给底盘的电机控制器如STM32驱动电机转动。这种模块化设计的好处是每个节点可以独立开发、调试和替换。例如我可以轻易地把Realsense感知节点换成一个纯激光雷达的感知节点而控制节点和驱动节点完全不用修改。实操心得硬件选型陷阱不要盲目追求高性能主机。我最初尝试在树莓派4B上直接跑YOLOv5模型帧率不到5FPS根本无法实时跟随。后来改用带独立显卡的迷你PCIntel NUC帧率提升到15FPS以上体验天壤之别。如果你的视觉算法较复杂主机的计算能力是首要瓶颈。对于纯激光方案树莓派4B则完全够用。3. 核心模块实现与关键技术细节确定了方案接下来就是动手实现。这一部分我将深入三个最核心的模块人体检测与跟踪、坐标变换、以及运动控制算法。这些都是决定跟随效果是否“丝滑”的关键。3.1 人体检测与跟踪从“看见”到“锁定”仅仅检测到人是不够的我们必须在一帧帧图像中持续跟踪同一个人尤其是在目标短暂被遮挡或转身时。1. 检测模型的选择与部署我测试过多种模型。YOLOv5s在精度和速度上取得了很好的平衡在NUC上使用ONNX Runtime或TensorRT加速后可以轻松达到实时性要求。相较于更轻量的MobileNet-SSDYOLO对小目标和遮挡情况更鲁棒。部署时关键是要针对你的使用场景主要是室内中近距离制作或微调数据集。直接使用COCO预训练模型可以工作但如果在机器人低视角下摄像头朝前平视或略微俯视模型对全身的检测效果会比直立视角稍差。一个技巧是收集一些机器人视角的图片对模型进行少量迭代的微调效果提升会非常明显。2. 目标跟踪算法的融合检测模型每帧独立运行输出的是一个个边界框。我们需要一个跟踪器来关联前后帧的框并为目标分配一个唯一的ID。我采用了经典的DeepSORT算法。SORT算法使用卡尔曼滤波预测目标下一帧的位置并用匈牙利算法进行框之间的关联。DeepSORT在SORT的基础上加入了外观特征通过一个简单的CNN提取这样即使目标短暂丢失如被遮挡一秒当再次出现时也能通过外观相似度重新关联上大大降低了跟丢的概率。3. 深度信息获取与目标点选择得到目标的边界框后如何获取它的三维坐标简单做法是取边界框底边中心点对应脚部位置的深度值。但脚部容易被遮挡且深度相机对近地面测距有时不准。更稳定的做法是取边界框上半部分中心点对应胸部位置的深度值。通过Realsense的API我们可以直接查询深度图像中对应像素点的深度值z再结合相机内参计算出该点在相机坐标系下的(x, y)。# 伪代码示例获取目标三维坐标 def get_target_3d_point(depth_frame, bbox, camera_intrinsics): # bbox: [x_min, y_min, x_max, y_max] # 计算胸部中心点像素坐标 u int((bbox[0] bbox[2]) / 2) v int((bbox[1] bbox[3]) * 0.3) # 取框高度30%处大约胸部位置 # 获取深度值单位米 depth depth_frame.get_distance(u, v) # 将像素坐标和深度值反投影到相机三维空间 # camera_intrinsics 包含 fx, fy, cx, cy 等参数 x (u - camera_intrinsics.cx) * depth / camera_intrinsics.fx y (v - camera_intrinsics.cy) * depth / camera_intrinsics.fy z depth return [x, y, z]3.2 坐标变换让机器人“理解”目标在哪从相机坐标系(x_cam, y_cam, z_cam)到机器人底盘坐标系(x_robot, y_robot)需要一个关键的坐标变换。相机安装在机器人身上它有自己的位置和朝向。我们需要知道相机光心相对于机器人旋转中心通常是两轮连线的中点的偏移(tx, ty, tz)和旋转角度通常是俯仰角。这个变换关系可以通过测量和标定得到。在ROS中我们使用TFTransform库来管理所有坐标系之间的关系。我们发布一个从base_link机器人底盘中心到camera_link相机光学中心的静态变换。这样感知节点只需要将目标点在相机坐标系下的坐标发布出来ROS的tf2库会自动帮我们转换到base_link坐标系下。# 在ROS中发布静态坐标变换通常在launch文件或初始化代码中完成 import tf2_ros import geometry_msgs.msg static_transform geometry_msgs.msg.TransformStamped() static_transform.header.stamp rospy.Time.now() static_transform.header.frame_id base_link static_transform.child_frame_id camera_depth_optical_frame # 设置变换假设相机在机器人前方0.1米高0.2米朝前无旋转 static_transform.transform.translation.x 0.1 static_transform.transform.translation.y 0.0 static_transform.transform.translation.z 0.2 static_transform.transform.rotation.w 1.0 # 无旋转的四元数表示 static_broadcaster.sendTransform(static_transform)注意事项坐标系的“右手定则”这是最容易出错的地方。在ROS中标准的坐标系是X轴向前Y轴向左Z轴向上。你的相机坐标系尤其是Realsense和机器人底盘坐标系必须遵循同一套规则。务必在图纸上画出来确认每一个平移和旋转参数的正负号。一个错误的符号会导致机器人朝完全相反的方向运动。3.3 运动控制算法PID控制器的调参艺术得到目标在机器人坐标系下的位置(x_target, y_target)后控制节点需要计算速度指令。我们的目标是让机器人移动到目标的正后方并保持一个期望的跟随距离D_desired比如1米。我们可以将这个问题分解为两个子问题角度控制调整机器人的朝向使其对准目标。距离控制控制机器人的前进后退以保持期望距离。我采用了两个独立的PID控制器分别控制角速度和线速度。角度误差theta_error atan2(y_target, x_target)。这个角度就是目标点相对于机器人正前方的偏角。我们希望这个误差为0。距离误差distance_error sqrt(x_target^2 y_target^2) - D_desired。当前距离减去期望距离我们希望这个误差也为0。角速度w由角度误差通过PID控制器计算w Kp_a * theta_error Ki_a * integral(theta_error) Kd_a * derivative(theta_error)线速度v由距离误差通过另一个PID控制器计算v Kp_d * distance_error Ki_d * integral(distance_error) Kd_d * derivative(distance_error)这里有一个关键技巧当角度误差较大时应优先转向减速或停止前进。否则机器人可能会斜着冲向目标轨迹不优雅也容易发生碰撞。我加入了一个基于角度误差的线速度缩放因子# 伪代码带耦合关系的速度计算 theta_error atan2(y_target, x_target) distance_error sqrt(x_target**2 y_target**2) - desired_distance # 角度误差大时降低最大线速度 max_linear_speed 0.5 # 米/秒 linear_speed_scale max(0, 1.0 - abs(theta_error) / (math.pi/4)) # 误差大于45度时缩放因子开始减小 max_linear_speed * linear_speed_scale # PID计算原始速度 w pid_angular.update(theta_error) v pid_linear.update(distance_error) # 限制速度范围 v clamp(v, -max_linear_speed, max_linear_speed) w clamp(w, -1.0, 1.0) # 弧度/秒PID调参实战经验先P后I再D这是黄金法则。首先将Ki和Kd设为0只调Kp。增大Kp会让响应变快但过大会导致振荡。找到机器人能快速响应又不剧烈振荡的临界Kp值。加入微分D增加Kd可以抑制振荡让运动更平滑。但Kd对噪声敏感如果传感器数据有抖动Kd大了反而会引入高频振动。谨慎使用积分I积分项用于消除静态误差比如始终差一点到目标距离。但在人跟随场景中目标是动态的积分项容易累积并导致“积分饱和”使机器人失控。我通常给Ki一个非常小的值或者加入积分限幅。实地测试在空旷安全的环境下让人以不同速度、不同路径行走观察机器人的跟随表现。记录下过冲、振荡、响应迟钝的情况回头有针对性地调整参数。4. 系统集成、调试与性能优化当各个模块单独测试通过后将它们集成到ROS系统中并让整个系统稳定运行是另一个挑战。这部分工作往往比算法本身更耗时。4.1 ROS工程组织与启动管理一个好的工程结构能极大提升开发效率。我推荐使用catkin工作空间并为每个功能模块创建独立的ROS功能包。例如perception_pkg包含人体检测、跟踪和坐标变换的节点。control_pkg包含PID控制器和速度计算节点。robot_base_pkg包含底盘通信和驱动的节点。bringup存放总的启动文件(launch file)和参数配置文件(yaml)。在bringup包的launch文件夹中创建一个主启动文件start_following.launch它负责一次性启动所有节点并加载所有参数。launch !-- 启动Realsense相机驱动 -- include file$(find realsense2_camera)/launch/rs_camera.launch arg nameenable_depth valuetrue/ /include !-- 启动感知节点 -- node pkgperception_pkg typehuman_tracker.py namehuman_tracker outputscreen rosparam commandload file$(find bringup)/config/tracker_params.yaml/ /node !-- 启动控制节点 -- node pkgcontrol_pkg typepid_controller.py namepid_controller outputscreen rosparam commandload file$(find bringup)/config/pid_params.yaml/ param namedesired_distance value1.0/ /node !-- 启动底盘驱动节点 -- node pkgrobot_base_pkg typebase_driver.py namebase_driver outputscreen/ /launch使用roslaunch bringup start_following.launch即可一键启动整个跟随系统。4.2 可视化调试工具Rviz与PlotJuggler“看不见”的调试是痛苦的。ROS提供了强大的可视化工具。Rviz这是机器人开发的“眼睛”。你可以添加PointCloud2显示深度点云添加Marker显示检测到的人体边界框和目标点添加TF查看坐标系是否正确添加RobotModel查看机器人的姿态。通过Rviz你可以直观地确认相机是否看到了人目标点坐标计算是否正确坐标系变换对不对PlotJuggler这是分析数据流的“神器”。你可以将ROS话题如/target_pose,/cmd_vel的数据以时间序列曲线的方式绘制出来。这对于PID调参至关重要。你可以同时观察距离误差、角度误差、实际输出的速度和角速度清晰地看到超调、振荡和响应延迟从而精准调整参数。4.3 性能优化与稳定性提升当基本功能跑通后你会发现一些影响体验的问题需要通过优化来解决。1. 延迟Latency问题从检测到目标到机器人开始运动存在一个处理延迟。这个延迟由图像采集、网络传输、算法处理、控制计算等多个环节叠加。过大的延迟会导致机器人总是“慢半拍”尤其在目标快速转弯时容易跟丢。优化手段使用更轻量的检测模型在ROS中使用rospy.Subscriber时指定队列大小queue_size1避免堆积旧消息确保控制节点的运行频率ros::Rate高于感知节点的发布频率如控制30Hz感知15Hz。2. 目标丢失处理策略人不可能永远在视野里可能会被遮挡或走出视野。策略在跟踪算法如DeepSORT中会有一个“丢失帧数”计数器。当目标丢失后不要立即停止机器人或重置状态。可以让机器人继续按最后已知的目标速度和方向运动一小段时间例如0.5秒或者缓慢减速停止。同时控制节点应能接收一个“目标是否有效”的标志当标志为假时输出零速度指令。3. 安全与防撞机器人需要具备基本的避障能力不能因为跟随而撞上其他物体或人。方案在控制指令最终发送给底盘前加入一层“安全过滤器”。可以订阅激光雷达或深度相机生成的障碍物地图如果发现前进方向上有障碍物则覆盖掉跟随计算出的速度优先执行避障如减速、停止或绕行。这可以通过ROS的costmap和move_base框架实现但对于简单跟随一个基于前方扇形区域距离检测的急停逻辑就足够了。5. 常见问题排查与实战避坑指南这一部分是我在调试过程中真实遇到的“坑”和解决方案希望能帮你节省大量时间。5.1 感知模块问题问题1检测框抖动严重目标点坐标跳变。现象在Rviz中看到的目标标记点不停闪烁、跳动导致机器人运动抽搐。原因深度学习模型单帧检测结果本身会有轻微波动背景复杂或有相似颜色干扰深度图像在物体边缘存在噪声。解决检测结果滤波对连续多帧的检测框位置进行卡尔曼滤波或简单的移动平均滤波。深度值滤波不要只取一个像素的深度值而是取目标点周围一个小区域如3x3的深度中值能有效去除飞点噪声。提高模型置信度阈值适当调高检测的置信度阈值如从0.5调到0.7过滤掉那些模棱两可的检测结果。问题2在特定光照下如窗户边逆光完全检测不到人。原因纯视觉或RGB-D相机的彩色传感器在极端光照下失效。解决启用相机自动曝光确保相机的自动曝光和白平衡是打开的。融合其他传感器这是根本性解决方案。可以考虑加入一个2D激光雷达当视觉失效时切换到基于腿部识别的激光跟踪模式。这需要设计一个简单的传感器融合状态机。软件HDR如果相机支持尝试使用高动态范围模式。5.2 控制与运动问题问题3机器人运动时“画龙”左右摇摆。现象机器人在直线跟随时不是走直线而是像喝醉了一样左右摇摆前进。原因PID参数特别是微分项Kd设置不当或者底盘轮子的编码器分辨率低、电机控制周期慢导致底层速度控制不精确。解决检查并降低Kd值过高的Kd会放大传感器噪声首先尝试将Kd设为0看是否改善。检查底盘性能给机器人发送一个恒定的速度指令观察它是否能平稳直行。如果不行可能是底盘的问题需要检查电机驱动器的PID参数或轮子是否打滑。加入死区当角度误差很小时例如小于5度直接让角速度输出为0避免因微小误差引起的持续抖动。问题4机器人靠近目标时停不下来或者来回“点头”。现象到达期望距离后机器人不是平稳停下而是冲过头又退回来反复振荡。原因线速度PID的积分项Ki过大或者机器人的惯性导致。解决调整PID减小线速度环的Ki和Kp。在接近目标时需要更柔和的控制。加入速度曲线不要直接用误差计算速度。当距离误差较小时使用一个更平缓的速度曲线例如v max_linear_speed * tanh(distance_error)这样在接近目标时速度会自然平滑地降到0。设置最小速度阈值当计算出的速度绝对值小于某个值如0.05 m/s时直接输出0。避免电机在极低速度下“爬行”。5.3 系统与工程问题问题5ROS节点频繁挂掉报错“段错误”或“内存错误”。原因C代码中存在指针错误Python代码中内存泄漏尤其是OpenCV和NumPy大对象循环创建或者USB设备如相机掉线导致节点崩溃。解决使用roslaunch的respawn属性在launch文件中为可能崩溃的节点添加respawntrue这样节点崩溃后会自动重启。加入异常捕获在Python节点的主循环外加上try...except捕获异常并打印日志至少让节点不会静默退出。检查硬件连接使用lsusb命令确认相机等USB设备连接稳定。对于重要外设可以在节点启动后增加一个检测环节。问题6系统延迟感觉很大从看到目标到机器人反应迟钝。排查流程测量各环节耗时在代码关键位置用rospy.Time.now()打时间戳计算感知、处理、控制各阶段的耗时。检查话题频率用rostopic hz /target_pose和rostopic hz /cmd_vel查看关键话题的发布频率是否达到预期。检查网络如果使用了多台设备如主机和机器人底盘分开确保它们之间的网络通信Wi-Fi或网线延迟低且稳定。不稳定的Wi-Fi是延迟的常见元凶。搭建一个稳定可靠的人跟随机器人是一个典型的“系统工程”它要求你对硬件、软件、算法都有所了解并且具备强大的调试和问题解决能力。这个过程充满挑战但当看到机器人灵巧地跟在你身后时那种成就感是无与伦比的。我的建议是从最简单的方案开始先让系统动起来再逐个模块深入优化。每解决一个实际问题你对整个系统的理解就会加深一层。这个项目没有唯一的正确答案它更像是一个开放的平台你可以不断尝试新的传感器、新的算法、新的交互方式让它变得越来越智能。