1. 程式人生 > >ZXing 相簿中識別二維碼和條形碼(直接引用就可以了)

ZXing 相簿中識別二維碼和條形碼(直接引用就可以了)

*百度了很久一直沒有找到關於相簿獲取條形碼的Demo,真心是醉了,只能苦逼的去自己看
閒話不說直接搞起

分析:

*核心
1,通過路徑轉換成bitmp物件
2,再bitmap物件轉換成二進位制圖片(二值化) == 將影象進行二值化處理,1 , 0 代表黑和白
3,最後解析二進位制圖片中的code(獲取到有用的資訊) ==對符號碼矩陣按照編碼規範進行解碼,得到需要的資訊

ZXing原始碼:

*ZXiong程式碼中是使用HybridBinarizer進行二值化計算的的,其實本質都是是使用Binarizer實現了
*看程式碼我們發現關係如下
*HybridBinarizer extends GlobalHistogramBinarizer
*GlobalHistogramBinarizer extends Binarizer
*在GlobalHistogramBinarizer中,有解析一二維碼的方法
解析一維的方法如下圖
這裡寫圖片描述

解析二維的方法如下圖
這裡寫圖片描述
*好了我們看看如何實現

==============================華麗的分割線================================

第一種:

第一步:在ZXing的CaptureActivity中,點選跳轉到系統的相簿選擇圖片,其實這裡因為android版本的不同返回的uri也會有三種情況,一共有兩種方式(相簿跳轉和路徑判斷)解決,在剛剛開始的部落格中,我兩種都寫了,看著看著就亂了,這裡我決定優化下我的部落格,就寫一種解決方式==路徑判斷

按照我的步驟寫,可以解決兩個問題,問題一:android系統不同返回照片路徑不同問題,問題二:解析圖片圖片OOM問題(進行了bitmap大小判斷是否壓縮),

    /**
     * 跳轉到系統相簿選圖片
     * android版本不同,返回路徑不同的情況(一共三種情況,無法解析圖片直接奔潰bug),
     *content://com.android.providers.media.documents/document/image%3A137424  sony
     *file:///storage/emulated/0/Tencent/QQ_Images/3afe8750f0b4b8ce.jpg       xiaomi
     *content://media/external/images/media/13323                           smartOS
     *
     */
private void switchSelectedImage() { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(intent, REQUESTCODE_IMAGE); }

