开发软件说明:
OpenCV 2.1.0
Visual Studio 2019
1. 内容简介
- 本次作业实现生成一个视频,包含片头,中间动画,片尾动画三部分,然后读取生成的视频并按下空格可以暂停
- 片头是个人信息+倒计时
- 中间动画,画了一个小猪佩奇追气球的故事
- 片尾动画,是四个彩色方块交替出现
2. 视频生成过程
-
创建视频
-
使用
VideoWrite
类,生成一个名叫test.avi
的视频,同时配置大小,帧率等参数VideoWriter video("test.avi", CV_FOURCC('X', 'V', 'I', 'D'), 270, Size(1280, 720));
-
-
制作片头
-
片头是由几个图片组合而成的,需要把图片作为视频中的一帧,插入进去
- 第一张封面是HW1
- 第二张是我的个人信息和含有校徽的背景
- 第三张是我的个人照片(5岁)
- 第四到六张是一个倒计时
- 第七张是Let's start,表示动画开始
-
设计delay函数,可以在video中,插入time数量个image图片
void delay(Mat image, VideoWriter video, int time) { for (int i = 0; i < time; i++) { video << image; } }
-
同时图与图的切换,本次实验中设计了百叶窗切换方法
- 其原理就是在图片1中根据一定规律载入图片2的像素,生成一帧图片,再插入到video中
- 百叶窗实现要点:
- 将图形中点的y坐标分为5层,每一层相隔为r,5层像素一起由pic0变为pic1,实现百叶窗变换
- 注意两个矩阵赋值时,因为是3通道,所以每一像素要赋三个值
void blindTransition(Mat pic0, Mat pic1, VideoWriter video) { // 百叶窗载入 int x_max = pic0.cols, y_max = pic0.rows; int y_step = y_max / 5; for (int r = 0; r < y_step; r++) { for (int y = r; y < y_max; y += y_step) { uchar* data = pic0.ptr<uchar>(y); uchar* p1 = pic1.ptr<uchar>(y); for (int x = 0; x < x_max; x++) { data[3 * x] = p1[3 * x]; data[x * 3 + 1] = p1[x * 3 + 1]; data[x * 3 + 2] = p1[x * 3 + 2]; } } for (int i = 0; i < 3; i++) { video << pic0; } } }
-
实现上述两个准备函数后,就可以读入已有的图片,经过百叶窗切换形成视频
-
在插入图片之前要将图片的size设为一致,否则会出现矩阵数组溢出的情况
for (size_t i = 2; i <= count; i++) { stringstream str1, str2; str1 << i-1 << ".png"; str2 << i << ".png"; Mat image1 = imread(img_path + str1.str()); Mat image2 = imread(img_path + str2.str()); if (!image1.empty() && !image2.empty()) { resize(image1, image1, Size(1280, 720)); resize(image2, image2, Size(1280, 720)); blindTransition(image1, image2, video); //让第二帧停留的久一点 if (i == 2) { delay(image2, video, 50); } video << image2; cout << "正在处理第" << i << "帧" << endl; } }
-
-
-
制作动画
-
作业中设计的动画内容是小猪佩奇追气球的情节,分为以下几个部分
- 画小猪佩奇和气球
- 小猪佩奇带着气球向右走
- 小猪佩奇不动,气球向左飞
- 小猪佩奇向左走追气球
- 小猪佩奇追不到气球,在画面中间静止,并变成哭脸
-
需要的函数
-
void drawEarc(Mat img, VideoWriter video, Point center, double radius, double start_angle, double end_angle, float a, float b, Scalar color, int thick, bool is_x, bool is_drawing)
-
参数
- 在img上绘制椭圆
- 椭圆的圆点在center,弧度为radius, 起始度数为start_angle, 终点度数为end_angel, 长半轴(水平)为a, 短半轴(竖直)为b
- 颜色为color, 线的宽度为thick
- is_drawing代表是否把img插入video
-
应用
- 如果画横线或竖线,可以令短半轴或长半轴为0, 绘制度数为(PI, 2*PI)或(0.5 * PI, 1.5 * PI)
- 画弧线或圆可以调整参数画出适合的图案
-
实现
-
在其中,应用椭圆的一些公式进行绘制
Point arc; double foot = 0.02; for (double r = start_angle; r <= end_angle; r = r + foot) { if (is_x) { arc.x = center.x + a * cos(r); arc.y = center.y + b * sin(r); } else { arc.x = center.x + b * cos(r); arc.y = center.y + a * sin(r); } if (r == start_angle) { s = arc; } if (r == end_angle) { s = arc; } drawPoint(img, arc, color, thick);
-
-
-
Mat drawPeppa(Mat image, VideoWriter video, int x, int y, bool is_drawing, int frame, bool is_cry)
-
参数
- 实现在image上绘制佩奇,并把Image插入video
- 以Point(x, y)作为佩奇的左上角进行绘制
- is_drawing代表是否显示画的过程
- 因为动画中有佩奇走路,frame代表当前走路是第几帧,一共有3帧,站着,抬左腿,抬右腿
- is_cry代表当前佩奇是哭脸还是笑脸
- 函数中所有的线条都是用
drawEarc
来实现的
-
实现步骤,这里以佩奇的头为例
//佩奇的头 drawEarc(image, video, Point(x + 265, y + 150), 50, 0.2 * PI, 0.6 * PI, 30, 30, Scalar(0, 0, 0), 1, true, is_drawing); drawEarc(image, video, Point(x + 210, y + 180), 50, 0, PI, 50, 50, Scalar(0, 0, 0), 1, true, is_drawing); drawEarc(image, video, Point(x + 250, y + 180), 50, PI, 1.6 * PI, 90, 50, Scalar(0, 0, 0), 1, true, is_drawing); drawEarc(image, video, Point(x + 282, y + 150), 50, 0, 2 * PI, 19, 19, Scalar(0, 0, 0), 1, true, is_drawing);
画出佩奇的头,如下
同理画出身体和其他细节
-
实现佩奇走路
共有3中情况,站着,抬左腿,抬右腿,只要循环实现站着->抬左腿->站着->抬右腿,就能实现佩奇走路了。
-
-
Mat drawballon(Mat image, VideoWriter video, int x, int y, bool is_drawing)
- 实现在image上绘制气球,并把image插入video
- 以Point(x, y)作为气球的左上角进行绘制
- is_drawing代表是否显示画的过程
-
Mat convertPappe(Mat image, Mat result, int c_x)
- 翻转佩奇,把image中的像素点以x = c_x这条直线进行翻转,结果存放在result中
-
-
绘制的过程
-
根据之前设计的动画情节,在不同的坐标上绘制图形,以下以前两个情景的代码为例,其他情景类似,只需要控制佩奇和气球的不同坐标即可
-
初始化并绘制佩奇
-
//初始化画板 Mat image = Mat(Size(1280, 720), CV_8UC3); //绘制佩奇和气球 drawPeppa(image, video, 400, 200, true, 0, false); drawballon(image, video, 400, 200, true); delay(image, video, 5);
-
-
佩奇带着气球右走5步
- 通过drawPeppa函数中参数frame的值来控制佩奇的腿实现走路的动作
//佩奇往右走5步 int location1; for (location1 = 0; location1 < 4 * 5; location1++) { Mat image = Mat(Size(1280, 720), CV_8UC3); image = drawPeppa(image, video, 400 + location1 * 10, 200, false, location1 % 4, false); image = drawballon(image, video, 400 + location1 * 10, 200, false); delay(image, video, 10); }
-
-
-
片尾动画
-
片尾动画是画面中间4个不同颜色的小正方形交替变换
-
关键函数
Mat drawBox(Mat image, int x, int y, int B, int G, int R)
- 在image上, 以Point(x, y)为左上角,画一个边长为50像素,颜色为Scalar(B, G, R),的正方形
-
实现
-
交替出现四个方块, 共10个循环
for (int i = 0; i < 4 * 10; i++) { Mat image_end = Mat(Size(1280, 720), CV_8UC3); if (i % 4 == 0) //blue image_end = drawBox(image_end, -60, -60, 0x2d, 0x85, 0xf0); else if (i % 4 == 1) //red image_end = drawBox(image_end, -60, 0, 0xf4, 0x43, 0x3c); else if (i % 4 == 2) //yellow image_end = drawBox(image_end, 0, 0, 0xff, 0xbc, 0x32); else if (i % 4 == 3) //green image_end = drawBox(image_end, 0, -60, 0xa, 0xa8, 0x58); delay(image_end, video, 80); }
-
-
3. 视频读取过程
-
读取视频文件
- 使用
VideoCapture
类, 读取之前生成的视频test.avi
- 使用
-
展示每一帧
- 读取每一帧,再使用imshow函数输出每一帧
-
设置空格暂停
-
如果没有按键,waitKey(delay)返回-1,不执行waitKey(0),进入下一次循环。
-
如果有按键,返回按键的ASCII值,waitKey(delay)>=32为true,执行waitKey(0),程序暂停,直到有键盘输出才进行下一次循环。
-
int delay = 4; if (delay >= 0 && waitKey(delay) >= 32) waitKey(0);
-
-
使用release方法,释放资源
4. 实验结果展示与分析
-
百叶窗切换
- 下图就是第一帧和第二帧的切换,可以看到实现了百叶窗的效果
(包含个人信息,这张图就不放了)
-
绘制佩奇的过程
-
可以看到下图中,逐步画出佩奇的效果
-
-
一些动画的场景
-
佩奇回头
-
佩奇哭脸
-
佩奇走路
这里图片不能表现出来,详见视频
-
-
片尾
- 如下的四个彩色方块循环出现消失,组成了片尾
5. 编程体会
- 本次作业中,我了解了opencv最基础的应用,比如读图片,读视频,生成视频,把图像一帧一帧插入图像等等
- 还学会了把一些简单的图形计算公式应用于图像显示上面,比如画椭圆, 画方块等等
- 还学会了一些基本的画图和矩阵操作,最基本的图形变换,比如百叶窗变换,图像翻转等
- 最后还学会通过简单的帧的变换做一些动画,比如小猪佩奇走路,最后的片尾动画
借鉴的网页 https://blog.csdn.net/u013794793/article/details/78787409