二值影象分析:案例實戰(文字分離+硬幣計數)

Tony沈哲發表於2017-04-17

影象的二值化

影象二值化就是將影象上的畫素點的灰度值設定為0或255,也就是將整個影象呈現出明顯的黑白效果。
將256個亮度等級的灰度影象通過適當的閾值選取而獲得仍然可以反映影象整體和區域性特徵的二值化影象。在數字影象處理中,二值影象佔有非常重要的地位,首先,影象的二值化有利於影象的進一步處理,使影象變得簡單,而且資料量減小,能凸顯出感興趣的目標的輪廓。其次,要進行二值影象的處理與分析,首先要把灰度影象二值化,得到二值化影象。

在實際應用中,很多影象的分析最終都轉換為二值影象的分析,比如:醫學影象分析、前景檢測、字元識別,形狀識別。二值化+數學形態學能解決很多計算機識別工程中目標提取的問題。

開操作演示---文字分離與切割

開操作是先腐蝕後膨脹的過程。用來消除小物體、在纖細點處分離物體、平滑較大物體的邊界的同時並不明顯改變其面積。

跟開操作相對應的是閉操作。另外,腐蝕和膨脹在下文中有介紹。

cv4j 中,我們封裝好了這些形態學的常用操作,比如開閉操作、腐蝕和膨脹等等。

其中,開操作的程式碼如下:

public class MorphOpen {
    /**
     * in order to remove litter noise block, erode + dilate operator
     *
     * @param binary
     * @param structureElement
     */
    public void process(ByteProcessor binary, Size structureElement) {
        Erode erode = new Erode();
        Dilate dilate = new Dilate();
        erode.process(binary, structureElement);
        dilate.process(binary, structureElement);
    }
}複製程式碼

先來看一個完整demo的效果圖

二值影象分析:案例實戰(文字分離+硬幣計數)
完整的demo效果.png

第三步如果看不太清楚,我們看一下放大的效果圖

二值影象分析:案例實戰(文字分離+硬幣計數)
放大第三步的操作.png

如上圖所示,demo完成了文字的切割。我們來看看具體的程式碼是怎麼實現的。

準備工作展示原圖

        Resources res = getResources();
        final Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.test_binary1);
        image0.setImageBitmap(bitmap);複製程式碼

第一步二值化

        CV4JImage cv4JImage = new CV4JImage(bitmap);
        Threshold threshold = new Threshold();
        threshold.process((ByteProcessor)(cv4JImage.convert2Gray().getProcessor()),Threshold.THRESH_TRIANGLE,Threshold.METHOD_THRESH_BINARY_INV,255);
        image1.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());複製程式碼

第二步開操作

MorphOpen morphOpen = new MorphOpen();
cv4JImage.resetBitmap();
morphOpen.process((ByteProcessor)cv4JImage.getProcessor(),new Size(5));

image2.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());複製程式碼

第三步連通元件標記

        ConnectedAreaLabel connectedAreaLabel = new ConnectedAreaLabel();
        byte[] mask = new byte[cv4JImage.getProcessor().getWidth() * cv4JImage.getProcessor().getHeight()];
        List<Rect> rectangles = new ArrayList<>();
        connectedAreaLabel.process((ByteProcessor)cv4JImage.getProcessor(),mask,rectangles,true);
        cv4JImage.resetBitmap();
        Bitmap newBitmap = cv4JImage.getProcessor().getImage().toBitmap();

        if (Preconditions.isNotBlank(rectangles)) {
            Tools.drawRects(newBitmap,rectangles);
        }

        image3.setImageBitmap(newBitmap);複製程式碼

其實,做完第三步再結合ocr就可以識別出具體文字啦。如果再結合一下網路爬蟲的話,意義更大。

雖然, cv4j 目前還只是移動端的庫,但是它畢竟是java開發的,改成適合desktop的很容易。

腐蝕操作演示---硬幣計數

