基于树莓派与Mediapipe的麦克纳姆轮跟随小车全栈实现
2026/7/28 3:44:18 网站建设 项目流程

1. 项目缘起:为什么选择这个组合来造一台“跟屁虫”小车?

几年前,我在一个机器人展会上看到一台能自动跟随人移动的展示车,当时就觉得这玩意儿太酷了,它不像传统的遥控车,而是像一个智能的“小跟班”。回来后我就琢磨着自己也搞一台。市面上有现成的跟随机器人套件,但价格不菲,而且“黑盒”操作居多,可玩性不高。作为一个喜欢折腾的硬件爱好者,我决定从零开始,用更开放、更具性价比的方案来实现它。

经过一番调研和选型,我最终敲定了“树莓派 + Mediapipe + 麦克纳姆轮”这个黄金组合。为什么是它们?这背后有非常实际的考量。首先,树莓派作为一款功能强大的微型计算机,它不仅能运行完整的操作系统(如Debian),处理复杂的视觉和逻辑运算,还提供了丰富的GPIO接口,可以轻松连接和控制各种电机驱动板、传感器,是项目当之无愧的“大脑”。其次,Mediapipe是谷歌开源的一个跨平台机器学习解决方案框架,它内置了现成的、经过高度优化的姿态估计模型。这意味着我不需要从零开始训练一个复杂的人体检测模型,也不用在树莓派上部署庞大的深度学习框架(如TensorFlow/PyTorch)来推理,Mediapipe提供了非常轻量级的Python API,能直接在树莓派上以不错的帧率实时检测出人体的关键点(如鼻子、肩膀、髋部)。最后,麦克纳姆轮是一种特殊的全向轮,它由许多小辊子以特定角度倾斜排列而成。通过控制四个麦克纳姆轮的不同转速和转向组合,小车可以实现平面内任意方向的平移、旋转,即“全向移动”。这对于跟随场景至关重要——想象一下,如果小车只能前进后退、左右转弯,那么当目标横向移动时,小车需要先转向再前进,动作笨拙且不连贯。而麦克纳姆轮能让小车像螃蟹一样直接横移,平滑地跟上目标的侧向步伐,体验感直接拉满。

这个项目的核心逻辑,就是让树莓派驱动摄像头捕捉图像,通过Mediapipe识别画面中的人体并计算出目标相对于画面中心的位置,再根据这个位置偏差,通过PID等控制算法计算出四个麦克纳姆轮各自应有的转速,最终通过电机驱动板控制小车移动,形成一个“感知-决策-控制”的闭环。整个过程充满了硬件组装、软件调试、算法调参的乐趣,最终当你走动时,一个小车静静地跟在你身后,那种成就感是无与伦比的。下面,我就把这套方案的完整实现过程、踩过的坑以及调优心得,毫无保留地分享出来。

2. 硬件选型与组装:打造小车的“躯体”与“神经”

一台能稳定运行的跟随车,硬件是基石。这部分工作看似是拧螺丝、接电线,但每一个选择都直接影响着后续软件开发的难度和小车的最终性能。我的硬件清单和考量如下:

2.1 核心控制器:树莓派4B/5的抉择

树莓派是项目的大脑,其算力决定了视觉处理的帧率和整体响应速度。

  • 树莓派4B (4GB/8GB RAM):这是经过市场长期检验的“老兵”。对于运行Mediapipe的BlazePose模型(轻量级)来说,其性能完全足够。在优化良好的情况下,处理640x480分辨率的图像,帧率能达到10-15 FPS,这对于跟随应用是可用的。其GPIO、USB、CSI摄像头接口等资源都非常成熟稳定。优点是社区支持极其广泛,任何问题几乎都能找到解决方案,性价比高。
  • 树莓派5:这是新一代的“猛将”,CPU和GPU性能均有显著提升,并且原生支持PCIe接口。对于这个项目,它的最大优势在于能轻松驱动更高分辨率的摄像头(如1080p),并在更高帧率下运行Mediapipe,使得目标跟踪更平滑、延迟更低。缺点是发布初期,一些外围驱动(如某些特定的摄像头模块驱动)的稳定性可能还在完善中,且功耗和发热相对4B更高。

