贝塞尔曲线平滑后的轨迹怎么规避静态障碍物?

如题,现考虑使用贝塞尔曲线对原路径进行平滑,但是现在不知道怎么考虑对静态障碍物的规避。需要让平滑后的路径根据已知的障碍物进行一定的调整,但仍保持整体的曲率连续。
所使用的是栅格地图,C++开发。

对于栅格地图中的贝塞尔曲线平滑路径,需要进行静态障碍物的规避,可以考虑以下步骤:

确定障碍物的位置:在栅格地图中,可以通过遍历地图,找到障碍物所在的栅格,记录下来。

在贝塞尔曲线上插入避障点:对于每个障碍物,可以在贝塞尔曲线上插入一个避障点,使得路径绕开障碍物。避障点的位置可以根据贝塞尔曲线的参数方程计算得到。具体来说,可以通过计算每个障碍物与贝塞尔曲线的最短距离,然后将避障点插入到该距离对应的曲线参数位置上。

调整贝塞尔曲线:在插入避障点后,需要重新计算贝塞尔曲线的控制点,使得路径保持曲率连续。具体来说,可以采用贝塞尔曲线的拟合方法,通过已知的路径点和避障点,计算出新的控制点。

根据调整后的贝塞尔曲线生成路径:根据调整后的贝塞尔曲线,可以生成新的平滑路径,用于避开障碍物。
需要注意的是,避障点的位置和数量需要根据具体情况进行调整,以保证路径能够避开障碍物并且不会离开可行区域。同时,对于动态障碍物,还需要实时更新避障点的位置,以保证路径的有效性。
以下是一个简单的C++代码示例,用于在贝塞尔曲线上插入避障点:


// 假设已经得到了贝塞尔曲线的参数方程
// p0, p1, p2, p3 分别表示控制点和起点终点
// obs_x, obs_y 分别表示障碍物的坐标
// threshold 表示插入避障点的阈值
void insert_obstacle_point(double p0, double p1, double p2, double p3, 
                           double obs_x, double obs_y, double threshold,
                           std::vector<double>& new_params) {
    double min_dist = std::numeric_limits<double>::max();
    double min_t = 0.0;
    for (double t = 0.0; t <= 1.0; t += 0.01) {
        double x = pow(1 - t, 3) * p0 + 3 * pow(1 - t, 2) * t * p1 
                 + 3 * (1 - t) * pow(t, 2) * p2 + pow(t, 3) * p3;
        double y = pow(1 - t, 3) * p0 + 3 * pow(1 - t, 2) * t * p1 
                 + 3 * (1 - t) * pow(t, 2) * p2 + pow(t, 3) * p3;
        double dist = std::sqrt(std::pow(x - obs_x, 2) + std::pow(y - obs_y, 2));
        if (dist < min_dist) {
            min_dist = dist;
            min_t = t;
        }
    }
    if (min_dist < threshold) {
        double new_t = std::max(0.0, std::min(1.0, min_t + (threshold - min_dist) / 10.0));
        new_params.push_back(p0);
        new_params.push_back(p1);
        new_params.push_back(p2);
        new_params.push_back(p3);
        new_params.push_back(new_t);
    }
    return;
}


其中,new_params 表示新的贝塞尔曲线参数,threshold 表示插入避障点的阈值,如果障碍物与曲线距离小于该值,则插入避障点。

对于栅格地图上的贝塞尔曲线平滑,您可以考虑使用如下方法对路径进行调整以避免静态障碍物:

  1. 将栅格地图转换为一个二维数组,其中障碍物的值为1,非障碍物的值为0。
  2. 将平滑后的路径离散化,即将其拆分为一系列离散的点,以便在栅格地图上进行操作。
  3. 对于每个路径点,找到其周围的栅格,并将这些栅格标记为不可通行。具体来说,您可以使用一个较小的矩形区域,例如3x3或5x5,来表示该点周围的栅格。如果该矩形区域中存在障碍物,则将该区域内的所有栅格标记为不可通行。
  4. 使用修改过的栅格地图,对路径进行重新平滑。您可以使用基于贝塞尔曲线的平滑算法来实现。在这种情况下,您需要确保贝塞尔曲线的控制点不会落在不可通行的栅格上。
  5. 在整个平滑路径上应用曲率连续性的约束,以确保平滑路径仍具有一定的弯曲性,同时避免出现太多的急转弯。您可以使用一些基于曲率的约束条件,例如最小曲率,以确保平滑路径的曲率不会太小。
  6. 最后,对于可能与障碍物相交的路径段,您可以使用更细致的检测算法来检测碰撞,并对路径进行进一步调整以避免碰撞。

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
对于贝塞尔曲线平滑后的路径,可以采用以下方法进行静态障碍物规避:

  1. 将栅格地图转化为障碍物网格地图,标记障碍物的网格为不可通过状态;

  2. 对平滑后的路径进行插值,将其转化为一系列连接起来的小线段(例如每5个像素插一个点),并求出每个点的局部梯度方向;

  3. 在每个小线段内进行检测,判断是否与障碍物发生了碰撞,如果发生碰撞,则根据该点的局部梯度方向和障碍物的位置,计算出一个新的路径点,避开障碍物后重新连接起来;

  4. 连接新的路径点,保证整体曲率连续,并继续向下一个路径点进行检测。

