【瀝血整理】灰度(二值)圖像重構算法及其應用(morphological reconstruction)。

        不記得是怎麼接觸並最終研究這個課題的了,認識我的人都知道我是沒有固定的研究對象的,一切看運氣和當時的興趣。本來研究完了就放在那裏了,一直比較懶的去做總結,但是想一想似乎在網絡上就沒有看到關於這個方面的資料,能搜索到的都是一些關於matlab相關函數的應用,決定還是抽空趁自己對這個算法還有點記憶的時候寫點東西吧,畢竟這個算法還有一些應用是值得回味和研究的。而且也具有一定的工程價值。

         怎麼說呢,其實在很早瀏覽matlab的圖像處理工具箱的時候,就無數次的看到過這些函數,但是無奈當時不知道他們有什麼用,就沒怎麼鳥他, 其實M還是很重視他們,這個從他們在工具箱裏佔用的函數列表篇幅裏就能完美的看的出:

                 

  在Intensity and Binary Images功能區域裏,除去紅線劃除掉的2個算法和這個重構沒有啥關係,其他的都可以認爲是imreconstruct衍生出來的產物。

      一、重點函數

         我們先重點來看下imrecontruct函數的介紹,重要的文字描述有以下內容:

          imreconstruct         Morphological reconstruction 

Syntax

      IM = imreconstruct(marker,mask)
      IM = imreconstruct(marker,mask,conn)

Description

  IM = imreconstruct(marker,mask) performs morphological reconstruction of the image marker under the image mask. marker and mask can be two intensity images or two binary images with the same size. The returned image IM is an intensity or binary image, respectively. marker must be the same size as mask, and its elements must be less than or equal to the corresponding elements of mask.

      By default, imreconstruct uses 8-connected neighborhoods for 2-D images and 26-connected neighborhoods for 3-D images. For higher dimensions, imreconstruct uses conndef(ndims(I),'maximal').

      IM = imreconstruct(marker,mask,conn) performs morphological reconstruction with the specified connectivity. conn can have any of the following scalar values.

  他的意思是從用戶提供的mask圖像中重建原圖,似乎講的很模糊啊,有點不知所云。

  看看有沒有更多的信息呢,在M對應幫助文檔的尾部,有這樣一段話:

Algorithms

    imreconstruct uses the fast hybrid grayscale reconstruction algorithm described in [1].
References

    [1] Vincent, L., "Morphological Grayscale Reconstruction in Image Analysis: Applications and Efficient Algorithms," IEEE Transactions on Image Processing, Vol. 2, No. 2, April, 1993, pp. 176-201.

      看到沒有,有參考資料,我就喜歡matlab和halcon這樣比較開明的軟件,即使不提供源代碼,他也會提供一些非常有用的資料,比如參考資料,比如論文或者計算的數學公式。

  拜託幾個朋友,FQ終於下載到了這個對應的文章,於是像飢餓的老鼠一樣撲在麪包上苦心的研究了幾天,終於有所成。

      要理解這個算法的數學原理呢,我想我是解釋不清楚,還是要專心的把那個論文打印出來,然後一個人關在小房間裏慢慢的啃裏面的數學公式,我呢,看了幾天,也就獲得這個原理的那麼一點點朦膿的感覺,在這裏可不敢隨便解釋。也就拿論文中的一個簡單的圖來說下事。

      以一個簡答的二值圖的重構來說明下這個算法大概在幹什麼,以下圖爲例:

               

  這個定義簡單翻譯就是從標記圖像J中重建圖像I的過程爲,在I中找到包含至少一個J像素的連續區域。

  那麼在左側圖中,1、2、3處是我們標記的位置圖J,原圖就是去掉1、2、3哪些黑色的(對應部分恢復爲周邊底色),根據這個定義,由1、2、3這個對應的位置去找包含他們的目標,最終找到右側的結果圖,而拋棄掉不含有他們的那些目標。

  如果給你一個這樣的需求,你如何寫代碼呢。

  這個定義只適合理解的他的意思和需求,但是還是無法從定義中尋找代碼的書寫方式的。 那麼文章裏又給出了第二種定義的方式:

  

  其中   

       一頭霧水是吧,我也是看的一頭霧水。

    好了,我不裝了,我攤牌了,其實就是這麼個意思,要從標記的圖像中恢復圖像,怎麼辦呢,我們進行迭代,每次迭代中呢,先求Marker圖像的3*3領域的最大值(standard dilation of size one),然後再把這個最大值和原始圖像求最小值得到一副臨時圖像,不斷的重複這個過程,知道圖像沒有任何的變換,則結果計算,這個沒有任何變化的圖像就是我們重構後的圖像。針對上面的二值圖像,好好的想一想,是不是這個過程確實可以實現剛剛定義1裏的需求呢,靜下心來想一想哦。

  擴展到灰度圖像,似乎上述定義1就成了無法理解的行爲了,確實是這樣的,但是我們如果不管這些,定義的操作從程序的角度來說灰度圖也是毫無區別的。那麼在論文中也是這樣推廣到灰度的。

  論文裏也給出代碼的實現的定義:

  很多人基礎差,寫不出代碼,我好人做到底,按照上述的意思一個簡單的代碼如下所示:

//    標準的可並行版本的8領域重建算法,雖可極度優化,但是無賴迭代次數太多
int IM_ReConstruct_Standard_Connected8(unsigned char *Src, unsigned char *Marker, unsigned char *Dest, int Width, int Height, int Stride)
{
    int Channel = Stride / Width;
    if (Src == NULL)                                        return IM_STATUS_NULLREFRENCE;
    if ((Width <= 0) || (Height <= 0))                        return IM_STATUS_INVALIDPARAMETER;
    if (Channel != 1)                                        return IM_STATUS_NOTSUPPORTED;
    int Status = IM_STATUS_OK;
    int MaxIteration = 65536, Iteration = 0;
    unsigned char *Temp = (unsigned char *)malloc(Height * Stride * sizeof(unsigned char));
    if (Temp == NULL)    return IM_STATUS_NULLREFRENCE;
    //    爲了不破壞Marker的數據,對其做個備份
    memcpy(Temp, Marker, Height * Stride * sizeof(unsigned char));

    while (Iteration < MaxIteration)
    {
        Iteration++;
        for (int Y = 0; Y < Height; Y++)
        {
            for (int X = 0; X < Width; X++)
            {
                int X0 = X - 1 >= 0 ? X - 1 : 1;
                int X2 = X + 1 < Width ? X + 1 : Width - 2;
                int Y0 = Y - 1 >= 0 ? Y - 1 : 1;
                int Y2 = Y + 1 < Height ? Y + 1 : Height - 2;

                int Index0 = Y0 * Stride;
                int Index1 = Y * Stride;
                int Index2 = Y2 * Stride;

                int V0 = Temp[Index0 + X0];
                int V1 = Temp[Index0 + X];
                int V2 = Temp[Index0 + X2];
                int V3 = Temp[Index1 + X0];
                int V4 = Temp[Index1 + X];
                int V5 = Temp[Index1 + X2];
                int V6 = Temp[Index2 + X0];
                int V7 = Temp[Index2 + X];
                int V8 = Temp[Index2 + X2];

                int Max1 = IM_Max(IM_Max(V0, V1), IM_Max(V2, V3));
                int Max2 = IM_Max(IM_Max(V5, V6), IM_Max(V7, V8));
                int Max = IM_Max(IM_Max(Max1, Max2), V4);
                Dest[Index1 + X] = Max;
            }
        }
        int DiffAmount = 0;
        for (int Y = 0; Y < Height; Y++)
        {
            int Index = Y * Stride;
            for (int X = 0; X < Width; X++)
            {
                int Value = IM_Min(Dest[Index], Src[Index]);
                if (Temp[Index] != Value)    DiffAmount++;
                Temp[Index++] = Value;
            }
        }
        if (DiffAmount == 0)            break;
    }
    memcpy(Dest, Temp, Height * Stride * sizeof(unsigned char));
    free(Temp);
    return IM_STATUS_OK;
}

  是不是很簡單。

       但是一看這個代碼就指導,可能速度不是很好,因爲每次迭代基本上也就是處理 Marker圖像外圍的一個像素左右寬度的圖,一般都需要迭代很多次才能收斂。

  接着論文有提出了一種改進的方式(序列化方式重建):

        

 

   其中額NG+和NG-的意思如下圖:  

                               

   通過上面的代碼IM_ReConstruct_Standard_Connected8你能否能寫出這個版本的算法呢,我就沒有必要提供了吧,不過這個雖然有所提高速度,但還是很慢。

  後續論文還給出了2個優化方面的代碼,一個叫reconstruction using a queue of pixels,這個的算法基礎呢,是什麼呢,就是上面的重建工作,其實沒有必要針對marker圖像J的每一個像素,而只需要針對邊緣進行處理,這個邊緣要是廣義的邊緣,對於二值圖,就是如果J中一個像素是1,那麼主要他3*3領域內有1個像素值不爲1,他就是一個邊緣,而對於灰度圖,這個概念得以擴展,指的是如果一個像素是其3*3領域的最大值,那他距考慮爲邊緣。 我們找到marker圖像中所有的邊緣點,並把它們加入到一個叫FIFO(First-In-First-Out )的數據結構中,好像C++裏有這種,似乎是叫dqueue。然後不斷的迭代處理,指導收斂,比如灰度的處理方法如下所示:

  

   論文最後提出了一種混合(翻譯爲雜交總覺得好畜生)的算法,即結合序列化算法和上述FIFO一起實現,第一步先用序列化算法執行一次循環,然後在用FIFO來處理。    

            

   matlab裏也是使用的這種算法來實現,但是我在用這種算法實現時,發現總是有些圖和matlab的結果不一致,但有些圖又正常,一直沒有找到問題的癥結所在,同時我發現第三種寫法實際上也沒有比最後的混合算法慢多少,而他的結果非常穩定,和matlab基本完全一致,因此,我選擇第三種算法的實現。

  二、算法的直接和間接應用

  理論部分講完,現在在來談談這個算法比較精彩的部分。

       1、基本功能

       接下來我們來了解下這個算法的直接應用,我們先來驗證下前面舉例的那個圖片吧,因爲我程序裏認爲白色部分爲目標,所以圖像的結果和上面有點反。

                   

        原圖I                                Marker圖J                    結果圖

  可以看到結果很完美的體現了論文的需求。

  二、清除邊界部分的目標

  在很多應用中,我們需要清除掉那些和邊界連接在一起的目標,要實現這個功能,一個可行的方法是構建一副這樣的Marker圖像,圖像中間部位全部填充爲0(就是最小值),而周邊區域則爲原始圖像的值,這樣從就從邊緣的部位開始向內重構,和邊緣連接的目標就找到了,而未和邊緣連接的對象則被去除掉,這不是和我們的目標相反了嘛,別急,我們在原圖減去這個圖不就可以了嗎。

  如下圖所示(未找到有代表性的二值圖,下圖只是示意功能)。

         

       原圖                    提取出的Marker圖像                                     提取出的邊緣圖像                                最終得到的結果圖

  當然這裏還有一些問題,有些邊界部分可能不需要被去除,那這個時候就需要通過其他的算法對這個結果再次進行補償了。

  這個對應了matlab裏的imclearborder。對應的描述如下所示:

                 IM2 = imclearborder(IM) suppresses structures that are lighter than their surroundings and that are connected to the image border. (In other words, use this function to clear the image border.) I

  簡單的代碼爲;