我的选择与建议:如果你是第一次尝试,追求极致的稳定性和丰富的参考资料,树莓派4B 4GB版本是稳妥且性价比最高的选择。如果你对性能有更高要求,不介意处理一些新硬件可能遇到的兼容性问题,并且希望小车反应更敏捷,那么树莓派5是更面向未来的选择。我本次项目使用的是树莓派4B 4GB,事实证明其性能完全胜任。

2.2 “眼睛”:摄像头模块的选择与安装

摄像头负责“看”世界,其画质、帧率和安装位置至关重要。

  • 官方CSI摄像头 vs USB摄像头:树莓派官方CSI摄像头通过排线直接连接到主板上的CSI接口,这种连接方式带宽高、延迟极低、CPU占用小,是首选方案。USB摄像头虽然即插即用,但在树莓派上需要占用USB带宽,并由CPU进行图像数据搬运和处理,会增加系统负载,在高帧率下可能成为瓶颈。
  • 分辨率与帧率:Mediapipe处理图像需要时间,更高的分辨率意味着更多的像素需要计算。对于跟随应用,我们不需要看清人的面部细节,只需要定位躯干。因此,640x480 (480p) 或 800x600 的分辨率是完全足够的。这样可以在树莓派上获得更高的处理帧率(FPS)。在raspistilllibcamera命令中,我们可以轻松设置分辨率。
  • 安装位置:摄像头应安装在小车前方,镜头轴线尽量与地面平行,且高度适中。如果镜头朝上或朝下,会导致透视畸变,影响Mediapipe检测的准确性,进而影响位置计算的精度。我使用了一个可调节角度的摄像头支架,方便微调。

实操步骤:连接与测试CSI摄像头

  1. 确保树莓派已关机,轻轻抬起CSI接口的卡扣。
  2. 将摄像头排线的蓝色一面背对以太网口(树莓派4B/5),金色触点朝向卡扣内侧,插入接口并按下卡扣锁紧。
  3. 启动树莓派,打开终端,首先启用摄像头接口(如果未启用):
    sudo raspi-config
    选择Interface Options->Legacy Camera(树莓派Bullseye及之前) 或Camera(新版Bookworm系统),启用它。
  4. 测试摄像头,使用libcamera命令(新系统推荐):
    libcamera-hello -t 0
    这会实时预览摄像头画面。按Ctrl+C退出。
  5. 拍摄一张测试照片:
    libcamera-jpeg -o test.jpg
    用文件管理器查看test.jpg,确认图像清晰无误。

2.3 “腿脚”:麦克纳姆轮、电机与驱动板

这是实现全向移动的核心,选型错误会导致小车行动怪异、无力甚至损坏。

  • 麦克纳姆轮:购买时务必注意四个轮的安装方向!麦克纳姆轮有左旋和右旋之分,必须按照“X”形或“O”形布局来安装,才能产生正确的合力。通常卖家会提供安装示意图。我选择的是60mm直径的橡胶轮,兼顾了速度与抓地力。
  • 直流减速电机:轮子需要电机来驱动。我选用的是TT马达(双轴),配套的减速箱提供了较大的扭矩,使得小车有足够的动力启动和克服轻微不平的地面。关键参数是额定电压(如3-6V)和空载转速。需要确保电机驱动板能提供足够的电流。
  • 电机驱动板:树莓派的GPIO引脚无法直接驱动电机,需要驱动板作为“肌肉”。我选用的是TB6612FNG双电机驱动模块。一块TB6612可以驱动两个电机,因此我们需要两块来驱动四个轮子。选择它的原因:① 效率高,发热远小于传统的L298N;② 支持PWM调速,控制精细;③ 内置保护电路。接线时,需要仔细连接树莓派的GPIO引脚到驱动板的控制端(PWMA, AIN1, AIN2等),以及驱动板的输出端到电机。