第二步:根據請求碼REQUESTCODE_IMAGE,在onActivityResult中進行結果的處理

      @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case REQUESTCODE_IMAGE:
                 /**
                 *解決問題1,路徑的判斷,拿到真確的路徑
                 */
                   String filePath = "$$";
                    int sdkInt = Build.VERSION.SDK_INT; //相容4.4
                    Uri contentUri = data.getData();
                    if (sdkInt == 19 && DocumentsContract.isDocumentUri(CaptureActivity.this, contentUri)) {
                        String wholeID = DocumentsContract
                                .getDocumentId(contentUri);
                        String id = wholeID.split(":")[1];
                        String[] column = {MediaStore.Images.Media.DATA};
                        String sel = MediaStore.Images.Media._ID + "=?";
                        Cursor cursor = CaptureActivity.this.getContentResolver().query(
                                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                                column, sel, new String[]{id}, null);
                        int columnIndex = cursor.getColumnIndex(column[0]);
                        if (cursor.moveToFirst()) {
                            filePath = cursor.getString(columnIndex);
                        }
                        cursor.close();
                    } else if (!TextUtils.isEmpty(contentUri.getAuthority())) {
                        Cursor cursor = getContentResolver().query(contentUri,
                                new String[]{MediaStore.Images.Media.DATA},
                                null, null, null);
                        if (null == cursor) {
                            showToast("圖片沒找到");
                        }
                        cursor.moveToFirst();
                        filePath = cursor.getString(cursor
                                .getColumnIndex(MediaStore.Images.Media.DATA));
                        cursor.close();
                    } else {
                        filePath = data.getData().getPath();
                    }
        /**到此為止路徑判斷完成,問題一解決*/
                    try {
                        Bitmap bitmap = BitmapFactory.decodeFile(filePath);
                        long bitmapsize = getBitmapsize(bitmap);
                        if (bitmapsize > Contants.SCAN_DEFAULT_SIZE) {  //大圖
                        //解析並且請求資料
                        /**這裡就解決了問題二*/
                            analysisAndRequestResultCode(filePath,false);
                        } else {  //小圖  true表示要不用壓縮
                            analysisAndRequestResultCode(filePath,true);
                        }
                    } catch (NotFoundException e) {
                        showToast("圖片解析失敗");
                        e.printStackTrace();
                    } catch (FileNotFoundException e) {
                        showToast("圖片解析失敗");
                        e.printStackTrace();
                    }
                    break;
                default:
                    LogUtil.d(TAG, "onActivityResult,error,default.");
                    break;
            }
        }
    }

    /**
     * 解析並且請求藥品(這裡請求藥品是我的業務邏輯 可以忽略)
     *RGBLuminanceSourcee(filePath,  isAvailableSize); 這個方法是我在ZXing裡面寫的過載的方法
     * @param filePath
     * @throws FileNotFoundException
     * @throws NotFoundException
     */
    private void analysisAndRequestResultCode(String filePath,boolean isAvailableSize ) throws FileNotFoundException, NotFoundException {
        RGBLuminanceSourcee rgbLuminanceSource = new RGBLuminanceSourcee(filePath,  isAvailableSize);
        BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));//把可檢視片轉為二進位制圖片
        Result result = new MultiFormatReader().decode(binaryBitmap);//解析圖片中的code
        handleDecode(result);//根據code,執行網路請求
    }


    /**
     * 獲取bitmap的size
     *
     * @param bitmap
     * @return
     */
    public long getBitmapsize(Bitmap bitmap) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
            return bitmap.getByteCount();
        }
        // Pre HC-MR1
        return bitmap.getRowBytes() * bitmap.getHeight();

    }



相簿中選擇圖片二維碼和條形碼解析的核心程式碼(理解看看就可以了,可以忽略)

//根據相簿的path,返回一個bitmap物件
    scanBitmap = BitmapFactory.decodeFile(path, options);

        //bitmap物件轉換成二進位制圖片 (說明:輸入bitmap獲得解析結果source二進位制的byte圖片RGBLuminanceSource這個類繼承了LuminanceSource,)
        RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);


        //二進位制圖片轉換成bitmap物件(說明:建立HybridBinarizer物件,需要傳入LuminanceSource,所以傳入source(二進位制的圖片),並且通過BinaryBitmap轉換成bitmap物件)
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        //CodaBarReader codaBarReader= new CodaBarReader();    //codaBarReader  二維碼
        try {
            //MultiFormatReader是讀取影象的類(在core包)  
            return  new MultiFormatReader().decode(bitmap1,hints);      //識別條形碼,和二維碼(說明:獲取到我們需要的資訊)

        } catch (NotFoundException e) {
            e.printStackTrace();
        }

這個是我在ZXing包中的RGBLuminanceSourcee類,對loadBitmap()寫的過載方法,在這裡,更具傳進來的boolean值=isOkSize;進行判斷是否進行圖片壓縮,true表示小圖不壓縮,false表示大圖要壓縮,

具體的壓縮臨界值我是更具自己打印出來的size來寫的


    /**掃描選擇相簿圖片,判斷是否壓縮的臨界值*/
    public static final Long SCAN_DEFAULT_SIZE=(long)4000000; 
    /**4000000,我發現的問題手機3000000就掛了,所以給了這個值具體的臨界值我也不是很清楚,你可能會問為什麼要給個臨界值盤判斷是否壓縮,那是因為有些小圖壓縮了不能解析識別,所以我進行了判斷,大圖壓縮解析,小圖直接解析*/
private static Bitmap loadBitmap(String path, boolean isOkSize) throws FileNotFoundException {
        Bitmap bitmap = null;
        if (isOkSize) {  //小圖,直接呼叫功能系統的解析返回
            bitmap = BitmapFactory.decodeFile(path);
        } else { //大圖 先壓縮在返回
            bitmap = BitmapUtil.compress(path);
        }
        //ZXing原始碼
        if (bitmap == null) {
            throw new FileNotFoundException("Couldn't open " + path);
        }
        return bitmap;
    }

