自动送餐机器人技术全解析——从路径规划到任务调度

前言

你在餐厅见过自动送餐机器人吗?

  • 它怎么知道要去哪个桌子?
  • 遇到人挡路怎么办?
  • 多个订单怎么安排?
  • 怎么不撞墙、不迷路?

今天从原理到代码,彻底搞懂送餐机器人的核心技术。

系统架构总览

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    送餐机器人系统架构                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                         云端/后台系统                               │  │
│   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                 │  │
│   │  │  订单系统   │  │  地图管理   │  │  调度中心   │                 │  │
│   │  │  (POS对接)  │  │             │  │  (多机协调) │                 │  │
│   │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘                 │  │
│   └─────────┼────────────────┼────────────────┼─────────────────────────┘  │
│             │                │                │                             │
│             └────────────────┼────────────────┘                             │
│                              │ WiFi/4G                                      │
│                              ▼                                              │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                         机器人本体                                  │  │
│   │                                                                     │  │
│   │   ┌───────────────────────────────────────────────────────────┐    │  │
│   │   │                    决策层                                  │    │  │
│   │   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │    │  │
│   │   │  │  任务管理   │  │  行为决策   │  │  异常处理   │       │    │  │
│   │   │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘       │    │  │
│   │   └─────────┼────────────────┼────────────────┼───────────────┘    │  │
│   │             │                │                │                     │  │
│   │   ┌─────────┼────────────────┼────────────────┼───────────────┐    │  │
│   │   │         ▼                ▼                ▼               │    │  │
│   │   │                    规划层                                  │    │  │
│   │   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │    │  │
│   │   │  │  全局规划   │  │  局部规划   │  │  速度规划   │       │    │  │
│   │   │  │  (A*/D*)    │  │  (DWA/TEB)  │  │             │       │    │  │
│   │   │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘       │    │  │
│   │   └─────────┼────────────────┼────────────────┼───────────────┘    │  │
│   │             │                │                │                     │  │
│   │   ┌─────────┼────────────────┼────────────────┼───────────────┐    │  │
│   │   │         ▼                ▼                ▼               │    │  │
│   │   │                    感知层                                  │    │  │
│   │   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │    │  │
│   │   │  │  SLAM定位   │  │  障碍物检测 │  │  语义识别   │       │    │  │
│   │   │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘       │    │  │
│   │   └─────────┼────────────────┼────────────────┼───────────────┘    │  │
│   │             │                │                │                     │  │
│   │   ┌─────────┼────────────────┼────────────────┼───────────────┐    │  │
│   │   │         ▼                ▼                ▼               │    │  │
│   │   │                    硬件层                                  │    │  │
│   │   │  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐  │    │  │
│   │   │  │ 激光雷达│ │ 深度相机│ │  IMU   │ │ 编码器 │ │ 电机   │  │    │  │
│   │   │  └────────┘ └────────┘ └────────┘ └────────┘ └────────┘  │    │  │
│   │   └───────────────────────────────────────────────────────────┘    │  │
│   │                                                                     │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

一、定位与建图(SLAM)

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    SLAM:机器人怎么知道自己在哪?                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   【问题】                                                                  │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   机器人需要同时解决两个问题:                                               │
│   1. 我在哪? (定位 Localization)                                          │
│   2. 周围是什么样? (建图 Mapping)                                         │
│                                                                             │
│   鸡生蛋问题: 定位需要地图,建图需要定位                                   │
│   解决方案: SLAM (同时定位与建图)                                          │
│                                                                             │
│   【激光SLAM流程】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   激光雷达扫描 ──► 点云数据 ──► 特征提取 ──► 扫描匹配                      │
│                                      │                                      │
│                                      ▼                                      │
│                               位姿估计 ◄──── 里程计融合                     │
│                                      │                                      │
│                                      ▼                                      │
│                               回环检测 ──► 地图优化                         │
│                                      │                                      │
│                                      ▼                                      │
│                               栅格地图输出                                  │
│                                                                             │
│   【栅格地图】                                                              │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌───────────────────────────────────────┐                                │
│   │ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ │  ■ = 障碍物 (100)                │
│   │ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ │  □ = 可通行 (0)                  │
│   │ ■ □ □ □ ■ ■ □ □ □ □ □ ■ ■ □ □ □ ■ │  ░ = 未知 (-1)                   │
│   │ ■ □ □ □ ■ ■ □ □ □ □ □ ■ ■ □ □ □ ■ │                                   │
│   │ ■ □ □ □ □ □ □ □ ★ □ □ □ □ □ □ □ ■ │  ★ = 机器人位置                   │
│   │ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ │                                   │
│   │ ■ □ □ ■ ■ ■ □ □ □ □ □ ■ ■ ■ □ □ ■ │  每个格子代表实际的                │
│   │ ■ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ ■ │  5cm x 5cm 区域                   │
│   │ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ │                                   │
│   └───────────────────────────────────────┘                                │
│                                                                             │
│   餐厅场景下还需要标注:                                                     │
│   - 桌子位置 (Table 1, Table 2, ...)                                       │
│   - 取餐点                                                                  │
│   - 充电桩位置                                                              │
│   - 禁行区域 (厨房内部等)                                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

SLAM定位代码

cpp 复制代码
/**
 * 简化的粒子滤波定位 (AMCL原理)
 */

#include <vector>
#include <random>
#include <cmath>

struct Pose2D {
    double x, y, theta;
};

struct Particle {
    Pose2D pose;
    double weight;
};

struct LaserScan {
    std::vector<double> ranges;  // 距离数据
    double angle_min;
    double angle_increment;
    int num_readings;
};

class ParticleFilter {
public:
    ParticleFilter(int num_particles = 500) 
        : num_particles_(num_particles), gen_(std::random_device{}()) {
        particles_.resize(num_particles);
    }
    
    /**
     * 初始化粒子
     */
    void initialize(double x, double y, double theta, 
                    double xy_std = 0.5, double theta_std = 0.2) {
        std::normal_distribution<> xy_dist(0, xy_std);
        std::normal_distribution<> theta_dist(0, theta_std);
        
        for (auto& p : particles_) {
            p.pose.x = x + xy_dist(gen_);
            p.pose.y = y + xy_dist(gen_);
            p.pose.theta = theta + theta_dist(gen_);
            p.weight = 1.0 / num_particles_;
        }
    }
    
    /**
     * 运动更新 (预测步骤)
     */
    void motionUpdate(double delta_x, double delta_y, double delta_theta) {
        // 运动噪声
        double trans = std::sqrt(delta_x * delta_x + delta_y * delta_y);
        double rot1 = std::atan2(delta_y, delta_x);
        double rot2 = delta_theta - rot1;
        
        std::normal_distribution<> trans_noise(0, 0.1 * trans + 0.01);
        std::normal_distribution<> rot_noise(0, 0.1 * std::abs(delta_theta) + 0.02);
        
        for (auto& p : particles_) {
            double noisy_rot1 = rot1 + rot_noise(gen_);
            double noisy_trans = trans + trans_noise(gen_);
            double noisy_rot2 = rot2 + rot_noise(gen_);
            
            p.pose.x += noisy_trans * std::cos(p.pose.theta + noisy_rot1);
            p.pose.y += noisy_trans * std::sin(p.pose.theta + noisy_rot1);
            p.pose.theta += noisy_rot1 + noisy_rot2;
            
            // 归一化角度
            while (p.pose.theta > M_PI) p.pose.theta -= 2 * M_PI;
            while (p.pose.theta < -M_PI) p.pose.theta += 2 * M_PI;
        }
    }
    
    /**
     * 观测更新 (使用激光雷达)
     */
    void sensorUpdate(const LaserScan& scan, const OccupancyGrid& map) {
        double total_weight = 0;
        
        for (auto& p : particles_) {
            // 计算该粒子位置下,激光扫描的似然
            p.weight = computeLikelihood(p.pose, scan, map);
            total_weight += p.weight;
        }
        
        // 归一化权重
        if (total_weight > 0) {
            for (auto& p : particles_) {
                p.weight /= total_weight;
            }
        }
    }
    
