结论
最近在研究3次样条曲线。曲线由四个控制点控制,依次记为P0,P1,P2,P3。在绘制Hermite曲线的时候,发现如果令P0处的导数为3倍P1-P0,P3处的导数为3倍P3-P2,则P0,P1,P2,P3构成的Hermite曲线与P0,P1,P2,P3构成的Bezier曲线完全相同。
下面详细分析说明这一点
分析
通用的三次Hermite曲线的参数表达式为
P
(
u
)
=
P
0
(
2
u
3
−
3
u
2
+
1
)
+
P
3
(
−
2
u
3
+
3
u
2
)
+
P
0
′
(
u
3
−
2
u
2
+
u
)
+
P
3
′
(
u
3
−
u
2
)
P(u)=P_0(2u^3-3u^2+1)+P_3(-2u^3+3u^2)+P'_0(u^3-2u^2+u)+P'_3(u^3-u^2)
P(u)=P0(2u3−3u2+1)+P3(−2u3+3u2)+P0′(u3−2u2+u)+P3′(u3−u2)
刚刚我们说明了,如果
P
0
′
=
3
(
P
1
−
P
0
)
P'_0=3(P_1-P_0)
P0′=3(P1−P0),
P
3
′
=
3
(
P
3
−
P
2
)
P'_3=3(P_3-P_2)
P3′=3(P3−P2),那么上式又可以化为
P
(
u
)
=
(
u
3
−
3
u
2
+
1
)
P
0
+
(
−
2
u
3
+
3
u
2
)
P
3
+
3
(
P
1
−
P
0
)
(
u
3
−
2
u
2
+
u
)
+
3
(
P
3
−
P
2
)
(
u
3
−
u
2
)
P(u)=(u^3-3u^2+1)P_0+(-2u^3+3u^2)P_3+3(P_1-P_0)(u^3-2u^2+u)+3(P_3-P_2)(u^3-u^2)
P(u)=(u3−3u2+1)P0+(−2u3+3u2)P3+3(P1−P0)(u3−2u2+u)+3(P3−P2)(u3−u2) 整理一下可以得到
P
(
u
)
=
(
1
−
3
u
+
3
u
2
−
u
3
)
P
0
+
(
3
u
−
6
u
2
+
3
u
3
)
P
1
+
(
3
u
2
−
3
u
3
)
P
2
+
u
3
P
3
P(u)=(1-3u+3u^2-u^3)P_0+ (3u-6u^2+3u^3)P_1+(3u^2-3u^3)P_2+u^3P_3
P(u)=(1−3u+3u2−u3)P0+(3u−6u2+3u3)P1+(3u2−3u3)P2+u3P3
P
(
u
)
=
(
1
−
u
)
3
P
0
+
3
u
(
1
−
u
)
2
P
1
+
3
u
2
(
1
−
u
)
P
2
+
u
3
P
3
P(u)=(1-u)^3P_0+3u(1-u)^2P_1+3u^2(1-u)P_2+u^3P_3
P(u)=(1−u)3P0+3u(1−u)2P1+3u2(1−u)P2+u3P3
这正是三次Bezier曲线的公式
Demo演示
下面用OpenCV分别画Hermite曲线和Bezier曲线,两条曲线完全重合。
Demo演示代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std
;
using namespace cv
;
string mainwindow
= "构造线";
Point2d control_points
[4];
int control_points_index
= 0;
int height
= 600;
int width
= 800;
Mat screen_mat
;
const double CHOOSE_THRESHOLD
= 20;
bool choose_flag
= false;
int choose_point
;
const int LINE_N
= 1000;
void on_mouse_handler(int event
, int x
, int y
, int flag
, void* param
)
{
double dist
;
if (event
== EVENT_LBUTTONDOWN
)
{
if (control_points_index
< 4)
{
control_points
[control_points_index
].x
= x
;
control_points
[control_points_index
].y
= y
;
control_points_index
++;
}
else
{
for (int i
= 0; i
< 4; i
++)
{
dist
= (control_points
[i
].x
- x
) * (control_points
[i
].x
- x
) + (control_points
[i
].y
- y
) * (control_points
[i
].y
- y
);
if (dist
< CHOOSE_THRESHOLD
* CHOOSE_THRESHOLD
)
{
choose_flag
= true;
choose_point
= i
;
return;
}
}
}
}
else if (event
== EVENT_LBUTTONUP
)
{
choose_flag
= false;
}
else if (event
== EVENT_MOUSEMOVE
)
{
if (choose_flag
)
{
control_points
[choose_point
].x
= x
;
control_points
[choose_point
].y
= y
;
}
}
}
void imag_update()
{
screen_mat
= Mat
::zeros(height
, width
, CV_8UC4
);
for (int i
= 0; i
< control_points_index
; i
++)
{
drawMarker(screen_mat
, control_points
[i
], Scalar(0, 0, 255, 255), MARKER_STAR
, 5);
}
if (control_points_index
== 4)
{
Point2d v0
, v1
, v2
, v3
;
Point2d bv0
, bv1
, bv2
, bv3
;
v0
= control_points
[0];
v1
= control_points
[3];
v2
= control_points
[1] - control_points
[0];
v3
= control_points
[3] - control_points
[2];
v2
= 3 * v2
;
v3
= 3 * v3
;
bv0
= control_points
[0];
bv1
= control_points
[1];
bv2
= control_points
[2];
bv3
= control_points
[3];
Point2d p0
= control_points
[0];
Point2d bp0
= control_points
[0];
Point2d p1
;
Point2d bp1
;
double u
;
double c0
, c1
, c2
, c3
;
double bc0
, bc1
, bc2
, bc3
;
for (int i
= 1; i
< LINE_N
; i
++)
{
u
= i
* 1.0 / LINE_N
;
c0
= 2 * u
* u
* u
- 3 * u
* u
+ 1;
c1
= -2 * u
* u
* u
+ 3 * u
* u
;
c2
= u
* u
* u
- 2 * u
* u
+ u
;
c3
= u
* u
* u
- u
* u
;
bc0
= (1 - u
) * (1 - u
) * (1 - u
);
bc1
= 3 * u
* (1 - u
) * (1 - u
);
bc2
= 3 * u
* u
* (1 - u
);
bc3
= u
* u
* u
;
p1
.x
= v0
.x
* c0
+ v1
.x
* c1
+ v2
.x
* c2
+ v3
.x
* c3
;
p1
.y
= v0
.y
* c0
+ v1
.y
* c1
+ v2
.y
* c2
+ v3
.y
* c3
;
bp1
.x
= bv0
.x
* bc0
+ bv1
.x
* bc1
+ bv2
.x
* bc2
+ bv3
.x
* bc3
;
bp1
.y
= bv0
.y
* bc0
+ bv1
.y
* bc1
+ bv2
.y
* bc2
+ bv3
.y
* bc3
;
line(screen_mat
, p0
, p1
, Scalar(0, 0, 255, 128), 1, LINE_AA
);
line(screen_mat
, bp0
, bp1
, Scalar(0, 255, 0, 128), 1, LINE_AA
);
p0
= p1
;
bp0
= bp1
;
}
}
imshow(mainwindow
, screen_mat
);
}
void window_initialize()
{
namedWindow(mainwindow
, WINDOW_AUTOSIZE
);
setMouseCallback(mainwindow
, on_mouse_handler
);
}
void main_loop()
{
bool finished
= false;
int pressed_key
= 0;
while (!finished
)
{
imag_update();
pressed_key
= waitKey(10);
if (pressed_key
== 27) finished
= true;
}
}
int main()
{
window_initialize();
main_loop();
return 0;
}