500元搭建具身AI学习平台:硬件选型与三个入门项目 2026

用500元预算搭建具身AI学习平台,涵盖硬件选型(树莓派5/ESP32+舵机)、三个入门项目(机械臂抓取、移动机器人导航、视觉跟随),附完整代码与教程

低成本具身AI 500元机器人 学习平台 树莓派5 ESP32 舵机 入门项目
500元搭建具身AI学习平台:硬件选型与三个入门项目 2026

500元搭建具身AI学习平台:硬件选型与三个入门项目 2026

具身AI(Embodied AI)是2026年最热门的AI方向之一,但动辄数万元的机器人平台让很多学生和爱好者望而却步。本文将展示如何用500元预算搭建一个功能完整的具身AI学习平台,通过三个递进式项目掌握从感知到控制的全流程。

你将学到:

  • ✅ 500元预算下的最优硬件选型
  • ✅ 项目一:3自由度机械臂抓取(视觉+控制)
  • ✅ 项目二:差速移动机器人自主导航(SLAM+路径规划)
  • ✅ 项目三:视觉跟随机器人(目标检测+跟踪)
  • ✅ 从仿真到实物的完整开发流程

1. 硬件选型(500元预算)

1.1 核心控制器选择

方案价格算力适用场景推荐度
树莓派5 (8GB)350元2.4GHz 四核视觉+SLAM+学习⭐⭐⭐⭐⭐
ESP32-S325元240MHz 双核基础控制⭐⭐⭐
Jetson Nano800元+GPU加速深度学习⭐⭐(超预算)
Orange Pi 5280元RK3588S性价比之选⭐⭐⭐⭐

推荐方案:树莓派5 (8GB)

  • 充足的算力运行OpenCV、轻量级CNN
  • 完善的ROS2支持
  • 丰富的GPIO和USB接口
  • 社区资源丰富

1.2 完整BOM清单

组件规格价格备注
树莓派5 (8GB)8GB RAM350元核心控制器
64GB TF卡A2等级50元系统+数据存储
5V 4A电源Type-C40元稳定供电
SG90舵机 x49g40元机械臂
MG90S舵机 x2金属齿轮30元移动平台
HC-SR04超声波测距10元避障
OV5647摄像头500万像素40元视觉输入
L298N电机驱动双H桥15元电机控制
TT减速电机 x2带编码器30元移动平台
3D打印件PLA材料50元结构件
杜邦线+面包板-15元连接
亚克力板/木板底座20元底盘
总计690元超预算

压缩到500元方案:

  • 树莓派4B (4GB) 替代 树莓派5:节省100元
  • 二手舵机:节省20元
  • 简化结构件:节省30元
  • 最终价格:约540元(可通过闲鱼进一步压缩)

1.3 硬件组装

机械臂结构(3自由度):

[底座舵机] → [大臂舵机] → [小臂舵机] → [夹爪]
   (旋转)       (俯仰)       (俯仰)       (开合)

移动平台结构:

[左电机] ← [树莓派] → [右电机]
    ↓          ↓          ↓
[左轮]    [摄像头]    [右轮]
          [超声波]

2. 软件环境搭建

2.1 系统安装

# 下载树莓派系统
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz

# 写入TF卡
xzcat 2024-03-15-raspios-bookworm-arm64-lite.img.xz | sudo dd of=/dev/sdX bs=4M status=progress

# 启用SSH和WiFi
sudo touch /media/boot/ssh
cat > /media/boot/wpa_supplicant.conf << EOF
country=CN
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    ssid="YOUR_WIFI"
    psk="YOUR_PASSWORD"
}
EOF

2.2 开发环境

# 系统更新
sudo apt update && sudo apt upgrade -y

# 安装基础工具
sudo apt install -y python3-pip python3-venv git cmake

# 创建虚拟环境
python3 -m venv ~/robot_env
source ~/robot_env/bin/activate

# 安装核心库
pip install numpy opencv-python-headless pillow
pip install gpiozero RPi.GPIO
pip install flask  # Web控制界面

# 安装ROS2(可选)
sudo apt install -y ros-humble-ros-base

2.3 开发工具

本地开发(推荐):

  • VS Code + Remote SSH
  • 直接在树莓派上编辑代码
  • 实时调试和可视化

远程访问:

# SSH连接
ssh pi@raspberrypi.local

# 或使用VNC
sudo apt install realvnc-vnc-server
sudo systemctl enable vncserver-x11-serviced

3. 项目一:3自由度机械臂抓取