    /**
     * 计算激光扫描的似然
     */
    double computeLikelihood(const Pose2D& pose, const LaserScan& scan,
                             const OccupancyGrid& map) {
        double likelihood = 1.0;
        
        // 每隔几个点采样计算
        int step = 10;
        
        for (int i = 0; i < scan.num_readings; i += step) {
            double angle = scan.angle_min + i * scan.angle_increment;
            double range = scan.ranges[i];
            
            if (range < 0.1 || range > 10.0) continue;  // 无效数据
            
            // 计算激光端点在地图坐标系中的位置
            double world_angle = pose.theta + angle;
            double hit_x = pose.x + range * std::cos(world_angle);
            double hit_y = pose.y + range * std::sin(world_angle);
            
            // 查询地图,计算最近障碍物距离
            double dist = map.distanceToNearestObstacle(hit_x, hit_y);
            
            // 高斯似然
            double sigma = 0.2;  // 传感器噪声
            double prob = std::exp(-dist * dist / (2 * sigma * sigma));
            likelihood *= prob;
        }
        
        return likelihood;
    }
    
    /**
     * 重采样
     */
    void resample() {
        std::vector<Particle> new_particles;
        new_particles.reserve(num_particles_);
        
        // 低方差重采样
        std::uniform_real_distribution<> dist(0, 1.0 / num_particles_);
        double r = dist(gen_);
        double c = particles_[0].weight;
        int i = 0;
        
        for (int m = 0; m < num_particles_; ++m) {
            double u = r + m * (1.0 / num_particles_);
            while (u > c && i < num_particles_ - 1) {
                i++;
                c += particles_[i].weight;
            }
            new_particles.push_back(particles_[i]);
            new_particles.back().weight = 1.0 / num_particles_;
        }
        
        particles_ = std::move(new_particles);
    }
    
    /**
     * 获取估计位姿 (加权平均)
     */
    Pose2D getEstimatedPose() const {
        Pose2D pose = {0, 0, 0};
        double sin_sum = 0, cos_sum = 0;
        
        for (const auto& p : particles_) {
            pose.x += p.weight * p.pose.x;
            pose.y += p.weight * p.pose.y;
            sin_sum += p.weight * std::sin(p.pose.theta);
            cos_sum += p.weight * std::cos(p.pose.theta);
        }
        
        pose.theta = std::atan2(sin_sum, cos_sum);
        return pose;
    }
    
private:
    int num_particles_;
    std::vector<Particle> particles_;
    std::mt19937 gen_;
};

二、全局路径规划

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    全局路径规划:从A到B怎么走?                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   【常用算法对比】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌─────────────┬─────────────┬─────────────┬─────────────────────────┐    │
│   │   算法      │   时间复杂度 │   最优性    │   特点                  │    │
│   ├─────────────┼─────────────┼─────────────┼─────────────────────────┤    │
│   │ Dijkstra    │ O(V²)       │ ✅ 最优     │ 无启发,慢              │    │
│   │ A*          │ O(V log V)  │ ✅ 最优     │ 有启发,快,最常用      │    │
│   │ D* Lite     │ 增量更新    │ ✅ 最优     │ 动态环境,可重规划      │    │
│   │ RRT         │ 概率完备    │ ❌ 次优     │ 高维空间               │    │
│   │ RRT*        │ 渐近最优    │ ✅ 渐近最优 │ 高维空间,质量更好      │    │
│   └─────────────┴─────────────┴─────────────┴─────────────────────────┘    │
│                                                                             │
│   餐厅场景推荐: A* (静态环境) 或 D* Lite (需要动态避障)                    │
│                                                                             │
│   【A*算法可视化】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────┐              │
│   │ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■               │              │
│   │ ■ S · · · · · · · · · · ■ · · · · · · ■               │              │
│   │ ■ · · · · · · · · · · · ■ · · · · · · ■               │              │
│   │ ■ · · ■ ■ ■ ■ · · · · · ■ · · · · · · ■               │              │
│   │ ■ · · · · · ■ · · · · · · · · ■ ■ · · ■               │              │
│   │ ■ · · · · · ■ · · · · · · · · ■ ■ · · ■               │              │
│   │ ■ · · · · · · · · · · · · · · · · · G ■               │              │
│   │ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■               │              │
│   └─────────────────────────────────────────────────────────┘              │
│                                                                             │
│   S = 起点 (Start)                                                         │
│   G = 终点 (Goal)                                                          │
│   ■ = 障碍物                                                               │
│   · = 可通行                                                               │
│   ★ = 最终路径                                                             │
│                                                                             │
│   f(n) = g(n) + h(n)                                                       │
│   - g(n): 从起点到n的实际代价                                              │
│   - h(n): 从n到终点的估计代价 (启发函数)                                   │
│   - f(n): 总估计代价                                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

A*算法实现

cpp 复制代码
/**
 * A*路径规划算法
 */

#include <vector>
#include <queue>
#include <unordered_set>
#include <unordered_map>
#include <cmath>
#include <algorithm>

struct GridCell {
    int x, y;
    
    bool operator==(const GridCell& other) const {
        return x == other.x && y == other.y;
    }
};

// 哈希函数
struct GridCellHash {
    size_t operator()(const GridCell& cell) const {
        return std::hash<int>()(cell.x) ^ (std::hash<int>()(cell.y) << 16);
    }
};

struct AStarNode {
    GridCell cell;
    double g_cost;  // 从起点到当前节点的代价
    double f_cost;  // g + h
    GridCell parent;
    
    bool operator>(const AStarNode& other) const {
        return f_cost > other.f_cost;
    }
};

class OccupancyGrid {
public:
    OccupancyGrid(int width, int height, double resolution)
        : width_(width), height_(height), resolution_(resolution) {
        data_.resize(width * height, 0);
    }
    
    bool isOccupied(int x, int y) const {
        if (x < 0 || x >= width_ || y < 0 || y >= height_) {
            return true;  // 边界外视为障碍
        }
        return data_[y * width_ + x] > 50;
    }
    
    void setOccupied(int x, int y, int value) {
        if (x >= 0 && x < width_ && y >= 0 && y < height_) {
            data_[y * width_ + x] = value;
        }
    }
    
    // 世界坐标转栅格坐标
    GridCell worldToGrid(double wx, double wy) const {
        return {
            static_cast<int>((wx - origin_x_) / resolution_),
            static_cast<int>((wy - origin_y_) / resolution_)
        };
    }
    
    // 栅格坐标转世界坐标
    void gridToWorld(int gx, int gy, double& wx, double& wy) const {
        wx = origin_x_ + (gx + 0.5) * resolution_;
        wy = origin_y_ + (gy + 0.5) * resolution_;
    }
    
    double distanceToNearestObstacle(double x, double y) const {
        GridCell cell = worldToGrid(x, y);
        // 简化实现:搜索周围区域
        double min_dist = 1e9;
        int search_radius = 20;
        
        for (int dx = -search_radius; dx <= search_radius; ++dx) {
            for (int dy = -search_radius; dy <= search_radius; ++dy) {
                if (isOccupied(cell.x + dx, cell.y + dy)) {
                    double dist = std::sqrt(dx * dx + dy * dy) * resolution_;
                    min_dist = std::min(min_dist, dist);
                }
            }
        }
        return min_dist;
    }
    
    int width_, height_;
    double resolution_;
    double origin_x_ = 0, origin_y_ = 0;
    std::vector<int> data_;
};

class AStarPlanner {
public:
    AStarPlanner(const OccupancyGrid& map) : map_(map) {}
    