封裝的壓縮px(存寸)的方法

public static Bitmap compress(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        //開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);//此時返回bm為空

        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        //現在主流手機比較多是800*480解析度,所以高和寬我們設定為
        float hh = 800f;//這裡設定高度為800f           這裡我寫死了尺寸
        float ww = 480f;//這裡設定寬度為480f           這裡我寫死了尺寸
        //縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
        int be = 1;//be=1表示不縮放
        if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;//設定縮放比例
        //重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);

        return bitmap;//壓縮好比例大小後再進行質量壓縮
        //return bitmap;
    }

總結:

**其實實現最為核心的就是這幾行程式碼

scanBitmap = BitmapFactory.decodeFile(path, options);
        //輸入bitmap解析出結果
        RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        //CodaBarReader codaBarReader= new CodaBarReader();    //codaBarReader  二維碼
        try {
            return  new MultiFormatReader().decode(bitmap1,hints);      //識別條形碼

        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        return null;

還有就是開啟相簿返回的路徑問題,系統版本不同一共有三種情況

解決方法1;直接使用這樣的方式開啟相簿

    Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);  
    intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");  
    activity.startActivityForResult(intent, START_ALBUM_CODE);  

解決方法2;在onActivityResult中對路徑進行三次判斷獲取到正確的路徑,在轉車bitmap物件或者直接把path傳遞給RGBLuminanceSourcee解析類,進行解析

        /**對路徑進行的判斷(因為我們不知道是三種情況中的那一種)*/
                    String filePath = "$$";
                    Uri contentUri = data.getData();
                    if (DocumentsContract.isDocumentUri(CaptureActivity.this, contentUri)) {
                        String wholeID = DocumentsContract
                                .getDocumentId(contentUri);
                        String id = wholeID.split(":")[1];
                        String[] column = {MediaStore.Images.Media.DATA};
                        String sel = MediaStore.Images.Media._ID + "=?";
                        Cursor cursor = CaptureActivity.this.getContentResolver().query(
                                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                                column, sel, new String[]{id}, null);
                        int columnIndex = cursor.getColumnIndex(column[0]);
                        if (cursor.moveToFirst()) {
                            filePath = cursor.getString(columnIndex);
                        }
                        cursor.close();
                    } else {
                        if (!TextUtils.isEmpty(contentUri.getAuthority())) {
                            Cursor cursor = getContentResolver().query(contentUri,
                                    new String[]{MediaStore.Images.Media.DATA},
                                    null, null, null);
                            if (null == cursor) {
                                showToast("圖片沒找到");
                            }
                            cursor.moveToFirst();
                            filePath = cursor.getString(cursor
                                    .getColumnIndex(MediaStore.Images.Media.DATA));
                            cursor.close();
                        } else {
                            filePath = data.getData().getPath();
                        }
                    }

圖片壓縮

public static Bitmap compress(String srcPath) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        //開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);//此時返回bm為空

        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        //現在主流手機比較多是800*480解析度,所以高和寬我們設定為
        float hh = 800f;//這裡設定高度為800f
        float ww = 480f;//這裡設定寬度為480f
        //縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
        int be = 1;//be=1表示不縮放
        if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;//設定縮放比例
        //重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);

        return bitmap;//壓縮好比例大小後再進行質量壓縮
        //return bitmap;
    }

在華麗的分割線上和下,都可以實現,我給的Demo就是第二種,其實第一種更為的簡單利索,直接在CaptureActivity,Copy我的程式碼,進行相對應的改動即可

—————以上的程式碼就能滿足相簿獲取二維碼,條形碼,和解決上述問題,下面的補充知識方法二,—————可以忽略不看,因為我也沒有實踐過,看網上的部落格分享下而已————–

========================華麗的分割線=============================
看了別人的一波部落格感覺也很好,但是隻能相簿選擇二維碼,想要實現二維碼和條形碼都都能識別,那麼只需要新增一行
new MultiFormatReader().decode(bitmap1,hints); 程式碼即可,最後面我會貼上修改後的Demo
*可以參考他的部落格http://blog.csdn.net/aaawqqq/article/details/24880209