下面是一个简单的代码示例:

// 障碍物网格地图(1表示障碍物,0表示可行)
int map[MAX_X][MAX_Y];

// 平滑后的路径点序列
vector<cv::Point2d> path;

// 每个线段上的采样点数量
const int SAMPLE_NUM = 5;

// 检测线段是否与障碍物碰撞
bool detect_collision(cv::Point2d start, cv::Point2d end) {
    // 采样SAMPLE_NUM个点
    int dx = end.x - start.x;
    int dy = end.y - start.y;
    double interval = sqrt(dx*dx + dy*dy) / (double)(SAMPLE_NUM-1);
    for (int i = 0; i < SAMPLE_NUM; i++) {
        double x = start.x + dx * (double)i / (double)(SAMPLE_NUM-1);
        double y = start.y + dy * (double)i / (double)(SAMPLE_NUM-1);
        // 判断该点是否在障碍物区域内
        if (map[(int)x][(int)y] == 1) {
            return true;
        }
    }
    return false;
}

// 计算新的路径点,避开障碍物
cv::Point2d avoid_obstacle(cv::Point2d start, cv::Point2d end) {
    // 根据梯度方向判断障碍物在左侧或右侧
    int dx = end.x - start.x;
    int dy = end.y - start.y;
    double gradient = atan2(dy, dx);
    bool is_left = (gradient >= -CV_PI/2 && gradient <= CV_PI/2);
    // 计算新的路径点(偏移量为5个像素宽度)
    double offset = 5.0;
    double new_x = end.x + offset * (is_left ? 1 : -1) * sin(gradient);
    double new_y = end.y - offset * (is_left ? 1 : -1) * cos(gradient);
    return cv::Point2d(new_x, new_y);
}

// 主函数
void main() {
    // 对路径进行插值
    vector<cv::Point2d> interpolated_path;
    for (int i = 0; i < path.size()-1; i++) {
        cv::Point2d start = path[i];
        cv::Point2d end = path[i+1];
        int dx = end.x - start.x;
        int dy = end.y - start.y;
        double interval = sqrt(dx*dx + dy*dy) / (double)(SAMPLE_NUM-1);
        for (int j = 0; j < SAMPLE_NUM; j++) {
            double x = start.x + dx * (double)j / (double)(SAMPLE_NUM-1);
            double y = start.y + dy * (double)j / (double)(SAMPLE_NUM-1);
            interpolated_path.push_back(cv::Point2d(x, y));
        }
    }
    // 检测并避开障碍物
    vector<cv::Point2d> new_path;
    new_path.push_back(interpolated_path[0]);
    for (int i = 1; i < interpolated_path.size(); i++) {
        if (detect_collision(new_path.back(), interpolated_path[i])) {
            cv::Point2d new_point = avoid_obstacle(new_path.back(), interpolated_path[i]);
            new_path.push_back(new_point);
        }
        new_path.push_back(interpolated_path[i]);
    }
    // 输出路径点
    for (int i = 0; i < new_path.size(); i++) {
        cout << new_path[i] << endl;
    }
}

注意,这只是一个简单的示例代码,实际应用中可能需要考虑更多的情况,比如障碍物形状、路径宽度等。同时,为了提高效率,可以使用空间分割等方法来优化碰撞检测算法。
如果我的回答解决了您的问题,请采纳!

