美文网首页
Zxing扫码优化(灰度优化)——纯干货

Zxing扫码优化(灰度优化)——纯干货

作者: AND独一无二ROID | 来源:发表于2020-08-17 19:06 被阅读0次

引言

有关zxing剪裁,线程池之类的就不说了, 简单扯一下从灰度上边做优化,为什么要用灰度优化? 因为你扫不出的东西, 剪裁的再小,线程再多也还是扫不出。

什么是灰度

要从这个方面入手就得先搞懂什么是灰度: 灰度使用黑色调表示物体,即用黑色为基准色,不同的饱和度的黑色来显示图像。 每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。 使用黑白或灰度扫描仪生成的图像通常以灰度显示。
度娘搜出来的一端话。 简单来说就是不带任何色彩只有黑色到白色这段区间范围黑白电视展示出来的这种。 纯黑色值是0, 纯白色是255。大概了解后可以接着往下看

灰度与YUV

YUV是什么? 度娘说:是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。
与RGB一对比就清楚了 , RGB我们都了解是红绿蓝三原色,根据不同的比例来展示不同的颜色, 那么一个像素就要存储RGB中各个信息也就是三个字节, 而描述中说到要降低色度的宽带那么目的就是降低每个像素的占用字节数。那么具体是如何实现的? YUV格式 这里不多说明(主要笔者我也不清楚这么多,不敢班门弄斧) 。好大概看了后我们清楚了YUV的数据格式排列, 要么是packed,要么是planar。 并且YUV中Y直接就是他的灰度值即上述说的范围(0-255)

获取灰度值

我们先以Camera2为例,Camera2如何获取YUV数据

        mYuvReader = ImageReader.newInstance(prelargest.getWidth(), prelargest.getHeight(),
                ImageFormat.YUV_420_888, /* maxImages */ 2);
        mYuvReader.setOnImageAvailableListener(mOnYuvAvailableListener, null);

大概看下这段代码, Camera2 在添加输出的时候是可以选定YUV的这里只谈YUV_420_888, 通过大量的资料查询了解到了YUV_420_888 是一种planar格式, 即, 先排列所有的Y, 在紧接着UV,UV.....。 来看下他的获取

            Image.Plane[] planes = image.getPlanes();
            int remaining0 = planes[0].getBuffer().remaining();
            int remaining2 = planes[2].getBuffer().remaining();
            byte[] yRawSrcBytes = new byte[remaining0];
            byte[] uvRawSrcBytes = new byte[remaining2];
            byte[] nv21 = new byte[remaining0 + remaining2];
            planes[0].getBuffer().get(yRawSrcBytes);
            planes[2].getBuffer().get(uvRawSrcBytes);
     
            System.arraycopy(yRawSrcBytes, 0, nv21, 0, yRawSrcBytes.length);
            System.arraycopy(uvRawSrcBytes, 0, nv21, yRawSrcBytes.length, uvRawSrcBytes.length);

上边这些代码按道理planes[0].getBuffer().get(yRawSrcBytes)即输出的Y,
planes[2].getBuffer().get(uvRawSrcBytes)输出V啊, 那U去那了??? 通过查阅了解到底层封装的时候为了上层好处理,把VU和UV封装到了一起,那么我们直接取UV即可。 这里最后的NV21 便是最后一个完整YUV数据组。

再聊聊Camera1的,

 mCamera.setPreviewCallback((data, camera) ->
                        mCallback.onPreviewByte(data));

在Camera1中我们都是通过这步操作来获取实施的数据, 其实这种数据默认就是NV21的,也就是YUV_420,并且格式为Y,Y,UV,UV。

如何优化

上边这么多就是为此时做铺垫。首先我们看看Zxing里边是如何读取数据的,我们知道他读取的是YUV格式, 那么到底仅仅只是Y,还是说UV也参与其中?

  public byte[] getMatrix() {
        int width = getWidth();
        int height = getHeight();

        // If the caller asks for the entire underlying image, save the copy and give them the
        // original data. The docs specifically warn that result.length must be ignored.
        if (width == dataWidth && height == dataHeight) {
            return yuvData;
        }

        int area = width * height;
        byte[] matrix = new byte[area];
        int inputOffset = top * dataWidth + left;

        // If the width matches the full width of the underlying data, perform a single copy.
        if (width == dataWidth) {
            System.arraycopy(yuvData, inputOffset, matrix, 0, area);
            return matrix;
        }

        // Otherwise copy one cropped row at a time.
        for (int y = 0; y < height; y++) {
            int outputOffset = y * width;
            System.arraycopy(yuvData, inputOffset, matrix, outputOffset, width);
            inputOffset += dataWidth;
        }
        return matrix;
    }