硬件组装流程与注意事项

  1. 车架:可以使用亚克力板、3D打印件或现成的机器人底盘。确保结构牢固,有足够空间安装树莓派、电池和驱动板。
  2. 安装电机与轮子:将四个电机固定在车架底板的四个角上。严格按照卖家提供的麦克纳姆轮布局图安装轮子,确保左前轮和右后轮的辊子倾斜方向一致,右前轮和左后轮的方向一致。这是全向移动的基础,装错了小车会“鬼畜”。
  3. 电路连接
    • 将一块TB6612的VM(电机电源)和VCC(逻辑电源)连接到电池(如7.4V锂电池)的正极,GND连接到电池负极。
    • 将两块TB6612的GND也与树莓派的GND引脚相连,共地。
    • 将树莓派的3.3V5V引脚连接到TB6612的VCC(如果模块支持3.3V逻辑电平,则接3.3V更安全)。
    • 连接控制信号线:例如,将树莓派的GPIO17、GPIO27、GPIO22连接到第一块TB6612的PWMA,AIN1,AIN2,分别控制一个电机的速度和方向。同理,连接其他GPIO控制其余三个电机。务必绘制一张接线表,记录每个GPIO引脚对应控制哪个轮子,后续编程全靠它。
  4. 供电强烈建议电机(驱动板)和树莓派使用独立的电源供电,并通过共地连接。因为电机启停时会产生很大的电流波动和电压尖峰,可能引起树莓派重启或损坏。我使用一块7.4V锂电池通过降压模块给驱动板供电,另一块移动电源或5V电池组给树莓派供电。

3. 软件环境搭建:为树莓派注入“灵魂”

硬件组装完毕,接下来是让树莓派“活”起来,安装必要的操作系统和软件库。

3.1 系统安装与基础配置

  1. 烧录系统:我选择Raspberry Pi OS (64-bit) Lite版本。因为跟随车作为独立设备运行,不需要图形桌面,Lite版本更轻量,资源占用少。使用Raspberry Pi Imager工具烧录到MicroSD卡,在烧录前即可设置主机名、开启SSH、配置Wi-Fi,非常方便。
  2. 无头启动与SSH连接:烧录完成后,将SD卡插入树莓派,上电启动。在路由器管理界面找到树莓派的IP地址,使用SSH客户端(如PuTTY)连接。用户名为pi,密码为你设置的密码。
  3. 系统更新与换源:为了获得更快的软件下载速度,建议更换为国内镜像源(如清华源、中科大源)。
    sudo nano /etc/apt/sources.list sudo nano /etc/apt/sources.list.d/raspi.list
    将文件中的archive.raspberrypi.orgraspbian.org域名替换为镜像源地址,然后更新:
    sudo apt update sudo apt upgrade -y
  4. 安装必要工具python3-pip,git,vim等。

3.2 Mediapipe及其依赖的安装

这是软件部分的核心,也是最容易踩坑的地方。Mediapipe的官方安装命令在树莓派上可能因为架构和依赖问题失败。

避坑指南:分步安装法

  1. 安装系统依赖
    sudo apt install -y python3-opencv libatlas3-base libgomp1 libqtgui4 libqt4-test
    python3-opencv是必须的,因为Mediapipe处理图像需要OpenCV的后端。
  2. 使用预编译的Wheel文件(推荐):直接pip install mediapipe在树莓派上很可能编译失败。幸运的是,社区有爱好者为ARM架构(树莓派)编译好了Wheel文件。我们可以直接安装。
    • 访问https://github.com/helgeerbe/mediapipe-arm64或类似仓库,找到适用于你Python版本(如3.9)和系统(bullseye/bookworm)的.whl文件。
    • 使用wget下载到树莓派,然后用pip安装:
    wget https://github.com/helgeerbe/mediapipe-arm64/releases/download/v0.10.3/mediapipe-0.10.3-cp39-cp39-linux_aarch64.whl pip3 install mediapipe-0.10.3-cp39-cp39-linux_aarch64.whl
    • 如果没有找到完全匹配的,可以尝试寻找其他来源,或者使用pip install mediapipe --no-deps尝试不安装依赖,然后手动安装其依赖(numpy,opencv-python,protobuf等),但这比较繁琐。
  3. 验证安装:创建一个简单的Python脚本test_mediapipe.py
    import cv2 import mediapipe as mp mp_pose = mp.solutions.pose print("MediaPipe Pose module imported successfully!")
    运行python3 test_mediapipe.py,如果没有报错,说明安装成功。

3.3 电机控制库与项目结构

我们需要一个库来方便地控制GPIO,从而驱动TB6612模块。RPi.GPIO是经典选择,但这里我推荐gpiozero,它的API更现代、易用。

sudo apt install -y python3-gpiozero

