Android zxing从图片扫码优化

要对从相册选择图片进行扫码进行优化,因为之前使用的方法很多图片会扫描不出来

对图片进行扫描要先对图片进行采样,将占用内存较大的图片采样获取小的版本防止在加载图片时发生OOM。如果只是采样有的图片仍然不能直接被识别出来,所以需要对图片进行缩放,这样能够将二维码识别出来。
本文是在不改动zxing源码的基础上通过外面编写代码来提高识别率,但是有的图片识别可能会比较慢,没有微信的那么快。代码使用kotlin编写。

简单的扫描版本

对图片进行简单采样

    fun getBitmapSameAsCI(path: String): Bitmap? {
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeFile(path, options)
        options.inJustDecodeBounds = false
        val fixWidth = 400
        var sampleSize = (options.outWidth / fixWidth + options.outHeight / fixWidth) / 2
        if (sampleSize < 0) {
            sampleSize = 1
        }
        options.inSampleSize = sampleSize
        return BitmapFactory.decodeFile(path, options)
    }

使用RGBLuminanceSource进行扫描的代码,直接对采样后的图片进行扫码不进行缩放

    fun scanFromBitmap(bitmap: Bitmap): Result? {
        val width = bitmap.width
        val height = bitmap.height
        val pixels = IntArray(width * height)
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
        var source = RGBLuminanceSource(width, height, pixels)
        var result: Result?
        try {
            result = QRCodeReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS)
            return result
        } catch (e: Exception) {
            if (decodeMultiSource) {
                try {
                    result = QRCodeReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS)
                    return result
                } catch (e2: Throwable) {
                    e2.printStackTrace()
                }
            }
        } catch (e: Exception) {
              // do nothing
        }
        return null
    }

上面的扫码能够对普通图片进行扫码,但是一些大的图片和过于小的含有二维码的图片就扫描不出来了

使用PlannarLumianceSource进行扫码

很多blog都说采样这个能优化扫码,但是根据我的测试结果好像并没有,甚至扫描成功率更差,不过还是贴出来,供大家参考。
bitmap获取YUV数据方法1

    public static byte[] convertBitmapToYUV(Bitmap image) {
        int w = image.getWidth();
        int h = image.getHeight();
        int[] rgb = new int[w * h];
        byte[] yuv = new byte[w * h];

        image.getPixels(rgb, 0, w, 0, 0, w, h);
        populateYUVLuminanceFromRGB(rgb, yuv, w, h);
        return yuv;
    }

    private static void populateYUVLuminanceFromRGB(int[] rgb, byte[] yuv420sp, int width, int height) {
        for (int i = 0; i < width * height; i++) {
            float red = (rgb[i] >> 16) & 0xff;
            float green = (rgb[i] >> 8) & 0xff;
            float blue = (rgb[i]) & 0xff;
            int luminance = (int) ((0.257f * red) + (0.504f * green) + (0.098f * blue) + 16);
            yuv420sp[i] = (byte) (0xff & luminance);
        }
    }

bitmap获取YUV数据方法2

    public static byte[] getBitmapYUVBytes(Bitmap sourceBmp) {
        if (null != sourceBmp) {
            int inputWidth = sourceBmp.getWidth();
            int inputHeight = sourceBmp.getHeight();
            int[] argb = new int[inputWidth * inputHeight];
            sourceBmp.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
            byte[] yuv = new byte[inputWidth
                    * inputHeight
                    + ((inputWidth % 2 == 0 ? inputWidth : (inputWidth + 1)) * (inputHeight % 2 == 0 ? inputHeight
                    : (inputHeight + 1))) / 2];
            encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
            sourceBmp.recycle();
            return yuv;
        }
        return null;
    }

    /**
     * 将bitmap里得到的argb数据转成yuv420sp格式
     * 这个yuv420sp数据就可以直接传给MediaCodec, 通过AvcEncoder间接进行编码
     *
     * @param yuv420sp 用来存放yuv429sp数据
     * @param argb     传入argb数据
     * @param width    bmpWidth
     * @param height   bmpHeight
     */
    private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
        // 帧图片的像素大小
        final int frameSize = width * height;
        // Y的index从0开始
        int yIndex = 0;
        // UV的index从frameSize开始
        int uvIndex = frameSize;
        // YUV数据, ARGB数据
        int Y, U, V, a, R, G, B;
        int argbIndex = 0;
        // ---循环所有像素点,RGB转YUV---
        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {

                // a is not used obviously
                a = (argb[argbIndex] & 0xff000000) >> 24;
                R = (argb[argbIndex] & 0xff0000) >> 16;
                G = (argb[argbIndex] & 0xff00) >> 8;
                B = (argb[argbIndex] & 0xff);
                argbIndex++;

                // well known RGB to YUV algorithm
                Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

                Y = Math.max(0, Math.min(Y, 255));
                U = Math.max(0, Math.min(U, 255));
                V = Math.max(0, Math.min(V, 255));

                // NV21 has a plane of Y and interleaved planes of VU each
                // sampled by a factor of 2
                // meaning for every 4 Y pixels there are 1 V and 1 U. Note the
                // sampling is every other
                // pixel AND every other scanline.
                // ---Y---
                yuv420sp[yIndex++] = (byte) Y;
                // ---UV---
                if ((j % 2 == 0) && (i % 2 == 0)) {
                    yuv420sp[uvIndex++] = (byte) V;
                    yuv420sp[uvIndex++] = (byte) U;
                }
            }
        }
    }

