描述
简单介绍B样条曲线,并给出实现代码
原理
有关B样条的介绍有很多,这里不会太详细的介绍B样条曲线的全部概念,只会列出一些我的理解
贝塞尔曲线
不得不提到贝塞尔曲线,之前我曾经写过一篇关于贝塞尔曲线的文章,将非常有助于理解B样条曲线。贝塞尔曲线是这样生成的,简述:
- 有n个控制点,那么第0个点和第1个点连接起来,第1个点和第2个点连接起来…第n-2个点和第n-1个点连接起来,这样我们有了n-1条线段
- 每条线段上,以一个间隔值去滑动取点,分别取直线长度的0.01倍、0.02倍…一直到1倍(也就是从线段的起点取到终点),n-1条线段又得到了n-1个新控制点
- 循环1、2步骤,直到剩下一个线段,这条线段上不断取的点构成的曲线就是贝塞尔曲线。
有兴趣的话可以去读我的原文,链接在这里https://blog.csdn.net/weixin_42156097/article/details/107919429
B样条曲线
贝塞尔曲线,会有一个公式,曲线上的点 = 求和(权重系数*每个控制点的坐标)
站在很高的角度来说,B样条曲线上的点是与贝塞尔曲线一直的,也是曲线上的点 = 求和(权重系数*每个控制点的坐标)
每个控制点的坐标,乘以它们各自的权重系数,然后综合所有控制点,就可以得到曲线上的插值点了
只不过,B样条曲线每个点的权重计算方式与贝塞尔曲线不同,贝塞尔曲线是一个固定的计算公式, 而B样条曲线每个控制点的权重系数是一个n次多项式,是由各自的0次项迭代而来的。这里面会涉及到迭代公式。这里我不写了,因为哪里都能看到。
这里列举两篇写的比较好的文章吧,可以去参考
https://zhuanlan.zhihu.com/p/144042470
https://zhuanlan.zhihu.com/p/50626506
优势
B样条曲线的生成,可以尽可能的与每个点解耦。
我这句话很好理解。贝塞尔曲线的生成过程可以看出,n个控制点只要有一个发生了变化,整个曲线的形状都会影响。
而B样条曲线的优势就在于此,某个控制点发生变化,即使是十分大的变化,整条曲线发生改变的也是控制点周围的一小块
而这一特性,我认为十分适合针对一些数据的平滑操作,B样条曲线可以使数据在渐进式变化时,已得到数据的曲线,不会受新数据点的影响。
代码
以下的代码还是很好理解的。
#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)
int g_d = 3;
int g_n = 5;
int g_u[8] = {0, 1, 2, 3, 4, 5, 6, 7};
// int g_u[8] = {0, 0, 0, 1, 2, 3, 3, 3};
// 当现在曲线在u位置时,第i个控制点的权重系数多项式中,d次项的表示
float get_B_u_d(int n, int d, float u) {
if (d == 1) {
if ( u >= g_u[n] && u <= g_u[n+1]) {
return 1.0;
} else {
return 0.0;
}
} else {
float value1, value2;
if (g_u[n + d-1] - g_u[n] == 0) {
value1 = u - g_u[n];
} else {
value1 = (u - g_u[n]) / (g_u[n + d-1] - g_u[n]);
}
if (g_u[n + d] - g_u[n + 1] == 0) {
value2 = g_u[n + d] - u;
} else {
value2 = (g_u[n + d] - u) / (g_u[n + d] - g_u[n + 1]);
}
return value1 * get_B_u_d(n, d - 1, u) + value2 * get_B_u_d(n + 1, d-1, u);
}
}
int main (int argc, const char** argv) {
std::vector<cv::Point2d> input; // 输入的点
int matrix_size = 800; // 空白图大小
cv::Mat image = cv::Mat(matrix_size, matrix_size, CV_8UC3, cv::Scalar(255,255,255));
srand((int)time(0)); // 提供随机种子,要加这句话不然还会生成不变的数
for (int i = 0 ; i < g_n; i++) { // 随机生成输入点
int x= random(0, matrix_size);
int y = random(0, matrix_size);
cv::Point2d p(x, y);
input.push_back(p);
}
// input = {cv::Point2d(100,50),
// cv::Point2d(100,300), cv::Point2d(350,100), cv::Point2d(380, 200), cv::Point2d(400, 400)};
// cv::circle(image, cv::Point(matrix_size/2+input[i].x, matrix_size/2-input[i].y), 3, cv::Scalar(0,255,0), -1);
for (int i = 0; i < input.size(); i++) {
cv::circle(image, cv::Point(input[i].x, input[i].y), 3, cv::Scalar(0,255,0), -1);
}
std::vector<cv::Point2d> output;
for (float u = g_u[0]; u < g_u[g_n + g_d - 1]; u = u + 0.01) {
std::cout<<u<<std::endl;
float output_point_x = 0;
float output_point_y = 0;
for (int j = 0; j < g_n; j++) {
float k = get_B_u_d(j, g_d, u);
output_point_x = output_point_x + k * input[j].x;
output_point_y = output_point_y + k * input[j].y;
}
output.push_back(cv::Point2d(output_point_x, output_point_y));
cv::circle(image, cv::Point(output[output.size()-1].x,
output[output.size()-1].y), 2, cv::Scalar(0,0,255), -1);
cv::imshow("Bezier curve", image);
cv::waitKey(50);
}
return 0;
}
代码中需要说明的有以下几点
- 贝塞尔曲线的插值过程是系数从0到1的,而B样条曲线的插值过程是从最小节点值取到最大节点值的。
- 代码中有两种节点值的设置,一种是顺序列表
{0, 1, 2, 3, 4, 5, 6, 7}
,另外一种是Clamped列表{0, 0, 0, 1, 2, 3, 3, 3}
- 代码中也有两种控制点的输入方式,一种是随机生成,注释掉的是一组固定点。同时B样条曲线中的点是循环来一点点画出来的,这些都仅仅是为了帮助理解
如下是不同节点值设置的截图,忽略图片名字,哈哈还写的是贝塞尔我忘改了
顺序节点表如下
Clamped节点表如下
对于未来使用,我把它写成了接口,以下代码就可以成为插件式使用了。这是我一直追求的目标。
int g_n;
int g_d;
std::vector<int> g_u;
// 当现在曲线在u位置时,第i个控制点的权重系数多项式中,d次项的表示
float get_B_u_d(int n, int d, float u) {
if (d == 1) {
if ( u >= g_u[n] && u <= g_u[n+1]) {
return 1.0;
} else {
return 0.0;
}
} else {
float value1, value2;
if (g_u[n + d-1] - g_u[n] == 0) {
value1 = u - g_u[n];
} else {
value1 = (u - g_u[n]) / (g_u[n + d-1] - g_u[n]);
}
if (g_u[n + d] - g_u[n + 1] == 0) {
value2 = g_u[n + d] - u;
} else {
value2 = (g_u[n + d] - u) / (g_u[n + d] - g_u[n + 1]);
}
return value1 * get_B_u_d(n, d - 1, u) + value2 * get_B_u_d(n + 1, d-1, u);
}
}
std::vector<cv::Point2d> getBSpline(std::vector<cv::Point2d> input) {
g_d = 3;
g_n = input.size();
g_u.clear();
for (int i = 0; i < g_d + g_n; i++) {
g_u.push_back(i);
}
std::vector<cv::Point2d> output;
output.clear();
int o = 0;
for (float u = g_u[5] + 0.01; u < g_u[g_n + g_d - 4] ; u = u + 0.01)
{
float output_point_x = 0;
float output_point_y = 0;
for (int j = 0; j < g_n; j++)
{
float k = get_B_u_d(j, g_d, u);
output_point_x = output_point_x + k * input[j].x;
output_point_y = output_point_y + k * input[j].y;
}
output.push_back(cv::Point2d(output_point_x, output_point_y));
}
return output;
}
总结
给定控制点,求出B样条曲线的简单理解,及代码
评论(0)
您还未登录,请登录后发表或查看评论