我在他的基礎上修改了,實現識別條形碼和二維碼

第二種:

實現思路

*在Zxing掃描識別和圖片識別的解析物件是相同的
1,獲取相簿的照片
2,解析二維碼圖片
3,返回結果

第一步:獲取相簿照片

    /**
     * 獲取帶二維碼的相片進行掃描
     */
    Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);  
    intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");  
    this.startActivityForResult(intent, START_ALBUM_CODE);  

第二步:選擇了照片後,然後返回的資料在onActivityResult方法中獲取

    /**
    * 對相簿獲取的結果進行分析
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub

        if (resultCode == RESULT_OK) {   //成功且有資料
            switch (requestCode) {
            case 1://不同手機返回的照片路徑可能不同
                try {   //情況一,不是絕對路徑的時候,遊標獲取路徑
                    Uri uri = data.getData();
                    if (!TextUtils.isEmpty(uri.getAuthority())) {
                        Cursor cursor = getContentResolver().query(uri,
                                new String[] { MediaStore.Images.Media.DATA },
                                null, null, null);
                        if (null == cursor) {
                            Toast.makeText(this, "圖片沒找到", Toast.LENGTH_SHORT)
                                    .show();
                            return;
                        }
                        cursor.moveToFirst();
                        photo_path = cursor.getString(cursor
                                .getColumnIndex(MediaStore.Images.Media.DATA));
                        cursor.close();
                    } else {//情況2,絕對路徑直接獲取
                        photo_path = data.getData().getPath();
                    }
                    Log.e("main","###############"+photo_path);
                    mProgress = new ProgressDialog(CaptureActivity.this);
                    //一些progressbar
                    mProgress.setMessage("正在掃描...");
                    mProgress.setCancelable(false);
                    mProgress.show();

                    /**重點,開啟執行緒,解析圖片返回code*/

                    new Thread(new Runnable() {
                        @Override
                            /**重點方法下面詳細分析其實和我上面分析的差不多*/
                            Result result = scanningImage(photo_path);

                            if (result != null) {
                                Message m = mHandler.obtainMessage();
                                m.what = 1;
                                m.obj = result.getText();

                                mHandler.sendMessage(m);
                            } else {
                                Message m = mHandler.obtainMessage();
                                m.what = 2;
                                m.obj = "Scan failed!";
                                mHandler.sendMessage(m);
                            }

                        }
                    }).start();
                } catch (Exception e) {
                    Toast.makeText(CaptureActivity.this, "解析錯誤!",
                            Toast.LENGTH_LONG).show();
                }

                break;

            default:
                break;
            }
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

重點方法的分析:scanningImage(String path)

/**
     * 掃描二維碼圖片的方法,返回結果
     *
     * @param path
     * @return 返回結果
     */
    public Result scanningImage(String path) {
        if (TextUtils.isEmpty(path)) {
            return null;
        }
        Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
        hints.put(DecodeHintType.TRY_HARDER, "UTF8"); // 設定二維碼內容的編碼
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 先獲取原大小
        scanBitmap = BitmapFactory.decodeFile(path, options);
        options.inJustDecodeBounds = false; // 獲取新的大小
        int sampleSize = (int) (options.outHeight / (float) 100);
        if (sampleSize <= 0)
            sampleSize = 1;
        options.inSampleSize = sampleSize;

        //獲取到bitmap物件(相簿圖片物件通過path)
        scanBitmap = BitmapFactory.decodeFile(path, options);
        //輸入bitmap解析的二值化結果(就是圖片的二進位制形式)
        RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
        //再把圖片的二進位制形式轉換成,圖片bitmap物件
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        //CodaBarReader codaBarReader= new CodaBarReader();    //codaBarReader  二維碼
        try {
            /**建立MultiFormatReader物件,呼叫decode()獲取我們想要的資訊,比如條形碼的code,二維碼的資料等等.這裡的MultiFormatReader可以理解為就是一個讀取獲取資料的類,最核心的就是decode()方法 */
            return  new MultiFormatReader().decode(bitmap1,hints);      //識別條形碼

        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }