描述
使用贝塞尔曲线生成路径 已知:若干个二维路径点(x, y),生成一段由一系列(x, y)点组成的点集
公式
网上有很多贝塞尔曲线的概念和知识,这里不做赘述 贝塞尔曲线上的路径点计算公式:
公式的理解: 假设我有6个点,按照点的顺序依次连接,这样我有5条线段 我有一个系数t,在每条线段上我都能通过线性插值找到一个点,这个点的位置是 t 倍的直线总距离。(比如线段长度为10,t为0.3时,生成的点就在距离起点3长度的位置上,自行二维拓展理解) 5个线段上有由t生成的5个插值点,这5个点依次连接,又生成了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;
}