该回答引用GPTᴼᴾᴱᴺᴬᴵ
对于贝塞尔曲线平滑后的轨迹进行静态障碍物规避,可以考虑使用以下方法:

  1. 离散化地图:将栅格地图离散化,将障碍物转换成离散化的点或者矩形区域,便于后续计算。
  2. 碰撞检测:在平滑后的路径上,对每个点进行碰撞检测,判断该点是否与障碍物相交,若相交,则需要对路径进行调整。可以使用线段和矩形相交的算法进行判断,如:线段与AABB(轴对齐的矩形)相交判断、线段与OBB(任意方向的矩形)相交判断、线段与圆形相交判断等等。
  3. 路径调整:若某个点与障碍物相交,则需要对路径进行调整。可以考虑修改该点的位置或者重新生成该点附近的曲线段,以确保整体的曲率连续。调整方法可以使用以下几种:
  • 使用A*等路径规划算法重新规划路径,使其绕过障碍物。
  • 将路径沿着法线方向偏移一定距离,使其远离障碍物。
  • 修改贝塞尔曲线的控制点,使其路径更接近原始路径,但又远离障碍物。
  1. 优化:如果进行路径调整后,发现路径的总长度或者曲率过大,可以考虑使用优化算法,如遗传算法、模拟退火算法等,对路径进行进一步优化,使其更加平滑、更接近原始路径,同时也保持足够的安全距离。

总体来说,贝塞尔曲线平滑后的路径规避静态障碍物,需要进行离散化地图、碰撞检测、路径调整和优化等一系列操作,具体实现可以根据具体场景和需求来选择相应的算法和数据结构。

参考GPT和自己的思路:对于栅格地图上的贝塞尔曲线平滑后的轨迹规避静态障碍物的问题,可以考虑使用A*算法进行路径规划。

具体思路如下:

1 将栅格地图上的障碍物用格子表示,建立地图;

2 将贝塞尔曲线平滑后的轨迹用格子表示,并标记为可通行区域;

3 将起点和终点也用格子表示,并标记为可通行区域;

4 利用A*算法,在地图上搜索起点到终点的最短路径;

5 在搜索路径时,对于搜索到的每一个格子,判断是否与已知的障碍物有重叠,如果有重叠,则将该格子标记为不可通行区域,避免路径与障碍物相交;
6 最终得到的搜索路径即为贝塞尔曲线平滑后的轨迹规避静态障碍物后的路径。

下面是一个C++代码示例:

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

struct Node {
    int x, y;   // 格子坐标
    int f, g, h;    // f = g + h
    Node* parent;   // 父节点指针

    Node(int _x, int _y, int _g, int _h, Node* _parent = nullptr) {
        x = _x;
        y = _y;
        g = _g;
        h = _h;
        f = g + h;
        parent = _parent;
    }

    bool operator < (const Node& other) const {
        return f > other.f;
    }
};

// 栅格地图
vector<vector<int>> map;

// 起点和终点坐标
int sx, sy, ex, ey;

// 搜索路径
vector<Node*> path;

// A*算法
void astar() {
    // 定义open表和closed表
    priority_queue<Node> open;
    vector<Node*> closed(map.size(), nullptr);
    vector<vector<bool>> visited(map.size(), vector<bool>(map[0].size(), false));

    // 将起点加入open表
    Node* start = new Node(sx, sy, 0, abs(sx-ex)+abs(sy-ey));
    open.push(*start);
    visited[sx][sy] = true;

    // A*主循环
    while (!open.empty()) {
        // 取出open表中f值最小的节点
        Node curr = open.top();
        open.pop();

        // 如果当前节点为终点,则搜索结束
        if (curr.x == ex && curr.y == ey) {
            while (curr.parent != nullptr) {
                path.push_back(new Node(curr.x, curr.y, curr.g, curr.h));
                curr = *curr.parent;
            }
            reverse(path.begin(), path.end());
            break;
        }

        // 将当前节点加入closed表
        closed[curr.x][curr.y] = new Node(curr);

        // 枚举周围的8个点
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
// 排除自身和斜向的点
if (i == 0 && j == 0) continue;
if (abs(i) == 1 && abs(j) == 1) continue;
            // 计算周围点的坐标
            int x = curr.x + i;
            int y = curr.y + j;

            // 判断周围的点是否越界
            if (x < 0 || x >= mapWidth || y < 0 || y >= mapHeight) continue;

            // 判断是否为障碍物或者已经在closed表中
            if (map[x][y] == OBSTACLE || closed[x][y] != NULL) continue;

            // 计算周围点的代价
            float g = curr.g + sqrt(i * i + j * j);
            float h = sqrt((x - end.x) * (x - end.x) + (y - end.y) * (y - end.y));
            float f = g + h;

            // 如果该点已经在open表中,更新其代价
            // 否则将该点加入open表中
            if (open[x][y] != NULL) {
                Node *tmp = open[x][y];
                if (tmp->f > f) {
                    tmp->f = f;
                    tmp->g = g;
                    tmp->h = h;
                    tmp->parent = closed[curr.x][curr.y];
                }
            } else {
                open[x][y] = new Node(x, y, f, g, h, closed[curr.x][curr.y]);
                openList.push(open[x][y]);
            }
        }
    }
}

