Games101--现代计算机图形学 作业4- 贝塞尔曲线-笔记及作业分析

//仿射变换:又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一次平移,变换为另一个向量空间;

1.作业要求

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 曲线上对应点的坐标。

2.算法:

De Casteljau 算法说明如下:

1. 考虑一个 p0, p1, ... pn 为控制点序列的 Bézier 曲线。首先,将相邻的点连接

起来以形成线段。

2. 用 t : (1 − t) 的比例细分每个线段,并找到该分割点。

3. 得到的分割点作为新的控制点序列,新序列的长度会减少一。

4. 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤 1。使用 [0,1] 中的多个不同的 t 来执行上述算法,你就能得到相应的 Bézier 曲线。

具体的算法解析可以看GAMES101-现代计算机图形学 Lecture11 Geometry2;

3.代码分析:

首先采用作业框架中的naive_bezier进行运行,得到以下结果:

注意下面这一行代码:

window.at <cv::Vec3b>(point.y, point.x)[1] = 255;//Mat.at<存储类型名称>(行,列)[通道]

通道:0-R,1-G,2-B,要求设为绿色,所以通道为1

接下来是我们要写的bezier函数以及 recursive_bezier函数:

3.1bezier函数

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.
    for (double t = 0.0; t <= 1.0; t = t + 0.0001)
    {
        //对于每一个微小值t,调用另外一个函数 recursive_bezier;
        cv::Point2f point = recursive_bezier(control_points,t);

        //将返回的点绘制在 OpenCV ::Mat 对象上(将point绘制到Opencv::Mat对象上);

        //此处由于PDF中要求将贝塞尔曲线绘制成绿色的,所以将RGB中的G通道设置成255
        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}

3.2recursive_bezier函数

//该函数使用一个控制点序列和一个浮点数 t 作为输入,实现 de Casteljau 算法来返回 Bézier 曲线上对应点的坐标
//算法的具体实现;//考虑采用迭代的算法;
//此处用迭代算法,就是将算出来的点不断带入recursive_bezier函数重新计算,直到仅剩下最后一个为止;
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    //如果仅剩下一个点,那么就直接返回即可;
    if (control_points.size() == 1)
    {
        return control_points[0];
    }
    //对于每一次迭代有两个点以上的情况进行下述操作;
    else
    { 
        //创建一个新的向量vector集数组,用来存放每两个点经过一次计算后得到的新的点;
        std::vector<cv::Point2f>control_points_temp;
        for (int i = 0; i < control_points.size()-1; i++)
        {
           control_points_temp.push_back(control_points[i] * (1 - t) + control_points[i+1] * t);
        }
        //再对上面算出的点进行递归;
        return recursive_bezier(control_points_temp, t);
    }
    //return cv::Point2f();
}

大致算法如下图:

注释掉main函数中的naive_bezier函数,改为bezier函数:

int main() 
{
    cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));
    cv::cvtColor(window, window, cv::COLOR_BGR2RGB);
    cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);

    cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);

    int key = -1;
    while (key != 27) 
    {
        for (auto &point : control_points) 
        {
            cv::circle(window, point, 3, {255, 255, 255}, 3);
        }

        if (control_points.size() == 4) 
        {
            //naive_bezier(control_points, window);
            bezier(control_points, window);

            cv::imshow("Bezier Curve", window);
            cv::imwrite("my_bezier_curve.png", window);
            key = cv::waitKey(0);

            return 0;
        }

        cv::imshow("Bezier Curve", window);
        key = cv::waitKey(20);
    }

return 0;
}

效果大致如下:

此外还可以试试多个点的情况:

4.提高部分(解决反走样)

程序在进行画线时是以点的形式,如果放大有较为明显的不连续,因此可以采用反走样来使其平滑过渡;对于每一个点,其必然会包含在一个像素之中,可按照比例和着色进行插值,以求得一个较为平滑的过渡;

如下图所示,该点一定会在下图所示的黄色方框内部,而该点离像素点的最远距离为根号2,按照该点到其他四个点的距离进行插值;

可以先找出距离该点最近的像素点,再找出其他三个点;

如果只是按简单的比例(点和像素中心距离/总距离),越远的点,着色其实是越重的,会产生线比较粗,而且还是有走样现象。可以将着色比例改成:(最大像素点距离-点和当前处理的像素距离)/(A1+A2+A3+A4),[最大像素点距离-点和当前处理的像素距离=Ai(i=1,2,3,4)]。这样子的话,越远的点,对应比例越小,越近的点,对应比例越大。此外,像素的颜色要在处理像素颜色通道基础上加上比例颜色,才可以和背景颜色分开。

