Blogger Template by Blogcrowds.

贝塞尔曲线

背景:
相信很多同学都知道“贝塞尔曲线”这个词,我们在很多地方都能经常看到。但是,可能并不是每位同学都清楚地知道,到底什么是“贝塞尔曲线”,又是什么特点让它有这么高的知名度。
贝塞尔曲线的数学基础是早在 1912 年就广为人知的伯恩斯坦多项式。但直到 1959 年,当时就职于雪铁龙的法国数学家 Paul de Casteljau 才开始对它进行图形化应用的尝试,并提出了一种数值稳定的 de Casteljau 算法。然而贝塞尔曲线的得名,却是由于 1962 年另一位就职于雷诺的法国工程师 Pierre Bézier 的广泛宣传。他使用这种只需要很少的控制点就能够生成复杂平滑曲线的方法,来辅助汽车车体的工业设计。正是因为控制简便却具有极强的描述能力,贝塞尔曲线在工业设计领域迅速得到了广泛的应用。不仅如此,在计算机图形学领域,尤其是矢量图形学,贝塞尔曲线也占有重要的地位。今天我们最常见的一些矢量绘图软件,如 Flash、Illustrator、CorelDraw 等,无一例外都提供了绘制贝塞尔曲线的功能。甚至像 Photoshop 这样的位图编辑软件,也把贝塞尔曲线作为仅有的矢量绘制工具(钢笔工具)包含其中。
形式:
1.线性贝塞尔曲线:

image
P0P1为给定点,t的范围为0-1,则线性贝塞尔曲线实际上就是一条直线
2.二次贝塞尔曲线:
二次贝塞尔曲线是给出三个点,得到的贝塞尔曲线,贝塞尔曲线的函数为:
image