    /**
     * 规划路径
     */
    std::vector<GridCell> planPath(GridCell start, GridCell goal) {
        // 优先队列 (最小堆)
        std::priority_queue<AStarNode, std::vector<AStarNode>, std::greater<AStarNode>> open_list;
        
        // 已访问节点
        std::unordered_set<GridCell, GridCellHash> closed_set;
        
        // 记录每个节点的信息
        std::unordered_map<GridCell, AStarNode, GridCellHash> node_info;
        
        // 初始化起点
        AStarNode start_node;
        start_node.cell = start;
        start_node.g_cost = 0;
        start_node.f_cost = heuristic(start, goal);
        start_node.parent = start;
        
        open_list.push(start_node);
        node_info[start] = start_node;
        
        // 8个方向的移动
        const int dx[] = {-1, 0, 1, -1, 1, -1, 0, 1};
        const int dy[] = {-1, -1, -1, 0, 0, 1, 1, 1};
        const double cost[] = {1.414, 1.0, 1.414, 1.0, 1.0, 1.414, 1.0, 1.414};
        
        while (!open_list.empty()) {
            AStarNode current = open_list.top();
            open_list.pop();
            
            // 到达目标
            if (current.cell == goal) {
                return reconstructPath(node_info, start, goal);
            }
            
            // 已经处理过
            if (closed_set.count(current.cell)) {
                continue;
            }
            closed_set.insert(current.cell);
            
            // 扩展邻居
            for (int i = 0; i < 8; ++i) {
                GridCell neighbor = {current.cell.x + dx[i], current.cell.y + dy[i]};
                
                // 检查是否可通行
                if (map_.isOccupied(neighbor.x, neighbor.y)) {
                    continue;
                }
                
                // 对角线移动时检查是否会穿过障碍物
                if (i == 0 || i == 2 || i == 5 || i == 7) {
                    if (map_.isOccupied(current.cell.x + dx[i], current.cell.y) ||
                        map_.isOccupied(current.cell.x, current.cell.y + dy[i])) {
                        continue;
                    }
                }
                
                if (closed_set.count(neighbor)) {
                    continue;
                }
                
                double tentative_g = current.g_cost + cost[i];
                
                // 检查是否是更好的路径
                auto it = node_info.find(neighbor);
                if (it == node_info.end() || tentative_g < it->second.g_cost) {
                    AStarNode neighbor_node;
                    neighbor_node.cell = neighbor;
                    neighbor_node.g_cost = tentative_g;
                    neighbor_node.f_cost = tentative_g + heuristic(neighbor, goal);
                    neighbor_node.parent = current.cell;
                    
                    node_info[neighbor] = neighbor_node;
                    open_list.push(neighbor_node);
                }
            }
        }
        
        // 无法找到路径
        return {};
    }
    
private:
    /**
     * 启发函数 (欧几里得距离)
     */
    double heuristic(const GridCell& a, const GridCell& b) {
        double dx = a.x - b.x;
        double dy = a.y - b.y;
        return std::sqrt(dx * dx + dy * dy);
    }
    
    /**
     * 重建路径
     */
    std::vector<GridCell> reconstructPath(
            const std::unordered_map<GridCell, AStarNode, GridCellHash>& node_info,
            const GridCell& start, const GridCell& goal) {
        std::vector<GridCell> path;
        GridCell current = goal;
        
        while (!(current == start)) {
            path.push_back(current);
            current = node_info.at(current).parent;
        }
        path.push_back(start);
        
        std::reverse(path.begin(), path.end());
        return path;
    }
    
    const OccupancyGrid& map_;
};

/**
 * 路径平滑
 */
class PathSmoother {
public:
    /**
     * 简化路径 (去除共线点)
     */
    static std::vector<GridCell> simplify(const std::vector<GridCell>& path) {
        if (path.size() < 3) return path;
        
        std::vector<GridCell> simplified;
        simplified.push_back(path[0]);
        
        for (size_t i = 1; i < path.size() - 1; ++i) {
            // 检查是否共线
            int dx1 = path[i].x - path[i-1].x;
            int dy1 = path[i].y - path[i-1].y;
            int dx2 = path[i+1].x - path[i].x;
            int dy2 = path[i+1].y - path[i].y;
            
            if (dx1 != dx2 || dy1 != dy2) {
                simplified.push_back(path[i]);
            }
        }
        
        simplified.push_back(path.back());
        return simplified;
    }
    
    /**
     * B样条平滑
     */
    static std::vector<Pose2D> smoothBSpline(const std::vector<GridCell>& path,
                                              const OccupancyGrid& map,
                                              int interpolation_points = 10) {
        std::vector<Pose2D> smooth_path;
        
        if (path.size() < 2) return smooth_path;
        
        for (size_t i = 0; i < path.size() - 1; ++i) {
            double x1, y1, x2, y2;
            map.gridToWorld(path[i].x, path[i].y, x1, y1);
            map.gridToWorld(path[i+1].x, path[i+1].y, x2, y2);
            
            for (int j = 0; j < interpolation_points; ++j) {
                double t = static_cast<double>(j) / interpolation_points;
                Pose2D pose;
                pose.x = x1 + t * (x2 - x1);
                pose.y = y1 + t * (y2 - y1);
                pose.theta = std::atan2(y2 - y1, x2 - x1);
                smooth_path.push_back(pose);
            }
        }
        
        // 添加终点
        double x, y;
        map.gridToWorld(path.back().x, path.back().y, x, y);
        smooth_path.push_back({x, y, smooth_path.back().theta});
        
        return smooth_path;
    }
};

三、局部路径规划与避障

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    局部规划:遇到障碍物怎么办?                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   全局路径是预先规划的,但实际运行中会遇到:                                 │
│   - 突然出现的行人                                                          │
│   - 移动的椅子                                                              │
│   - 其他机器人                                                              │
│                                                                             │
│   需要局部规划器实时调整                                                    │
│                                                                             │
│   【DWA (Dynamic Window Approach)】                                        │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   核心思想: 在速度空间中采样,评估每个速度的好坏                           │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                                                                     │  │
│   │         速度空间 (v, ω)                                            │  │
│   │                                                                     │  │
│   │      ω (角速度)                                                    │  │
│   │         ▲                                                          │  │
│   │         │    ┌─────────────┐                                       │  │
│   │         │    │  动态窗口   │  受限于:                              │  │
│   │         │    │  (可达范围) │  - 最大加速度                         │  │
│   │         │    │      ★     │  - 最大速度                           │  │
│   │         │    │   当前速度  │  - 避障约束                           │  │
│   │         │    └─────────────┘                                       │  │
│   │         │                                                          │  │
│   │  ───────┼──────────────────────► v (线速度)                        │  │
│   │         │                                                          │  │
│   │                                                                     │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│   评价函数:                                                                 │
│   G(v,ω) = α·heading(v,ω) + β·dist(v,ω) + γ·velocity(v,ω)                │
│                                                                             │
│   - heading: 朝向目标的程度                                                │
│   - dist: 与最近障碍物的距离                                               │
│   - velocity: 速度大小 (鼓励快速移动)                                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

DWA局部规划器

cpp 复制代码
/**
 * DWA局部路径规划器
 */

#include <vector>
#include <cmath>
#include <limits>

struct RobotState {
    double x, y, theta;     // 位姿
    double v, omega;        // 线速度、角速度
};

struct Velocity {
    double v, omega;
};

struct DWAConfig {
    // 速度限制
    double max_speed = 0.5;           // 最大线速度 m/s
    double min_speed = 0.0;           // 最小线速度
    double max_yaw_rate = 1.0;        // 最大角速度 rad/s
    
    // 加速度限制
    double max_accel = 0.5;           // 最大线加速度 m/s²
    double max_yaw_accel = 1.5;       // 最大角加速度 rad/s²
    
    // 采样分辨率
    double v_resolution = 0.05;       // 线速度分辨率
    double yaw_rate_resolution = 0.1; // 角速度分辨率
    
    // 预测参数
    double predict_time = 2.0;        // 预测时间 s
    double dt = 0.1;                  // 时间步长
    
    // 评价函数权重
    double heading_weight = 1.0;
    double dist_weight = 0.5;
    double velocity_weight = 0.3;
    double path_weight = 0.5;         // 跟踪全局路径
    
    // 机器人参数
    double robot_radius = 0.3;        // 机器人半径
};

class DWAPlanner {
public:
    DWAPlanner(const DWAConfig& config) : config_(config) {}
    
