贝塞尔曲线 (Bézier curve) 理论及绘制方法

演示地址

数学中公式是很重要的,我们先看公式。

公式

线性公式

给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:

B(t)=P0+(P1-P0)t=(1-t)P0+tP1, t ∈[0, 1]

且其等同于线性插值。

二次方公式

二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
这里写图片描述

TrueType字型就运用了以贝兹样条组成的二次贝兹曲线。

三次方公式

P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
曲线的参数形式为:
这里写图片描述

现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓。

一般参数公式

阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:
这里写图片描述

如上公式可如下递归表达: 用表示由点P0、P1、…、Pn所决定的贝兹曲线。
用平常话来说,阶的贝兹曲线,即双阶贝兹曲线之间的插值。

公式说明

1.开始于P0并结束于Pn的曲线,即所谓的端点插值法属性。
2.曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝塞尔曲线是直线的充分必要条件是控制点共线。
3.曲线的起始点(结束点)相切于贝塞尔多边形的第一节(最后一节)。
4.一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝塞尔曲线。
5.一些看似简单的曲线(如圆)无法以贝塞尔曲线精确的描述,或分段成贝塞尔曲线(虽然当每个内部控制点对单位圆上的外部控制点水平或垂直的的距离为时,分成四段的贝兹曲线,可以小于千分之一的最大半径误差近似于圆)。
6.位于固定偏移量的曲线(来自给定的贝塞尔曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝兹曲线精确的形成(某些琐屑实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值。

转换矩阵

好,我们一般使用的贝塞尔曲线应该是三阶的。
如上公式,这个公式可以写成矩阵(线性代数自己看,图形编程中常用)的形式。

这里写图片描述

其中,t 等同于公式3中的t,是一个自变量,而 P 代表一个点,0-3代表4个点,其上的 x, y 分别代表 坐标。所以给定四个点的坐标,t 变换的时候,会得到无数个点坐标,由此点坐标绘制,即成 贝塞尔曲线。

js代码参考:

obite-bezier.js

var matrix = function(config) {
    this._init(config)
};
matrix.prototype = {
    _init: function(config) {
        if (config && config.data)
            this.data = config.data;
    },
    /**
     * 矩阵相乘
     * @param {matrix} m 被乘的矩阵
     */
    mul: function(m) {
        var r = [],
            s = this.data,
            m = m.data,
            p = s[0].length //每次运算相加的次数
        if (m.length != s[0].length) {
            T.trace("矩阵不能相乘")
        }
        for (var i = 0; i < s.length; i++) {
            r[ i ] = []
            for (var n = 0; n < m[0].length; n++) {
                r[ i ][n] = 0;
                for (var l = 0; l < p; l++) {
                    r[ i ][n] += s[ i ][l] * m[l][n];
                }
            }
        }
        this.data = r;
        return this;
    },
    set: function(data) {
        this.data = data;
    },
    get: function() {
        return this.data
    },
    toString: function() {
        return this.data.to_s()
    }
};


var cubicBezier = function(config) {
    this._init(config)
};
cubicBezier.prototype = {
    _init: function(config) {
        var p = this.points = config.points;
        this.m1 = new obite.matrix();
        this.m2 = new obite.matrix({
            data: [
                [1, 0, 0, 0],
                [-3, 3, 0, 0],
                [3, -6, 3, 0],
                [-1, 3, -3, 1]
            ]
        });
        this.m3 = new matrix({
            data: [
                p.p0.toArray(),
                p.p1.toArray(),
                p.p2.toArray(),
                p.p3.toArray()
            ]
        });

        this.m = null
    },
    /**
     * 获取某个时间点计算出来的坐标值,时间线不由此类控制
     */
    get: function(t) {
        /*this.m1.set([
            [1, t * t, t * t * t, t * t * t * t]
        ]);*/
        this.m1.set([
            [1, t, t * t, t * t * t]
        ]);

        this.m = this.m1.mul(this.m2).mul(this.m3)

        return new obite.vector(this.m.get()[0][0], this.m.get()[0][1]);
    }
};

var vector = function(x, y) {
    this._init(x, y);
};
vector.prototype = {
    _init: function(x, y) {
        this.x = x;
        this.y = y;
    },
    toArray: function() {
        return [this.x, this.y];
    }
};


function drawPoint2d(canvas, vector) {
    if (typeof canvas === 'string') {
        canvas = document.getElementById(canvas);
    }
    var canvasContext = canvas.getContext('2d');
    //在点P处画一个点
    with(canvasContext) {
        beginPath();
        lineWidth = 0.5;
        moveTo(vector.x, vector.y);
        lineTo(vector.x + 1, vector.y + 1);
        strokeStyle = "green";
        stroke();
    }
}


var obite = function() {};
obite.extend = function(json) {
    for (var i in json) {
        obite[ i ] = json[ i ];
    }
};
obite.extend({
    matrix: matrix,
    cubicBezier: cubicBezier,
    vector: vector,
    drawPoint2d: drawPoint2d
});

window.o = obite;

points.js

var points = {
    p0: new o.vector(100, 10),
    p1: new o.vector(1000, 300),
    p2: new o.vector(500, 33),
    p3: new o.vector(5, 400)
};
var cb = new o.cubicBezier({
    points: points
});
var p;
var t = 0;
var inter = setInterval(function() {
    t += 0.001;
    if (t > 1) {
        end()
    }
    p = cb.get(t)
        //根据p的坐标画点
    o.drawPoint2d(canvas,p);
}, 10);

function end() {
    clearInterval(inter);
}

bezier.html

<!doctype html>
<html lang="zh">

<head>
    <script type="text/javascript" src="obite-bezier.js"></script>
</head>

<body>
    <canvas id="c" height="600" width="800"></canvas>
</body>
<script type="text/javascript">
var canvas = document.getElementById("c");


</script>
<script type="text/javascript" src="points.js"></script>

</html>

参考:1. 百度百科:http://baike.baidu.com/view/60154.htm
2. http://bbs.9ria.com/thread-71296-1-1.html

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在OpenCV中,可以使用 `cv::polylines` 函数绘制贝塞尔曲线。具体步骤如下: 1. 定义控制点。贝塞尔曲线需要通过一系列控制点来定义其形状。在OpenCV中,可以使用 `cv::Point` 类型的向量来存储控制点。 2. 使用 `cv::approxPolyDP` 函数来拟合贝塞尔曲线。该函数可以根据一组点的形状,自动确定最适合的贝塞尔曲线。 3. 使用 `cv::polylines` 函数绘制贝塞尔曲线。该函数接受一个包含所有点的向量,并将其连成平滑的曲线。 下面是一个简单的代码示例: ```c++ #include <opencv2/opencv.hpp> using namespace cv; using namespace std; int main() { // 定义控制点 vector<Point> controlPoints = {Point(50, 50), Point(100, 100), Point(150, 50)}; // 拟合贝塞尔曲线 vector<Point> curvePoints; approxPolyDP(controlPoints, curvePoints, 1.0, true); // 绘制贝塞尔曲线 Mat image(200, 200, CV_8UC3, Scalar(255, 255, 255)); polylines(image, curvePoints, false, Scalar(0, 0, 255), 2, LINE_AA); imshow("Bezier Curve", image); waitKey(0); return 0; } ``` 在上面的代码中,我们定义了三个控制点,然后使用 `approxPolyDP` 函数拟合贝塞尔曲线。最后,使用 `polylines` 函数将拟合后的曲线绘制到图像上,并显示出来。 注意,`approxPolyDP` 函数的第三个参数表示拟合精度,可以根据需要进行调整。此外,`polylines` 函数的第四个参数表示曲线颜色,第五个参数表示曲线粗细。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值