作业描述
Bézier 曲线是一种用于计算机图形学的参数曲线。在本次作业中,你需要实现 de Casteljau 算法来绘制由 4 个控制点表示的 Bézier 曲线 (当你正确实现该算法时,你可以支持绘制由更多点来控制的 Bézier 曲线)。你需要修改的函数在提供的 main.cpp 文件中。
• bezier:该函数实现绘制 Bézier 曲线的功能。它使用一个控制点序列和一个OpenCV::Mat对象作为输入,没有返回值。它会使 t 在 0 到 1 的范围内进行迭代,并在每次迭代中使 t 增加一个微小值。对于每个需要计算的 t,将调用另一个函数 recursive_bezier,然后该函数将返回在 Bézier 曲线上 t处的点。最后,将返回的点绘制在 OpenCV ::Mat 对象上。
• recursive_bezier:该函数使用一个控制点序列和一个浮点数 t 作为输入,实现 de Casteljau 算法来返回 Bézier 曲线上对应点的坐标
De Casteljau 算法说明如下:
- 考虑一个 p0, p1, … pn 为控制点序列的 Bézier 曲线。首先,将相邻的点连接起来以形成线段。
- 用 t : (1 − t) 的比例细分每个线段,并找到该分割点。
- 得到的分割点作为新的控制点序列,新序列的长度会减少一。
- 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤 1。使用 [0,1] 中的多个不同的 t 来执行上述算法,你就能得到相应的 Bézier 曲线
解
bezier
由作业描述中可得,整个De Casteljau算法就是由n个点算出n-1个点,然后不断递归到只剩一个点时即为所求,所以为方便,这里我使用了两个容器points1, points2来记录每次求得的点,用flag来区分每次应该用哪个容器来算。
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window)
{
// TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's
// recursive Bezier algorithm.
int size = control_points.size();
std::vector<cv::Point2f> points1 = control_points, points2;
bool flag = true, bflag;
for (double t = 0.0; t <= 1.0; t += 0.001)
{
bflag = false;
while(!bflag){
if(flag)
bflag = recursive_bezier(control_points, points1, points2, t, window);
else
bflag = recursive_bezier(control_points, points2, points1, t, window);
flag = !flag;
}
}
}
recursive_bezier
这里我对 recursive_bezier函数做了一点修改,增加了一些参数以方便将点画在屏幕上,同时也将返回类型设置成了bool,方便在bezier中进行结束的判断(若返回true,则证明只剩一个点了,bezier中就结束了一个t点的运算)
bool recursive_bezier(const std::vector<cv::Point2f> &control_points, std::vector<cv::Point2f> &points1, std::vector<cv::Point2f> &points2, float t, cv::Mat &window){
int size;
size = points1.size();
if(size == 1){
window.at<cv::Vec3b>(points1[0].y, points1[0].x)[1] = 255;//设置该点的绿通道为255
points2 = control_points;
points1.clear();//结束计算后记得初始化容器
return true;
}
for(int i = 0; i < size - 1; i++)
points2.push_back((1 - t) * points1[i] + t * points1[i + 1]);
points1.clear();
return false;
}
效果
只用bezier:
naive_bezier和bezier一起用:
发现画出的曲线颜色为黄色,说明计算结果一致,算法无误
同时还可以尝试八个控制点的效果:
提高
要求:实现对 Bézier 曲线的反走样。(对于一个曲线上的点,不只把它对应于一个像素,你需要根据到像素中心的距离来考虑与它相邻的像素的颜色。)
根据要求中的提示,每得到一个曲线上的点时,我们就根据它到自己周围的3×3个像素中心的距离d来为这些像素填色以达到平滑过渡的效果(每个像素的颜色是255*ratio,d的范围是[0,3/√2],ratio的范围是[0,1],那么ratio关于d的函数就是ratio=1-√2/3d),重复计算的点就按照该点的颜色最大值算,这样就不会在线段中间出现暗点了,按道理这样应该就能达成反走样的要求:
bool recursive_bezier(const std::vector<cv::Point2f> &control_points, std::vector<cv::Point2f> &points1, std::vector<cv::Point2f> &points2, float t, cv::Mat &window){
int size;
size = points1.size();
double ratio = 1;
if(size == 1){
for(int i = -1; i <= 1; i++){
for(int j = -1; j <= 1; j++){//遍历9个像素
if(points1[0].y + j > 700 || points1[0].y + j < 0 || points1[0].x + i > 700 || points1[0].x + i < 0)//不处理越界像素
continue;
ratio = 1 - sqrt(2)*sqrt(pow(points1[0].y - int(points1[0].y + j) - 0.5, 2) + pow(points1[0].x - int(points1[0].x + i) - 0.5, 2)) / 3;//计算ratio
window.at<cv::Vec3b>(points1[0].y + j, points1[0].x + i)[1] = std::fmax(window.at<cv::Vec3b>(points1[0].y + j, points1[0].x + i)[1], 255 * ratio);//计算像素颜色
}
}
points2 = control_points;
points1.clear();
return true;
}
for(int i = 0; i < size - 1; i++)
points2.push_back((1 - t) * points1[i] + t * points1[i + 1]);
points1.clear();
return false;
}
反走样前:
反走样后:
可以看到,曲线的锯齿效果的确被优化了许多
附件
附上源代码,有兴趣的朋友可以自己尝试一下效果:
CSDN:【GAMES101】作业4(提高)
GITHUB:【GAMES101】作业合集