3.1 项目目标

  • 通过摄像头识别物体位置
  • 计算逆运动学求解关节角度
  • 控制机械臂完成抓取动作

3.2 硬件连接

# pin_config.py
"""舵机引脚配置"""
from gpiozero import Servo
from gpiozero.pins.pigpio import PiGPIOPin

# 使用pigpio提高精度
import pigpio
pi = pigpio.pi()

# 舵机GPIO引脚
BASE_SERVO_PIN = 17    # 底座旋转
SHOULDER_SERVO_PIN = 27  # 大臂
ELBOW_SERVO_PIN = 22   # 小臂
GRIPPER_SERVO_PIN = 23  # 夹爪

class ServoController:
    def __init__(self):
        self.pi = pigpio.pi()
        self.servos = {
            'base': BASE_SERVO_PIN,
            'shoulder': SHOULDER_SERVO_PIN,
            'elbow': ELBOW_PIN,
            'gripper': GRIPPER_PIN
        }
        
        # 初始化PWM
        for name, pin in self.servos.items():
            self.pi.set_mode(pin, pigpio.OUTPUT)
            self.pi.set_PWM_frequency(pin, 50)  # 50Hz
        
        # 初始位置(中位)
        self.positions = {
            'base': 1500,      # 微秒
            'shoulder': 1500,
            'elbow': 1500,
            'gripper': 1500
        }
    
    def set_angle(self, joint, angle_deg):
        """设置关节角度(0-180度)"""
        # 转换为脉宽(500-2500微秒)
        pulse_width = 500 + (angle_deg / 180.0) * 2000
        self.pi.set_servo_pulsewidth(self.servos[joint], pulse_width)
        self.positions[joint] = pulse_width
    
    def move_to(self, positions, duration=1.0):
        """平滑移动到目标位置"""
        import time
        steps = int(duration * 50)  # 50Hz更新率
        
        start_positions = self.positions.copy()
        
        for i in range(steps + 1):
            t = i / steps
            for joint, target in positions.items():
                current = start_positions[joint]
                new_pos = current + (target - current) * t
                self.pi.set_servo_pulsewidth(self.servos[joint], new_pos)
            time.sleep(0.02)
    
    def stop(self):
        """停止所有舵机"""
        for pin in self.servos.values():
            self.pi.set_servo_pulsewidth(pin, 0)

3.3 视觉识别

# vision.py
"""物体检测与定位"""
import cv2
import numpy as np

class ObjectDetector:
    def __init__(self):
        # 加载预训练模型(MobileNet-SSD)
        self.net = cv2.dnn.readNetFromCaffe(
            'models/deploy.prototxt',
            'models/mobilenet_iter_73000.caffemodel'
        )
        
        # 相机标定参数(需要实际标定)
        self.camera_matrix = np.array([
            [500, 0, 320],
            [0, 500, 240],
            [0, 0, 1]
        ])
        self.dist_coeffs = np.zeros((4, 1))
        
        # 目标类别(COCO数据集)
        self.target_classes = {
            0: 'person',
            15: 'cat',
            16: 'dog',
            47: 'cup',
            67: 'cell phone'
        }
    
    def detect(self, frame):
        """检测物体并返回位置"""
        h, w = frame.shape[:2]
        
        # 预处理
        blob = cv2.dnn.blobFromImage(
            frame, 0.007843, (300, 300), 127.5
        )
        self.net.setInput(blob)
        detections = self.net.forward()
        
        objects = []
        for i in range(detections.shape[2]):
            confidence = detections[0, 0, i, 2]
            if confidence > 0.5:
                class_id = int(detections[0, 0, i, 1])
                
                # 边界框
                box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
                x1, y1, x2, y2 = box.astype('int')
                
                # 中心点
                cx = (x1 + x2) // 2
                cy = (y1 + y2) // 2
                
                # 估计深度(基于边界框大小)
                box_area = (x2 - x1) * (y2 - y1)
                depth = self.estimate_depth(box_area)
                
                # 转换为3D坐标
                x_3d, y_3d, z_3d = self.pixel_to_3d(cx, cy, depth)
                
                objects.append({
                    'class': self.target_classes.get(class_id, 'unknown'),
                    'confidence': confidence,
                    'bbox': (x1, y1, x2, y2),
                    'center_2d': (cx, cy),
                    'position_3d': (x_3d, y_3d, z_3d)
                })
        
        return objects
    
    def estimate_depth(self, box_area):
        """基于边界框面积估计深度(经验公式)"""
        # 需要根据实际情况标定
        return 500.0 / np.sqrt(box_area)
    
    def pixel_to_3d(self, u, v, depth):
        """像素坐标转3D坐标"""
        x = (u - self.camera_matrix[0, 2]) * depth / self.camera_matrix[0, 0]
        y = (v - self.camera_matrix[1, 2]) * depth / self.camera_matrix[1, 1]
        z = depth
        return x, y, z

