1. 程式人生 > >n階貝塞爾曲線的理解以及c++程式設計實現

n階貝塞爾曲線的理解以及c++程式設計實現

貝塞爾曲線的原理(BezierCruve)

檢視原理戳我
這裡就不介紹推導過程了,值得注意的是文章中的線段都是向量,然後線段都可以被拆分成兩個座標的差來表示 A B = B A

AB=B-A 一開始看有點矇蔽,代入這個等式後,之後所有的推導都可以理解。這裡就直接介紹一下結論 P i k ( t
) = { P i
( t )
k=0
( 1 t ) P i k 1 ( t ) + t P i + 1 k 1 ( t ) , k=1,2,3 n;i=0,1,2 n-k
P_i^k(t)= \begin{cases} P_i(t) & \text {k=0} \\ (1-t)P_i^{k-1}(t)+tP_{i+1}^{k-1}(t), & \text{k=1,2,3…n;i=0,1,2…n-k} \end{cases}
原文中的介紹沒有帶t,一開始以為 P i k P_i^k 是常量,覺得十分詭異,後來重新看了下推導過程,其實都是變數,這裡補上自變數t,並且這裡的 k k 不是指k次方的意思,是指第k階,可以理解為一個區分開來的不同階數的上標。

貝塞爾曲線的理解

首先從結果的公式上來看

  1. 一條貝塞爾至少要有兩個控制點(此時的貝塞爾曲線為一條直線)

控制點: 當k等於0時,對應的 P i k ( t ) P_i^k(t) ,即輸入點

  1. 這是一條迭代公式,每次迭代都會少掉一個“點”。

“點”: P i + 1 P_{i+1} ,可以看到每次的每次新迭代生成的 P i k ( t ) P_i^k(t) 都由 ( 1 t ) P i k 1 ( t ) + t P i + 1 k 1 ( t ) (1-t)P_i^{k-1}(t)+tP_{i+1}^{k-1}(t) 生成,所以第k次迭代的時候,只剩下n-k個點,即 i < = n k i<=n-k

  1. 我們最終要得到的貝塞爾曲線是 P 0 n ( t ) P_0^n(t) ,所以迭代的終止條件便是i得最大值只能為1(因為從0開始計數),即 P 0 n ( t ) = ( 1 t ) P 0 n 1 ( t ) + t P 1 n 1 ( t ) P_0^n(t)=(1-t)P_0^{n-1}(t)+tP_{1}^{n-1}(t)
  2. 貝塞爾曲線必然經過第一個控制點和最後一個控制點。

貝塞爾曲線的程式設計實現

  1. 由於貝塞爾曲線本身的數學表示式便是一條遞迴式,所以決定採用遞迴的方式來實現。
  2. 由於都是對點進行四則運算,直接採用opencv的資料結構,都已經過載好了,順便可以用opencv來顯示結果。
  3. 當然如果不需要顯示和不嫌麻煩的話,可以自己寫Point2f這個結構,並且過載乘法運算子,也是可以實現的,那樣就不用呼叫opencv了。

#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
using std::cout;
using std::endl;
using std::vector;
vector<Point2f> bezierCurve(vector<Point2f> src);
int main(int argc, char const* argv[])
{
   while (1) {
       vector<Point2f> path;
       CvRNG rng;
       rng = cvRNG(cvGetTickCount());
       for (int i = 1; i < 6; i++)
           path.push_back(Point2f(i * 800 / 6.0, cvRandInt(&rng) % 800));

       Mat img(800, 800, CV_8UC3);
       img = 0;
       for (int i = 0; i < path.size(); i++)
           circle(img, path[i], 3, Scalar(0, 0, 255), 3); //BGR
       vector<Point2f> bezierPath = bezierCurve(path);
       for (int i = 0; i < bezierPath.size(); i++) {
           //circle(img, bezierPath[i], 3, Scalar(0, 255, 255), 3); //BGR
           img.at<cv::Vec3b>(cvRound(bezierPath[i].y), cvRound(bezierPath[i].x)) = { 0, 255, 255 };
       }
       imshow("black", img);
       if (waitKey(0) == 'q')
           break;
   }
   return 0;
}
vector<Point2f> bezierCurve(vector<Point2f> src)
{
   if (src.size() < 1)//這種情況是不允許出現的,出現只能證明程式出錯了
       return src;
   const float step = 0.01;//採集100個點,即1.0/step
   vector<Point2f> res;
   if (src.size() == 1) {//遞迴結束條件,k=0
       for (float t = 0; t < 1; t += step)
           res.push_back(src[0]);//為了和其他情況保持一致,生成了1.0/step個一樣的點
       return res;
   }
   vector<Point2f> src1;
   vector<Point2f> src2;
   src1.assign(src.begin(), src.end() - 1);//分成兩部分,即Pi和Pi+1
   src2.assign(src.begin() + 1, src.end());
   for (int i = 0; i < src1.size(); i++)
       cout << src1[i] << endl;
   cout << endl;
   for (int i = 0; i < src2.size(); i++)
       cout << src2[i] << endl;
   cout << endl;
   vector<Point2f> pln1 = bezierCurve(src1);
   vector<Point2f> pln2 = bezierCurve(src2);
   for (float t = 0; t < 1; t += step) {
       Point2f temp;
       temp = (1.0 - t) * pln1[cvRound(1.0 / step * t)] + t * pln2[cvRound(1.0 / step * t)];
       res.push_back(temp);
   }
   return res;
}

實現效果

貝塞爾曲線