用opencv将图片变成水波纹效果
又是很久很久没有写博客了。不知道为什么,还是没有这个习惯。总是感觉没什么好写的。倘若是,将学到的东西,记录下来。如果是仅仅是这样的话,我自知大多没有自己的思考,也不过是将别人的东西搬到自己的博客里面而已,网上一搜一大片,又是何苦呢。
还是上学期,有个练习题目是,将一幅图片变成水波纹效果。我在网上找到一份源码,参考之下,顺着思路用opencv2重写之。望原作者看到勿怪。
思路如下:
1.将图片中的坐标点(x,y)换成极坐标,有现成的函数。
2.极坐标下,用三角函数算出新半径。
3.在新半径之下,转换成新的坐标(x0,y0),如果新坐标是小数,用双线性插值的方法处理。
关键代码如下:
其中一些变量声明如下:
Mat imageInfo;//原图片 int imageWidth; int imageHeight; int imageX;//图像中心点的横坐标 int imageY;//图像中心点的纵坐标 float A;//波纹幅度 float B;//波纹周期 Asin(Bx); int imageChannels;//通道数 Mat imageWater;//转换后的图片 void reCalcAB(int i,int j,float &a,float &b);//坐标转换 uchar BLIP(float a,float b,int k); //k为通道数,值为-1为单通道,灰度图
1 void imagetest::imageprocess() 2 { 3 imageInfo.copyTo(imageWater); 4 5 float a; 6 float b;//临时坐标 7 8 for(int i=0;i<imageHeight-1;i++) 9 { 10 uchar *Data = imageWater.ptr<uchar>(i); 11 for(int j=0;j<imageWidth-1;j++) 12 { 13 reCalcAB(i,j,a,b); 14 if(imageChannels == 1)//彩色与灰度图像要单独处理,否则 15 { //会出现椭圆的情况 16 *(Data+j) = BLIP(a,b,-1);//-1指灰度图 17 } 18 else if(imageChannels == 3) 19 { 20 for(int k = 0;k<imageChannels;k++) 21 { 22 *(Data+j*imageChannels+k)= BLIP(a,b,k); 23 } 24 } 25 } 26 } 27 }
reCalcAB是坐标转换函数:
1 void imagetest::reCalcAB(int i,int j,float &a,float &b) 2 { 3 float y0 = (float)(i-imageY); 4 float x0 = (float)(j-imageX);//(i,j)相对于原点的坐标 5 float theta0 = atan2f(y0,x0);//转化成角坐标 6 float r0 = sqrtf(x0*x0+y0*y0);//初始半径 7 8 float r1 = r0+ A*imageWidth*0.01*sin(B*0.1*r0);//计算新的半径 9 a = imageX + r1*cos(theta0); 10 b = imageY + r1*sin(theta0);//转换后的坐标 11 if(a>imageWidth) 12 a = imageWidth-1; 13 else if(a<0) 14 a = 0; //超出边界的处理 15 if(b>imageHeight) 16 b = imageHeight-1; 17 else if(b<0) 18 b = 0; 19 }
双线性插值函数:(这个方法看着很高级,实际很简单。仔细看代码就明白怎么回事情了)
1 uchar imagetest::BLIP(float a,float b,int k) 2 { 3 uchar newData;//保存结果 4 uchar DataTemp1; 5 uchar DataTemp2;//两个中间变量 6 int x[2]; 7 int y[2];//存储周围四个点。 8 9 x[0] = (int)a; 10 y[0] = (int)b; 11 x[1] = x[0]+1; 12 y[1] = y[0]+1;//(a,b)周围四个整点坐标 13 //取值 14 uchar *data1 = imageWater.data + y[0]*imageWater.step + x[0]*imageChannels; 15 uchar *data2 = imageWater.data + y[0]*imageWater.step + x[1]*imageChannels; 16 uchar *data3 = imageWater.data + y[1]*imageWater.step + x[0]*imageChannels; 17 uchar *data4 = imageWater.data + y[1]*imageWater.step + x[1]*imageChannels; 18 if(k!=-1)//如果是彩色,转换一下 19 { 20 data1 += k; 21 data2 += k; 22 data3 += k; 23 data4 += k; 24 } 25 26 if((fabsf(a-x[0])<0.00001) && (fabsf(b-y[0])<0.00001))//整点,直接返回 27 { 28 newData = *data1; 29 return newData; 30 } 31 32 float dx = fabsf(a-x[0]);//x轴的比例 33 float dy = fabsf(b-y[0]);//y轴的比例 34 35 DataTemp1 = (*data1)*(1.0-dx) + (*data2)*dx; 36 DataTemp2 = (*data3)*(1.0-dx) + (*data4)*dx; 37 newData = DataTemp1*(1.0-dy) + DataTemp2*dy;//核心插值过程 38 39 return newData; 40 }
效果如下:
这个效果看起来倒是不错,总感觉不是那么真实。
而且,这个程序有严重的问题。如果我换一张图片,重新设置 A和B的参数
就会出现如下的效果:
中间水平方向出现了明显的一条横线。
目前还没有解决的问题主要就是这条横线,然后就是怎么样才能使得水波纹看起来更真实。我想把用一张图片做成视频,不知道这个效果最后做出来是个什么样子。
如果是坐标转换出错了的话,理论上来说应该会水平、竖直都应该出现一条直线,现在只有水平方向有一条直线。
抽时间再仔细琢磨琢磨。