腐蝕操作是一種消除邊界點,使邊界向內部收縮的過程。可以用來消除小且無意義的物體。腐蝕操作掃描影象的每一個畫素,用結構元素與其覆蓋的二值影象做“與”操作:如果都為1,結果影象的該畫素為1,否則為0。

跟腐蝕操作相對的是膨脹操作。腐蝕用於分割獨立的影象元素,而膨脹用於連線相鄰的元素。

腐蝕的演算法:

二值影象分析:案例實戰(文字分離+硬幣計數)
腐蝕操作.png

其中,g(x,y)為腐蝕後的灰度影象,f(x,y)為原灰度影象,B為結構元素。腐蝕運算是由結構元素確定的鄰域塊中選取影象值與結構元素值的差的最小值。

可以簡化為:

二值影象分析:案例實戰(文字分離+硬幣計數)
簡化的腐蝕操作.png

來看一個例子,原圖中有很多硬幣,通過一步步的分析計算出硬幣的個數。

二值影象分析:案例實戰(文字分離+硬幣計數)
硬幣計數1.png

二值影象分析:案例實戰(文字分離+硬幣計數)
硬幣計數2.png

準備工作展示原圖

        Resources res = getResources();
        final Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.test_coins);
        image0.setImageBitmap(bitmap);複製程式碼

第一步二值化

        CV4JImage cv4JImage = new CV4JImage(bitmap);
        Threshold threshold = new Threshold();
        threshold.process((ByteProcessor)(cv4JImage.convert2Gray().getProcessor()),Threshold.THRESH_OTSU,Threshold.METHOD_THRESH_BINARY_INV,255);
        image1.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());複製程式碼

第二步腐蝕操作

        Erode erode = new Erode();
        cv4JImage.resetBitmap();
        erode.process((ByteProcessor)cv4JImage.getProcessor(),new Size(3),10);
        image2.setImageBitmap(cv4JImage.getProcessor().getImage().toBitmap());複製程式碼

第三步連通元件標記

        ConnectedAreaLabel connectedAreaLabel = new ConnectedAreaLabel();
        byte[] mask = new byte[cv4JImage.getProcessor().getWidth() * cv4JImage.getProcessor().getHeight()];

        int num = connectedAreaLabel.process((ByteProcessor)cv4JImage.getProcessor(),mask,null,false); // 獲取連通元件的個數

        SparseIntArray colors = new SparseIntArray();
        Random random = new Random();

        int height = cv4JImage.getProcessor().getHeight();
        int width = cv4JImage.getProcessor().getWidth();
        int size = height * width;
        for (int i = 0;i<size;i++) {
            int c = mask[i] & 0xff;
            colors.put(c,Color.argb(255, random.nextInt(255),random.nextInt(255),random.nextInt(255)));
        }

        cv4JImage.resetBitmap();
        Bitmap newBitmap = cv4JImage.getProcessor().getImage().toBitmap();

        for(int row=0; row<height; row++) {
            for (int col = 0; col < width; col++) {

                int c = mask[row*width+col] & 0xff;
                if (c>0) {
                    newBitmap.setPixel(col,row,colors.get(c));
                }
            }
        }

        image3.setImageBitmap(newBitmap);

        if (num>0)
            numTextView.setText(String.format("總計識別出%d個硬幣",num));複製程式碼

最終獲取了連通元件的個數也就是硬幣的個數,並且在已經識別的硬幣上隨機著色。

#總結
cv4jgloomyfish和我一起開發的影象處理庫,純java實現,目前還處於早期的版本。這周,我們開始做二值影象的分析(腐蝕、膨脹、開閉操作、輪廓提取等等),這個模組並沒有完成全部功能,預計下週能完工。

先前的文章:
Java實現高斯模糊和影象的空間卷積
Java實現圖片濾鏡的高階玩法
Java實現圖片的濾鏡效果

相關文章