具体的代码如下:

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.
    for (float t = 0.0; t <= 1.0; t = t + 0.001)
    {
        //接收每一个t所求得的点
        cv::Point2f point = recursive_bezier(control_points,t);
        //找到该点所对应的最近的像素点坐标,如下;
        cv::Point2f point_1 = cv::Point2f(point.x - std::floor(point.x) < 0.5 ? std::floor(point.x) : std::ceil(point.x),
            point.y - std::floor(point.y) < 0.5 ? std::floor(point.y) : std::ceil(point.y));
        cv::Point2f point_2 = cv::Point2f(point_1.x - 1.0, point_1.y);//左上角的点;
        cv::Point2f point_3 = cv::Point2f(point_1.x - 1.0, point_1.y - 1.0);//左下角的点;
        cv::Point2f point_4 = cv::Point2f(point_1.x, point_1.y - 1.0);//右下角的点;
        //再求出point点到个点的距离占最远距离根号2的权重,用于后面颜色的计算;

        //创建一个vector容器,用来存储刚刚求得的四个像素点坐标;
        std::vector<cv::Point2f> distance_dot{ point_1,point_2,point_3,point_4 };

        float MaxDistance = sqrt(2.0);//点到临近四个像素点包含的四方体的最远距离;
        float SumDistance = 0.0f;//用来计算总长度;->为后面计算距离权重;
        float pi_distance = 0.0f;//用来计算该点到四个像素中心点的距离;
        std::vector<float> distance_List = {};//创建一个列表,用于存放该点到四个像素中心点的距离,类型为float;
        //将所求得的距离加入列表;

        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;//Mat.at<存储类型名称>(行,列)[通道]
        for (int i = 0; i < 4; i++)
        {
            cv::Point2f point_coordinate(distance_dot[i].x + 0.5, distance_dot[i].y + 0.5);//记录像素中心点;
            pi_distance = MaxDistance - sqrt(std::pow(point.x-point_coordinate.x,2) + std::pow(point.y - (point_coordinate.y),2));
            //上述操作的原因在于:
            //越远的点的颜色占比应该较少,上述操作可以实现;
            distance_List.push_back(pi_distance);//将每一个距离传入distance_List中,此处的距离越小代表与该点距离越远;
                                                 //注意用push_back()放到队尾;
            SumDistance += pi_distance;
        }
        for (int i = 0; i < 4; i++)
        {
            ///求出距离占比系数d,离的远相对应的占比应该小一点,作为每个像素颜色的比例;
            float d = distance_List[i] / SumDistance;
            window.at<cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1] = std::min(255.f, window.at<cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1] + 255.f * d);
            //这里是对每个像素自身进行操作,自身一开始的通道颜色就是 window.at <cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1],
            //在此基础上要加上比例颜色,为了不超过255.f,要进行最小值比较
        }
    }
}

结果如下:

与之前对比可以明显看出反走样处理得还是挺不错的;

5.补充:

5.1关于OpenCV的cv::Mat详解,可以看看这一篇博客:

(6条消息) OpenCV基础类型4--cv::Mat详解_网络通杀108的博客-CSDN博客_cv::mat

https://blog.csdn.net/czsnooker/article/details/118345494;

5.2关于OpenCV固定向量类cv::Vec<>、Vec2i、Vec3i、Vec3f、Vec2f的详解,可以看这一篇文章:

(6条消息) OpenCV基础类型3(固定向量类cv::Vec<>、Vec2i、Vec3i、Vec3f、Vec2f)_网络通杀108的博客-CSDN博客_cv::vec2f

https://blog.csdn.net/czsnooker/article/details/118314514?spm=1001.2014.3001.5501

5.3.004-Mat对象详解:

https://mbd.baidu.com/ug_share/mbox/4a83aa9e65/share?product=smartapp&tk=d4a14cfab5e6f9d436e55c8ebede9c90&share_url=https%3A%2F%2Fyebd1h.smartapps.cn%2Fpages%2Fblog%2Findex%3FblogId%3D114162214%26_swebfr%3D1%26_swebFromHost%3Dbaiduboxapp&domain=mbd.baidu.com

5.4.【Opencv】CV_8UC3的解析:

http://t.csdn.cn/QykfF

5.5.Opencv学习cvtColor函数:

https://mbd.baidu.com/ug_share/mbox/4a83aa9e65/share?product=smartapp&tk=1ba0200aff628380f2d467d7e54ad73a&share_url=https%3A%2F%2Fyebd1h.smartapps.cn%2Fpages%2Fblog%2Findex%3FblogId%3D113941015%26_swebfr%3D1%26_swebFromHost%3Dbaiduboxapp&domain=mbd.baidu.com

5.6.深度学习中为什么普遍使用BGR而不用RGB:

https://mbd.baidu.com/ug_share/mbox/4a83aa9e65/share?product=smartapp&tk=283013e2ba37426a636718e266019f4f&share_url=https%3A%2F%2Fyebd1h.smartapps.cn%2Fpages%2Fblog%2Findex%3FblogId%3D88211219%26_swebfr%3D1%26_swebFromHost%3Dbaiduboxapp&domain=mbd.baidu.com

5.7.鼠标操作setMouseCallback:

https://mbd.baidu.com/ug_share/mbox/4a83aa9e65/share?product=smartapp&tk=007080f64dcf4d02b14228ddcfdd02a7&share_url=https%3A%2F%2Fyebd1h.smartapps.cn%2Fpages%2Fblog%2Findex%3FblogId%3D105925838%26_swebfr%3D1%26_swebFromHost%3Dbaiduboxapp&domain=mbd.baidu.com

5.8.反走样相关算法的参考:

https://zhuanlan.zhihu.com/p/464122963?utm_id=0

5.9.Opencv函数copyTo()与clone():

https://www.zhihu.com/tardis/sogou/art/400725262

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值