贝塞尔曲线生成路径

描述

使用贝塞尔曲线生成路径

已知:若干个二维路径点(x, y),生成一段由一系列(x, y)点组成的点集

公式

网上有很多贝塞尔曲线的概念和知识,这里不做赘述

贝塞尔曲线上的路径点计算公式
P ( t )    = ∑ i = 0 n k ( i ,    n ,    t ) ⋅ P i ,          t ∈ [ 0 , 1 ] P\left( t \right)\; =\sum_{i=0}^{n}{k\left( i,\; n,\; t \right)\cdot P_{i}},\; \; \; \; t\in \left[ 0,1 \right] P(t)=i=0nk(i,n,t)Pi,t[0,1]
k ( i , n , t )    =    C n i ⋅ t i ⋅ ( 1 − t ) n − i    = n ! i ! ⋅ ( n − i ) ! ⋅ t i ⋅ ( 1 − t ) n − i ,      i    =    0 , 1 , . . . , n k\left( i,n,t \right)\; =\;{C}_{n}^{i}\cdot t^{i}\cdot \left( 1-t \right)^{n-i}\; =\frac{n!}{i!\cdot \left( n-i \right)!}\cdot t^{i}\cdot \left( 1-t \right)^{n-i},\; \; i\; =\; 0,1,...,n k(i,n,t)=Cniti(1t)ni=i!(ni)!n!ti(1t)ni,i=0,1,...,n

公式的理解

  1. 假设我有6个点,按照点的顺序依次连接,这样我有5条线段
  2. 我有一个系数t,在每条线段上我都能通过线性插值找到一个点,这个点的位置是 t 倍的直线总距离。(比如线段长度为10,t为0.3时,生成的点就在距离起点3长度的位置上,自行二维拓展理解)
  3. 5个线段上有由t生成的5个插值点,这5个点依次连接,又生成了4条新的线段
  4. 依次执行1,2,3步骤,直到我们只剩下两个点形成的一条线段,在这条线段上的插值点就是贝塞尔曲线点

在这里插入图片描述

代码

由输入点直接得到输出点的函数,请尽情拷贝使用

int fac(int x) // 求阶乘
{
    int f=1;  
	for(int i = 1; i <= x; i++)
    {
        f*=i;
    }
	return f;
}
// 由t和输入点,得到输出的贝塞尔曲线点
std::vector<cv::Point2d> Bezier(double dt, std::vector<cv::Point2d> input)
{
    std::vector<cv::Point2d> output;

    double t = 0;
    while(t<=1)
    {
        cv::Point2d p;
        double x_sum = 0.0;
        double y_sum = 0.0;
        int i = 0;
        int n = input.size()-1;
        while (i<=n)
        {
            double k = fac(n)/(fac(i)*fac(n-i))*pow(t,i)*pow(1-t,n-i);
            x_sum += k*input[i].x;
            y_sum += k*input[i].y;
            i++;
        }
        p.x = x_sum;
        p.y = y_sum;
        output.push_back(p);
        t += dt;
    }
    return output;
}

可以动态看到每个点生成过程的代码

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>

#define random(a,b) (rand()%(b-a)+a)

cv::Point2d lerp(double t, double x1, double y1, double x2, double y2)
{
    cv::Point p;
    p.x = (1-t) * x1 + t * x2;
    p.y = (1-t) * y1 + t * y2;
    return p;
}

std::vector<cv::Point2d> getLine(double t, std::vector<cv::Point2d> input)
{
    std::vector<cv::Point2d> result;

    for (int i = 0 ; i < input.size()-1 ; i++)
    {   
        cv::Point2d p;
        p = lerp(t, input[i].x, input[i].y, input[i+1].x, input[i+1].y);
        result.push_back(p);
    }

    return result;
}

int main(int argc, char** argv)
{
    int points_size = 5; // 输入的点有多少个
    double dt = 0.005; // t的变化量,能决定曲线生成的有多密

    int matrix_size = 1600; // 空白图大小
    
    std::vector<cv::Point2d> input; // 输入的点
    std::vector<cv::Point2d> output; // 贝塞尔曲线生成的点

    srand((int)time(0));  // 提供随机种子,要加这句话不然还会生成不变的数
    for (int i = 0 ; i < points_size; i++) // 随机生成输入点
    {   
        int x= random(-matrix_size/2,matrix_size/2);
        int y = random(-matrix_size/2,matrix_size/2);
        cv::Point2d p(x, y);
        input.push_back(p);
    }

    double t = 0 ; // 贝塞尔曲线算法的t参数

    std::vector<cv::Point2d> pt_tmp; // 用于储存每步递归的点
    pt_tmp = input;

    // 以t==1为最终目标
    while (t<1)
    {
        cv::Mat image = cv::Mat(matrix_size, matrix_size, CV_8UC3, cv::Scalar(255,255,255));
        while (pt_tmp.size() >1)
        {
            // 降维至不同个数时,设置直线为不同的颜色
            cv::Scalar color;
            if (pt_tmp.size() == 2)
            {
                // 这句话比较重要, 意思是最后只有两个点的时候,插值出的点就是我们需要的贝塞尔曲线
                cv::Point2d p = lerp(t, pt_tmp[0].x, pt_tmp[0].y, pt_tmp[1].x, pt_tmp[1].y);
                output.push_back(p);

                color = cv::Scalar(0,255,0);
            }
            else if (pt_tmp.size() == 3)
            {
                color = cv::Scalar(0,220,0);
            }
            else if (pt_tmp.size() == 4)
            {
                color = cv::Scalar(0,190,0);
            }
            // ...颜色可以继续补充

            // 画点和直线
            for (int i = 0 ; i < pt_tmp.size(); i++)
            {
                cv::circle(image, cv::Point(matrix_size/2+pt_tmp[i].x, matrix_size/2-pt_tmp[i].y), 1, color, 1);
            }
            for (int i = 0 ; i < pt_tmp.size()-1; i++)
            {
                cv::line(image, cv::Point(matrix_size/2+pt_tmp[i].x, matrix_size/2-pt_tmp[i].y), cv::Point(matrix_size/2+pt_tmp[i+1].x, matrix_size/2-pt_tmp[i+1].y), color, 1);
            }   

            // 计算点由n个变为n-1个
            pt_tmp = getLine(t, pt_tmp);

        }

        // 每一次都画出贝塞尔曲线点
        for (int i = 0 ; i < output.size(); i++)
        {
            cv::circle(image, cv::Point(matrix_size/2+output[i].x, matrix_size/2-output[i].y), 5, cv::Scalar(0,0,255), -1);
        }

        // 计算点变为输入的全部点
        pt_tmp = input;

        cv::imshow("Bezier curve", image);
        cv::waitKey(10);

        std::cout<<"t: "<<t<<std::endl;
        
        t = t + 0.005;
    }

    return 1;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值