这是PlanarYUVLuminanceSource 类中的一步剪裁操作。 可以看到剪裁的截止是area = width * height; 即像素的个数, 而YUV的总长是Y+U+V的长度, 很显然跳过了UV, 仅仅只需要灰度Y。 那么图像能不能扫出来只取决于Y的值。好, 为了直观表现出来, 我只读取了YUV中Y的值然后做成了图片,操作如下


            byte[] cropNv21 = new byte[nv21.length / 2 * 3];

            for (int i = 0; i < w * h; i++) {
                cropNv21[i] = nv21[i];
            }
            for (int i = 0; i < w * h / 2; i++) {
                cropNv21[nv21.length + i] = (byte) 0b10000000;
            }
            System.arraycopy(cropNv21, 0, cropNv21, 0, w * h);

            YuvImage yuvImage = new YuvImage(cropNv21, ImageFormat.NV21, w, h, null);

            try {
                yuvImage.compressToJpeg(new Rect(0, 0, w, h), 100, new FileOutputStream(file));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

由于我是在zxing截取后操作的, 截取后没了UV数据为了输出一张完整的图片只能手动添加一些UV, 经实测给二进制 0b10000000也就是十进制-128能得到纯的灰度值图片。

UV值为-128

当UV值为0如下,所以猜测UV的解析值范围为 [-128 ,127]


UV值为0

好这样就清楚了 , 我们把扫不出的码转成灰度图, 看它到底长什么样。。
这是一长彩色二维码


在这里插入图片描述
转换后
在这里插入图片描述

我们不用关注Zxing源码到底是如何扫出来的(太复杂,让人望而生畏),但是根据转换后的灰度图可以清楚看到, 上半部分分颜色偏白 。 所以我们要做的不是修改Zxing源码,而是尽可能保证图像更贴近一个规整的二维码图像。

伽马增强

在这里插入图片描述
在这里插入图片描述

观察上图在某些等级r范围内,输入的灰度级越大输出降低越小, 输入的越小变化越大, 而为了保证白色底色变化小,黑色再黑也没关系, 浅黑色能加深一些即可, 所以我选定的区间是[3,7],给一个4看看输出的效果

 for (int i = 0; i < w * h; i++) {
                cropNv21[i] = (byte) (255 * Math.pow((cropNv21[i] & 0xff) / 255f, 4f));
            }
在这里插入图片描述

这时候拿出我们的zxing,我擦成了。

总结:java中伽马优化由于频繁调的Math.pow的还是比较吃性能, 后期可以想办法优化。

线性增强

来看下这种二维码
[图片上传失败...(image-2bbca5-1597662384837)]
没有任何色彩, 灰度化后跟原图应该是差不多, zxing扫不出的原因就是理应的黑色区域灰度值过高,这里要想办法降低。这里笔者经过大量测试发现一个极其巧妙的处理方式

     byte[] newByte = data.clone();
        short random = (short) (Math.random() * 4 + 3);
        for (int i = 0; i < width * height; i++) {
            newByte[i] = (byte) (newByte[i] * random);
        }

上边运算等同于( newByte[i] & 0xff ) % 250 , 没错, 一个灰度值恰好大于255存储在字节里相当于又从0开始, 白色瞬间成黑色, 而且由于线性扩大,黑色和白色差值越来越来。
看看处理后效果


在这里插入图片描述

搞定。

总结: 由于没有接入openCv,识别能力取决于适配的程度, 这个还是比较困难, 但是优点也很明显,非常轻量。几k基本就能处理一类。

关注我的github持续更新, 一个很完善的扫码库。

github

NBZxing

相关文章

网友评论

      本文标题:Zxing扫码优化(灰度优化)——纯干货

      本文链接:https://www.haomeiwen.com/subject/zvzqjktx.html