【算法隨記五】使用FFT變換自動去除圖像中嚴重的網紋。

  這個課題在好久之前就已經有所接觸,不過一直沒有用代碼去實現過。最近買了一本《機器視覺算法與應用第二版》書,書中再次提到該方法:使用傅里葉變換進行濾波處理的真正好處是能夠經過使用定製的濾波器來消除圖像中某些特定頻率,例如這些特定頻率可能表明着圖像中重複出現的紋理。算法

  在網絡上不少的PS教程中,也有提到使用FFT來進行去網紋的操做,其中最爲普遍的是使用PS小插件FOURIER TRANSFORM,使用過程爲:打開圖像--進行FFT RGB操做,而後定位到紅色通道,選取通道中除了最中心處的以外的白點區域,而後填充黑色,在返回綜合通道,點擊IFFT RGB,就OK了, 網絡

      

                原圖                            FFR RGB  頻譜圖ide

     

           用於消除與紋理對應的頻率的濾波器                      IFFT RGB處理的結果圖  函數

  針對這一幅,我曾嘗試在PS中用其餘的方法來去背景紋理,但是通常去網的同時也把相片模糊了,只有FFT去網紋插件能完美去掉相片的網紋並且不損傷畫質。測試

  這個插件有個特性,他要求輸入必須是3通道或者4通道的圖,可是用他處理完成後的圖雖然表面上看仍是3通道仍是4通道的,可是他已經失去了彩色信息了,咱們注意到他在進行FFT RGB操做後,RGB三個通道中,R通道保存了頻譜圖,G通道了保存了相位圖,B通道爲固定值128,頻譜和相位組合在一塊兒,只能回覆一個通道的信息,所以處理後的圖也只能是一個顏色了,這是這個插件的缺陷或者說做爲插件的必然性。spa

  按照這個思路,若是用戶提供了用於消除與紋理對應的頻率的濾波器,則該過程的一個大概算法流程以下所示:插件

int IM_TextureRemoval(unsigned char *Src, unsigned char *Mask, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; if ((Src == NULL) || (Dest == NULL))                        return IM_STATUS_NULLREFRENCE; if ((Width <= 0) || (Height <= 0))                            return IM_STATUS_INVALIDPARAMETER; if ((Channel != 1) && (Channel != 3))                        return IM_STATUS_INVALIDPARAMETER; if (Channel == 1) { Complex *Data = (Complex*)malloc(Width * Height * sizeof(Complex)); if (Data == NULL)     return IM_STATUS_OUTOFMEMORY; for (int Y = 0; Y < Height; Y++) { unsigned char *LinePS = Src + Y * Stride;                // 填充FFT變換的複數數據
            Complex *LinePD = Data + Y * Width; for (int X = 0; X < Width; X++) { LinePD[X].Real = LinePS[X]; LinePD[X].Imag = 0; } } IM_FFT2D(Data, Data, Width, Height, false, 0, 0);            // FFT變換
        IM_FFTShift(Data, Data, Width, Height);                      // 平移中心到圖像的中心 
        for (int Y = 0; Y < Height; Y++)                             // FFT變換的結果乘以用於消除與紋理對應的頻率的濾波器 
 { unsigned char *LinePS = Mask + Y * Stride; Complex *LinePD = Data + Y * Width; for (int X = 0; X < Width; X++) { LinePD[X].Real *= LinePS[X] * IM_INV255; LinePD[X].Imag *= LinePS[X] * IM_INV255; } } IM_IFFTShift(Data, Data, Width, Height); // 在反中心化
        IM_FFT2D(Data, Data, Width, Height, true, 0, 0);        // FFT逆變換

        for (int Y = 0; Y < Height; Y++)                        // 轉換成圖像
 { Complex *LinePS = Data + Y * Width; unsigned char *LinePD = Dest + Y * Stride; for (int X = 0; X < Width; X++) { LinePD[X] = IM_ClampToByte(LinePS[X].Real); } } free(Data); } else { } return IM_STATUS_OK; }

  這個過程也是很是簡單的。3d

  對於彩色的圖像,能夠把他們先劈成3個獨立的通道,而後調用上述單通道的處理方法,而後在合成。code

  不過這個方法仍是有限制的,他能處理的對象是有很是嚴重網紋的圖像,咱們測試過對於普通的身份證照片、摩爾紋等是起不到去除做用的,從頻譜上來講,就是要在頻譜上能看到分佈在四周處有一些很明顯的獨立的亮點。這些亮點就對應着紋理的頻率。對象

  上面的過程須要人工的參與,咱們這裏進行一下擴展,嘗試下對這類圖像進行自動的紋理去除。這裏的核心是找到紋理的頻率,也就是那些白色獨立的亮點。

  咱們看上面的FFT頻譜圖,這種顯示基本上都是對直接進行FFT變換後的浮點數據進行對數變換後,在線性映射到0到255範圍內的,有進行了log操做,數據壓縮了不少,致使頻譜圖的對比度不是很強,也不利於咱們分隔出那些亮點,若是咱們不記性這種操做,而是直接絕對值Clamp顯示,大概能獲得下面的效果: 

         

  這種效果的FFT圖很明顯更有利於紋理特徵的提取。

  下面的步驟就是:OSTU二值化 -- 》膨脹  --》 腐蝕 -- 》 反色  ---》中心覈保留  -- 》中值  獲得紋理頻率的濾波器。整個效果以下圖:

    

          二值化                     膨脹(半徑2)                腐蝕(半徑2)

     

                反色                    保留中心區域                中值(半徑1)

  稍微分析下原理吧(也不必定科學)。

  首先二值化,沒啥好說的。 二值後,咱們看到白色部分有不少零碎的部分,特別是圖像的中心區域的零碎化對最後的效果有很是很差的影響(咱們必須保持中心部分沒啥變化),因此後續使用了開操做來改善效果,先膨脹後腐蝕。 接着咱們反色一下,由於後續的濾波器是非中心區域的白色部分是要變爲黑色的,第五步,也是比較核心的步驟,咱們須要把中心部分的黑色部分變爲白色,由於這部分保留着圖像的大部分信息, 這裏咱們能夠採用基於4領域的區域生長法,由於在頻譜中的中心點,這一點二值後確定是白色的,在反色後就是白色,就以這一點爲種子點,向四周進行區域生長,這樣就能夠把中心處的黑色反色過來,而其餘地方的黑色保持不變。

  第五步的中值,或者能夠用其餘模糊來代替,也是有點必要的,對於有些圖像,通過前面的處理後,有些核心的線(垂直或者水平方向)也被標記爲黑色的了,正在處理完成的圖像中會帶來本來沒有的新條紋。

   

                原圖                                     頻譜圖

            去除中值濾波後的濾波器                                  對應的結果(有瑕疵)

 

 

          增長中值後的濾波器                                    對應的結果

  上述過程先關的函數以下所示:

// 根據頻譜圖預估紋理的頻譜蒙版區域,支持InPlace操做
int IM_GetTextureMask(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; if ((Src == NULL) || (Dest == NULL))                return IM_STATUS_NULLREFRENCE; if ((Width <= 0) || (Height <= 0))                    return IM_STATUS_INVALIDPARAMETER; if (Channel != 1)                                    return IM_STATUS_INVALIDPARAMETER; int Status = IM_STATUS_OK; unsigned char *Temp = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char)); if (Temp == NULL){ Status = IM_STATUS_OUTOFMEMORY;        goto FreeMemory; } int Threshold = 0; Status = IM_GetOSTUThreshold(Src, Width, Height, Stride, Threshold);    // 使用OSTU方法二值化
    if (Status != IM_STATUS_OK)        goto FreeMemory; Status = IM_Threshold(Src, Temp, Width, Height, Stride, Threshold);        // 二值化
    if (Status != IM_STATUS_OK)        goto FreeMemory; Status = IM_Dilate(Temp, Dest, Width, Height, Stride, 2, false);        // 先膨脹下(最大值),注意膨脹和腐蝕函數不支持InPlace操做
    if (Status != IM_STATUS_OK)        goto FreeMemory; Status = IM_Erode(Dest, Temp, Width, Height, Stride, 2, false);            // 而後在腐蝕(最小值),恢復原來的差很少大小,可是這樣中心區域不相鄰的點就少了不少
    if (Status != IM_STATUS_OK)        goto FreeMemory; Status = IM_Invert(Temp, Dest, Width, Height, Stride);                    // 這個時候的圖,紋理的頻譜和其餘核心能量區域都仍是白色,爲後續的處理須要先反色
    if (Status != IM_STATUS_OK)        goto FreeMemory; Status = IM_InvertCenter(Dest, Temp, Width, Height, Stride);            // 把中心的能量區域保留(白色),其餘的紋理的頻譜刪除(黑色)
    if (Status != IM_STATUS_OK)        goto FreeMemory; Status = IM_MedianBlur(Temp, Dest, Width, Height, Stride, 1, 50);        // 執行半徑爲1的中值,這樣可能能夠減小部分垂直或者水平的核心能力被刪除
    if (Status != IM_STATUS_OK)        goto FreeMemory; FreeMemory: if (Temp != NULL) free(Temp); return Status; }

  咱們注意到,上面的操做對紋理處頻率處對應的濾波器係數都爲0了,也就是這一塊的信息所有被消除了,固然實際操做時也能夠稍微羽化一下,對最後的結果影響不大。

      《任何未通知的轉載或轉發,都是豬狗不如的做爲》。

  根據上述的步驟,有選擇性的處理了幾幅圖,結果以下所示:

     

     

     

  能夠看出,雖然能再必定程度上去除網紋,可是也就有一些去除的不徹底,這主要仍是由於自動提取的濾波器仍是不夠準確,要想獲取更爲理想的結果,必須手動的予以修繕。

  對於常規的圖片,或者說紋理信息不明顯的圖,及時執行了上面的去紋理,圖片也基本上沒有什麼變化,由於按照上述方法獲得的濾波器基本都爲白色。

  本文算法的測試例程見 : http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar,位於菜單FFT-->TextureRemoval下。

相關文章
相關標籤/搜索