zoukankan      html  css  js  c++  java
  • 图像水波纹特效原理分析和实现

       前段时间注意到一些软件上有图像的水波纹特效,似乎很炫,想深入了解下该效果的具体原理与实现方式,上网搜了不少些资料,都讲得不清不楚,没办法只能靠自己了。花了一整个下午先去复习了高中物理的波的知识,试着自己来推导原理并实现了下。下面的推导是我根据一些资料以及自己分析出的,如有错误,望请指出。上张效果图先:

    基本原理 


      水波效果反映到图像上,则是像素点的偏移。因此对图像的处理就如同图像缩放一样,对于输出图像的每个点,计算其对应于原始输入图像的像素点,通过插值的方法即可计算其颜色值。 

      先说下关于水波的基础知识。波的传播方向与质点振动方向垂直的为横波,相同则为纵波,水波是横波和纵波的叠加,因此水波在传播时,每个质点存在水平运动和上下运动,合起来就是一个椭圆的圆周运动,如下图的蓝色椭圆。 

      如图,红轴表示水面,对于图像来讲,只有在垂直于成像视线的方向(图中蓝色线段)产生的运动才会引起像素的偏移,而平行于实现方向则可以忽略。那么对于某个点x,其运动轨迹上的每个点都可以分解为与视线平行和垂直的2个方向上的位移,我们只要计算出垂直视线的位移y即可计算出水波导致像素点的偏移位移。 

       假定水波是平面简谐波,则能得到其波动方程

       式中,A是振幅,v是波速,t是时间,lamda是波长, x为距离波源的距离,phi为初始相位。但是随着时间的推移和波的传播,其能量会衰减,即其振幅会随着时间和位置衰减,则上面的公式需修改为:

       函数表征了水波能量的衰减情况,因此该函数必须是关于x和t的单调递减函数,实际水波的递减是什么规律我不清楚,但这里自行设定一个即可,一般的衰减函数有线性衰减和指数衰减,具体好坏只要实验结果才具有说服力。 

      一个波源可以由波长,波速,初始相位和振幅唯一确定,因此对于某个时刻的某个点,我们可以计算出其位移。另一方面,水波是向四面八分同时传播的,对图像坐标系来看,每个点的位移都可以分解为水平与垂直位移,从而就可以确定像素点的精确偏移了。当然,对于多个波源,可以分别考虑,每个点的位移是所有波源引起的位移和。

    水波纹特效具体实现 


     下面就是具体实现了,这个效果实现应该比较简单的。首先有一个波源对象。

     1 typedef unsigned char BYTE;
     2 #define PI 3.1415926
     3 //一个波源
     4 class CWaveSource
     5 {
     6 public:
     7     CWaveSource(float f32Cx = 100.0f, float f32Cy = 100.0f, 
     8                 float f32MaxAmp = 20.0f, float f32Phase = -PI/2,
     9                 float f32WaveLen = 10.0f, float f32V = 1.0f);
    10     ~CWaveSource();
    11 
    12     //该波源在当前时刻,x,y位置处的振幅
    13     void GetAmplitude(int x, int y, float &f32ax, float &f32ay);
    14 
    15     //波传播到下一时刻,需修改相应的波的状态
    16     bool Propagate();
    17 
    18 private:
    19     float m_f32Cx;        //波源中心点坐标
    20     float m_f32Cy;
    21     float m_f32MaxAmp;    //t时刻中心点最大振幅,t=0最大
    22     float m_f32InitPhase; //初始相位
    23     float m_f32WaveLen;   //波长
    24     float m_f32Velocity;  //波速
    25     int   m_nTime;        //时间
    26 
    27     bool  m_bDisappear;   //该波源是否可以消失,随着时间变化,其能量衰减到很小的时候可以认为其消失了
    28     float m_f32CurRadius; //当前时刻波的半径,用于节约计算量的变量
    29 };
    CWaveSource

    这里面注释比较清楚,就不多说了。该对象对应的实现为:

     1 CWaveSource::CWaveSource(float f32Cx, float f32Cy, 
     2                          float f32MaxAmp, float f32Phase, 
     3                          float f32WaveLen, float f32V)
     4 {
     5     m_f32Cx = f32Cx;
     6     m_f32Cy = f32Cy;
     7     m_f32MaxAmp = f32MaxAmp;
     8     m_f32InitPhase = f32Phase;
     9     m_f32WaveLen = f32WaveLen;
    10     m_f32Velocity = f32V;
    11     m_nTime = 0;
    12     m_bDisappear = false;
    13     m_f32CurRadius = 1e-12;
    14 }
    15 CWaveSource::~CWaveSource()
    16 {
    17 
    18 }
    19 
    20 bool CWaveSource::Propagate()
    21 {
    22     //下一个时刻
    23     m_nTime++;
    24     m_f32CurRadius += m_f32Velocity;
    25     //考虑最大振幅随时间的衰减,这里的衰减函数可以自己设定
    26     //只要振幅随着时间递减即可,最好能做到比较自然
    27     m_f32MaxAmp /= 1.01f;
    28 
    29     //能量衰减到一定程度后,可以认为该波源已消失,通过返回让外面把它删掉
    30     if(m_f32MaxAmp < 1e-3)
    31     {
    32         m_bDisappear = true;
    33     }
    34     return m_bDisappear;
    35 }
    36 
    37 void CWaveSource::GetAmplitude(int x, int y, float &f32ax, float &f32ay)
    38 {
    39     f32ax = f32ay = 0.0f;
    40     //波源已消失,防止上层未删掉,耗计算量
    41     if(m_bDisappear) return;
    42     //注:这里可以建一个表,图像区域每个点相对波源中心点位置是固定的,即是f32Dist固定
    43     //那么位置对应的衰减系数就应该是固定的
    44     //这样对于一个波源只需计算一次f32Dist和衰减系数r了
    45     //此处为了代码的可读性,暂时不那样实现    
    46     float f32Radius = m_f32CurRadius;
    47     float f32Dx = (m_f32Cx - x);
    48     float f32Dy = (m_f32Cy - y);
    49 
    50     float f32Dist = f32Dx * f32Dx + f32Dy * f32Dy;
    51     if(f32Dist > f32Radius * f32Radius) return;
    52 
    53     f32Dist = sqrt(f32Dist);
    54     //考虑当前点最大振幅随位置的衰减,这里的衰减函数也可以自己设定
    55     float r = 1 - f32Dist / 1000.0f;//exp(-f32Dist/10);
    56     float f32MaxAmp = m_f32MaxAmp * r;
    57 
    58     //计算当前点的振幅,每个点的相位在不停变化的 y = Acos(2*pi*(vt-x)/wavelen + phase)
    59     float f32Temp = 2 * PI * (m_f32Velocity * m_nTime - f32Dist) / m_f32WaveLen + m_f32InitPhase;
    60     float f32CurAmp = f32MaxAmp * sin(f32Temp);
    61 
    62     f32ax = f32CurAmp * ABS(f32Dx)/f32Dist;
    63     f32ax = f32CurAmp * ABS(f32Dy)/f32Dist;
    64 }
    View Code

    好了,这样一个波源就已经构造好了。对图像的水波特效处理对象如下:

      1 class CRippling
      2 {
      3 public:
      4     CRippling();
      5     ~CRippling();
      6     
      7     //增加一个波源,该函数最好能重载一个直接输波源参数的,这里暂时忽略
      8     void AddSource(CWaveSource &WaveSource);
      9     
     10     //当前已有的波源对图像进行处理
     11     void Process(BYTE *pu8Dst, BYTE *pu8Src, int nWidth, int nHeight, int nChannels = 1);
     12     
     13     //所有波源传播
     14     void Propagate();
     15 
     16 private:
     17     //获取当前时刻所有波源作用下,像素点x,y对应于原始图像的像素位置,返回到x,y中
     18     void GetPos(float &x, float &y);
     19 
     20     //获取图像亚像素值,采用双线性插值
     21     void GetSubPixel(BYTE *pu8Dst, BYTE *pu8Src, int nWidth, int nHeight, int nChannels, float x, float y);
     22 
     23     //波源相关信息,因为需要插入和删除故采用list管理,也可以使用数组或者其他数据结构
     24     list<CWaveSource> m_lWaveSrc;
     25 };
     26 
     27 CRippling::CRippling()
     28 {
     29 }
     30 
     31 CRippling::~CRippling()
     32 {
     33 }
     34 
     35 void CRippling::AddSource(CWaveSource &WaveSource)
     36 {
     37     m_lWaveSrc.push_back(WaveSource);
     38 }
     39 
     40 void CRippling::Process(BYTE *pu8Dst, BYTE *pu8Src, int nWidth, int nHeight, int nChannels)
     41 {
     42     int nRow, nCol;
     43     float x,y;
     44     for(nRow = 0; nRow < nHeight; nRow++)
     45     {
     46         for(nCol = 0; nCol < nWidth; nCol++)
     47         {
     48             x = nCol;
     49             y = nRow;
     50             GetPos(x, y);
     51             GetSubPixel(pu8Dst, pu8Src, nWidth, nHeight, nChannels, x, y);
     52             pu8Dst += nChannels;
     53         }
     54     }
     55 }
     56 
     57 void CRippling::Propagate()
     58 {
     59     //每个波源都需要传播
     60     list<CWaveSource>::iterator It = m_lWaveSrc.begin();
     61     while(It != m_lWaveSrc.end())
     62     {
     63         bool bDisappear = It->Propagate();
     64         //波源可以消失,则删掉
     65         if(bDisappear) It = m_lWaveSrc.erase(It); 
     66         else It++;
     67     }
     68 }
     69 
     70 void CRippling::GetPos(float &x, float &y)
     71 {
     72     //根据平面简谐波的特性,某个点的位移是所有波位移和
     73     float f32AmpX = 0;
     74     float f32AmpY = 0;
     75     list<CWaveSource>::iterator It = m_lWaveSrc.begin();
     76     while(It != m_lWaveSrc.end())
     77     {
     78         It->GetAmplitude(x, y, f32AmpX, f32AmpY);
     79         x += f32AmpX;
     80         y += f32AmpY;
     81         It++;
     82     }
     83 }
     84 
     85 void CRippling::GetSubPixel(BYTE *pu8Dst, BYTE *pu8Src, int nWidth, int nHeight, int nChannels, float x, float y)
     86 {
     87     //采用双线性插值,就实际效果来看,最近邻插值也可以的
     88     int x0 = x;
     89     int y0 = y;
     90     int x1 = x0 + 1;
     91     int y1 = y0 + 1;
     92 
     93     float wx1 = x - x0;
     94     float wy1 = y - y0;
     95     float wx0 = 1.0f - wx1;
     96     float wy0 = 1.0f - wy1;
     97 
     98     x0 = MAX(x0, 0);
     99     x1 = MAX(x1, 0);
    100     y0 = MAX(y0, 0);
    101     y1 = MAX(y1, 0);
    102 
    103     x0 = MIN(x0, nWidth-1);
    104     x1 = MIN(x1, nWidth-1);
    105     y0 = MIN(y0, nHeight-1);
    106     y1 = MIN(y1, nHeight-1);
    107     for(int i = 0; i < nChannels; i++)
    108     {
    109         u8 * pu8Y0 = pu8Src + y0 * nWidth * nChannels;
    110         u8 * pu8Y1 = pu8Src + y1 * nWidth * nChannels;
    111 
    112         float sum_y0 = (pu8Y0[x0 * nChannels + i] * wx0 + pu8Y0[x1 * nChannels + i] * wx1);
    113         float sum_y1 = (pu8Y1[x0 * nChannels + i] * wx0 + pu8Y1[x1 * nChannels + i] * wx1);
    114         pu8Dst[i] = (BYTE)(sum_y0 * wy0 + sum_y1 * wy1);
    115     }
    116 }
    View Code

    测试代码采用隔一定时间增加一个波源,可以产生动态效果。其中调用了opencv读写和显示图像,相关的头文件和库自己加上即可。

     1 void test_rippling()
     2 {    
     3     Mat mImg = imread("C:/test2.jpg");
     4     imshow("org", mImg);
     5     CRippling rippling;
     6     for(int t = 0; t < 10000; t++)
     7     {
     8         if(t % 20 == 0)
     9         {
    10             CWaveSource wavesource(50 + rand() % 800, 50 + rand() % 300);
    11             rippling.AddSource(wavesource);
    12         }
    13 
    14         printf("
    times: %d", t);
    15         Mat mSrc = mImg.clone();
    16         Mat mDst = mImg.clone();
    17 
    18         rippling.Process(mDst.data, mSrc.data, mSrc.cols, mSrc.rows, 3);
    19         rippling.Propagate();
    20         imshow("pro", mDst);
    21         waitKey(1);
    22     }
    23     waitKey(0);
    24 }
    View Code

    这样基本就完成了,可以实现最开始那张图的效果了。

    总结 


      上面的分析与实现只是按照原始的产生与传播过程来做的,可以想象,随着波源数的增加,算法的耗时会不断增加,当然在代码注释中也在有的地方说明了可以优化的点。另外,如果我们仅仅是实现这样一个效果,很多波源参数给固定下来,那么可以在算法上进一步推导,从而简化运算。比如,固定波速与波长,让波的周期是4个或2个单位之间,那么应该可以推导出后一时刻位移为前面几个时刻位移的关系,这样就不用每次计算sin函数了。

      当然上面的推导只考虑了简单情况,特别地,成像一般会近大远小,因此在图像上下方波的扩张速度和质点偏移都是不一样的,对于完整的模型这些都是应该要考虑的因素。


  • 相关阅读:
    俞洪敏励志演讲
    svn 提交排除目录
    (二)2005年我的第一次软件行业创业,烧掉30万、2年时间打水漂的惨痛教训
    COM+ System Application 服务无法启动方法
    版本库迁移(合并)的实现
    没有为此解决方案配置选中要生成的项目
    Oracle 10g 下载地址
    JQuery插件开发[转]
    Web开发中的Form Submit Successful
    网页栅格系统研究(4):技术实现
  • 原文地址:https://www.cnblogs.com/jcchen1987/p/4275964.html
Copyright © 2011-2022 走看看