作业4:Bézier 曲线

一、功能

实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更多点来控制的Bézier 曲线)。实现对Bézier 曲线的反走样。

函数功能:

• recursive_bezier():实现de Casteljau 算法计算贝塞尔曲线上对应点的坐标 ,输入的是一个控制点序列和一个浮点数t。注意这个只能找到一个点,要找到若干个点,就需要若干个t 。

• bezier():该函数实现绘制Bézier 曲线的功能。输入参数是控制点序列和Mat对象,绘制贝塞尔曲线,迭代t并将t作为参数传入 recursive_bezier()函数,返回点,画在Mat对象上。

二、算法

De Casteljau 算法说明如下:
1. 考虑一个p0, p1, ... pn 为控制点序列的Bézier 曲线。首先,将相邻的点连接起来以形成线段。
2. 用t : (1 − t) 的比例细分每个线段,并找到该分割点。
3. 得到的分割点作为新的控制点序列,新序列的长度会减少一。
4. 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤1。
使用[0,1] 中的多个不同的t 来执行上述算法,得到相应的Bézier 曲线。

实现反走样:对于一个曲线上的点,不只把它对应于一个像素,你需要根据到像素中心的距离来考虑与它相邻的像素的颜色。

三、程序

#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <cmath>
#include <algorithm>

std::vector<cv::Point2f> control_points;       //控制点 
//event是CV_EVENT_*  变量之一,x和y是鼠标指针在图像坐标系的坐标(不是窗口坐标系),flags是CV_EVENT_FLAG的组合,userdata是用户定义的传递到setMouseCallback函数调用的参数 
void mouse_handler(int event, int x, int y, int flags, void *userdata) 
{
    if (event == cv::EVENT_LBUTTONDOWN && control_points.size() < 4)    //如果触发左键按下事件并且控制点的数目小于4 
    {
        std::cout << "Left button of the mouse is clicked - position (" << x << ", "
        << y << ")" << '\n';
        control_points.emplace_back(x, y);   //在末尾插入一个点 
    }     
}

void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window) 
{
    auto &p_0 = points[0];
    auto &p_1 = points[1];
    auto &p_2 = points[2];
    auto &p_3 = points[3];

    for (double t = 0.0; t <= 1.0; t += 0.001) //[0,1]之间循环寻找t的值 
    {
    	//三次贝塞尔曲线 
        auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
                 3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;

        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}

//实现de Casteljau 算法计算贝塞尔曲线上对应点的坐标 ,输入的是一个控制点序列和一个浮点数t
//注意这个只能找到一个点,要找到若干个点,就需要若干个t 
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    //使用递推实现 
//    int n = control_points.size();     //计算控制点序列中点的个数
//	for(int i = 1; i <= n; i++){       //控制循环次数 
//		for(int j = 0; j < n-i; j++){  //控制每次将相邻点连接起来后的点,控制的是分割点的情况 
//			//用t : (1 . t) 的比例细分每个线段,并找到该分割点。 
//			//以下实现不行 
//			//control_points[j].x = (1-t) * control_points[j].x + t * control_points[j+1].x;
//			//control_points[j].y = (1-t) * control_points[j].y + t * control_points[j+1].y;
//		}                                                                      
//	} 
//	return control_points[0];
	
    //递归方法
    int n = control_points.size();    //计算控制点序列中点的个数 
    if(n == 1)                       //递归出口 
	return control_points[0];
    std::vector<cv::Point2f> new_control_points;
    for(int i = 0; i < n - 1 ; i++)    //每次对控制点序列进行循环 
    {
        new_control_points.push_back(cv::Point2f(
            control_points[i].x * (1-t) + t * control_points[i+1].x,
            control_points[i].y * (1-t) + t * control_points[i+1].y
            ));
    }
    return recursive_bezier(new_control_points,t);    //递归调用 
	
}              

//输入控制点序列和Mat对象,绘制贝塞尔曲线,迭代t并将t作为参数传入 recursive_bezier()函数,返回点,画在Mat对象上 
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 += 0.001){
    	cv::Point2f point = recursive_bezier(control_points, t);
    
    	window.at<cv::Vec3b>(point.y, point.x)[1] = 255;//attention(y,x) 
    	
    	// anti-aliasing, cv::Mat,rows是行数,图像的高度;cols是列数,图像的宽度。
		// 计算出最小的x,y和最大的x,y 
    	// int min_x = std::max(0, (int)floor(point.x));
        // int max_x = std::min(window.cols-1, (int)ceil(point.x));  
        // int min_y = std::max(0, (int)floor(point.y));
        // int max_y = std::min(window.rows-1, (int)ceil(point.y));

        // static float pow_antialiasing = 0.5;

        // window.at<cv::Vec3b>(min_y, min_x)[1] = (uint) ( 255 * pow((1.0f - sqrt( (min_x-point.x)*(min_x-point.x) + (min_y-point.y)*(min_y-point.y))), pow_antialiasing) );
        // window.at<cv::Vec3b>(max_y, min_x)[1] = (uint) ( 255 * pow((1.0f - sqrt( (min_x-point.x)*(min_x-point.x) + (max_y-point.y)*(max_y-point.y))), pow_antialiasing) );
        // window.at<cv::Vec3b>(min_y, max_x)[1] = (uint) ( 255 * pow((1.0f - sqrt( (max_x-point.x)*(max_x-point.x) + (min_y-point.y)*(min_y-point.y))), pow_antialiasing) );
        // window.at<cv::Vec3b>(max_y, max_x)[1] = (uint) ( 255 * pow((1.0f - sqrt( (max_x-point.x)*(max_x-point.x) + (max_y-point.y)*(max_y-point.y))), pow_antialiasing) );

	} 
}

int main() 
{
    //创建一个700*700类型为8为的uchar类型三通道的颜色为 
    cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));   //创建Mat对象window,指定矩阵行列、类型、颜色的构造函数Scalar()颜色列表依次为b,g,r 
    //cv::cvtColor()用于将图像从一个颜色空间转换到另一个颜色空间的转换,参数的顺序为输入序列,输出序列,颜色映射码 
    cv::cvtColor(window, window, cv::COLOR_BGR2RGB); //将图像从一个颜色空间转换到另一个颜色空间的转换。默认为 BGR顺序,而其他软件一般使用RGB,所以需要转换 
    //namedWindow()的功能就是新建一个显示窗口。可以指定窗口的类型。参数1:新建的窗口的名称,参数2:WINDOWS_AUTOSIZE窗口大小自动适应图片大小,并且不可手动更改。 
    cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);
    //鼠标事件响应,第一个参数是窗口的名字,第二个参数:鼠标响应函数,回调函数,第三个参数:传给回调函数的参数 
    cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);

    int key = -1;
    while (key != 27)    //‘Esc’的ASCII码为27 
    {
        for (auto &point : control_points)    //对控制点进行循环 
        {
        	//绘制圆形,图像window,圆心坐标point,半径为3,线条颜色,组成圆的线条的粗细程度 
            cv::circle(window, point, 3, {255, 255, 255}, 3);
        }

        if (control_points.size() == 4)       // 当控制点的个数为4时 
        {
            naive_bezier(control_points, window);
            bezier(control_points, window);

            cv::imshow("Bezier Curve", window);     //imread()功能是载入一张图片,imshow()功能是把载入的图片显示出来 
            cv::imwrite("my_bezier_curve.png", window);   // imwrite函数是用来输出图像到文件,把Mat类型的图像数据window写入 my_bezier_curve.png,这个文件后缀必须加后缀 
            key = cv::waitKey(0);    //等待按下函数:参数如果写负数或者0。当显示图片后,按下任意键后程序退出。如果参数写为3000就是3秒后程序自动退出。 

            return 0;
        }

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

	return 0;
}

四、注意

window.at<cv::Vec3b>(point.y, point.x)[1] = 255;

这里point.y和point.x不要写反了。

从2阶到7阶的贝赛尔曲线 private static final int MAX_COUNT = 7; // 贝塞尔曲线最大阶数 private static final int REGION_WIDTH = 30; // 合法区域宽度 private static final int FINGER_RECT_SIZE = 60; // 矩形尺寸 private static final int BEZIER_WIDTH = 10; // 贝塞尔曲线线宽 private static final int TANGENT_WIDTH = 6; // 切线线宽 private static final int CONTROL_WIDTH = 12; // 控制点连线线宽 private static final int CONTROL_RADIUS = 12; // 控制点半径 private static final int TEXT_SIZE = 40; // 文字画笔尺寸 private static final int TEXT_HEIGHT = 60; // 文本高度 private static final int RATE = 10; // 移动速率 private static final int HANDLER_WHAT = 100; private static final int FRAME = 1000; // 1000帧 private static final String[] TANGENT_COLORS = {"#7fff00", "#7a67ee", "#ee82ee", "#ffd700", "#1c86ee", "#8b8b00"}; // 切线颜色 private static final int STATE_READY = 0x0001; private static final int STATE_RUNNING = 0x0002; private static final int STATE_STOP = 0x0004; private static final int STATE_TOUCH = 0x0010; private Path mBezierPath = null; // 贝塞尔曲线路径 private Paint mBezierPaint = null; // 贝塞尔曲线画笔 private Paint mMovingPaint = null; // 移动点画笔 private Paint mControlPaint = null; // 控制点画笔 private Paint mTangentPaint = null; // 切线画笔 private Paint mLinePaint = null; // 固定线画笔 private Paint mTextPointPaint = null; // 点画笔 private Paint mTextPaint = null; // 文字画笔 private ArrayList mBezierPoints = null; // 贝塞尔曲线点集 private PointF mBezierPoint = null; // 贝塞尔曲线移动点 private ArrayList mControlPoints = null; // 控制点集 private ArrayList<ArrayList<ArrayList>> mTangentPoints; // 切线点集 private ArrayList<ArrayList> mInstantTangentPoints; private int mR = 0; // 移动速率 private int mRate = RATE; // 速率 private int mState; // 状态 private boolean mLoop = false; // 设置是否循环 private boolean mTangent = true; // 设置是否显示切线 private int mWidth = 0, mHe
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值