3.4 逆运动学

# kinematics.py
"""3自由度机械臂逆运动学"""
import numpy as np

class ArmKinematics:
    def __init__(self):
        # 机械臂参数(米)
        self.L1 = 0.10  # 底座高度
        self.L2 = 0.15  # 大臂长度
        self.L3 = 0.15  # 小臂长度
        
        # 关节限制(度)
        self.joint_limits = {
            'base': (0, 180),
            'shoulder': (0, 180),
            'elbow': (0, 180)
        }
    
    def inverse_kinematics(self, x, y, z):
        """
        逆运动学求解
        输入:目标位置 (x, y, z) 单位:米
        输出:关节角度 (base, shoulder, elbow) 单位:度
        """
        # 底座旋转角度
        base_angle = np.degrees(np.arctan2(y, x))
        
        # 转换到机械臂坐标系
        r = np.sqrt(x**2 + y**2)
        h = z - self.L1
        
        # 计算肘部角度(余弦定理)
        cos_elbow = (self.L2**2 + self.L3**2 - r**2 - h**2) / (2 * self.L2 * self.L3)
        cos_elbow = np.clip(cos_elbow, -1, 1)
        elbow_angle = 180 - np.degrees(np.arccos(cos_elbow))
        
        # 计算肩部角度
        k1 = self.L2 + self.L3 * np.cos(np.radians(180 - elbow_angle))
        k2 = self.L3 * np.sin(np.radians(180 - elbow_angle))
        
        shoulder_angle = np.degrees(np.arctan2(h, r) - np.arctan2(k2, k1))
        
        # 检查关节限制
        if not self.check_limits(base_angle, shoulder_angle, elbow_angle):
            return None  # 不可达
        
        return base_angle, shoulder_angle, elbow_angle
    
    def check_limits(self, base, shoulder, elbow):
        """检查关节是否在限制范围内"""
        limits = [
            self.joint_limits['base'],
            self.joint_limits['shoulder'],
            self.joint_limits['elbow']
        ]
        angles = [base, shoulder, elbow]
        
        for angle, (min_val, max_val) in zip(angles, limits):
            if not (min_val <= angle <= max_val):
                return False
        return True
    
    def forward_kinematics(self, base, shoulder, elbow):
        """正运动学(用于验证)"""
        base_rad = np.radians(base)
        shoulder_rad = np.radians(shoulder)
        elbow_rad = np.radians(elbow)
        
        # 末端位置
        x = (self.L2 * np.cos(shoulder_rad) + 
             self.L3 * np.cos(shoulder_rad + elbow_rad - np.pi))
        y = 0
        z = self.L1 + (self.L2 * np.sin(shoulder_rad) + 
                       self.L3 * np.sin(shoulder_rad + elbow_rad - np.pi))
        
        # 应用底座旋转
        x_rot = x * np.cos(base_rad)
        y_rot = x * np.sin(base_rad)
        
        return x_rot, y_rot, z

3.5 抓取控制

# grasp.py
"""抓取控制主程序"""
import cv2
import time
from pin_config import ServoController
from vision import ObjectDetector
from kinematics import ArmKinematics