    /**
     * 计算最优速度
     */
    Velocity computeVelocity(const RobotState& state,
                             const Pose2D& goal,
                             const std::vector<Pose2D>& global_path,
                             const std::vector<std::pair<double, double>>& obstacles) {
        
        // 1. 计算动态窗口
        auto [v_min, v_max, omega_min, omega_max] = calculateDynamicWindow(state);
        
        // 2. 在动态窗口内采样
        Velocity best_velocity = {0, 0};
        double best_score = -std::numeric_limits<double>::infinity();
        
        for (double v = v_min; v <= v_max; v += config_.v_resolution) {
            for (double omega = omega_min; omega <= omega_max; 
                 omega += config_.yaw_rate_resolution) {
                
                // 3. 预测轨迹
                auto trajectory = predictTrajectory(state, v, omega);
                
                // 4. 检查碰撞
                if (checkCollision(trajectory, obstacles)) {
                    continue;
                }
                
                // 5. 评估轨迹
                double score = evaluateTrajectory(trajectory, goal, global_path, obstacles);
                
                if (score > best_score) {
                    best_score = score;
                    best_velocity = {v, omega};
                }
            }
        }
        
        return best_velocity;
    }
    
private:
    /**
     * 计算动态窗口 (可达速度范围)
     */
    std::tuple<double, double, double, double> 
    calculateDynamicWindow(const RobotState& state) {
        // 速度限制
        double v_min = config_.min_speed;
        double v_max = config_.max_speed;
        double omega_min = -config_.max_yaw_rate;
        double omega_max = config_.max_yaw_rate;
        
        // 加速度限制 (一个控制周期内可达的速度)
        double v_min_dyn = state.v - config_.max_accel * config_.dt;
        double v_max_dyn = state.v + config_.max_accel * config_.dt;
        double omega_min_dyn = state.omega - config_.max_yaw_accel * config_.dt;
        double omega_max_dyn = state.omega + config_.max_yaw_accel * config_.dt;
        
        // 取交集
        v_min = std::max(v_min, v_min_dyn);
        v_max = std::min(v_max, v_max_dyn);
        omega_min = std::max(omega_min, omega_min_dyn);
        omega_max = std::min(omega_max, omega_max_dyn);
        
        return {v_min, v_max, omega_min, omega_max};
    }
    
    /**
     * 预测轨迹
     */
    std::vector<RobotState> predictTrajectory(const RobotState& state,
                                               double v, double omega) {
        std::vector<RobotState> trajectory;
        RobotState s = state;
        s.v = v;
        s.omega = omega;
        
        double time = 0;
        while (time <= config_.predict_time) {
            trajectory.push_back(s);
            s = motionModel(s, v, omega, config_.dt);
            time += config_.dt;
        }
        
        return trajectory;
    }
    
    /**
     * 运动模型
     */
    RobotState motionModel(const RobotState& state, 
                           double v, double omega, double dt) {
        RobotState new_state = state;
        
        if (std::abs(omega) < 1e-6) {
            // 直线运动
            new_state.x += v * std::cos(state.theta) * dt;
            new_state.y += v * std::sin(state.theta) * dt;
        } else {
            // 圆弧运动
            double r = v / omega;
            new_state.x += r * (std::sin(state.theta + omega * dt) - std::sin(state.theta));
            new_state.y += r * (std::cos(state.theta) - std::cos(state.theta + omega * dt));
            new_state.theta += omega * dt;
        }
        
        new_state.v = v;
        new_state.omega = omega;
        
        return new_state;
    }
    
