一、问题描述
使用任意方式实现图片的时钟和中心圆形扩大效果。
二、问题分析
在视频的制作中能够看到很多图片切换效果。在本次实验中要求实现图片呈时钟状显示(即新图像顺时针擦除原图像),和中心圆形向外扩散的效果。由于没有要求实现方式,首先需要确定实验平台。处理图像的平台或工具主要有Visual Studio 的GDI(GDI+),图像处理开源库openCV,matlab等。这里选择openCV,进行像素级的处理。openCV中的矩阵,以先行后列的顺序进行储存,左上角为坐标原点,向下为y轴,向右为x轴。将两幅不同的图片存储到矩阵Mat类中,以不同的方式进行像素的替换。绘制时钟擦效果时,需要考虑四个象限角度的变化。绘制中心圆扩散效果时,需要考虑半径的变化。将每一帧的图片写入到视频中,生成图片切换的视频。
算法分析
中心圆放大效果
从画面中心开始,新图像以圆形向外扩大,在这个过程中可以将时间和半径联系起来,t=0时r=0,t_max=r_max,两者始终保持相等。
最大半径即为图像对角线长度的一半,即r_max= 。
首先结果矩阵result赋值为pic0。
对圆的外切正方形进行扫描,根据像素的x,y值计算点到圆心的距离,计算出r=。
If(r<t)
Result[x][y]=pic1[x][y];
由于使用OpenCV,使用迭代器进行像素的赋值。
时钟擦效果
分别对四个象限进行绘制。由于函数库中的tan参数为弧度制,需要进行角度的转换。定义常量PI为3.14159265。
绘制第一象限时,y的范围是0到y_max/2,x的绘制范围是x_max/2到(y_max / 2 - y)*tan(t*(PI/180))。需要注意边界情况,圆可能扩展到图片范围之外,需要使用min函数限制,x不超过x_max。
绘制第二象限时,y的范围为y_max/2到y_max,x的范围为((y - y_max / 2) / tan(t*(PI / 180)) +x_max / 2)到x_max。由于tan值处于分母的位置,需要保证不为0,因此剔除t=0的情况,单独绘制x轴正半轴的像素。
第三象限, y =y_max/2~ y_max,x= (x_max / 2 -(y - y_max / 2)*tan(t*(PI / 180)))~x_max。
第四象限,同样需要单独绘制x轴的负半轴。
y = 0~ y_max/2-1,x = 0~ (x_max / 2 - (y_max / 2 - y) /tan(t*(PI / 180)))
对于部分由于小数取整产生的填充空缺,在前面绘制完毕后,进行像素的补充填充(分布在x轴附近)。
详细设计(从算法到程序)
1. 主模块设计
主函数中读取两张图片,分别为pic0,pic1。调用图片切换效果函数circleTransition(Mat,Mat)和clockTransition(Mat,Mat)。
2. 圆形中心方法效果circleTransition(Mat pic0,Mat pic1)
设置视频格式,包括压缩帧率和尺寸,此处直接设置为常数,如果需要根据图片设置宽高,也可以方便地读取图片尺寸从而进行设置。
doublefps=20;//视频压缩帧率 CvSizesize=cvSize(440,440);//图片序列中每张图片的宽高 CvVideoWriter*writer=cvCreateVideoWriter("CircleTransition.avi",CV_FOURCC('M','J','P','G'),fps,size,true); |
设置循环中需要用到的常量和变量:
intr_max=0,r=0; intx_max=0,y_max=0; intx=0,y=0; x_max=pic0.cols; y_max=pic0.rows; r_max=int(sqrt(pow(float(x_max/2),2)+pow(float(y_max/2),2))); intt_max=r_max,t=0;//t=r |
初始化result矩阵为pic0,接下来在三重循环中进行图像的扫描和像素的替换。由于圆形不能超过图片范围,需限制循环的终止条件,取半径和长宽一半的最小值。
for(t=0;t<t_max;t++){ //t=r 时间等于半径 for(x=max(0,x_max/2-t);x<min(x_max/2+t,x_max);x++){//中心正方形,判断起始和终止的值,调用Min,max函数 uchar*data=result.ptr<uchar>(x); uchar*p1=pic1.ptr<uchar>(x); for(y=max(0,y_max/2-t);y<min(y_max/2+t,y_max);y++){ r=sqrt(pow(float(x-x_max/2),2)+pow(float(y-y_max/2),2)); if(r<=t){//如果在中心圆内,绘制新图片 data[y*3]=p1[y*3]; data[y*3+1]=p1[y*3+1]; data[y*3+2]=p1[y*3+2]; } } } cvWriteFrame(writer,&IplImage(result)); } |
最后释放videoWriter。
3. 时钟擦效果clockTransition(Mat pic0,Mat pic1)
同样需要设置视频格式以及循环中需要使用的常量和变量(包括角度a,时间t等)。
doublea_max=360.0,a=0.0; intt_max=360,t=0; intx=0,y=0; intx_max=0,y_max=0; Matresult=pic0; x_max=result.cols; y_max=result.rows; |
以第二象限为例,最外层循环为角度,范围是1~90(剔除x轴)。第二层循环为y,因为mat的存储方式先行后列,指针必须一行一行地遍历。
for(t=1;t<90;t++){ for(y=y_max/2+1;y<y_max;y++){ uchar*data=result.ptr<uchar>(y); uchar*p1=pic1.ptr<uchar>(y); for(x=((y-y_max/2)/tan(t*(PI /180))+x_max/2);x<x_max;x++){ data[x*3]=p1[x*3]; data[x*3+1]=p1[x*3+1]; data[x*3+2]=p1[x*3+2]; } } cvWriteFrame(writer,&IplImage(result)); cout<<"第二象限 角度"<<t<<endl; } |
对于x轴正半轴,单独进行绘制:
uchar*data=result.ptr<uchar>(y_max/2); uchar*p1=pic1.ptr<uchar>(y_max/2); for(x=x_max/2;x<x_max;x++){ data[x*3]=p1[x*3]; data[x*3+1]=p1[x*3+1]; data[x*3+2]=p1[x*3+2]; } |
调试与测试
由于算法的限制,图片的绘制速度较慢,一张440*440大小的图片,通过中心圆扩散效果需要15秒完成绘制。
当圆超过图片边缘时,同样可以正常绘制。
时钟擦效果,需要花费18s,绘制过程较为流畅。
但在x轴附近会出现一定的抖动,出现缝隙或者是绘制速度较慢。
分析与总结
本次实验理解了两种视频切换效果的原理,并利用openCV实现了绘制效果。完成了实验的要求,但绘制速度较慢,这是由算法所局限的。
在进行中心圆扩散的效果实现时,可以进行优化,缩小扫描面积。如果扫面外切正方形,当半径很大时,扫描面积很大,有很多重复填充的操作。可以考虑只扫描圆的外切和内切正方形所包围的平面,这样能够大大缩减扫描面积,从而提高绘制效率。
对于时钟擦效果,也可以减小重复填充的次数,或者考虑其余的绘制方法,例如增加角度时填充线条,而不是扫描行和列进行像素的填充。由于tan函数的特殊性,需要考虑值为无穷或tan值为分母(不能为0)的情况,进行单独的绘制,从而改善绘制效果,避免瑕疵或缝隙的出现。
资源链接:http://download.csdn.net/detail/rachelkong/9735501