基於局部均方差相關信息的圖像去噪及其在實時磨皮美容算法中的應用。


     在1979年Lee發表的論文《Lee Filter Digital Image Enhancement and Noise Filtering by Use of Local Statistics》中,提出了基於局部信息去除加性噪音、乘性噪音及加性乘性混合噪音的方法,經過仔細的學習和編碼,發現其去除加性噪音的方法效果非常好,具有現在一些EPF算法類似的邊緣保留功能,並且其運算並不復雜,可以應用到類似於磨皮這種項目中。
     簡單的算法描述如下,對於一幅N*M大小灰度圖像,用表示(i,j)位置處的像素值,那么在(2*n+1)*(2*m+1)窗口內部的局部平均值及局部均方差可表示為:
                  
       及
             
       加性去噪后的結果為:
           
   其中:
                
       式(4)中σ為用戶輸入的參數。
       就是這么個簡單的過程,能平滑圖像但同時保持邊緣基本不受影響,比如下圖的結果:
 
       
      這個優良的品質讓其能在圖像磨皮方面發揮一定的作用。
      在來看看這個算法的效率如何。由上面的計算公式可以看到,其主要的計算量是局部均值以及均布均方差,均值的計算優化方式很多,典型的比如累計積分圖。而關於均布均方差的優化,推薦大家看這里:http://fiji.sc/Integral_Image_Filters,其核心的推倒公式為:
  

                  {\text{Var}}(X)={\frac  {1}{n}}\sum _{{i=1}}^{n}(x_{i}-\mu )^{2}\quad {\text{and}}\quad \mu ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}

        which expands to

                     {\text{Var}}(x)={\frac  {1}{n}}\sum _{{i=1}}^{n}\left(x_{i}^{2}-2x_{i}\mu +\mu ^{2}\right)

                                  ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}^{2}-{\frac  {1}{n}}\sum _{{i=1}}^{n}2x_{i}\mu +\mu ^{2}

                                  ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}^{2}-{\frac  {1}{n}}\sum _{{i=1}}^{n}2x_{i}\mu +{\frac  {1}{n^{2}}}\left(\sum _{{i=1}}^{n}x_{i}\right)^{2}

                                 ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}^{2}-{\frac  {2\mu }{n}}\sum _{{i=1}}^{n}x_{i}+{\frac  {1}{n^{2}}}\left(\sum _{{i=1}}^{n}x_{i}\right)^{2}

                                 ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}^{2}-{\frac  {2}{n^{2}}}\left(\sum _{{i=1}}^{n}x_{i}\right)^{2}+{\frac  {1}{n^{2}}}\left(\sum _{{i=1}}^{n}x_{i}\right)^{2}

                                 ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}^{2}-{\frac  {1}{n^{2}}}\left(\sum _{{i=1}}^{n}x_{i}\right)^{2}

                                 ={\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}^{2}-\left({\frac  {1}{n}}\sum _{{i=1}}^{n}x_{i}\right)^{2}

                                ={\frac  {1}{n}}\left(\sum _{{i=1}}^{n}x_{i}^{2}-{\frac  {1}{n}}\left(\sum _{{i=1}}^{n}x_{i}\right)^{2}\right)

       Both sums can be generated from two Integral Images over I({\vec  {x}}) and I({\vec  {x}})^{2} respectively.

       經過這樣的推導,可以看到局部均方差也可以通過累計積分圖快速實現,這樣的結果是程序的效率和局部的半徑參數無關,因此,效率非常高。
       我個人在這個的基礎上,從編碼角度進一步進行了優化,對於一幅30W(500*600)像素的彩色圖像處理時間約為20ms(I3的筆記本CPU)。
 
       上述公式是針對灰度圖像進行的,對於常見的RGB彩色圖,只要對R/G/B三通道分別進行處理就OK了。
 
       有了上述基礎,經過個人的摸索,對於磨皮應用,這個算法的兩個參數(1)半徑可取:max(Src->Width, Src->Height) * 0.02, 用戶輸入的σ可取10 + DenoiseLevel * DenoiseLevel * 5,其中DenoiseLevel 為磨皮的程度參數,范圍從1到10,越大磨皮越厲害。
        
       正如我在文章《 一種具有細節保留功能的磨皮算法 》中提出的一樣,磨皮算法需要考慮眼睛頭發等非膚色部位不被過分處理,一種簡單的處理方式就是用非常簡易的顏色判斷來決定是每個像素點是否為需要處理的部位,比如我在實際的處理中就簡單用了如下的方式:
           
     for (Y = 0; Y < Height; Y++)
    {
        LinePS = Src->Data + Y * Src->WidthStep;
        LinePD = Skin->Data + Y * Skin->WidthStep;
        for (X = 0; X < Width; X++)
        {
            if (LinePS[0] >20 && LinePS[1] > 40 && LinePS[2] > 50)  //    皮膚識別有很多算法,但沒有一個能完美的包含所有的皮膚區域,我認為寧願多處理一些非皮膚                                                                           區域,也比少處理更合理些
                LinePD[X] = 255;                                    //    為什么取這些數據,我只能告訴這些數據是前人的一些經驗沒有什么數學公式來推導和證明的
            LinePS += 3;            
        }
    }                                                               //    以上處理得到的是硬邊界,直接使用會到底結果圖有較為明顯的痕跡,因此可使用模糊平滑下

     為了讓識別的區域邊界不是特別生硬,需要對識別區域進行一定的處理,一般就是用羽化算法,標准的羽化算法是高斯模糊,但是高斯模糊有浮點計算,一種替代方式就是用方框模糊來替代,實際上兩種方式得到的結果在視覺上是沒有太多的區別的,但是方框模糊只有整數運算,效率上會高很多。

     這樣的識別處理后,把處理后圖和原圖進行Alpha混合,這樣即保留了磨皮的光滑區域,又能保證頭發等部位不被平滑掉。

        for (Y = 0; Y < Height; Y++)
        {
            LinePS = Src->Data + Y * Src->WidthStep;
            LinePD = Dest->Data + Y * Dest->WidthStep;
            LinePM = Mask->Data + Y * Mask->WidthStep;
            for (X = 0; X < Width; X++)
            {
                Alpha = LinePM[0]; 
                if (Alpha != 255)
                {
                    InvertAlpha = 255 - Alpha;                                         //    AlphaBlend的混合過程,使用DIV255來提高速度
                    LinePD[0] = Div255(LinePD[0] * Alpha + LinePS[0] * InvertAlpha);    //  Blue分量
                    LinePD[1] = Div255(LinePD[1] * Alpha + LinePS[1] * InvertAlpha);    //    Green分量    
                    LinePD[2] = Div255(LinePD[2] * Alpha + LinePS[2] * InvertAlpha);    //    Red分量
                }
                LinePS += 3;                                                            //    移動到下一個像素,24位                
                LinePD += 3;
                LinePM++;                                                               //    移動到下一個Mask值
            }
        }

     在處理完成后,從視覺角度考慮,整體圖還是有點模糊,這個時候應該進行了適度的銳化來增強圖像的整體銳度,最合適的是USM銳化,但是USM銳化是基於高斯模糊的,因此又非常耗時,這個時候可以考慮用最簡答的領域銳化方式來處理,比如借助於下面的卷積矩陣,就能獲得不錯的視覺效果。

        -1   0    -1 

        0     8    0      /     4 

       -1     0    -1

     考慮到這個算法對每個像素的亮度值的改變並不是很大,對於彩色圖像,如果能夠轉換到其他的包含亮度分量的顏色空間后,只對亮度進行處理,然后在轉換回來,應該對視覺的影響不大,這樣去除顏色空間的轉換時間,可以提高三倍的速度,是相當可觀的,常見的包含亮度的顏色空間有LAB,HSV,YUV,YCbCr等,其中YCbCr和RGB的轉換公式非常簡單,沒有浮點計算,對整體的效率影響不大,因此可以選用這個空間。

    再者,經過實驗,發現也可以把銳化操作放在對亮度處理這步,而不是放在最后,這樣,銳化操作也只需要處理一個分量,同樣能提高效率。

    貼出我處理的函數的流程:

/// <summary>
/// 實現圖像的磨皮。
/// <param name="Src">需要處理的源圖像的數據結構。</param>
/// <param name="Dest">需要處理的源圖像的數據結構。</param>
/// <param name="DenoiseMethod">磨皮的算法,0為雙邊磨皮,1為均方差磨皮。</param>
/// <param name="DenoiseLevel">磨皮的程度,有效范圍[1,10],數據越大,磨皮越明顯。</param>
/// <param name="WhiteMethod">美白的算法,0為Log曲線美白,1為色彩平衡磨皮。</param>
/// <param name="NonSkinLevel">美白的程度,有效范圍[1,10],數據越大,美白越明顯。</param>
///    <remarks>原圖、目標圖必須都是24位的。</remarks>
IS_RET __stdcall SkinBeautification(TMatrix *Src, TMatrix *Dest, int DenoiseLevel, int WhiteMethod, int WhiteLevel)
{
    if (Src == NULL || Dest == NULL) return IS_RET_ERR_NULLREFERENCE;
    if (Src->Data == NULL || Dest->Data == NULL) return IS_RET_ERR_NULLREFERENCE;
    if (Src->Width != Dest->Width || Src->Height != Dest->Height || Src->Channel != Dest->Channel || Src->Depth != Dest->Depth) return IS_RET_ERR_PARAMISMATCH;
    if (Src->Channel != 3) return IS_RET_ERR_NOTSUPPORTED;
    if ((DenoiseLevel < 1 || DenoiseLevel > 10)) return IS_RET_ERR_ARGUMENTOUTOFRANGE;
    if ((WhiteMethod != 0 && WhiteMethod != 1) || (WhiteLevel < 1 || WhiteLevel > 10)) return IS_RET_ERR_ARGUMENTOUTOFRANGE;

    IS_RET Ret = IS_RET_OK;

    TMatrix *Skin = NULL, *Y = NULL, *Cb = NULL, *Cr = NULL, *YY = NULL;
    unsigned char *Table = (unsigned char *)IS_AllocMemory(256, true);        
    Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Skin);                //    分配皮膚區域的內存
    if (Ret != IS_RET_OK) goto Done;

    Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Y);
    if (Ret != IS_RET_OK) goto Done;

    Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Cb);
    if (Ret != IS_RET_OK) goto Done;

    Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &Cr);
    if (Ret != IS_RET_OK) goto Done;

    Ret = IS_CreateMatrix(Src->Width, Src->Height, Src->Depth, 1, &YY);
    if (Ret != IS_RET_OK) goto Done;

    Ret = RGBToYCbCr(Src, Y, Cb, Cr);                                                             // 第一步: 將RGB轉換到YCbCr空間
    if (Ret != IS_RET_OK) goto Done;
    
    int SpaceError = 10 + DenoiseLevel * DenoiseLevel * 5;

    Ret = LeeAdditvieNoiseFilter(Y, YY, max(Src->Width, Src->Height) * 0.02, SpaceError);        //    第二步:對Y分量進行加性噪音的去除
    if (Ret != IS_RET_OK)  goto Done;

    Ret = Sharpen(YY, YY);                                                                       //    第三步:對處理后的Y分量進行銳化
    if (Ret != IS_RET_OK)  goto Done;

    YCbCrToRGB(YY, Cb, Cr, Dest);                                                                //    第四步:將圖像從YCbCr空間轉換會RGB空間

    if (WhiteMethod == 0)
    {
        for (int V = 0; V < 256; V++)
        {
            //  Table[V] = .........
        }
    }
    else if (WhiteMethod == 1)
    {
        for(int V = 0; V < 256; V++)
        {
            //  Table[V] = ............
        }
    }

    Ret = Curve(Dest, Dest, Table, Table, Table);                              //    第五步:在RGB空間對磨皮后的圖進行美白處理
    if (Ret != IS_RET_OK)  goto Done;

    Ret = GetRawSkinRegion(Src, Skin);                                         //    第六步:分析圖像大概的皮膚區域
    if (Ret != IS_RET_OK)  goto Done;

    Ret = BlendImageWithMask(Src, Dest, Skin);                                 //    第七步:對全局磨皮、美白后的圖和原始圖按照屬於皮膚區域的程度進行混合
    if (Ret != IS_RET_OK)  goto Done;

