CV HW1

开发软件说明:

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);
          

          画出佩奇的头,如下

          image-20201130162045355.png

          同理画出身体和其他细节

        • 实现佩奇走路

          共有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. 实验结果展示与分析

  • 百叶窗切换

    • 下图就是第一帧和第二帧的切换,可以看到实现了百叶窗的效果

    (包含个人信息,这张图就不放了)

  • 绘制佩奇的过程

    • 可以看到下图中,逐步画出佩奇的效果

      image-20201130163307911.png

      image-20201130163328956.png

      image-20201130163406035.png

  • 一些动画的场景

    • 佩奇回头

      image-20201130163511846.png

    • 佩奇哭脸

      image-20201130163541350.png

    • 佩奇走路

      这里图片不能表现出来,详见视频

  • 片尾

    • 如下的四个彩色方块循环出现消失,组成了片尾

    image-20201130163659383.png

    image-20201130163720609.png

5. 编程体会

  • 本次作业中,我了解了opencv最基础的应用,比如读图片,读视频,生成视频,把图像一帧一帧插入图像等等
  • 还学会了把一些简单的图形计算公式应用于图像显示上面,比如画椭圆, 画方块等等
  • 还学会了一些基本的画图和矩阵操作,最基本的图形变换,比如百叶窗变换,图像翻转等
  • 最后还学会通过简单的帧的变换做一些动画,比如小猪佩奇走路,最后的片尾动画

借鉴的网页 https://blog.csdn.net/u013794793/article/details/78787409

posted @ 2020-12-08 14:57  一只超级香的小白  阅读(55)  评论(2编辑  收藏  举报