class GraspController:
    def __init__(self):
        self.servo = ServoController()
        self.vision = ObjectDetector()
        self.kinematics = ArmKinematics()
        
        # 相机到机械臂基座的变换(需要标定)
        self.camera_to_base = np.array([
            [1, 0, 0, 0],    # X
            [0, 1, 0, 0],    # Y
            [0, 0, 1, 0.2],  # Z (相机高度)
            [0, 0, 0, 1]
        ])
        
        # 初始化位置
        self.home_position = {
            'base': 1500,
            'shoulder': 1500,
            'elbow': 1500,
            'gripper': 2000  # 张开
        }
        self.servo.move_to(self.home_position)
    
    def run(self):
        """主控制循环"""
        cap = cv2.VideoCapture(0)
        
        print("机械臂抓取系统启动")
        print("按 'q' 退出")
        
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            
            # 检测物体
            objects = self.vision.detect(frame)
            
            if objects:
                # 选择置信度最高的物体
                target = max(objects, key=lambda x: x['confidence'])
                
                # 绘制检测结果
                x1, y1, x2, y2 = target['bbox']
                cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
                cv2.putText(frame, target['class'], (x1, y1-10),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
                
                # 尝试抓取
                if self.try_grasp(target['position_3d']):
                    print("抓取成功!")
                    time.sleep(2)
                    self.return_home()
            
            # 显示图像
            cv2.imshow('Grasp Control', frame)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        
        cap.release()
        cv2.destroyAllWindows()
        self.servo.stop()
    
    def try_grasp(self, target_pos):
        """尝试抓取目标"""
        x, y, z = target_pos
        
        # 1. 移动到目标上方
        approach_pos = (x, y, z + 0.05)  # 上方5cm
        angles = self.kinematics.inverse_kinematics(*approach_pos)
        if angles is None:
            print("目标不可达")
            return False
        
        base, shoulder, elbow = angles
        self.servo.set_angle('base', base)
        self.servo.set_angle('shoulder', shoulder)
        self.servo.set_angle('elbow', elbow)
        time.sleep(1)
        
        # 2. 下降到目标位置
        self.servo.set_angle('shoulder', shoulder - 10)
        self.servo.set_angle('elbow', elbow + 10)
        time.sleep(0.5)
        
        # 3. 闭合夹爪
        self.servo.set_angle('gripper', 1000)  # 闭合
        time.sleep(0.5)
        
        # 4. 抬起
        self.servo.set_angle('shoulder', shoulder)
        self.servo.set_angle('elbow', elbow)
        time.sleep(0.5)
        
        return True
    
    def return_home(self):
        """返回初始位置"""
        self.servo.move_to(self.home_position, duration=2.0)

if __name__ == '__main__':
    controller = GraspController()
    controller.run()

4. 项目二:差速移动机器人自主导航

4.1 项目目标

  • 构建2D栅格地图
  • 实现A*路径规划
  • 完成点到点自主导航

4.2 硬件组装

# motor_control.py
"""差速驱动控制"""
import RPi.GPIO as GPIO
import time

class DifferentialDrive:
    def __init__(self):
        # 电机引脚
        self.LEFT_FORWARD = 17
        self.LEFT_BACKWARD = 27
        self.RIGHT_FORWARD = 22
        self.RIGHT_BACKWARD = 23
        
        # PWM引脚
        self.LEFT_PWM = 18
        self.RIGHT_PWM = 19
        
        GPIO.setmode(GPIO.BCM)
        GPIO.setup([self.LEFT_FORWARD, self.LEFT_BACKWARD,
                   self.RIGHT_FORWARD, self.RIGHT_BACKWARD], GPIO.OUT)
        GPIO.setup([self.LEFT_PWM, self.RIGHT_PWM], GPIO.OUT)
        
        # PWM初始化
        self.left_pwm = GPIO.PWM(self.LEFT_PWM, 1000)
        self.right_pwm = GPIO.PWM(self.RIGHT_PWM, 1000)
        self.left_pwm.start(0)
        self.right_pwm.start(0)
        
        # 机器人参数
        self.wheel_base = 0.15  # 轮距(米)
        self.wheel_radius = 0.03  # 轮半径(米)
        
        # 里程计
        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0
    
    def set_velocity(self, linear, angular):
        """
        设置速度
        linear: 线速度 (m/s)
        angular: 角速度 (rad/s)
        """
        # 差速运动学
        v_left = linear - angular * self.wheel_base / 2
        v_right = linear + angular * self.wheel_base / 2
        
        # 转换为PWM占空比(0-100)
        max_speed = 0.5  # 最大速度 0.5 m/s
        duty_left = min(100, abs(v_left) / max_speed * 100)
        duty_right = min(100, abs(v_right) / max_speed * 100)
        
        # 设置方向
        GPIO.output(self.LEFT_FORWARD, GPIO.HIGH if v_left > 0 else GPIO.LOW)
        GPIO.output(self.LEFT_BACKWARD, GPIO.LOW if v_left > 0 else GPIO.HIGH)
        GPIO.output(self.RIGHT_FORWARD, GPIO.HIGH if v_right > 0 else GPIO.LOW)
        GPIO.output(self.RIGHT_BACKWARD, GPIO.LOW if v_right > 0 else GPIO.HIGH)
        
        # 设置速度
        self.left_pwm.ChangeDutyCycle(duty_left)
        self.right_pwm.ChangeDutyCycle(duty_right)
    
    def stop(self):
        """停止"""
        self.set_velocity(0, 0)
    
    def update_odometry(self, dt):
        """更新里程计(需要编码器反馈)"""
        # 这里简化为基于命令值的积分
        # 实际应使用编码器
        pass
    
    def cleanup(self):
        """清理GPIO"""
        self.stop()
        self.left_pwm.stop()
        self.right_pwm.stop()
        GPIO.cleanup()

4.3 地图构建

# mapping.py
"""2D栅格地图"""
import numpy as np
import cv2

class OccupancyGrid:
    def __init__(self, width=100, height=100, resolution=0.05):
        """
        width: 地图宽度(格)
        height: 地图高度(格)
        resolution: 分辨率(米/格)
        """
        self.width = width
        self.height = height
        self.resolution = resolution
        
        # 0: 未知, 1: 空闲, 2: 占用
        self.grid = np.zeros((height, width), dtype=np.int8)
        
        # 机器人位置
        self.robot_x = width // 2
        self.robot_y = height // 2
        self.robot_theta = 0
    
    def update_from_lidar(self, ranges, angles, robot_x, robot_y, robot_theta):
        """从激光雷达数据更新地图"""
        self.robot_x = robot_x
        self.robot_y = robot_y
        self.robot_theta = robot_theta
        
        for r, angle in zip(ranges, angles):
            if r < 0.1 or r > 5.0:  # 过滤无效值
                continue
            
            # 端点坐标
            end_x = robot_x + r * np.cos(robot_theta + angle)
            end_y = robot_y + r * np.sin(robot_theta + angle)
            
            # 转换为栅格坐标
            grid_x = int(end_x / self.resolution) + self.width // 2
            grid_y = int(end_y / self.resolution) + self.height // 2
            
            if 0 <= grid_x < self.width and 0 <= grid_y < self.height:
                self.grid[grid_y, grid_x] = 2  # 占用
                
                # Bresenham画线算法标记空闲区域
                self._bresenham_line(
                    int(robot_x / self.resolution) + self.width // 2,
                    int(robot_y / self.resolution) + self.height // 2,
                    grid_x, grid_y
                )
    
    def _bresenham_line(self, x0, y0, x1, y1):
        """Bresenham画线算法"""
        dx = abs(x1 - x0)
        dy = abs(y1 - y0)
        sx = 1 if x0 < x1 else -1
        sy = 1 if y0 < y1 else -1
        err = dx - dy
        
        while True:
            if 0 <= x0 < self.width and 0 <= y0 < self.height:
                if self.grid[y0, x0] == 0:  # 只更新未知区域
                    self.grid[y0, x0] = 1  # 空闲
            
            if x0 == x1 and y0 == y1:
                break
            
            e2 = 2 * err
            if e2 > -dy:
                err -= dy
                x0 += sx
            if e2 < dx:
                err += dx
                y0 += sy
    
    def save_map(self, filename):
        """保存地图为图像"""
        # 0: 灰色, 1: 白色, 2: 黑色
        colors = {0: 128, 1: 255, 2: 0}
        img = np.vectorize(colors.get)(self.grid).astype(np.uint8)
        cv2.imwrite(filename, img)

4.4 路径规划

# path_planner.py
"""A*路径规划"""
import numpy as np
import heapq

class AStarPlanner:
    def __init__(self, occupancy_grid):
        self.grid = occupancy_grid
        self.width = occupancy_grid.width
        self.height = occupancy_grid.height
    
    def plan(self, start, goal):
        """
        规划路径
        start: (x, y) 起点(栅格坐标)
        goal: (x, y) 终点(栅格坐标)
        返回: 路径点列表 [(x, y), ...]
        """
        # 开放列表(优先队列)
        open_list = []
        heapq.heappush(open_list, (0, start))
        
        # 已访问集合
        closed_set = set()
        
        # 父节点映射
        came_from = {}
        
        # g值和f值
        g_score = {start: 0}
        f_score = {start: self._heuristic(start, goal)}
        
        while open_list:
            current = heapq.heappop(open_list)[1]
            
            if current == goal:
                return self._reconstruct_path(came_from, current)
            
            closed_set.add(current)
            
            # 遍历邻居
            for neighbor in self._get_neighbors(current):
                if neighbor in closed_set:
                    continue
                
                # 检查是否可通行
                if self.grid.grid[neighbor[1], neighbor[0]] == 2:
                    continue
                
                # 计算tentative g值
                tentative_g = g_score[current] + self._distance(current, neighbor)
                
                if tentative_g < g_score.get(neighbor, float('inf')):
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g
                    f_score[neighbor] = tentative_g + self._heuristic(neighbor, goal)
                    
                    if neighbor not in [item[1] for item in open_list]:
                        heapq.heappush(open_list, (f_score[neighbor], neighbor))
        
        return None  # 无路径
    
    def _heuristic(self, a, b):
        """启发函数(欧氏距离)"""
        return np.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
    
    def _distance(self, a, b):
        """两点间距离"""
        return self._heuristic(a, b)
    
    def _get_neighbors(self, node):
        """获取邻居节点(8方向)"""
        x, y = node
        neighbors = [
            (x+1, y), (x-1, y), (x, y+1), (x, y-1),
            (x+1, y+1), (x-1, y-1), (x+1, y-1), (x-1, y+1)
        ]
        
        # 过滤边界外
        return [(nx, ny) for nx, ny in neighbors
                if 0 <= nx < self.width and 0 <= ny < self.height]
    
    def _reconstruct_path(self, came_from, current):
        """重建路径"""
        path = [current]
        while current in came_from:
            current = came_from[current]
            path.append(current)
        path.reverse()
        return path

4.5 导航控制

# navigation.py
"""自主导航主程序"""
import time
import numpy as np
from motor_control import DifferentialDrive
from mapping import OccupancyGrid
from path_planner import AStarPlanner

class Navigator:
    def __init__(self):
        self.drive = DifferentialDrive()
        self.grid = OccupancyGrid()
        self.planner = AStarPlanner(self.grid)
        
        # 控制参数
        self.kp_linear = 0.5
        self.kp_angular = 1.0
        self.goal_threshold = 0.1  # 10cm
    
    def navigate_to(self, target_x, target_y):
        """导航到目标位置"""
        # 转换为栅格坐标
        start = (self.grid.robot_x, self.grid.robot_y)
        goal = (
            int(target_x / self.grid.resolution) + self.grid.width // 2,
            int(target_y / self.grid.resolution) + self.grid.height // 2
        )
        
        # 规划路径
        path = self.planner.plan(start, goal)
        if path is None:
            print("无法找到路径")
            return False
        
        print(f"找到路径,共 {len(path)} 个点")
        
        # 执行路径
        for waypoint in path:
            if not self._follow_waypoint(waypoint):
                return False
        
        print("到达目标")
        return True
    
    def _follow_waypoint(self, waypoint):
        """跟随路径点"""
        wx, wy = waypoint
        
        # 转换为世界坐标
        target_x = (wx - self.grid.width // 2) * self.grid.resolution
        target_y = (wy - self.grid.height // 2) * self.grid.resolution
        
        while True:
            # 计算误差
            dx = target_x - self.drive.x
            dy = target_y - self.drive.y
            distance = np.sqrt(dx**2 + dy**2)
            
            if distance < self.goal_threshold:
                break
            
            # 计算目标角度
            target_theta = np.arctan2(dy, dx)
            angle_error = target_theta - self.drive.theta
            
            # 归一化角度到 [-pi, pi]
            while angle_error > np.pi:
                angle_error -= 2 * np.pi
            while angle_error < -np.pi:
                angle_error += 2 * np.pi
            
            # 计算速度
            linear = self.kp_linear * distance * np.cos(angle_error)
            angular = self.kp_angular * angle_error
            
            # 限制速度
            linear = np.clip(linear, -0.2, 0.2)
            angular = np.clip(angular, -1.0, 1.0)
            
            # 执行
            self.drive.set_velocity(linear, angular)
            time.sleep(0.05)
        
        return True

if __name__ == '__main__':
    nav = Navigator()
    try:
        nav.navigate_to(1.0, 0.5)  # 导航到 (1m, 0.5m)
    finally:
        nav.drive.cleanup()

5. 项目三:视觉跟随机器人

5.1 项目目标

  • 检测并跟踪特定目标(人脸/颜色/二维码)
  • 保持目标在视野中心
  • 维持固定距离

5.2 视觉跟踪

# visual_tracking.py
"""视觉跟踪"""
import cv2
import numpy as np

class VisualTracker:
    def __init__(self, mode='face'):
        """
        mode: 'face' | 'color' | 'qrcode'
        """
        self.mode = mode
        
        if mode == 'face':
            self.detector = cv2.CascadeClassifier(
                cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
            )
        elif mode == 'color':
            # HSV颜色范围(红色)
            self.lower_hsv = np.array([0, 100, 100])
            self.upper_hsv = np.array([10, 255, 255])
        elif mode == 'qrcode':
            self.detector = cv2.QRCodeDetector()
        
        # 目标状态
        self.target_center = None
        self.target_size = None
    
    def detect(self, frame):
        """检测目标"""
        if self.mode == 'face':
            return self._detect_face(frame)
        elif self.mode == 'color':
            return self._detect_color(frame)
        elif self.mode == 'qrcode':
            return self._detect_qrcode(frame)
    
    def _detect_face(self, frame):
        """人脸检测"""
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.detector.detectMultiScale(
            gray, 1.3, 5
        )
        
        if len(faces) > 0:
            # 选择最大的人脸
            x, y, w, h = max(faces, key=lambda f: f[2] * f[3])
            self.target_center = (x + w // 2, y + h // 2)
            self.target_size = w * h
            return True
        return False
    
    def _detect_color(self, frame):
        """颜色检测"""
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, self.lower_hsv, self.upper_hsv)
        
        # 形态学操作
        kernel = np.ones((5, 5), np.uint8)
        mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
        mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
        
        # 找轮廓
        contours, _ = cv2.findContours(
            mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
        )
        
        if contours:
            # 选择最大轮廓
            cnt = max(contours, key=cv2.contourArea)
            x, y, w, h = cv2.boundingRect(cnt)
            self.target_center = (x + w // 2, y + h // 2)
            self.target_size = w * h
            return True
        return False
    
    def _detect_qrcode(self, frame):
        """二维码检测"""
        retval, points, _ = self.detector.detectAndDecode(frame)
        
        if points is not None:
            points = points[0].astype(int)
            x, y, w, h = cv2.boundingRect(points)
            self.target_center = (x + w // 2, y + h // 2)
            self.target_size = w * h
            return True
        return False
    
    def get_error(self, frame_shape):
        """计算跟踪误差"""
        if self.target_center is None:
            return None, None
        
        h, w = frame_shape[:2]
        center_x, center_y = w // 2, h // 2
        
        # 像素误差
        error_x = self.target_center[0] - center_x
        error_y = self.target_center[1] - center_y
        
        # 归一化到 [-1, 1]
        error_x_norm = error_x / (w / 2)
        error_y_norm = error_y / (h / 2)
        
        return error_x_norm, error_y_norm

5.3 跟随控制

# follower.py
"""视觉跟随主程序"""
import cv2
import time
from motor_control import DifferentialDrive
from visual_tracking import VisualTracker

class VisualFollower:
    def __init__(self):
        self.drive = DifferentialDrive()
        self.tracker = VisualTracker(mode='face')
        
        # 控制参数
        self.kp_angular = 1.5  # 角度控制增益
        self.kp_distance = 0.002  # 距离控制增益
        self.target_size = 10000  # 期望目标大小(像素^2)
        self.dead_zone = 0.1  # 死区
    
    def run(self):
        """主循环"""
        cap = cv2.VideoCapture(0)
        
        print("视觉跟随系统启动")
        print("按 'q' 退出")
        
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            
            # 检测目标
            detected = self.tracker.detect(frame)
            
            if detected:
                # 计算误差
                error_x, error_y = self.tracker.get_error(frame.shape)
                
                # 角度控制(水平跟踪)
                if abs(error_x) > self.dead_zone:
                    angular = self.kp_angular * error_x
                else:
                    angular = 0
                
                # 距离控制(基于目标大小)
                size_error = self.target_size - self.tracker.target_size
                linear = self.kp_distance * size_error
                
                # 限制速度
                linear = np.clip(linear, -0.2, 0.3)
                angular = np.clip(angular, -1.0, 1.0)
                
                # 执行
                self.drive.set_velocity(linear, angular)
                
                # 可视化
                cv2.circle(frame, self.tracker.target_center, 5, (0, 255, 0), -1)
                cv2.putText(frame, f'Linear: {linear:.2f}', (10, 30),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
                cv2.putText(frame, f'Angular: {angular:.2f}', (10, 60),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            else:
                # 未检测到目标,原地旋转搜索
                self.drive.set_velocity(0, 0.5)
                cv2.putText(frame, 'Searching...', (10, 30),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
            
            # 显示图像
            cv2.imshow('Visual Follower', frame)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        
        cap.release()
        cv2.destroyAllWindows()
        self.drive.cleanup()

if __name__ == '__main__':
    follower = VisualFollower()
    follower.run()

6. 进阶建议

6.1 硬件升级路径

  1. 增加激光雷达(RPLidar A1,约300元)

    • 实现真正的SLAM建图
    • 提升导航精度
  2. 升级到Jetson Nano(约800元)

    • GPU加速深度学习
    • 运行YOLO等复杂模型
  3. 添加机械臂(SO-100,约500元)

    • 实现移动操作
    • 抓取+运输任务

6.2 软件进阶

  1. ROS2集成

    • 使用Nav2导航框架
    • 集成MoveIt2机械臂控制
  2. 深度学习

    • 训练自定义目标检测模型
    • 实现语义分割
  3. SLAM算法

    • 实现Cartographer或RTAB-Map
    • 多传感器融合

6.3 学习资源

  • 官方文档:树莓派、ROS2、OpenCV
  • 在线课程:Coursera “Modern Robotics”
  • 开源项目:GitHub搜索 “raspberry pi robot”
  • 社区论坛:ROS Discourse、Raspberry Pi Forum

7. 常见问题(FAQ)

Q1: 500元预算够吗?会不会超支?

实际采购时,树莓派5 (8GB) 约350元,加上其他组件确实会超预算。建议采用替代方案:使用树莓派4B (4GB) 可节省约100元,二手舵机和简化结构件再节省50元,最终可控制在540元左右。如果通过闲鱼采购二手硬件,500元内完全可以搞定。

Q2: 没有激光雷达,项目二的导航能实现吗?

可以。本文使用超声波传感器 + 里程计实现简易避障和建图。虽然精度不如激光雷达,但足以理解SLAM和路径规划的核心原理。后续可升级到RPLidar A1(约300元)获得更好的体验。

Q3: 这些项目需要多少编程基础?

建议具备Python基础(变量、函数、循环)。本文所有代码都提供完整注释,即使零基础也能跟着做。项目一侧重控制逻辑,项目二侧重算法,项目三侧重视觉处理,难度递进。

Q4: 树莓派能运行深度学习模型吗?

树莓派5可以运行轻量级模型(如MobileNet-SSD),但推理速度较慢(约2-5 FPS)。如果需要实时目标检测,建议升级到Jetson Nano或使用Intel Neural Compute Stick加速。

Q5: 如何从这些入门项目过渡到专业机器人开发?

建议路径:完成本文三个项目 → 学习ROS2(参考ROS2入门教程)→ 尝试SO-100机械臂等开源项目 → 参与具身AI技术栈的系统学习 → 探索Isaac Lab等仿真平台。

FAQ Schema:

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "500元预算搭建具身AI平台够吗?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "使用树莓派4B替代树莓派5,配合二手舵机和简化结构件,可控制在540元左右。通过闲鱼采购二手硬件,500元内完全可以搞定。"
      }
    },
    {
      "@type": "Question",
      "name": "没有激光雷达,移动机器人导航能实现吗?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "可以。使用超声波传感器 + 里程计可实现简易避障和建图,足以理解SLAM和路径规划原理。后续可升级到RPLidar A1获得更好体验。"
      }
    },
    {
      "@type": "Question",
      "name": "这些项目需要多少编程基础?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "建议具备Python基础(变量、函数、循环)。本文所有代码都提供完整注释,即使零基础也能跟着做。三个项目难度递进,从控制逻辑到算法再到视觉处理。"
      }
    },
    {
      "@type": "Question",
      "name": "树莓派能运行深度学习模型吗?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "树莓派5可以运行轻量级模型(如MobileNet-SSD),但推理速度较慢(约2-5 FPS)。需要实时检测建议升级到Jetson Nano或使用Intel Neural Compute Stick加速。"
      }
    },
    {
      "@type": "Question",
      "name": "如何从入门项目过渡到专业机器人开发?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "建议路径:完成入门项目 → 学习ROS2 → 尝试SO-100等开源项目 → 系统学习具身AI技术栈 → 探索Isaac Lab等仿真平台。"
      }
    }
  ]
}

总结

通过500元预算,我们搭建了一个功能完整的具身AI学习平台,完成了三个递进式项目:

  1. ✅ 机械臂抓取(视觉+运动学)
  2. ✅ 自主导航(SLAM+路径规划)
  3. ✅ 视觉跟随(目标检测+跟踪)

这个平台虽然简单,但涵盖了具身AI的核心概念:感知、决策、控制。通过动手实践,你将深入理解机器人系统的各个环节,为后续学习更复杂的系统打下坚实基础。

下一步建议:

  • 参加机器人竞赛(如RoboMaster)
  • 贡献开源项目
  • 探索Sim2Real迁移(在仿真中训练,部署到实物)
  • 研究前沿论文(ICRA、IROS)

相关资源:

记住:最好的学习方式是动手实践。从今天开始,搭建你的第一个机器人吧!