从以上t的变化可以看出,二次贝塞尔曲线就是一条抛物线
3.三次贝塞尔曲线:
image
三次贝塞尔曲线的过程为:曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1P2;这两个点只是在那里提供方向资讯。P0P1之间的间距,决定了曲线在转而趋进P2之前,走向P1方向的“长度有多长”。
4.N次贝塞尔曲线
由以上公式可以推出,对于一般化的贝塞尔曲线,有如下形式:
image
贝塞尔曲线的性质:
贝塞尔曲线的构造很简单,但是却有一些十分有趣的性质,对于一个贝塞尔曲线来说,如:贝塞尔曲线是光滑曲线。贝塞尔曲线还有一个十分重要的性质:起始线和第二个点决定了点的起始方向,倒数第二个点和最后一个点确定了曲线的出射方向,使用这一特性在机器人足球,避障等算法中都有大量应用:
下图可以很好的帮助大家理解贝塞尔曲线(图片来自wiki:https://zh.wikipedia.org/wiki/%E8%B2%9D%E8%8C%B2%E6%9B%B2%E7%B7%9A#.E8.A1.93.E8.AA.9E
240px-Bézier_3_big240px-Bézier_4_big240px-BezierCurve
贝塞尔曲线的Opencv实现:
//opencv贝塞尔曲线工具
const int WW_MAX_MARK_COUNT = 40; //最大40个控制点
static int mark_count = 4;
static int conner_pt_index = -1;
static CvPoint3D32f Control_pts[WW_MAX_MARK_COUNT];
static IplImage *image = NULL; //原始图像
static bool is_showControlLines = true;
class BezierCurve
{
private:
    // 两个向量相加,p=p+q
    static CvPoint3D32f BezierCurve_PointAdd(CvPoint3D32f p, CvPoint3D32f q) {
        p.x += q.x;        p.y += q.y;        p.z += q.z;
        return p;
    }
    // 向量和标量相乘p=c*p
    static CvPoint3D32f BezierCurve_PointTimes(float c, CvPoint3D32f p) {
        p.x *= c;    p.y *= c;    p.z *= c;
        return p;
    }
    // 计算贝塞尔方程的值
    // 变量u的范围在0-1之间
    //P1*t^3 + P2*3*t^2*(1-t) + P3*3*t*(1-t)^2 + P4*(1-t)^3 = Pnew
    static CvPoint3D32f BezierCurve_Bernstein(float u, CvPoint3D32f *p) {
        CvPoint3D32f    a, b, c, d, r;
        a = BezierCurve_PointTimes(pow(u, 3), p[0]);
        b = BezierCurve_PointTimes(3 * pow(u, 2)*(1 - u), p[1]);
        c = BezierCurve_PointTimes(3 * u*pow((1 - u), 2), p[2]);
        d = BezierCurve_PointTimes(pow((1 - u), 3), p[3]);
        r = BezierCurve_PointAdd(BezierCurve_PointAdd(a, b), BezierCurve_PointAdd(c, d));
        return r;
    }
    //画控制线
    static void BezierCurve_DrawControlLine(CvPoint3D32f *p) {
        CvPoint pc[4];
        for (int i = 0; i<4; i++)
        {
            pc[i].x = (int)p[i].x;
            pc[i].y = (int)p[i].y;
        }
        cvLine(image, pc[0], pc[1], CV_RGB(0, 0, 255), 1, CV_AA, 0);
        cvLine(image, pc[2], pc[3], CV_RGB(0, 0, 255), 1, CV_AA, 0);
    }
    //得到最近Control_pts的index
    static int BezierCurve_GetNearPointIndex(CvPoint mouse_pt)
    {
        CvPoint pt;
        for (int i = 0; i<mark_count; i++)
        {
            pt.x = mouse_pt.x - (int)Control_pts[i].x;
            pt.y = mouse_pt.y - (int)Control_pts[i].y;
            float distance = sqrt((float)(pt.x*pt.x + pt.y*pt.y));
            if (distance<10) return i;
        }
        return -1;
    }
    static void BezierCurve_On_mouse(int event, int x, int y, int flags, void *param)
    {
        if (event == CV_EVENT_LBUTTONDOWN)
        {
            CvPoint pt = cvPoint(x, y);
            //cout<<x<<","<<y<<endl;
            if (conner_pt_index >-1)
                conner_pt_index = -1;
            else
            {
                conner_pt_index = BezierCurve_GetNearPointIndex(pt);
                //添加新的控制点
                if (conner_pt_index == -1)
                {
                    if (mark_count <= (WW_MAX_MARK_COUNT - 1))
                    {
                        Control_pts[mark_count].x = (float)pt.x;
                        Control_pts[mark_count].y = (float)pt.y;
                        Control_pts[mark_count].z = 0;
                        mark_count++;
                    }
                }
            }
        }
        else if (event == CV_EVENT_MOUSEMOVE) //修改控制点坐标
        {
            if (conner_pt_index >-1)
            {
                Control_pts[conner_pt_index].x = (float)x;
                Control_pts[conner_pt_index].y = (float)y;
            }
        }
    };
public:
    //绘制贝塞尔曲线
    void BezierCurve_BezierDraw();
};

void BezierCurve::BezierCurve_BezierDraw()
{
    CvSize image_sz = cvSize(1000, 1000);
    image = cvCreateImage(image_sz, 8, 3);
    cvNamedWindow("Win", 0);
    cvSetMouseCallback("Win", &BezierCurve::BezierCurve_On_mouse, 0);
    cvResizeWindow("Win", 500, 500);
    printf("==============   Bezier curve DEMO  ==============\n");
    printf(" \n");
    printf("1.use mouse to click control point (red) to select a control point\n");
    printf("2.use mouse to modify control point");
    printf("3.click mouse on somewhere to add a control point,add three points for add a new curve\n");
    printf("4.use 'W','S' to add precision or reduce precision.\n");
    printf("5.press 'Z' to show control points.\n");
    printf("===press ESC to exit===\n");
    //初始化四个控制点
    Control_pts[0].x = 200;
    Control_pts[0].y = 200;
    Control_pts[0].z = 0;
    Control_pts[1].x = 300;
    Control_pts[1].y = 500;
    Control_pts[1].z = 0;
    Control_pts[2].x = 400;
    Control_pts[2].y = 560;
    Control_pts[2].z = 0;
    Control_pts[3].x = 500;
    Control_pts[3].y = 100;
    Control_pts[3].z = 0;
    int divs = 50; //控制精细度
    for (;;)
    {
        CvPoint pt_now, pt_pre;
        cvZero(image);

        //绘制控制点
        if (is_showControlLines)
        {
            for (int i = 0; i<mark_count; i++)
            {
                CvPoint ptc;
                ptc.x = (int)Control_pts[i].x;
                ptc.y = (int)Control_pts[i].y;
                cvCircle(image, ptc, 4, CV_RGB(255, 0, 0), 1, CV_AA, 0);
            }
        }
        //绘制Bezier曲线
        CvPoint3D32f *pControls = Control_pts;
        for (int j = 0; j<mark_count - 3; j += 3)
        {
            for (int i = 0; i <= divs; i++)
            {
                float u = (float)i / divs;
                CvPoint3D32f newPt = BezierCurve_Bernstein(u, pControls);
                pt_now.x = (int)newPt.x;
                pt_now.y = (int)newPt.y;
                if (i>0)    cvLine(image, pt_now, pt_pre, CV_RGB(230, 255, 0), 2, CV_AA, 0);
                pt_pre = pt_now;
            }
            //画控制线
            if (is_showControlLines)BezierCurve_DrawControlLine(pControls);
            pControls += 3;
        }
        cvShowImage("Win", image);
        int keyCode = cvWaitKey(20);
        if (keyCode == 27) break;
        if (keyCode == 'w' || keyCode == 'W') divs += 2;
        if (keyCode == 's' || keyCode == 'S') divs -= 2;
        if (keyCode == 'z' || keyCode == 'Z') is_showControlLines = is_showControlLines ^ 1;
        //cout<<"precision : "<<divs<<endl;
    }
    return ;
}
以上为贝塞尔曲线的Opencv实现,实现了根据输入点绘制贝塞尔曲线和适时调整贝塞尔曲线的功能,实现结果如图:
image
然而实际上,对于应用来说,平面贝塞尔曲线的应用并不广泛,在各种设计,工业应用中使用得比较多的是贝塞尔曲面,通过贝塞尔曲面模拟光滑曲面,进行曲面设计有很大的用图

0 Comments:

Post a Comment



较新的博文 较早的博文 主页