int IM_ClearBorder(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Connectivity)
{
    int Channel = Stride / Width;
    if (Src == NULL)                                        return IM_STATUS_NULLREFRENCE;
    if ((Width <= 0) || (Height <= 0))                        return IM_STATUS_INVALIDPARAMETER;
    if (Channel != 1)                                        return IM_STATUS_NOTSUPPORTED;
    int Status = IM_STATUS_OK;

    unsigned char *Temp = (unsigned char *)malloc(Height * Stride);
    if (Temp == NULL)    return IM_STATUS_NULLREFRENCE;
    memset(Temp, 0, Height * Stride);        //    先全部賦值爲0
    memcpy(Temp, Src, Width);                //    邊緣部位填充爲原圖的值
    memcpy(Temp + (Height - 1) * Stride, Src + (Height - 1) * Stride, Width);
    for (int Y = 1; Y < Height - 1; Y++)
    {
        Temp[Y * Stride] = Src[Y * Stride];
        Temp[Y * Stride + Width - 1] = Src[Y * Stride + Width - 1];
    }

    Status = IM_ReConstruct(Src, Temp, Dest, Width, Height, Stride, Connectivity);
    if (Status != IM_STATUS_OK)        goto FreeMemory;

    Status = IM_SubtractImage(Src, Dest, Dest, Width, Height, Stride);

    if (Status != IM_STATUS_OK)        goto FreeMemory;

FreeMemory:
    free(Temp);
    return Status;
}

  那麼針對灰度圖像,這個還真有一些比較牛逼的功能,我們看看下面這個圖的處理結果:

                   

                               原圖               提取出的Marker圖像                                    提取出的邊緣圖像                                最終得到的結果圖

  我們利用這個清除邊界的算法成功的提取出了原圖中的五個中心十字架的圖像。

  這個感覺有點像從邊緣位置的像素(每個點都作爲種子)向內部進行區域生長,這樣中間這幾個十字架因爲基本被黑色的區域包圍而沒有和周邊的圓形環接觸,就被獨立出來了。

       三、填充孔洞

  什麼是孔洞,針對二值圖像,我們的定義爲:孔洞指的是那些不和邊界連接在一起的最小局部區域(簡易的就是黑色區域)。怎麼又和邊界扯上了呢,呵呵,就是這樣。有了這個定義,那和我們的重構算法有什麼關係呢。

  其實啊,你想啊,如果我把原圖反相後(白變黑,黑變白),這個時候我在同樣以反相後的圖像的邊界圖像爲Marker圖像,是不是就那些沒有被邊界連接起來的最大局部區域(最小的區域已經被反色)就被隔離了呢,這樣我把結果再次反色後是不是就得到了想要的結果呢(針對下面的測試圖,連通域需要選擇爲4)。

            

                       原圖                  反色圖像                                          從反色圖像提取出的邊緣圖像                           基於反色後的邊緣重構                                    再次反色

 matlab對應的函數爲:

             BW2 = imfill(BW,'holes') fills holes in the binary image BW. A hole is a set of background pixels that cannot be reached by filling in the background from the edge of the image.

  我們可以看下這個函數的部分M代碼:

    marker = mask;
    idx = cell(1,ndims(I));
    for k = 1:ndims(I)
        idx{k} = 2:(size(marker,k) - 1);
    end
    marker(idx{:}) = Inf;

    mask = imcomplement(mask);
    marker = imcomplement(marker);
    I2 = imreconstruct(marker, mask, conn);
    I2 = imcomplement(I2);
    I2 = I2(idx{:});

  邏輯稍微有點不一樣,他是先提取邊緣圖像後在反色邊緣圖像,結果是一樣的。

  當然,這個填充孔洞有個缺點,就是他是填充了所有的孔洞,而不可以運用一些其他的規則連限制孔洞的特性,比如孔洞的大小,圓度等等。這個就需要另外寫函數了。

  如果對於灰度圖像,這個函數也有一些表有意思的結果:

                 

      原圖                填充孔洞後的結果  

  可以看到,他把中間那些文字和一些比較黑的地方都去除了,也許這個結果某些場合下比較有用。

  四、雙閾值圖像分割

  有些圖像比較複雜, 要從複雜的背景圖像中分割出目標圖像,單個閾值很多情況是難以做到的,如果存在這一種情況:即較小的閾值能分割出目標的主體部分,但是也會帶入一些背景,但是背景和主體部分部想連,而較大的閾值側能分割出目標的部分主體,但是基本沒有啥背景圖影響,那麼這個時候就可以用較大閾值分割後的二值圖作爲Marker圖像,較小閾值得到的那個圖作爲原圖進行重構,這樣就能得到較爲滿意的結果。

      

  5、丟失目標的恢復

  我們在進行目標查找的時候,經常進行各種預處理操作,比如開閉操作,頂帽變換等等,這些變換在達到了預處理的目的後,總會多多少少的改變了哪些正常的目標圖,這往往不是不是我們想要的。這個時候我們就可以用重構算法來回復他們,比如下圖:

               

          原始圖              某種處理後去掉了不需要的目標,但改變了正常目標原始形態         使用重建進行回覆   

  6、區域最大值和最小值

  這裏的區域最大值和最小值不是我們立即的普通意義的最大值和最小值,其嚴格的定義應該是:

          A regional minimum M of an image f at elevation t is a connected component of pixels with the value t whose external boundary pixels have a value strictly greater than t。

   也就是這不是指的一個像素,而是一個連續的區域,這些區域具有相同的像素值t,並且其領域的像素都比他或者小。

   論文裏這樣的描述: Image minima and maxima are important morphological features because they often mark relevant image objects: minima for dark objects ami maxima for bright objects. In morphology, the term minimum is used in the sense ofregional minimum, i.e. , a minimum whose extent is not necessarily restricted to a unique pixel.

  至於這個東西在形態說如何重要,我還真的不瞭解。我在halcon的有關函數裏也看到這樣的算子,比如: local_max   local_min

  Local_min extracts all points from Image having a gray value smaller than the gray value of all its neighbors and returns them in LocalMinima. 

  matlab提供了imregionalmax、imregionalmin這樣的算子來實現這個功能。

  在實現上,其實就是以原圖的值-1作爲marker圖像,對原圖進行重構。、

                    

  這個算法主要針對的是灰度圖像,對二值圖沒有什麼意義。

           

        原圖                    regional max             halcon的local_max  算子(疊加顯示)

  那麼還可以進一步擴展爲extendedmax, extendedmin, Hmax, Hmin等算子,不過目前我對他們的應用瞭解的不多,有點不知道如何讓他們產生價值。

       7、圖像分割封面的輔助功能

  這個方面我也不太瞭解,有興趣的作者可以去看看相關的論文。僅僅貼兩張圖予以展示。

  

   8、其他

    這算法還有其他的一些應用,比如matlab裏面的imimposemin函數,還比如終極腐蝕點的定位等等,都可以,待將來有時間來再仔細的研究研究。

  我已經將這個功能集成到我自己的DEMO中了,速度上划算行,因爲這個算法不太好用SIMD指令集優化,只能純C實現。

  

  再次列出參考文獻的名稱供有需要的朋友瞭解:

  1、Soille, P., Morphological Image Analysis: Principles and Applications, Springer-Verlag, 1999, pp. 172-173.

  2、 Vincent, L., "Morphological Grayscale Reconstruction in Image Analysis: Applications and Efficient Algorithms," IEEE Transactions on Image Processing, Vol. 2, No. 2, April, 1993, pp. 176-201.

       可執行的DEMO下載地址爲:https://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar?t=1660121429

       如果想時刻關注本人的最新文章,也可關注公衆號:

                              

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章