建立项目目录,结构如下:

follow_car/ ├── car_controller.py # 电机控制类,封装麦克纳姆轮运动逻辑 ├── pose_tracker.py # Mediapipe姿态检测与目标定位类 ├── main.py # 主程序,整合跟踪与控制逻辑 ├── config.py # 配置文件(GPIO引脚定义、PID参数等) └── requirements.txt # Python依赖列表

4. 核心代码实现:从“看见”到“跟随”

一切准备就绪,现在开始编写让小车动起来的核心代码。我们将逻辑分层,便于调试和维护。

4.1 底层驱动:麦克纳姆轮运动学与控制封装

首先,我们需要理解麦克纳姆轮的运动学。小车的运动可以分解为三个分量:沿X轴平移(左右)、沿Y轴平移(前后)、绕Z轴旋转(自转)。每个轮子的转速是这三个分量的线性组合。

car_controller.py中,我们创建一个MecanumCar类:

import time from gpiozero import PWMOutputDevice, DigitalOutputDevice class MecanumCar: def __init__(self, config): # 根据config.py中的定义,初始化四个电机的PWM和方向控制引脚 # 例如:motor_fl_pwm = PWMOutputDevice(pin=config.MOTOR_FL_PWM) # motor_fl_in1 = DigitalOutputDevice(pin=config.MOTOR_FL_IN1) # motor_fl_in2 = DigitalOutputDevice(pin=config.MOTOR_FL_IN2) self.motors = self._init_motors(config) # 电机转向校准系数,因为安装或接线可能导致实际转向与理论相反 self.calibration = config.MOTOR_CALIBRATION def _init_motors(self, config): # 初始化四个电机的PWM和方向控制对象,返回一个字典方便管理 motors = {} # ... 初始化代码 ... return motors def move(self, vx, vy, omega): """ 根据速度向量控制小车运动。 :param vx: X轴速度(右为正),范围[-1.0, 1.0] :param vy: Y轴速度(前为正),范围[-1.0, 1.0] :param omega: 旋转角速度(逆时针为正),范围[-1.0, 1.0] """ # 麦克纳姆轮运动学公式(简化版,假设轮子位于正方形四角) # 每个轮子的速度是vx, vy, omega的线性组合 wheel_speeds = [ vy + vx + omega, # 左前轮 vy - vx - omega, # 右前轮 vy - vx + omega, # 左后轮 vy + vx - omega, # 右后轮 ] # 将速度归一化到[-1, 1]区间,防止超过PWM最大占空比 max_speed = max(abs(s) for s in wheel_speeds) if max_speed > 1.0: wheel_speeds = [s / max_speed for s in wheel_speeds] # 应用校准系数并设置电机 for i, speed in enumerate(wheel_speeds): calibrated_speed = speed * self.calibration[i] self._set_motor_speed(i, calibrated_speed) def _set_motor_speed(self, motor_index, speed): """根据速度值(-1到1)设置指定电机的PWM和方向。""" motor = self.motors[motor_index] if speed >= 0: motor['in1'].on() motor['in2'].off() else: motor['in1'].off() motor['in2'].on() # 取绝对值作为PWM占空比 motor['pwm'].value = abs(speed) def stop(self): """停止所有电机。""" for motor in self.motors.values(): motor['pwm'].value = 0

关键点与避坑

  • 运动学公式:上述公式是一个基础模型。实际中,因为轮子摩擦、重心等因素,可能需要微调系数。这是一个需要实际测试和调整的地方。
  • 速度归一化:这一步至关重要。当计算出的某个轮子速度超过1.0时,如果不归一化,PWM值会超限,导致电机控制异常,小车运动失真。
  • 校准系数config.MOTOR_CALIBRATION是一个包含4个数字的列表(如[1, 1, -1, 1])。如果发现某个轮子转向反了,可以通过修改对应位置的符号(1或-1)来纠正,而无需重新焊接电线。

4.2 视觉感知:Mediapipe人体检测与目标定位

接下来,在pose_tracker.py中,我们编写从摄像头画面中找出人并计算其位置的代码。

import cv2 import mediapipe as mp import numpy as np class PoseTracker: def __init__(self, camera_index=0, target_height=480): self.cap = cv2.VideoCapture(camera_index) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, target_height) # 初始化MediaPipe Pose模型,选择轻量级模型以提升速度 self.mp_pose = mp.solutions.pose self.pose = self.mp_pose.Pose( static_image_mode=False, model_complexity=1, # 0:轻量,1:中等,2:重度。选1平衡精度速度。 smooth_landmarks=True, enable_segmentation=False, min_detection_confidence=0.5, min_tracking_confidence=0.5 ) self.mp_draw = mp.solutions.drawing_utils self.frame_center_x = 320 # 初始化,会在第一帧更新 self.frame_center_y = target_height // 2 def get_target_offset(self): """ 从摄像头捕获一帧,检测人体,返回目标中心点相对于画面中心的偏移。 :return: (dx, dy, detected) dx, dy为归一化偏移(-1到1),detected为是否检测到人 """ success, frame = self.cap.read() if not success: return 0, 0, False # MediaPipe处理需要RGB图像 frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = self.pose.process(frame_rgb) dx, dy = 0, 0 detected = False if results.pose_landmarks: detected = True # 获取画面尺寸 h, w, _ = frame.shape self.frame_center_x, self.frame_center_y = w // 2, h // 2 # 选择用于跟踪的关键点。鼻尖(0)或躯干中点(左右髋部中心)比较稳定。 # 这里使用鼻尖 landmark = results.pose_landmarks.landmark[self.mp_pose.PoseLandmark.NOSE] # 将关键点坐标从归一化坐标转换为像素坐标 target_px = int(landmark.x * w) target_py = int(landmark.y * h) # 计算归一化偏移: (目标坐标 - 中心坐标) / (半宽或半高) # 这样偏移范围在[-1, 1]左右,方便后续控制 dx = (target_px - self.frame_center_x) / (w / 2) dy = (target_py - self.frame_center_y) / (h / 2) # 可选:在画面上绘制关键点和连线,用于调试 # self.mp_draw.draw_landmarks(frame, results.pose_landmarks, self.mp_pose.POSE_CONNECTIONS) # cv2.circle(frame, (target_px, target_py), 10, (0, 255, 0), -1) # cv2.imshow('Pose Tracking', frame) # cv2.waitKey(1) return dx, dy, detected def release(self): self.cap.release() cv2.destroyAllWindows()

关键点与调优

  • 关键点选择:跟踪鼻尖(NOSE)是最直观的,但人转头时鼻尖位置变化大。跟踪左右髋部(LEFT_HIP,RIGHT_HIP)的中点作为“躯干中心”会更稳定,因为人走路时躯干相对稳定。你可以根据实际效果选择或融合多个点。
  • 置信度阈值min_detection_confidencemin_tracking_confidence很重要。调高它们可以减少误检,但可能在人被部分遮挡时丢失目标;调低则更敏感,但也更容易引入噪声。需要在实际场景中权衡。
  • 图像分辨率:在VideoCapture中设置分辨率。分辨率越低,Mediapipe处理越快,但目标定位的像素精度会下降。480p是一个很好的起点。
  • 归一化偏移:将偏移量dx,dy归一化到[-1, 1]区间,是为了与上层控制器的输出范围匹配,方便设计控制律。

4.3 控制大脑:PID控制器与主循环

现在,我们需要一个“大脑”将视觉感知的偏移量,转化为电机控制的速度指令。一个简单有效的方法是使用PID控制器。PID通过比例(P)、积分(I)、微分(D)三个环节,计算出一个控制量来消除误差(这里是目标偏移量)。

main.py中,我们整合所有模块:

import time from car_controller import MecanumCar from pose_tracker import PoseTracker import config class PIDController: def __init__(self, kp, ki, kd, max_output=1.0): self.kp, self.ki, self.kd = kp, ki, kd self.max_output = max_output self.integral_x = 0 self.integral_y = 0 self.previous_error_x = 0 self.previous_error_y = 0 def compute(self, error_x, error_y, dt): # 比例项 p_x = self.kp * error_x p_y = self.kp * error_y # 积分项(防止积分饱和) self.integral_x += error_x * dt self.integral_x = max(min(self.integral_x, 2.0), -2.0) # 积分限幅 i_x = self.ki * self.integral_x self.integral_y += error_y * dt self.integral_y = max(min(self.integral_y, 2.0), -2.0) i_y = self.ki * self.integral_y # 微分项 d_x = self.kd * (error_x - self.previous_error_x) / dt if dt > 0 else 0 d_y = self.kd * (error_y - self.previous_error_y) / dt if dt > 0 else 0 self.previous_error_x = error_x self.previous_error_y = error_y # 合成输出并限幅 output_x = p_x + i_x + d_x output_y = p_y + i_y + d_y output_x = max(min(output_x, self.max_output), -self.max_output) output_y = max(min(output_y, self.max_output), -self.max_output) return output_x, output_y def main(): car = MecanumCar(config) tracker = PoseTracker(target_height=config.CAMERA_HEIGHT) pid_x = PIDController(kp=0.8, ki=0.05, kd=0.2, max_output=0.6) # 控制左右偏移 pid_y = PIDController(kp=0.6, ki=0.03, kd=0.15, max_output=0.5) # 控制前后偏移 last_time = time.time() lost_target_count = 0 MAX_LOST_COUNT = 10 # 连续丢失目标N帧后,小车停止 try: while True: current_time = time.time() dt = current_time - last_time last_time = current_time # 1. 获取目标偏移 dx, dy, detected = tracker.get_target_offset() if detected: lost_target_count = 0 # 2. PID计算控制量 # 注意:dx为正表示目标在画面右侧,我们需要小车右移(正vx)来对准,所以误差取反或直接在PID中处理符号 control_vx, control_vy = pid_x.compute(-dx, -dy, dt) # 这里简单起见,用同一个PID计算,实际最好分开 # control_vy = pid_y.compute(-dy, dt) # 更精细的做法是XY独立PID # 3. 发送速度指令给小车 # 这里将控制量直接映射为vx, vy。omega(旋转)可以设为0,或根据身体朝向计算。 car.move(vx=control_vx, vy=control_vy, omega=0) print(f"Target detected. dx={dx:.2f}, dy={dy:.2f}, vx={control_vx:.2f}, vy={control_vy:.2f}") else: lost_target_count += 1 print(f"Target lost. Count: {lost_target_count}") if lost_target_count > MAX_LOST_COUNT: car.stop() # 长时间丢失目标,停止小车 # 可选:进入搜索模式,让小车缓慢旋转寻找目标 # 控制循环频率,避免CPU占用过高 time.sleep(0.05) # 约20Hz except KeyboardInterrupt: print("\nProgram stopped by user.") finally: car.stop() tracker.release() if __name__ == "__main__": main()

PID参数整定心得: 这是整个项目调试中最“玄学”但也最有趣的部分。参数没有标准答案,完全取决于你的小车物理特性、摄像头视野、期望的跟随速度和平滑度。

  • Kp (比例):决定了系统对当前误差的反应速度。Kp太大,小车会对着目标位置剧烈振荡,像“醉汉”一样;Kp太小,小车反应迟钝,慢悠悠地挪过去。
  • Ki (积分):用来消除静态误差。如果小车总是停在离目标中心差一点的位置,可以适当增加Ki。但Ki太大会引起超调和振荡,甚至系统不稳定。
  • Kd (微分):预测误差变化趋势,起到阻尼作用,能抑制振荡,让运动更平滑。但对噪声敏感,如果Mediapipe检测的坐标有抖动,Kd大了反而会放大抖动。
  • 调参步骤
    1. 归零法:先将Ki和Kd设为0,只调Kp。逐渐增大Kp,直到小车开始出现明显的、持续的振荡。然后取这个Kp值的50%-70%作为初始值。
    2. 加积分:引入一个很小的Ki(如0.01),观察小车能否更准确地停在中心。如果出现缓慢振荡或超调,说明Ki大了,要减小。
    3. 加微分:最后引入Kd,从0.1开始尝试,观察振荡是否被抑制,运动是否更平滑。注意观察对检测抖动的敏感性。
    4. 分开调:X轴(左右)和Y轴(前后)的响应特性可能不同,最好有两套独立的PID参数。

5. 调试、优化与功能扩展

代码跑通只是第一步,要让小车稳定可靠地工作,还需要大量的调试和优化。

5.1 基础调试与问题排查

  1. 电机不转或转向错误

    • 检查供电:用万用表测量驱动板VM和VCC电压是否正常。电机电源是否独立且功率足够?
    • 检查接线:反复核对GPIO引脚编号是否与代码中config.py的定义一致。TB6612的AIN1/AIN2电平组合是否正确(01正转,10反转,00刹车,11刹车)。
    • 检查校准系数:在car_controller.pymove函数里打印计算出的wheel_speeds,看正负号是否符合预期。通过修改config.MOTOR_CALIBRATION来纠正转向。
  2. Mediapipe检测不到人或抖动严重

    • 光照:确保环境光线充足、均匀,避免强逆光或昏暗环境。
    • 背景:尽量选择背景简洁、与人体对比度高的环境进行初始测试。
    • 置信度阈值:适当降低min_detection_confidence(如0.4)看看。
    • 摄像头:用libcamera-hello确认摄像头画面本身是否清晰、有无畸变。
    • 性能:在终端运行主程序时,观察输出帧率。如果远低于5 FPS,考虑降低摄像头分辨率或使用Mediapipe的model_complexity=0(最轻量级)。
  3. 小车运动振荡或“画圈”

    • PID参数:这是最常见原因。回到上一节的PID整定步骤,优先降低Kp
    • 机械结构:检查轮子是否安装牢固,车架有无松动。松动的结构会引入无法用软件弥补的抖动。
    • 延迟:从检测到控制的总延迟过高。可以尝试在main.py循环中减少time.sleep的时间,或优化代码(如不在每一帧都绘制图像预览)。

5.2 进阶优化策略

  1. 目标丢失处理与搜索:当前代码在丢失目标后只是停止。可以改进为“搜索模式”:例如,让小车缓慢自转(设置一个小的omega),同时持续进行检测,一旦重新发现目标,立即切换回跟随模式。
  2. 滤波:Mediapipe返回的关键点坐标可能存在抖动。可以在pose_tracker.py中对计算出的dx, dy进行低通滤波移动平均滤波,平滑数据后再送给PID控制器,能显著提升小车运动的平滑度。
    # 简单的移动平均滤波示例 class MovingAverageFilter: def __init__(self, window_size=5): self.window_size = window_size self.values = [] def update(self, new_value): self.values.append(new_value) if len(self.values) > self.window_size: self.values.pop(0) return sum(self.values) / len(self.values) # 在PoseTracker类中使用 self.filter_x = MovingAverageFilter(5) self.filter_y = MovingAverageFilter(5) # ... 计算dx, dy后 ... dx_filtered = self.filter_x.update(dx) dy_filtered = self.filter_y.update(dy)
  3. 距离估计与速度规划:目前我们只控制了方向(让目标处于画面中心),但没有控制距离。可以通过人体关键点(如肩宽)在画面中的像素大小,粗略估计人与车的距离。然后设计一个分层控制器:外环控制距离(通过vy),内环控制对准(通过vxomega)。这样小车就能在跟随的同时,保持一个你设定的理想距离。
  4. 多目标与目标选择:Mediapipe可以检测多个人。可以修改逻辑,选择距离画面中心最近的人,或者通过某种交互(如挥手)来指定跟随目标。

5.3 扩展想法:从跟随到交互

基础跟随实现后,这个平台还有巨大的扩展空间:

  • 增加激光雷达:结合二维激光雷达(如RPLidar A1),可以实现更精确的避障和地图构建,让小车在跟随的同时绕开障碍物。
  • 语音交互:接入语音识别(如Vosk离线库)和语音合成,实现“过来”、“停下”、“跟我走”等语音指令控制。
  • 手势控制:利用Mediapipe的手部关键点检测,实现手势控制,例如手掌张开表示停止,握拳表示前进,向左挥手表示左转等。
  • 远程监控与控制:在树莓派上搭建一个简单的Web服务器(使用Flask),通过网页可以实时查看摄像头画面,并手动控制小车运动。

这个项目就像一把钥匙,打开了嵌入式AI机器人世界的大门。它融合了硬件集成、计算机视觉、自动控制等多个领域的知识。调试过程中,你会遇到各种各样的问题,但每一个问题的解决都会让你对这套系统的理解加深一分。当小车第一次稳稳地跟在你身后时,所有的折腾都值了。希望这份详细的指南能帮助你少走弯路,顺利打造出属于自己的智能跟随车。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询