作业4是使用 De Casteljau 算法实现 Bézier 曲线

De Casteljau 算法的核心是递归,使用递归,通过输入的多个顶点计算出连续插值后的最后一个顶点

核心函数:

recursive_bezier()

 1cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
 2{
 3    // TODO: Implement de Casteljau's algorithm
 4    if (control_points.size() == 1) return control_points.front();
 5
 6    std::vector<cv::Point2f> next_points;
 7    for (int i = 0; i < control_points.size() - 1; i++)
 8        {
 9            next_points.push_back(((1 - t) * control_points[i] + t * control_points[i + 1]));
10        }
11    
12    return recursive_bezier(next_points, t);
13}

在循环中计算当前顶点与下一顶点的插值点,并将其push_back进要在下一层递归中使用的顶点组;每次递归都会使总顶点数减一,直到只剩最后一个顶点,将其作为返回值输出

bezier()

 1void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
 2{
 3    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
 4    // recursive Bezier algorithm.
 5    for (double t = 0; t <= 1; t += 0.0001)
 6        {
 7            auto point = recursive_bezier(control_points, t);
 8            window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
 9        }
10}

在这个函数中,通过对t值进行细微的调整,可以得到平滑的曲线效果(本质就是在两个点之间计算出大量的插值点)

Extra:为曲线添加反走样

我们在循环中所计算的坐标是有零1有整的,比如(10.2,15.8),但是程序会把它转换为距离最近的整数(10,16),这也是锯齿产生的原因

bezier()

 1void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
 2{
 3    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
 4    // recursive Bezier algorithm.
 5
 6    for (double t = 0; t <= 1; t += 0.0001) 
 7        {
 8            int color = 0;
 9            auto point = recursive_bezier(control_points, t);
10            int x = std::floor(point.x);
11            int y = std::floor(point.y);
12
13            std::vector<cv::Point2f> near_pixels
14                {
15                cv::Point2f(x + 1, y + 1), cv::Point2f(x + 1, y),
16                cv::Point2f(x, y + 1), cv::Point2f(x, y)
17                };
18            for (const auto& near : near_pixels)
19                {
20                    int nx = static_cast<int>(near.x);
21                    int ny = static_cast<int>(near.y);
22
23                    if (nx < 0 || nx >= window.cols || ny < 0 || ny >= window.rows) continue;
24
25                    float distance = std::sqrt((point.x - near.x) * (point.x - near.x) +
26                    (point.y - near.y) * (point.y - near.y));
27
28                    float weight = 1.0f - (distance / 1.414f);
29                    int new_green = std::max(0, static_cast<int>(weight * 255.0f));
30
31                    auto& pixel = window.at<cv::Vec3b>(ny, nx);
32                    pixel[1] = std::max(static_cast<int>(pixel[1]), new_green);
33                }
34        }
35}

有两种方法,一种是通过周围的点计算当前的颜色值,但是在这个循环中,这个方法有些麻烦

所以,另一种方法是使用当前点的颜色值,通过欧氏距离插值,赋予周围的点颜色

流程:

1.先对当前坐标取整,并计算包围着当前的的四个点(2x2采样)

2.通过遍历,分别计算distance并赋予颜色值(取最大值)

原文:4