影象中二維碼的檢測和定位

Tony沈哲發表於2017-08-23

二維碼

二維條碼/二維碼(2-dimensional bar code)是用某種特定的幾何圖形按一定規律在平面(二維方向上)分佈的黑白相間的圖形記錄資料符號資訊的;在程式碼編制上巧妙地利用構成計算機內部邏輯基礎的“0”、“1”位元流的概念,使用若干個與二進位制相對應的幾何形體來表示文字數值資訊,通過圖象輸入裝置或光電掃描裝置自動識讀以實現資訊自動處理:它具有條碼技術的一些共性:每種碼制有其特定的字符集;每個字元佔有一定的寬度;具有一定的校驗功能等。同時還具有對不同行的資訊自動識別功能、及處理圖形旋轉變化點。

QR-Code-Overview.jpeg
QR-Code-Overview.jpeg

定點陣圖案

  • Position Detection Pattern是定點陣圖案,用於標記二維碼的矩形大小。這三個定點陣圖案有白邊叫Separators for Postion Detection Patterns。之所以三個而不是四個意思就是三個就可以標識一個矩形了。
  • Timing Patterns也是用於定位的。原因是二維碼有40種尺寸,尺寸過大了後需要有根標準線,不然掃描的時候可能會掃歪了。
  • Alignment Patterns 只有Version 2以上(包括Version2)的二維碼需要這個東東,同樣是為了定位用的。

通過查詢定點陣圖案,可以實現二維碼掃描的檢測和定位。

檢測和定位的步驟

先對圖片進行灰度處理:

image = image.getImage().convert2Gray().getProcessor();
ByteProcessor src = ((ByteProcessor)image);複製程式碼

再對影象做二值化處理:

Threshold t = new Threshold();
t.process(src, Threshold.THRESH_OTSU, Threshold.METHOD_THRESH_BINARY_INV, 20);複製程式碼

然後是對y、x方向進行形態學上的開操作

        MorphOpen mOpen = new MorphOpen();
        byte[] data = new byte[width*height];
        System.arraycopy(src.getGray(), 0, data, 0, data.length);
        ByteProcessor copy = new ByteProcessor(data, width, height);
        mOpen.process(src, new Size(n1, n2)); // Y方向開操作
        src.getImage().resetBitmap();

        mOpen.process(copy, new Size(n2, n1)); // X方向開操作
        CV4JImage cv4JImage = new CV4JImage(width,height);
        ((ByteProcessor)cv4JImage.getProcessor()).putGray(copy.getGray());複製程式碼

所謂開操作是指先腐蝕後膨脹的操作。在之前的文章二值影象分析:案例實戰(文字分離+硬幣計數)曾經介紹過開操作的用途。

import com.cv4j.core.datamodel.ByteProcessor;
import com.cv4j.core.datamodel.Size;

public class MorphOpen {

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

接下來是標記聯通區域,找到二維碼的三個特徵區域,也就是定點陣圖案。

        // 聯通元件查詢連線區域
        ConnectedAreaLabel ccal = new ConnectedAreaLabel();
        ccal.setFilterNoise(true);
        List<Rect> rectList = new ArrayList<>();
        int[] labelMask = new int[width*height];
        ccal.process(src, labelMask, rectList, true);
        float w = 0;
        float h = 0;
        float rate = 0;
        List<Rect> qrRects = new ArrayList<>();
        for(Rect roi : rectList) {

            if (roi == null) continue;

            if((roi.width > width/4 || roi .width < 10) || (roi.height < 10 || roi.height > height/4))
                continue;

            if((roi.x < 10 || roi.x > width -10)|| (roi.y < 10 || roi.y > height-10))
                continue;

            w = roi.width;
            h = roi.height;
            rate = (float)Math.abs(w / h  - 1.0);
            if(rate < 0.05 && isRect(roi, labelMask, width, height,true)) {
                qrRects.add(roi);
            }
        }複製程式碼

最後,通過定點陣圖案能夠找到二維碼所在的區域,如果找不到會返回空的矩形。否則返回一個Rect,它表示找到的二維碼所在影象中的區域。

我們可以對該區域進行標識,下面是演算法的具體使用,找到影象中的二維碼之後,用紅色的邊框框起來。

        CV4JImage cv4JImage = new CV4JImage(bitmap);

        QRCodeScanner qrCodeScanner = new QRCodeScanner();
        Rect rect = qrCodeScanner.findQRCodeBounding(cv4JImage.getProcessor(),1,6);

        Bitmap bm = bitmap.copy(Bitmap.Config.ARGB_8888, true);
        Canvas canvas = new Canvas(bm);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth((float) 10.0);
        paint.setStyle(Paint.Style.STROKE);

        android.graphics.Rect androidRect = new android.graphics.Rect(rect.x-20,rect.y-20,rect.br().x+20,rect.br().y+20);
        canvas.drawRect(androidRect,paint);
        image.setImageBitmap(bm);複製程式碼

定點陣圖片中的二維碼區域.png
定點陣圖片中的二維碼區域.png

定位有創意的二維碼.png
定位有創意的二維碼.png

截圖微信的二維碼.png
截圖微信的二維碼.png

對於iPhone截圖之後的圖片,該圖片尺寸是1242 × 2208。在沒有對圖片做任何縮放處理的情況下,使用該演算法進行定位二維碼的區域也是ok的。

大圖中的二維碼.png
大圖中的二維碼.png

當然,對於大圖如果適當地降取樣處理或者縮放的話,演算法速度會更快。

寫在最後

彩色二維碼和小程式的圓形二維碼目前能夠檢測嗎?
暫時不能。因為影象在二值化之後,彩色的部分畫素點會變成白色的畫素點,導致二維碼輪廓不完整,最終導致無法實現二值分析。我們會在完成模版匹配的功能之後,繼續優化演算法完善該功能,加上檢測彩色和圓形二維碼的能力。

演算法的原始碼位於cv4jQRCodeScanner中,該演算法不能識別二維碼的字串,只能找到二維碼的區域,如果需要識別二維碼還是需要使用Google Zxing。

總結

cv4jgloomyfish和我一起開發的影象處理庫,純java實現,目前還處於早期的版本。

文章中的演算法是對二值影象分析的綜合運用,使用它再結合Google的ZXing能夠提高二維碼的識別率。當然,由於它是pure java實現的,稍作改動能夠用它來判斷出某張圖片中是否包含有二維碼。

如果您想看該系列先前的文章可以訪問下面的文集:
www.jianshu.com/nb/10401400

相關文章