// 如果open表为空,说明无法到达目标点
return NULL;
}


将栅格地图转换为一个网格图形,其中每个网格表示地图中的一个区域。

将原始路径与网格图形对齐,这可以通过将路径离散化为网格的序列来实现。确保路径在网格中的每个格子中只有一条路径线段。

1.对于每个网格,检查它是否包含障碍物。如果网格包含障碍物,则需要调整路径以避免障碍物。这可以通过在网格中创建一个平滑的可行路径来实现,该路径必须绕过障碍物。

2.使用贝塞尔曲线算法来对每个网格中的路径进行平滑处理,确保在平滑后的路径中保持整体的曲率连续。

3.在完成平滑后,将所有网格中的路径线段连接在一起,形成最终的平滑路径。

以下是一个简单的 C++ 代码示例,用于计算两个网格之间的平滑路径:

// 贝塞尔曲线的控制点数量
const int kNumControlPoints = 3;

// 计算两个点之间的贝塞尔曲线
std::vector<Point> ComputeBezierCurve(const Point& p0, const Point& p1, const Point& p2, double t) {
  std::vector<Point> curve;
  double u = 1.0 - t;
  Point point = u * u * p0 + 2.0 * u * t * p1 + t * t * p2;
  curve.push_back(point);
  return curve;
}

