二維碼掃描開源庫ZXing定制化


最近在用ZXing這個開源庫做二維碼的掃描模塊,開發過程的一些代碼修改和裁剪的經驗和大家分享一下。

建議:

如果需要集成到自己的app上,而不是做一個demo,不推薦用ZXing的Android外圍開發模塊,只用核心的core目錄的代碼就好了。android和android-core的代碼設計的不好,擴展性太差了(我在后期開發新需求的時候改修改了很多CameraManager的邏輯)。只使用core目錄的集成方法很簡單,參考:

https://github.com/zxing/zxing/blob/master/android/src/com/google/zxing/client/android/DecodeHandler.java

中的decode函數,把攝像頭數據轉換成二值化圖像,然后傳入MultiFormatReader解碼。

 

以下是正文:

我的代碼庫:(基於官方3.2.0)

https://github.com/SickWorm/ZXingDialog

代碼沒有在github維護,所以沒有log。但是所有修改的地方我都加上了“@ch”的注釋,以方便定位

官方源碼:

https://github.com/zxing/zxing

 

實現功能:

1、功能裁剪(只保留QRCode二維碼掃描功能,去掉條形碼等其他碼掃描功能)

2、移除資源依賴,提供Dialog形式的掃碼功能

3、API兼容(源碼只兼容4.0以上,現兼容至2.1)

4、轉換為豎屏(源碼為橫屏)

5、掃碼速度優化(主要分三點,現只完成了一點)

6、設備兼容(針對低分辨率設備)

本文還會提到:

7、自定義界面

8、優化調試方法

 

1、建立工程

ZXing源碼並沒有提供一個完整的實例工程給我們使用,構建一個工程我們需要源碼下的三個文件夾的文件:

core/

android-core/

android/

大概步驟如下:

1、創建一個新工程

2、把android目錄下的所有文件覆蓋到新工程(內含有資源文件和AndroidManifest.xml等構建app所需的文件)

3、把android-core所有Java文件拷入到src目錄下(注意!android-core中的src文件夾需要進行一些改動,原來路徑是android-core\src\main\java\com\google\zxing\client\android\camera,我們要把中間的main\java兩層文件夾去掉,不然在Eclipse中無法識別包路徑)

4、把core目錄下的所有Java文件拷入到src目錄下(注意!和步驟3一樣需要去掉main\java兩層文件夾)。這樣ZXing已經可以運行了,我的src目錄是這樣的:

可以直接運行,效果還不錯。如果你遇到一些錯誤,有可能是編譯的JDK版本低於1.7導致的。源碼里使用了ArrayList<>這樣的寫法,1.7以前是不支持的。你可以選擇修改源碼或者提高編譯JDK版本。

但你可能不滿足於這個界面,掃描框太大了,而且是橫屏全屏的,還要求API 15(Android 4.0.3)。下面我們會對這些需求進行修改。

2、代碼優化

1、功能裁剪(只保留QRCode二維碼掃描功能,去掉條形碼等其他碼掃描功能)

我的目標是只保留二維碼識別,不需要其他多余的功能。這一部分的步驟我不打算詳細說明,因為我已經不記得了。。大家可以直接看我的代碼的結果。

可以直接刪掉的是:

com.google.zxing.aztec.**  aztec格式的二維碼

com.google.zxing.client.android.book.*  Google 圖書相關的功能

com.google.zxing.client.android.clickboard.*  不清楚,復制黏貼?

com.google.zxing.client.android.encode.*  用於生成各種碼

com.google.zxing.client.android.history.*  保存掃碼記錄

com.google.zxing.client.android.result.**  掃碼應用功能相關的功能性代碼

com.google.zxing.client.android.share.*  分享功能

com.google.zxing.client.android.wifi.*  WiFi相關,不清楚具體用途

com.google.zxing.datamatrix.**  datamatrix格式二維碼

com.google.zxing.maxicode.**  maxicode格式二維碼

com.google.zxing.multi.**  貌似是用於多格式支持的?我沒有用到這個包,如果有了解的麻煩告知

com.google.zxing.oned.**  one dimension一維碼,也就是條形碼(你去百度搜oned會發現奇怪的東西。。)

com.google.zxing.pdf417.**  PDF417格式條形碼

需要修改的是:

com.google.zxing.client.android.CaptureActivity:去掉其他功能的相關代碼,只保留核心功能,即掃碼功能。界面為一個FrameLayout里面包含一個SurfaceView。代碼移除就不詳細說了,直接看上傳的代碼吧,這個文件我參考了http://www.cnblogs.com/keyindex/archive/2011/06/08/2074900.html這個鏈接里的CaptureActivity的修改。

