//仿射变换:又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一次平移,变换为另一个向量空间;
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 算法说明如下:
1. 考虑一个 p0, p1, ... pn 为控制点序列的 Bézier 曲线。首先,将相邻的点连接
起来以形成线段。
2. 用 t : (1 − t) 的比例细分每个线段,并找到该分割点。
3. 得到的分割点作为新的控制点序列,新序列的长度会减少一。
4. 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤 1。使用 [0,1] 中的多个不同的 t 来执行上述算法,你就能得到相应的 Bézier 曲线。
具体的算法解析可以看GAMES101-现代计算机图形学 Lecture11 Geometry2;
首先采用作业框架中的naive_bezier进行运行,得到以下结果:
注意下面这一行代码:
window.at (point.y, point.x)[1] = 255;//Mat.at<存储类型名称>(行,列)[通道]
通道:0-R,1-G,2-B,要求设为绿色,所以通道为1
接下来是我们要写的bezier函数以及 recursive_bezier函数:
void bezier(const std::vector &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(point.y, point.x)[2] = 255;
}
}
//该函数使用一个控制点序列和一个浮点数 t 作为输入,实现 de Casteljau 算法来返回 Bézier 曲线上对应点的坐标
//算法的具体实现;//考虑采用迭代的算法;
//此处用迭代算法,就是将算出来的点不断带入recursive_bezier函数重新计算,直到仅剩下最后一个为止;
cv::Point2f recursive_bezier(const std::vector &control_points, float t)
{
// TODO: Implement de Casteljau's algorithm
//如果仅剩下一个点,那么就直接返回即可;
if (control_points.size() == 1)
{
return control_points[0];
}
//对于每一次迭代有两个点以上的情况进行下述操作;
else
{
//创建一个新的向量vector集数组,用来存放每两个点经过一次计算后得到的新的点;
std::vectorcontrol_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;
}
效果大致如下:
此外还可以试试多个点的情况:
程序在进行画线时是以点的形式,如果放大有较为明显的不连续,因此可以采用反走样来使其平滑过渡;对于每一个点,其必然会包含在一个像素之中,可按照比例和着色进行插值,以求得一个较为平滑的过渡;
如下图所示,该点一定会在下图所示的黄色方框内部,而该点离像素点的最远距离为根号2,按照该点到其他四个点的距离进行插值;
可以先找出距离该点最近的像素点,再找出其他三个点;
如果只是按简单的比例(点和像素中心距离/总距离),越远的点,着色其实是越重的,会产生线比较粗,而且还是有走样现象。可以将着色比例改成:(最大像素点距离-点和当前处理的像素距离)/(A1+A2+A3+A4),[最大像素点距离-点和当前处理的像素距离=Ai(i=1,2,3,4)]。这样子的话,越远的点,对应比例越小,越近的点,对应比例越大。此外,像素的颜色要在处理像素颜色通道基础上加上比例颜色,才可以和背景颜色分开。
具体的代码如下:
void bezier(const std::vector &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 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 distance_List = {};//创建一个列表,用于存放该点到四个像素中心点的距离,类型为float;
//将所求得的距离加入列表;
window.at(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(distance_dot[i].y, distance_dot[i].x)[1] = std::min(255.f, window.at(distance_dot[i].y, distance_dot[i].x)[1] + 255.f * d);
//这里是对每个像素自身进行操作,自身一开始的通道颜色就是 window.at (distance_dot[i].y, distance_dot[i].x)[1],
//在此基础上要加上比例颜色,为了不超过255.f,要进行最小值比较
}
}
}
结果如下:
与之前对比可以明显看出反走样处理得还是挺不错的;
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