上面两个转换bitmap到yuv是在网上找到的方法,哪个正确并不知道,通过比较发现方法1要比方法2速度更快,在获取二维码的时候成功率也更高,但是看转换而成的二维码图片方法2更符合对yuv的描述,因为方法1转换成的图片是绿色的。
再贴一个yuv数据转换成图片的方法

public static Bitmap convertYUVToBitmap(byte[] data, int width, int height){
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
    yuvImage.compressToJpeg(new Rect(0, 0, width, height), 50, out);
    byte[] imageBytes = out.toByteArray();
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}

使用YUVLuminance进行扫码

    fun scanFromBitmap(bitmap: Bitmap): Result? {
        val yuvData = getBitmapYUVBytes(bitmap)
        var source = PlanarYUVLuminanceSource(yuvData, bitmap.width, bitmap.height, 0, 0, bitmap.width, bitmap.height, false)
        var result: Result?
        try {
            result = QRCodeReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS)
            return result
        } catch (e: Exception) {
            if (decodeMultiSource) {
                try {
                    result = QRCodeReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS)
                    return result
                } catch (e2: Throwable) {
                    e2.printStackTrace()
                }
            }
        } catch (e: Exception) {
              // do nothing
        }
        return null
    }

优化的扫描版本

这里对扫描的优化比较简单,就是通过不断的变换采样参数和缩放菜蔬来获取最佳扫描图片然后提高了扫码成功率。因为通过固定的采样和缩放参数往往很多图片都不能成功识别,所以再不考虑时间的基础上基本都能够识别到二维码。

    val loopScaleMax = 6
    val loopScaleMin = 1
    val loopScaleStep = 1


    private fun scanByLoopSample(path: String, s: Int):Result?{
        var scanResult: Result?
        for (s in sampleMaxValue downTo sampleMinValue step loopSampleStep) {
            // s:采样的大小
            val bitmap = AlbumUtil.getBitmapByPath(path, true, s * 100)
            scanResult = decodeByLoopScale(bitmap)
            if (scanResult != null) {
                return scanResult
            }
        }
    }

    private fun decodeByLoopScale(bitmap: Bitmap): Result? {
        var scanResult: Result? = null
        for (i in loopScaleMin..loopScaleMax step loopScaleStep) {
            if (scaleStep * i > bitmap!!.width * sizeTime && scaleStep * i > bitmap.height * sizeTime) {
                break
            }
            if (i == 0 && bitmap.width > 2000 && bitmap.height > 2000) {
                scalePosition = 0
                scanResult = scanFromPic(bitmap, bitmap.width, bitmap.height, callback)
            } else {
                scalePosition = i
                scanResult = scanFromPic(bitmap, scaleStep * (i), scaleStep * (i), callback)
            }
            if (scanResult != null) {
                return scanResult
            }
        }
        return scanResult
    }


    fun scanFromPic(bitmap: Bitmap?, fixWidth: Int, fixHeight: Int): Result? {
        if (bitmap == null) {
            return null
        }
        var scale = 1f
        if (fixWidth < bitmap.width) {
            scale = fixWidth.toFloat() / Math.max(bitmap.width, bitmap.height)
        } else {
            scale = fixWidth.toFloat() / Math.max(bitmap.width, bitmap.height)
        }
        val newBitmap = Bitmap.createScaledBitmap(bitmap,
                (bitmap.width * scale).toInt(),
                (bitmap.height * scale).toInt(), false)
        scaledBitmap = newBitmap
        return scanFromPicImpl(newBitmap, callback)
    }


    fun scanFromPicImpl(bitmap: Bitmap): Result? {
        val width = bitmap.width
        val height = bitmap.height
        val pixels = IntArray(width * height)
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
        var source = RGBLuminanceSource(width, height, pixels)
        var result: Result?
        try {
            result = QRCodeReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS)
            return result
        } catch (e: Exception) {
            if (decodeMultiSource) {
                try {
                    result = QRCodeReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS)
                    return result
                } catch (e2: Throwable) {
                    e2.printStackTrace()
                }
            }
        } catch (e: Exception) {

        }
        return null
    }

通过循环采样和循环缩放,最终找到能够识别出二维码的最佳参数。这种方法比较笨,对于没有二维码的图片也要经过这样的计算,所以如果再实际使用中最好定制一个时间,超过时间就终止扫码。在测试过程中发现有的图片的识别超级慢而且同样大小的图片有的采样从1000开始比较容易识别有的从200开始比较容易识别。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容