com.google.zxing.MultiFormatReader:這個是指定支持解碼的格式,需要把除QR_CODE以外的格式全部去掉,否則會因為刪掉了解碼包而報錯。具體也請看上傳的代碼。

com.google.zxing.client.result.ProductResultParser:parse函數中,同上。

com.google.zxing.client.android.DecodeThread:構造器中,同上。

另外:

com.google.zxing.client.result 我沒有刪減這個包的代碼,應該也是能優化的

 

2、移除資源依賴,提供Dialog形式的掃碼功能

經過了第1步的精簡,其實只剩下了2個地方需要修改:

1.掃描界面

2.掃描成功時播放的beep聲音文件

 

1:去除了其余功能后,對於核心功能我們只需要一個SurfaceView和一個畫界面的View就可以了。代碼如下:

    /**
     * use Java code to build layout instead of xml file
     * @ch
     */
    private void buildLayout() {
        requestWindowFeature(Window.FEATURE_NO_TITLE); 
        FrameLayout layout = new FrameLayout(this);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        layout.setLayoutParams(params);

        surfaceView = new SurfaceView(this);
        surfaceView.setLayoutParams(params);
        layout.addView(surfaceView);

        viewfinderView = new ViewfinderView(this, null);
        layout.addView(viewfinderView);

        setContentView(layout);
    }

ViewfinderView是ZXing自帶的View,如果要修改界面,直接修改它就可以了,我們第7點會提到。

2:由於我最終的目的是能打包成jar包,所以beep文件不能放在res里,而是放在assets里。

//待補充

 

3、API兼容(源碼只兼容4.0以上,現兼容至2.1)

這部分修改在源碼中標記為//@ch api compatible。

CaptureActivity.java:
        //@ch api compatible
        if (VERSION.SDK_INT < 11) {
            //surfaceview will push buffer automatically
            surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
//API 11之前需要手動設置,否則會無法顯示。API11之后為默認設置。

CameraConfigurationManager.java:
    //@ch api compatible
    if (VERSION.SDK_INT < 13) {
        theScreenResolution.x = display.getWidth();
        theScreenResolution.y = display.getHeight();
    } else {
        display.getSize(theScreenResolution);
    }
//getSize是API 13之后的新API,之前需要用getWidth和getHeight。

AutoFocusManager.java:
        //@ch api compatible
        if (VERSION.SDK_INT > 11) {
        newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        } else {
            newTask.execute();
        }
//源碼中這里設置了多線程的模式,THREAD_POOL_EXECUTOR表示這個task最多只有5個線程同時運行,超過5個的就要等待。在低於API 11的版本中,此為默認選項。其實這里只有單線程,所以隨便執行吧。

OpenCameraInterface.java:
    //@ch api compatible
    if (VERSION.SDK_INT < 9) {
        return openWithLowApi();
    }
......
  /**
   * for lower than API 9
   * @ch api compatible
   */
  public static Camera openWithLowApi() {
      //If the device does not have a back-facing camera, this returns null
      Camera camera = Camera.open();
      return camera;
  }
//源碼的打開攝像頭是能區分前后攝像頭的,然而API 9之前並沒有前置攝像頭這個概念,所以做了一下處理

就幾個地方,不過也找了我個把小時了。

 

4、轉換為豎屏(源碼為橫屏)

ZXing默認是橫屏,但是我們一般的APP都會做成豎屏,如果掃碼的時候強制切換成橫屏那樣體驗就不好了。在修改ZXing的豎屏的時候,我按照的是一般APP的豎屏設置方法,結果發現沒有源碼的效果好,需要把碼放到很小才能完成。后面在調試過程中發現掃碼解析的區域和屏幕畫出來的區域不一樣,才知道這部分的修改出了問題。然后我搜索找到一篇前輩的文章,參考了一下發現沒有改完全。附上文件鏈接:

http://blog.csdn.net/aaawqqq/article/details/24804939

其中第五點我沒有修改,文章中的源碼可能比較舊,並不適合替換。第5點不替換替換是沒有問題的。

此外還有一點需要修改的是:

CameraManager.java:
  //@ch change 240 to 120 for 320x240 machine
  private static final int MIN_FRAME_WIDTH = 120;
  private static final int MIN_FRAME_HEIGHT = 120;
  //@ch change to vertical
  private static final int MAX_FRAME_HEIGHT = (int) 1080;
  private static final int MAX_FRAME_WIDTH = (int) 1080;
//此處為掃描框最大邊界和最小邊界的界定。但因為我最后做成了正方形,所以這里數值是一樣的。如果為矩形,需要把兩個值交換一下。
//設置最小值是為了保證解碼的成功率,畢竟分辨率太小就沒法識別了。最大值是為了保證解碼速度。其實最大值應該通過插值來重新構圖,不然框的大小不一致體驗就不好了。不過這樣的分辨率起碼是2K屏以上了,所以最大值設定不會有什么影響

5、掃碼速度優化(主要分三點,現只完成了一點)

1.二值化算法優化。ZXing一共提供了兩種二值化算法,一種是HybridBinarizer,另一種是GlobalHistogramBinarizer,默認使用的是HybridBinarizer。我搜集資料的時候發現,HybridBinarizer算法用了更高級的算法,運算要求更高,而總體來講GlobalHistogramBinarizer識別率要更高。在我的實際測試中(我用的Nexus5,Android 5.0.1),GlobalHistogramBinarizer效果確實是要好不少。

這里的修改很簡單,換一個類就可以了:

DecodeHandler.java:
private void decode(byte[] data, int width, int height) {
    ......
    //@ch you can use HybridBinarizer or GlobalHistogramBinarizer
    //but in most of situations HybridBinarizer is shit
    BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
    ......
}

2.對焦優化。ZXing中的對焦功能在AutoFocusManager.java中,功能非常簡單,設置自動對焦並2秒對焦一次。但自動對焦可能會帶來一個問題,如下圖:

jingshen

(圖片源自網絡)

把二維碼當作圖中的那朵花,自動對焦則容易使攝像頭對焦到背景(圖中女性)中去。我在測試中使用三星S4的自動對焦經常對不了二維碼。這里我們可以使用區域對焦的方法(對焦的區域即是掃描框的區域):

//定點對焦的代碼   
     private void pointFocus(int x, int y) {
                if (cameraParameters.getMaxNumMeteringAreas() > 0) {
                        List<Camera.Area> areas = new ArrayList<Camera.Area>();
                        Rect area = new Rect(x - 100, y - 100, x + 100, y + 100);
                        areas.add(new Camera.Area(area, 600));
                        cameraParameters.setMeteringAreas(areas);
                }
                mCamera.cancelAutoFocus();
                cameraParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
                mCamera.setParameters(cameraParameters);
                mCamera.autoFocus(autoFocusCallBack);
        }

區域對焦需要API 14以上。關於對焦暫時沒有更多的想法,但我覺得確實是有優化的余地的。

3.算法優化。算法主要分兩部分,第一部分是二值化,第二部分是提取碼值。第二部分又分為1.尋找定位符,2.尋找校正符,3.轉換矩陣。在測試過程中,影響識別的最大問題就是找不到定位符,即二維碼左上角、右上角、左下角的三個黑白相間的矩形點。比較大的原因可能是二值化部分的問題。這一部分暫時也還沒有深入。

6、設備兼容(針對低分辨率設備)

 CameraManager.java中有設置最小掃描框大小的參數MIN_FRAME_WIDTH和MIN_FRAME_HEIGHT。默認是320x240。這對於屏幕分辨率為320x240的設備,掃描框就會變成全屏的。這里我改成了120x120,實際在屏幕分辨率為320x240的設備上也可以掃到二維碼。

 

7、自定義界面

界面寫在ViewFinderView.java中。掃描框大小由CameraManager決定。修改的時候需要注意和CameraManager的配置關聯起來,否則會出現掃描框和實際解碼的區域不一致。(ZXing的android外圍模塊代碼默認使用的是全屏,如果你想改為非全屏(比如加一個action bar),肯定會造成掃描框區域和實際解碼的區域不一致的問題。這也是文首建議只使用core模塊的原因)

 

8、優化調試方法

為了應對7可能帶來的問題,我自己做了一個調試的方法,以保證掃描框內容和實際解碼內容一致。首先在CaptureActivity.java初始化cameraManager的地方,把

cameraManager = new CameraManager(getApplication())

改為
cameraManager = new CameraManager(this)

這樣做的目的是:傳入了activity給CameraManager,使得后面在CameraManager中的調試內容可以直接輸出在activity上(dialog也一樣,改為getContext()即可)。但注意這樣會帶來Activity生命周期問題和因為互相引用導致內存泄漏的問題。所以只能在調試階段修改成上面的寫法。

然后在CameraManager.buildLumianceSource中加入:

if (context instanceof Activity) {
    final Bitmap bitmap = Bitmap.createBitmap(BitmapUtil.createBitmapfromYUV420(data, width, height), rect.left, rect.top, rect.width(), rect.height());
    CaptureActivity activity = ((CaptureActivity) context);
    final ViewfinderView view = activity.getViewfinderView();
    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            view.drawResultBitmap(bitmap);
        }
    });
}

這樣,屏幕上的掃描框中就會顯示出實際解碼的圖像,你可以通過比對預覽圖像查看是否對齊。

記得調試完改回來getApplication()

 

版權所有,轉載請注明出處:

http://www.cnblogs.com/sickworm/p/4562081.html


免責聲明!

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



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