// 平滑网格中的路径
std::vector<Point> SmoothPath(const std::vector<Point>& path, const GridMap& grid_map) {
  std::vector<Point> smoothed_path;

  // 处理路径中的每个网格
  for (int i = 0; i < path.size() - 2; i++) {
    Point p0 = path[i];
    Point p1 = path[i + 1];
    Point p2 = path[i + 2];

    // 计算网格之间的贝塞尔曲线控制点
    Point cp1 = (1.0 / 3.0) * p0 + (2.0 / 3.0) * p1;
    Point cp2 = (2.0 / 3.0) * p1 + (1.0 / 3.0) * p2;

    // 初始化曲线的起始点
    std::vector<Point> curve;
    curve.push_back(p1);

    // 对贝塞尔曲线进行平滑处理
    for (int j = 1; j <= kNumControlPoints; j++) {
      double t = static_cast<double>(j) / static_cast<double>(kNumControlPoints + 1);
      Point point = ComputeBezierCurve(p1, cp1, cp2, t)[

您可以将路径和障碍物都表示为栅格地图上的像素点,并使用贝塞尔曲线对路径进行平滑处理。然后,您可以使用障碍物的像素点来调整平滑路径以避免碰撞。

一种简单的方法是在路径上的每个控制点周围的像素点中找到最近的障碍物像素点。然后,您可以将路径的控制点移动到相应的位置,使路径避开障碍物。但是,这种方法可能会导致曲率不连续,因为在调整路径时控制点的位置可能会发生大的变化。

因此,另一种更复杂的方法是使用反向A*算法或其他路径规划算法来计算路径上的障碍物的最短距离。这将产生一个距离场,它表示每个像素点到最近的障碍物的距离。然后,您可以使用这个距离场来调整路径,使路径避开障碍物,但仍保持曲率连续。一种可能的实现方法是使用光滑的变形技术,如光滑变形(smoothing deformation)或最小曲率变形(minimum curvature deformation)。

在实现过程中,您可能需要使用一些基本的计算几何和图像处理技术,如计算曲线的切线和法线、计算两个曲线之间的距离、计算图像的梯度等。

最后,需要注意的是,这种方法可能会降低路径的效率,因为调整路径需要进行额外的计算。因此,您需要权衡路径平滑和路径规划的效率之间的平衡,并根据具体情况进行调整。

不知道你这个问题是否已经解决, 如果还没有解决的话:
  • 文章:通过多点平滑曲线的实现 中也许有你想要的答案,请看下吧
  • 除此之外, 这篇博客: 通过多点平滑曲线的实现中的 通过多点平滑曲线的实现 部分也许能够解决你的问题, 你可以仔细阅读以下内容或者直接跳转源博客中阅读:

    ​ 大家都知道使用贝塞尔曲线算法来画曲线,但是如果想经过特定的一系列点来画曲线,只使用普通的贝塞尔算法就不够用了.

    ​ 通过参考网上文章:https://www.jianshu.com/p/64305821087a
    ​ 确定了实现方法要点:
    1 在连续两点之间使用三次贝塞尔曲线来绘制.
    2 为了保证两段贝塞尔之间的圆滑,连接点对应的两个控制点与曲线相切.

    然后剩下的问题就是确定每个点所连接点的角度,参考上面的文章实现功能,但是发现这篇文章确定控制点角度的是通过计算斜率来实现.这样连续两点X轴相等的时候,就会出现问题.它所提供的算法只适应X单向增长的环境.如果需要普遍适应,需要进一步的处理,其后虽然实现适应性,但是斜率的计算非常不清晰不利于调整绘制曲线的样式.
    ​ 计算斜率,就是确定每个点法线的角度.发现使用向量来计算法线,更加便捷和清晰.
    ​ 将连续的三点{p1 , p2 ,p3} 以p1点为原点,获得两个向量:
    ​ v1 = p1->p2
    ​ v2 = p1 ->p3
    ​ 做v1 v2的和:
    v3 = v1 + v2
    ​ v3加在v1与v2之间,完全符合p1点法线的要求.由于p1点的切线斜率受到v1向量的影响大,v2向量的影响小,最后确定p1点的切线向量公式为:
    ​ v3 = 2v1 + v2
    ​ p1点前后两个控制点,在一条直线上,那么另外一个控制点的向量就是简单的 -v3
    ​ 然后根据p1点前后两个线段长度决定两个权值l1 和 l2
    ​ 将v3单位化
    ​ 获得p1的两个控制点:
    ​ p1_forward = p1 + l1 * v3
    ​ p1_backward = p1 - l2
    v3
    ​ 按照这个方面获得所有点的控制点.
    ​ 然后使用贝塞尔方法,依次绘制曲线.
    ​ 每个三次贝塞尔曲线的序列为[p1,p1_forward ,p2_backward,p2]
    代码为python实现,csdn下载中搜"通过多点平滑曲线的python实现"

    在这里插入图片描述

    以后需要完善的有两个方面:

    1 实现曲线的闭合.现在闭合点没有圆滑过渡.并不能简单后点循环接到前点来计算斜率,需要一直循环下去直到找到斜率误差小于一定值的点停止.

    2 相邻非常近的点,在实现的时候是否给合并处理.需要在实现中加入精度参数.

    贝塞尔算法的实现来自于:

    https://github.com/TheAlgorithms/Python/blob/master/graphics/bezier_curve.py

    最后补充一下,这种方法实现三维的连续曲线也是可以的,只要找到三维的贝塞尔算法实现,然后把上面的二维的向量改为三维向量就可以了.


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

对于对静态障碍物的规避,通常有以下几种方式:

  1. 障碍物预处理
    在进行贝塞尔曲线平滑前,可以先对障碍物进行处理,将其转换为可行驶的区域或禁止通行区域。可以使用典型的轮廓线方法,比如扫描线法、多边形剖分等,对障碍物进行划分,生成可行驶的区域。这样再进行贝塞尔曲线平滑就可以避免障碍物的碰撞。

  2. 动态规划
    可以使用动态规划来规避静态障碍物,先将栅格地图转换为网格图,然后使用动态规划算法来规划路径。动态规划需要建立状态空间和状态转移方程,通过优化目标函数来寻找最优路径。在目标函数中可以考虑到障碍物的影响,使路径不会穿过障碍物,同时也要保持曲率连续。

  3. 调整贝塞尔曲线控制点
    在进行贝塞尔曲线平滑时,可以考虑将贝塞尔曲线的控制点向外偏移一定距离,使路径远离障碍物。具体偏移距离可以根据障碍物大小和机器人大小进行计算。这样可以保证路径顺畅,并且可以避免障碍物碰撞。

以上是几种常用的方法,可以根据实际情况选择合适的方法。另外,需要注意的是,对于动态障碍物,需要使用实时感知和规划算法,在运行时动态调整路径,避免障碍物碰撞。