    /**
     * 碰撞检测
     */
    bool checkCollision(const std::vector<RobotState>& trajectory,
                        const std::vector<std::pair<double, double>>& obstacles) {
        for (const auto& state : trajectory) {
            for (const auto& [ox, oy] : obstacles) {
                double dist = std::sqrt(std::pow(state.x - ox, 2) + 
                                       std::pow(state.y - oy, 2));
                if (dist <= config_.robot_radius + 0.1) {  // 安全余量
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * 评估轨迹
     */
    double evaluateTrajectory(const std::vector<RobotState>& trajectory,
                              const Pose2D& goal,
                              const std::vector<Pose2D>& global_path,
                              const std::vector<std::pair<double, double>>& obstacles) {
        if (trajectory.empty()) return -std::numeric_limits<double>::infinity();
        
        const RobotState& final_state = trajectory.back();
        
        // 1. 朝向目标
        double goal_angle = std::atan2(goal.y - final_state.y, goal.x - final_state.x);
        double angle_diff = std::abs(normalizeAngle(goal_angle - final_state.theta));
        double heading_score = M_PI - angle_diff;
        
        // 2. 与障碍物距离
        double min_dist = std::numeric_limits<double>::infinity();
        for (const auto& state : trajectory) {
            for (const auto& [ox, oy] : obstacles) {
                double dist = std::sqrt(std::pow(state.x - ox, 2) + 
                                       std::pow(state.y - oy, 2));
                min_dist = std::min(min_dist, dist);
            }
        }
        double dist_score = std::min(min_dist, 3.0);  // 上限3米
        
        // 3. 速度 (鼓励快速移动)
        double velocity_score = trajectory[0].v;
        
        // 4. 跟踪全局路径
        double path_score = 0;
        if (!global_path.empty()) {
            double min_path_dist = std::numeric_limits<double>::infinity();
            for (const auto& path_point : global_path) {
                double dist = std::sqrt(std::pow(final_state.x - path_point.x, 2) +
                                       std::pow(final_state.y - path_point.y, 2));
                min_path_dist = std::min(min_path_dist, dist);
            }
            path_score = 1.0 / (1.0 + min_path_dist);
        }
        
        // 综合评分
        double score = config_.heading_weight * heading_score +
                       config_.dist_weight * dist_score +
                       config_.velocity_weight * velocity_score +
                       config_.path_weight * path_score;
        
        return score;
    }
    
    double normalizeAngle(double angle) {
        while (angle > M_PI) angle -= 2 * M_PI;
        while (angle < -M_PI) angle += 2 * M_PI;
        return angle;
    }
    
    DWAConfig config_;
};

四、任务调度系统

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    任务调度:怎么安排送餐顺序?                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   【问题场景】                                                              │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   餐厅有多个订单同时待送:                                                   │
│   - 订单1: 3号桌,出餐时间 10:00                                           │
│   - 订单2: 7号桌,出餐时间 10:02                                           │
│   - 订单3: 5号桌,出餐时间 10:01                                           │
│                                                                             │
│   机器人一次最多装3个餐,怎么安排最高效?                                  │
│                                                                             │
│   【任务状态机】                                                            │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌──────────┐    新任务    ┌──────────┐                                  │
│   │  IDLE    │─────────────►│ PENDING  │                                  │
│   │  空闲    │              │ 等待分配 │                                  │
│   └──────────┘              └────┬─────┘                                  │
│        ▲                         │ 分配给机器人                            │
│        │                         ▼                                          │
│        │                   ┌──────────┐                                    │
│        │                   │ ASSIGNED │                                    │
│        │                   │ 已分配   │                                    │
│        │                   └────┬─────┘                                    │
│        │                        │ 开始执行                                  │
│        │                        ▼                                          │
│        │    ┌──────────┐  ┌──────────┐  ┌──────────┐                      │
│        │    │ PICKING  │─►│DELIVERING│─►│ ARRIVED  │                      │
│        │    │ 取餐中   │  │ 送餐中   │  │ 已到达   │                      │
│        │    └──────────┘  └──────────┘  └────┬─────┘                      │
│        │                                      │ 确认送达                    │
│        │                                      ▼                             │
│        │                               ┌──────────┐                        │
│        └───────────────────────────────│COMPLETED │                        │
│                  返回待命              │ 已完成   │                        │
│                                        └──────────┘                        │
│                                                                             │
│   【调度策略】                                                              │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   1. FIFO: 先来先服务                                                      │
│   2. 最短路径优先: 送最近的桌子                                            │
│   3. 批量配送: 合并顺路订单                                                │
│   4. 优先级调度: VIP客户优先                                               │
│   5. 负载均衡: 多机器人均匀分配                                            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

任务调度系统实现

cpp 复制代码
/**
 * 送餐任务调度系统
 */

#include <vector>
#include <queue>
#include <map>
#include <memory>
#include <chrono>
#include <algorithm>
#include <functional>

using TimePoint = std::chrono::steady_clock::time_point;

// 任务状态
enum class TaskState {
    PENDING,      // 等待分配
    ASSIGNED,     // 已分配给机器人
    PICKING,      // 取餐中
    DELIVERING,   // 送餐中
    ARRIVED,      // 已到达
    COMPLETED,    // 已完成
    CANCELLED     // 已取消
};

// 任务优先级
enum class TaskPriority {
    LOW = 0,
    NORMAL = 1,
    HIGH = 2,
    URGENT = 3
};

// 送餐任务
struct DeliveryTask {
    int task_id;
    int table_id;           // 目标桌号
    int pickup_point;       // 取餐点
    TaskState state;
    TaskPriority priority;
    TimePoint created_time;
    TimePoint deadline;     // 最晚送达时间
    std::string order_info; // 订单信息
    int assigned_robot;     // 分配的机器人ID
    
    // 位置信息
    double table_x, table_y;
    double pickup_x, pickup_y;
};

// 机器人状态
enum class RobotState {
    IDLE,           // 空闲
    MOVING,         // 移动中
    PICKING,        // 取餐中
    DELIVERING,     // 送餐中
    CHARGING,       // 充电中
    ERROR           // 故障
};

struct Robot {
    int robot_id;
    RobotState state;
    double x, y, theta;         // 当前位置
    int current_task;           // 当前任务ID
    std::vector<int> task_queue;// 任务队列 (批量配送)
    double battery_level;       // 电量百分比
    int max_capacity;           // 最大载餐数
    int current_load;           // 当前载餐数
};

// 位置点
struct Location {
    int id;
    std::string name;
    double x, y;
    int type;  // 0=桌子, 1=取餐点, 2=充电桩
};

/**
 * 任务调度器
 */
class TaskScheduler {
public:
    TaskScheduler() {
        next_task_id_ = 1;
    }
    
    /**
     * 创建新任务
     */
    int createTask(int table_id, int pickup_point, TaskPriority priority = TaskPriority::NORMAL) {
        DeliveryTask task;
        task.task_id = next_task_id_++;
        task.table_id = table_id;
        task.pickup_point = pickup_point;
        task.state = TaskState::PENDING;
        task.priority = priority;
        task.created_time = std::chrono::steady_clock::now();
        task.deadline = task.created_time + std::chrono::minutes(10);  // 10分钟内送达
        task.assigned_robot = -1;
        
        // 获取位置坐标
        if (locations_.count(table_id)) {
            task.table_x = locations_[table_id].x;
            task.table_y = locations_[table_id].y;
        }
        if (locations_.count(pickup_point)) {
            task.pickup_x = locations_[pickup_point].x;
            task.pickup_y = locations_[pickup_point].y;
        }
        
        tasks_[task.task_id] = task;
        pending_tasks_.push_back(task.task_id);
        
        printf("[调度] 创建任务 #%d: 从取餐点%d送到%d号桌\n", 
               task.task_id, pickup_point, table_id);
        
        return task.task_id;
    }
    
    /**
     * 分配任务给机器人
     */
    void assignTask(int task_id, int robot_id) {
        if (!tasks_.count(task_id) || !robots_.count(robot_id)) {
            return;
        }
        
        auto& task = tasks_[task_id];
        auto& robot = robots_[robot_id];
        
        task.state = TaskState::ASSIGNED;
        task.assigned_robot = robot_id;
        robot.task_queue.push_back(task_id);
        
        // 从待分配列表移除
        pending_tasks_.erase(
            std::remove(pending_tasks_.begin(), pending_tasks_.end(), task_id),
            pending_tasks_.end());
        
        printf("[调度] 任务 #%d 分配给机器人 #%d\n", task_id, robot_id);
    }
    
    /**
     * 调度算法:选择最优机器人
     */
    int selectBestRobot(int task_id) {
        if (!tasks_.count(task_id)) return -1;
        
        const auto& task = tasks_[task_id];
        int best_robot = -1;
        double best_score = -1e9;
        
        for (auto& [robot_id, robot] : robots_) {
            // 跳过不可用的机器人
            if (robot.state == RobotState::CHARGING || 
                robot.state == RobotState::ERROR) {
                continue;
            }
            
            // 检查容量
            if (robot.current_load >= robot.max_capacity) {
                continue;
            }
            
            // 检查电量
            if (robot.battery_level < 20) {
                continue;
            }
            
            // 计算分数
            double score = calculateRobotScore(robot, task);
            
            if (score > best_score) {
                best_score = score;
                best_robot = robot_id;
            }
        }
        
        return best_robot;
    }
    
    /**
     * 批量配送优化:TSP问题
     */
    std::vector<int> optimizeDeliveryOrder(const std::vector<int>& task_ids, 
                                            const Robot& robot) {
        if (task_ids.size() <= 1) return task_ids;
        
        // 简化版TSP:最近邻算法
        std::vector<int> order;
        std::vector<bool> visited(task_ids.size(), false);
        
        double current_x = robot.x;
        double current_y = robot.y;
        
        for (size_t i = 0; i < task_ids.size(); ++i) {
            double min_dist = 1e9;
            int nearest = -1;
            
            for (size_t j = 0; j < task_ids.size(); ++j) {
                if (visited[j]) continue;
                
                const auto& task = tasks_[task_ids[j]];
                double dist = std::sqrt(std::pow(task.table_x - current_x, 2) +
                                       std::pow(task.table_y - current_y, 2));
                
                if (dist < min_dist) {
                    min_dist = dist;
                    nearest = j;
                }
            }
            
            if (nearest >= 0) {
                visited[nearest] = true;
                order.push_back(task_ids[nearest]);
                
                const auto& task = tasks_[task_ids[nearest]];
                current_x = task.table_x;
                current_y = task.table_y;
            }
        }
        
        return order;
    }
    
    /**
     * 主调度循环
     */
    void schedulingLoop() {
        // 1. 处理待分配任务
        for (int task_id : pending_tasks_) {
            int robot_id = selectBestRobot(task_id);
            if (robot_id >= 0) {
                assignTask(task_id, robot_id);
            }
        }
        
        // 2. 优化各机器人的配送顺序
        for (auto& [robot_id, robot] : robots_) {
            if (robot.task_queue.size() > 1) {
                robot.task_queue = optimizeDeliveryOrder(robot.task_queue, robot);
            }
        }
        
        // 3. 检查超时任务
        auto now = std::chrono::steady_clock::now();
        for (auto& [task_id, task] : tasks_) {
            if (task.state != TaskState::COMPLETED && 
                task.state != TaskState::CANCELLED &&
                now > task.deadline) {
                printf("[警告] 任务 #%d 即将超时!\n", task_id);
                // 提高优先级
                task.priority = TaskPriority::URGENT;
            }
        }
        
        // 4. 检查需要充电的机器人
        for (auto& [robot_id, robot] : robots_) {
            if (robot.battery_level < 15 && robot.state == RobotState::IDLE) {
                printf("[调度] 机器人 #%d 电量低,安排充电\n", robot_id);
                robot.state = RobotState::CHARGING;
                // 发送充电命令
            }
        }
    }
    
    /**
     * 添加位置点
     */
    void addLocation(int id, const std::string& name, double x, double y, int type) {
        locations_[id] = {id, name, x, y, type};
    }
    
    /**
     * 添加机器人
     */
    void addRobot(int robot_id, int max_capacity = 3) {
        Robot robot;
        robot.robot_id = robot_id;
        robot.state = RobotState::IDLE;
        robot.x = 0;
        robot.y = 0;
        robot.theta = 0;
        robot.current_task = -1;
        robot.battery_level = 100;
        robot.max_capacity = max_capacity;
        robot.current_load = 0;
        
        robots_[robot_id] = robot;
    }
    
    /**
     * 更新任务状态
     */
    void updateTaskState(int task_id, TaskState new_state) {
        if (tasks_.count(task_id)) {
            tasks_[task_id].state = new_state;
            printf("[任务] #%d 状态更新: %s\n", task_id, stateToString(new_state).c_str());
        }
    }
    
    /**
     * 更新机器人位置
     */
    void updateRobotPose(int robot_id, double x, double y, double theta) {
        if (robots_.count(robot_id)) {
            robots_[robot_id].x = x;
            robots_[robot_id].y = y;
            robots_[robot_id].theta = theta;
        }
    }
    
private:
    /**
     * 计算机器人对任务的适合度分数
     */
    double calculateRobotScore(const Robot& robot, const DeliveryTask& task) {
        double score = 0;
        
        // 1. 距离分数 (越近越好)
        double dist_to_pickup = std::sqrt(
            std::pow(robot.x - task.pickup_x, 2) +
            std::pow(robot.y - task.pickup_y, 2));
        score += 10.0 / (1.0 + dist_to_pickup);
        
        // 2. 空闲状态加分
        if (robot.state == RobotState::IDLE) {
            score += 5;
        }
        
        // 3. 当前负载少加分
        score += (robot.max_capacity - robot.current_load) * 2;
        
        // 4. 电量充足加分
        score += robot.battery_level / 20.0;
        
        // 5. 如果机器人正在送餐且顺路
        if (robot.state == RobotState::DELIVERING && !robot.task_queue.empty()) {
            // 检查是否顺路
            int current_task_id = robot.task_queue[0];
            const auto& current_task = tasks_[current_task_id];
            
            // 如果新任务的桌子在当前送餐路径附近
            double detour = calculateDetour(robot, current_task, task);
            if (detour < 5.0) {  // 绕路小于5米
                score += 8;  // 大幅加分
            }
        }
        
        return score;
    }
    
    /**
     * 计算绕路距离
     */
    double calculateDetour(const Robot& robot, 
                           const DeliveryTask& current_task,
                           const DeliveryTask& new_task) {
        // 原路线长度
        double original = std::sqrt(
            std::pow(robot.x - current_task.table_x, 2) +
            std::pow(robot.y - current_task.table_y, 2));
        
        // 新路线长度 (先送新任务再送原任务)
        double new_route = std::sqrt(
            std::pow(robot.x - new_task.table_x, 2) +
            std::pow(robot.y - new_task.table_y, 2)) +
            std::sqrt(
            std::pow(new_task.table_x - current_task.table_x, 2) +
            std::pow(new_task.table_y - current_task.table_y, 2));
        
        return new_route - original;
    }
    
    std::string stateToString(TaskState state) {
        switch (state) {
            case TaskState::PENDING: return "待分配";
            case TaskState::ASSIGNED: return "已分配";
            case TaskState::PICKING: return "取餐中";
            case TaskState::DELIVERING: return "送餐中";
            case TaskState::ARRIVED: return "已到达";
            case TaskState::COMPLETED: return "已完成";
            case TaskState::CANCELLED: return "已取消";
            default: return "未知";
        }
    }
    
    int next_task_id_;
    std::map<int, DeliveryTask> tasks_;
    std::map<int, Robot> robots_;
    std::map<int, Location> locations_;
    std::vector<int> pending_tasks_;
};

五、语义识别与人机交互

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    语义识别与交互                                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   【识别什么?】                                                            │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   1. 桌号识别                                                              │
│      - 二维码/ArUco标签                                                    │
│      - 桌牌文字识别                                                        │
│      - 预存位置匹配                                                        │
│                                                                             │
│   2. 人员检测                                                              │
│      - 行人检测 (避障)                                                     │
│      - 工作人员识别                                                        │
│      - 手势识别 (召唤/让路)                                               │
│                                                                             │
│   3. 场景理解                                                              │
│      - 门/电梯识别                                                         │
│      - 楼梯/台阶检测                                                       │
│      - 动态障碍物分类                                                      │
│                                                                             │
│   【ArUco标签识别】                                                        │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌───────────────┐                                                        │
│   │ ■ □ ■ □ ■ │                                                        │
│   │ □ ■ □ ■ □ │  每个桌子放一个ArUco标签                               │
│   │ ■ □ ■ □ ■ │  编码了桌号信息                                        │
│   │ □ ■ □ ■ □ │  可以精确定位(厘米级)                                  │
│   │ ■ □ ■ □ ■ │                                                        │
│   └───────────────┘                                                        │
│                                                                             │
│   【人机交互】                                                              │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   输入:                                                                     │
│   - 触摸屏                                                                  │
│   - 语音指令                                                                │
│   - 手势                                                                    │
│   - 遥控器                                                                  │
│                                                                             │
│   输出:                                                                     │
│   - 语音播报 ("您的餐品到了")                                              │
│   - 显示屏信息                                                              │
│   - 灯光提示                                                                │
│   - 声音提示                                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

ArUco标签识别

cpp 复制代码
/**
 * ArUco标签识别
 */

#include <opencv2/opencv.hpp>
#include <opencv2/aruco.hpp>

struct MarkerInfo {
    int id;
    std::vector<cv::Point2f> corners;
    cv::Vec3d rvec, tvec;  // 旋转向量和平移向量
    double distance;        // 距离
    double angle;           // 相对角度
};

class ArucoDetector {
public:
    ArucoDetector() {
        // 使用预定义的字典
        dictionary_ = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
        parameters_ = cv::aruco::DetectorParameters::create();
        
        // 相机内参 (需要标定获取)
        camera_matrix_ = (cv::Mat_<double>(3, 3) <<
            500, 0, 320,
            0, 500, 240,
            0, 0, 1);
        
        dist_coeffs_ = cv::Mat::zeros(5, 1, CV_64F);
        
        // 标签实际尺寸 (米)
        marker_size_ = 0.1;  // 10cm
    }
    
    /**
     * 检测图像中的ArUco标签
     */
    std::vector<MarkerInfo> detect(const cv::Mat& image) {
        std::vector<MarkerInfo> results;
        
        std::vector<int> ids;
        std::vector<std::vector<cv::Point2f>> corners;
        std::vector<std::vector<cv::Point2f>> rejected;
        
        // 检测标签
        cv::aruco::detectMarkers(image, dictionary_, corners, ids, 
                                  parameters_, rejected);
        
        if (ids.empty()) {
            return results;
        }
        
        // 估计位姿
        std::vector<cv::Vec3d> rvecs, tvecs;
        cv::aruco::estimatePoseSingleMarkers(corners, marker_size_,
                                              camera_matrix_, dist_coeffs_,
                                              rvecs, tvecs);
        
        for (size_t i = 0; i < ids.size(); ++i) {
            MarkerInfo info;
            info.id = ids[i];
            info.corners = corners[i];
            info.rvec = rvecs[i];
            info.tvec = tvecs[i];
            
            // 计算距离
            info.distance = cv::norm(tvecs[i]);
            
            // 计算相对角度
            info.angle = std::atan2(tvecs[i][0], tvecs[i][2]);
            
            results.push_back(info);
        }
        
        return results;
    }
    
    /**
     * 绘制检测结果
     */
    void drawDetection(cv::Mat& image, const std::vector<MarkerInfo>& markers) {
        for (const auto& marker : markers) {
            // 绘制标签边框
            std::vector<std::vector<cv::Point2f>> corners = {marker.corners};
            cv::aruco::drawDetectedMarkers(image, corners);
            
            // 绘制坐标轴
            cv::aruco::drawAxis(image, camera_matrix_, dist_coeffs_,
                               marker.rvec, marker.tvec, marker_size_ * 0.5);
            
            // 显示信息
            cv::Point2f center = (marker.corners[0] + marker.corners[2]) / 2;
            std::string text = "ID:" + std::to_string(marker.id) + 
                              " D:" + std::to_string(int(marker.distance * 100)) + "cm";
            cv::putText(image, text, center, cv::FONT_HERSHEY_SIMPLEX, 
                       0.5, cv::Scalar(0, 255, 0), 2);
        }
    }
    
    /**
     * 根据标签ID获取对应的桌号
     */
    int getTableFromMarker(int marker_id) {
        // 标签ID到桌号的映射
        static std::map<int, int> marker_to_table = {
            {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5},
            {6, 6}, {7, 7}, {8, 8}, {9, 9}, {10, 10}
        };
        
        auto it = marker_to_table.find(marker_id);
        return (it != marker_to_table.end()) ? it->second : -1;
    }
    
private:
    cv::Ptr<cv::aruco::Dictionary> dictionary_;
    cv::Ptr<cv::aruco::DetectorParameters> parameters_;
    cv::Mat camera_matrix_;
    cv::Mat dist_coeffs_;
    double marker_size_;
};

六、多机器人协调

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    多机器人协调                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   当餐厅有多个机器人时,需要解决:                                          │
│                                                                             │
│   【问题1: 路径冲突】                                                       │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌────────────────────────────────────────┐                               │
│   │                                        │                               │
│   │    🤖A ──────────►                    │                               │
│   │              交叉点 ◄────────── 🤖B    │                               │
│   │                ×                       │                               │
│   │                                        │                               │
│   └────────────────────────────────────────┘                               │
│                                                                             │
│   解决方案:                                                                 │
│   - 时间窗预约: 每个路径段有时间窗,避免同时占用                          │
│   - 优先级: 高优先级机器人先通过                                           │
│   - 等待/绕行: 检测到冲突时重新规划                                        │
│                                                                             │
│   【问题2: 死锁】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   ┌─────────────────────┐                                                  │
│   │   🤖A ◄───── 🤖B    │  两个机器人互相等待                             │
│   │    │          │     │                                                  │
│   │    └──────────┘     │                                                  │
│   └─────────────────────┘                                                  │
│                                                                             │
│   解决方案:                                                                 │
│   - 死锁检测: 构建等待图,检测环                                           │
│   - 死锁预防: 资源编号,按顺序申请                                         │
│   - 死锁恢复: 让一个机器人后退/绕行                                        │
│                                                                             │
│   【问题3: 任务分配】                                                       │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   多个机器人如何分配任务?                                                  │
│   - 拍卖算法: 机器人竞标任务                                               │
│   - 匈牙利算法: 最优分配                                                   │
│   - 市场机制: 动态调整                                                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

多机器人协调代码

cpp 复制代码
/**
 * 多机器人协调系统
 */

#include <vector>
#include <map>
#include <set>
#include <queue>

struct PathSegment {
    int from_node;
    int to_node;
    double start_time;
    double end_time;
    int robot_id;
};

class MultiRobotCoordinator {
public:
    /**
     * 检测路径冲突
     */
    bool checkConflict(const PathSegment& seg1, const PathSegment& seg2) {
        // 检查是否在同一路段
        if ((seg1.from_node == seg2.from_node && seg1.to_node == seg2.to_node) ||
            (seg1.from_node == seg2.to_node && seg1.to_node == seg2.from_node)) {
            // 检查时间是否重叠
            return !(seg1.end_time < seg2.start_time || seg2.end_time < seg1.start_time);
        }
        
        // 检查节点冲突 (同时到达同一节点)
        if (seg1.to_node == seg2.to_node) {
            double arrival_diff = std::abs(seg1.end_time - seg2.end_time);
            return arrival_diff < 2.0;  // 2秒内到达视为冲突
        }
        
        return false;
    }
    
    /**
     * 解决冲突:调整时间窗
     */
    void resolveConflict(PathSegment& seg1, PathSegment& seg2) {
        // 优先级高的先通过
        if (getPriority(seg1.robot_id) >= getPriority(seg2.robot_id)) {
            // seg2延迟
            double delay = seg1.end_time - seg2.start_time + 2.0;
            seg2.start_time += delay;
            seg2.end_time += delay;
        } else {
            // seg1延迟
            double delay = seg2.end_time - seg1.start_time + 2.0;
            seg1.start_time += delay;
            seg1.end_time += delay;
        }
    }
    
    /**
     * 死锁检测
     */
    bool detectDeadlock(const std::map<int, int>& wait_for) {
        // wait_for: robot_id -> waiting_for_robot_id
        std::set<int> visited;
        
        for (auto& [robot_id, _] : wait_for) {
            std::set<int> path;
            int current = robot_id;
            
            while (wait_for.count(current) && !path.count(current)) {
                path.insert(current);
                current = wait_for.at(current);
            }
            
            if (path.count(current)) {
                // 发现环,存在死锁
                printf("[警告] 检测到死锁: 机器人 %d\n", current);
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 匈牙利算法:最优任务分配
     */
    std::vector<std::pair<int, int>> hungarianAssignment(
            const std::vector<std::vector<double>>& cost_matrix) {
        int n = cost_matrix.size();
        if (n == 0) return {};
        int m = cost_matrix[0].size();
        
        // 简化版:贪心匹配
        std::vector<std::pair<int, int>> assignment;
        std::vector<bool> robot_assigned(n, false);
        std::vector<bool> task_assigned(m, false);
        
        // 创建成本优先队列
        struct Assignment {
            int robot, task;
            double cost;
            bool operator>(const Assignment& other) const {
                return cost > other.cost;
            }
        };
        
        std::priority_queue<Assignment, std::vector<Assignment>, std::greater<Assignment>> pq;
        
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                pq.push({i, j, cost_matrix[i][j]});
            }
        }
        
        while (!pq.empty() && assignment.size() < std::min(n, m)) {
            auto [robot, task, cost] = pq.top();
            pq.pop();
            
            if (!robot_assigned[robot] && !task_assigned[task]) {
                assignment.push_back({robot, task});
                robot_assigned[robot] = true;
                task_assigned[task] = true;
            }
        }
        
        return assignment;
    }
    
    /**
     * 避让策略:计算避让点
     */
    std::pair<double, double> calculateYieldPoint(
            int robot_id,
            double rx, double ry,           // 当前位置
            double ox, double oy,           // 另一机器人位置
            double gx, double gy) {         // 目标位置
        
        // 计算垂直于目标方向的偏移
        double dx = gx - rx;
        double dy = gy - ry;
        double len = std::sqrt(dx * dx + dy * dy);
        
        if (len < 0.1) return {rx, ry};
        
        // 归一化
        dx /= len;
        dy /= len;
        
        // 垂直方向
        double px = -dy;
        double py = dx;
        
        // 选择远离另一机器人的方向
        double side = px * (rx - ox) + py * (ry - oy);
        if (side < 0) {
            px = -px;
            py = -py;
        }
        
        // 偏移1米
        return {rx + px * 1.0, ry + py * 1.0};
    }
    
private:
    int getPriority(int robot_id) {
        // 可以根据任务紧急程度、电量等返回优先级
        return robot_id;  // 简化:ID小的优先
    }
    
    std::map<int, std::vector<PathSegment>> robot_paths_;
};

完整系统集成

cpp 复制代码
/**
 * 送餐机器人主控制器
 */

#include <thread>
#include <atomic>
#include <mutex>

class DeliveryRobotController {
public:
    DeliveryRobotController(int robot_id) : robot_id_(robot_id) {
        running_ = false;
    }
    
    void initialize() {
        // 初始化各模块
        printf("[Robot %d] 初始化中...\n", robot_id_);
        
        // 加载地图
        loadMap("restaurant_map.yaml");
        
        // 初始化定位
        localization_.initialize(0, 0, 0);  // 初始位置
        
        // 初始化规划器
        global_planner_ = std::make_unique<AStarPlanner>(map_);
        local_planner_ = std::make_unique<DWAPlanner>(DWAConfig());
        
        // 连接后台调度系统
        connectToScheduler();
        
        printf("[Robot %d] 初始化完成\n", robot_id_);
    }
    
    void start() {
        running_ = true;
        
        // 启动各处理线程
        localization_thread_ = std::thread(&DeliveryRobotController::localizationLoop, this);
        planning_thread_ = std::thread(&DeliveryRobotController::planningLoop, this);
        control_thread_ = std::thread(&DeliveryRobotController::controlLoop, this);
        
        printf("[Robot %d] 启动完成,等待任务...\n", robot_id_);
    }
    
    void stop() {
        running_ = false;
        
        if (localization_thread_.joinable()) localization_thread_.join();
        if (planning_thread_.joinable()) planning_thread_.join();
        if (control_thread_.joinable()) control_thread_.join();
    }
    
    /**
     * 接收新任务
     */
    void onNewTask(const DeliveryTask& task) {
        std::lock_guard<std::mutex> lock(task_mutex_);
        current_task_ = task;
        has_task_ = true;
        
        printf("[Robot %d] 收到任务: 送到 %d 号桌\n", robot_id_, task.table_id);
        
        // 规划全局路径
        planGlobalPath();
    }
    
private:
    /**
     * 定位循环
     */
    void localizationLoop() {
        while (running_) {
            // 1. 获取传感器数据
            auto laser_scan = getLaserScan();
            auto odom = getOdometry();
            
            // 2. 运动更新
            localization_.motionUpdate(odom.dx, odom.dy, odom.dtheta);
            
            // 3. 观测更新
            localization_.sensorUpdate(laser_scan, map_);
            
            // 4. 重采样
            localization_.resample();
            
            // 5. 获取位姿估计
            {
                std::lock_guard<std::mutex> lock(pose_mutex_);
                current_pose_ = localization_.getEstimatedPose();
            }
            
            std::this_thread::sleep_for(std::chrono::milliseconds(50));  // 20Hz
        }
    }
    
    /**
     * 规划循环
     */
    void planningLoop() {
        while (running_) {
            if (!has_task_) {
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                continue;
            }
            
            // 获取当前位姿
            Pose2D pose;
            {
                std::lock_guard<std::mutex> lock(pose_mutex_);
                pose = current_pose_;
            }
            
            // 获取目标
            Pose2D goal;
            {
                std::lock_guard<std::mutex> lock(task_mutex_);
                goal.x = current_task_.table_x;
                goal.y = current_task_.table_y;
            }
            
            // 检查是否到达
            double dist = std::sqrt(std::pow(pose.x - goal.x, 2) + 
                                   std::pow(pose.y - goal.y, 2));
            if (dist < 0.5) {
                onArrived();
                continue;
            }
            
            // 获取障碍物
            auto obstacles = detectObstacles();
            
            // 局部规划
            Velocity cmd = local_planner_->computeVelocity(
                {pose.x, pose.y, pose.theta, current_velocity_.v, current_velocity_.omega},
                goal,
                global_path_,
                obstacles);
            
            // 发送速度命令
            {
                std::lock_guard<std::mutex> lock(cmd_mutex_);
                cmd_velocity_ = cmd;
            }
            
            std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 10Hz
        }
    }
    
    /**
     * 控制循环
     */
    void controlLoop() {
        while (running_) {
            Velocity cmd;
            {
                std::lock_guard<std::mutex> lock(cmd_mutex_);
                cmd = cmd_velocity_;
            }
            
            // 发送到电机
            sendVelocityCommand(cmd.v, cmd.omega);
            
            std::this_thread::sleep_for(std::chrono::milliseconds(20));  // 50Hz
        }
    }
    
    /**
     * 规划全局路径
     */
    void planGlobalPath() {
        Pose2D pose;
        {
            std::lock_guard<std::mutex> lock(pose_mutex_);
            pose = current_pose_;
        }
        
        DeliveryTask task;
        {
            std::lock_guard<std::mutex> lock(task_mutex_);
            task = current_task_;
        }
        
        GridCell start = map_.worldToGrid(pose.x, pose.y);
        GridCell goal = map_.worldToGrid(task.table_x, task.table_y);
        
        auto grid_path = global_planner_->planPath(start, goal);
        
        // 平滑路径
        global_path_ = PathSmoother::smoothBSpline(grid_path, map_);
        
        printf("[Robot %d] 规划路径: %zu 个路点\n", robot_id_, global_path_.size());
    }
    
    /**
     * 到达目标
     */
    void onArrived() {
        printf("[Robot %d] 到达目标桌位\n", robot_id_);
        
        // 播放语音
        playVoice("您的餐品到了,请取餐");
        
        // 等待取餐确认
        waitForConfirmation();
        
        // 通知后台
        notifyTaskCompleted();
        
        has_task_ = false;
    }
    
    // 辅助函数 (实际实现需要对接硬件)
    void loadMap(const std::string& path) {
        // 加载地图文件
        map_ = OccupancyGrid(200, 200, 0.05);  // 10m x 10m
    }
    
    LaserScan getLaserScan() { return {}; }
    struct Odom { double dx, dy, dtheta; };
    Odom getOdometry() { return {0, 0, 0}; }
    std::vector<std::pair<double, double>> detectObstacles() { return {}; }
    void sendVelocityCommand(double v, double omega) {}
    void connectToScheduler() {}
    void playVoice(const std::string& text) {}
    void waitForConfirmation() {}
    void notifyTaskCompleted() {}
    
    int robot_id_;
    std::atomic<bool> running_;
    
    OccupancyGrid map_;
    ParticleFilter localization_;
    std::unique_ptr<AStarPlanner> global_planner_;
    std::unique_ptr<DWAPlanner> local_planner_;
    
    Pose2D current_pose_;
    std::mutex pose_mutex_;
    
    DeliveryTask current_task_;
    bool has_task_ = false;
    std::mutex task_mutex_;
    
    std::vector<Pose2D> global_path_;
    Velocity current_velocity_;
    Velocity cmd_velocity_;
    std::mutex cmd_mutex_;
    
    std::thread localization_thread_;
    std::thread planning_thread_;
    std::thread control_thread_;
};

int main() {
    DeliveryRobotController robot(1);
    robot.initialize();
    robot.start();
    
    // 模拟收到任务
    DeliveryTask task;
    task.task_id = 1;
    task.table_id = 5;
    task.table_x = 8.0;
    task.table_y = 6.0;
    robot.onNewTask(task);
    
    // 运行一段时间
    std::this_thread::sleep_for(std::chrono::seconds(60));
    
    robot.stop();
    return 0;
}

总结

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                    送餐机器人技术栈总结                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   【核心技术模块】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   1. 定位 (SLAM/AMCL)                                                      │
│      - 激光雷达 + 粒子滤波                                                 │
│      - 精度: 厘米级                                                        │
│                                                                             │
│   2. 全局路径规划 (A*/D*)                                                  │
│      - 基于栅格地图                                                        │
│      - 考虑静态障碍物                                                      │
│                                                                             │
│   3. 局部路径规划 (DWA/TEB)                                                │
│      - 实时避障                                                            │
│      - 速度空间采样                                                        │
│                                                                             │
│   4. 任务调度                                                              │
│      - 多任务排序 (TSP)                                                    │
│      - 优先级管理                                                          │
│      - 批量配送优化                                                        │
│                                                                             │
│   5. 语义识别                                                              │
│      - ArUco标签识别                                                       │
│      - 行人检测                                                            │
│                                                                             │
│   6. 多机协调                                                              │
│      - 路径冲突检测                                                        │
│      - 死锁预防                                                            │
│      - 任务分配优化                                                        │
│                                                                             │
│   【常用开源框架】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   - ROS/ROS2: 机器人操作系统                                               │
│   - Navigation2: 导航栈                                                    │
│   - SLAM Toolbox: SLAM算法                                                 │
│   - OpenCV: 视觉处理                                                       │
│   - PCL: 点云处理                                                          │
│                                                                             │
│   【硬件配置参考】                                                          │
│   ─────────────────────────────────────────                                 │
│                                                                             │
│   - 激光雷达: 思岚A1/A2, Hokuyo                                           │
│   - 深度相机: Intel RealSense, Orbbec                                     │
│   - 主控: Jetson Nano/Xavier, RK3588                                      │
│   - 底盘: 差速驱动, 麦克纳姆轮                                             │
│   - 电池: 24V锂电池, 续航4-8小时                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

一句话总结:

送餐机器人 = SLAM定位 + A*全局规划 + DWA局部避障 + 任务调度 + 多机协调。技术成熟,ROS有现成框架,核心难点在于复杂环境的鲁棒性和多机协调效率。


参考资料:

  • ROS Navigation2文档
  • 《概率机器人》(Probabilistic Robotics)
  • SLAM Toolbox
  • 普渡机器人技术方案
相关推荐
会的东西有点杂7 小时前
ROS 2和ROS 1的比较
机器人
酉鬼女又兒7 小时前
零基础入门 DeepSeek V4 Pro API 开发:从环境搭建、消息格式规范到翻译函数实战、少样本提示、多轮对话聊天机器人与常见报错全流程详解指南
大数据·网络·数据库·人工智能·macos·机器人·github
小顿的企业观察9 小时前
从人形机器人到AI销售智能体,AI正在重塑中企出海的底层逻辑
大数据·运维·人工智能·机器人·产品运营·制造
沫儿笙12 小时前
安川弧焊机器人混合气动态调节技术
人工智能·机器人
学术小白人14 小时前
第二届智能制造、机器人与自动化国际学术会议(IMRA 2026)
运维·人工智能·机器人·自动化·制造
人生百态,人生如梦15 小时前
情感交互仿生人从技术到落地构想2——技术交流贴(2026.7)
人工智能·机器人·人机交互·交互
TMT星球17 小时前
机器人管家系统上线!傅利叶携多款康养陪伴新品方案亮相WAIC 2026
机器人
咕噜咕噜啦啦17 小时前
ROS2自主导航
机器人·ros·gazebo
田里的水稻17 小时前
EP_局域网传输音视频的主流方案
运维·人工智能·机器人·音视频
蜜桃味女焊匠人19 小时前
2026焊接保护气体降耗最优改造方案
人工智能·经验分享·其他·机器人