Done:;
    IS_FreeMatrix(&Skin);                                                      //    注意釋放內存
    IS_FreeMatrix(&Y);
    IS_FreeMatrix(&Cb);
    IS_FreeMatrix(&Cr);
    IS_FreeMatrix(&YY);
    IS_FreeMemory(Table);
    return IS_RET_OK;
}

   第五步的美白處理如果放在對亮度分量的處理過程中,圖像整體會有所偏色,似乎有一種膚色紅潤的效果,但是對部分圖像會感覺顏色不自然。

      各部分耗時比例見下圖,測試圖像大小是500*600。

序號

函數名

用時(ms)

百分比(%)

備注

1

RGBToYCbCr

2

10

 

2

LeeAdditvieNoiseFilter

7

35

 

3

Sharpen

2

10

 

4

YCbCrToRGB

2

10

 

5

Curve

1

5

 

6

GetRawSkinRegion

4

20

 

7

BlendImageWithMask

2

10

 

8

合計

20

100

 

 

 

 

       
      
 
 
       

 

 

 

 

 

 

     

 

    各種效果比較如下:

        

                                                原圖                                                                     磨皮后的圖

        

                                  刪除第三步后的圖                                                                                                                刪除第六第七步后的圖

 

  這個算法最大的優點是整個過程的任何函數都沒有浮點計算,這對於某些硬件來說是很重要的,但是一個缺點是優化后的算法不能並行,在我的I3筆記本電腦上30W的像素處理時間20ms,完全能實現實時效果。

     有興趣的朋友可根據我的描述自己實現,測試程序: 快速磨皮

     可調參數如下界面所示:

 

 

 

****************************作者: laviewpbt   時間: 2015.7.27    聯系QQ:  33184777 轉載請保留本行信息**********************

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM