Android高仿微信/支付宝 扫一扫(弱光检测扫一扫自动放大功能)

原文地址

前言


 目前市面上App携带的扫一扫功能大多是乞丐版,怎么说,就是只有扫一扫.而目前来说扫一扫做的最好的还是微信,微信有弱光环境的检测(可以自动提示用户打开闪光灯),同时,当发现扫描目标距离过远时,还可以自动的放大镜头,亲测可以多次的放大,所以说细节决定成败,支付宝虽然也有微信的功能,但是我觉得支付宝的弱光做的一般,自动放大也有点鸡肋,不过也很不错了,毕竟一般来说,实现扫一扫乞丐版就基本完事了,而我也遇到了这个需求,就是要实现微信和支付宝类似的效果.


效果图走一波(用的gif大师,录制的质量比较低,质量过高的传不上去,见谅)


第一帧当为弱光时,动态显示“打开手电筒”,点击打开后,则一直显示“关闭手电筒”.

第二帧就是扫一扫自动放大的效果.

 



需求分析


1.中间的frame框就不说了,比较的简单,ondraw里边修改,用安卓纯纯的坐标系,就可以实现.
2.弱光检测: 这块我花了两天的时间研究,ios获取后置摄像头的光感比较的方便,几行代码就可以获取,他们的是brightnessvalue这个值;而安卓第一版我用的光传感器,你要知道,光传感器是在前置摄像头附近,而扫一扫是用后置摄像头来扫描的,光传感器晚上是没有问题的,白天不是非常的精确,就放弃了这个方案,最后查了相关的资料我使用jpegReader.metadata(),exifinterface来读取实时帧流,均以失败告终,我想Camera2应该提供了某些的api,但是要求是5.0之后了,我也就没有细研究,之后,我看到支付宝的效果后,我就明白了,他分析的是后摄像头拍照的图片颜色来区分的,多次尝试发现,是这样,同理,微信应该也是类似的实现,只不过他调的比较细,优化的比较好而已.
3.扫一扫自动放大:这个你思考下,其实也很简单,Camera有放大的属性,无非是触发条件怎么来判断,微信扫一扫是当镜头中有二维码的是才会进行自动放大,并且会多次的放大.


代码实现


我们项目用的是zxing,不用说了要修改源码.

ui层就不说了,真的简单,安卓坐标系,cavas 画布api,来绘制rect区域,在ViewFindView这个类里边的onDraw方法修改即可.


弱光检测


   上面分析完后,就知道了,咱们要实时的分析图片的颜色平均值(agb平均值),既然说到了实时的分析,我们就要找到二维码处理解码实时帧的方法,zxing使用decodeThread,decodeHanlder,decodeThread线程不断的分析流并解码.
/**
     * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
     * reuse the same reader objects from one decode to the next.
     *
     * @param data   The YUV preview frame.
     * @param width  The width of the preview frame.
     * @param height The height of the preview frame.
     */
    private void decode(byte[] data, int width, int height) 

这个data是YUV格式的,谷歌也提供了相关的转换方法Yuvimage.
将YUV转换为agb方法
private int[] decodeYUV420SP(byte[] yuv420sp, int width, int height) {
        final int frameSize = width * height;
 
        int rgb[] = new int[width * height];
        for (int j = 0, yp = 0; j < height; j++) {
            int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
            for (int i = 0; i < width; i++, yp++) {
                int y = (0xff & ((int) yuv420sp[yp])) - 16;
                if (y < 0) y = 0;
                if ((i & 1) == 0) {
                    v = (0xff & yuv420sp[uvp++]) - 128;
                    u = (0xff & yuv420sp[uvp++]) - 128;
                }
 
                int y1192 = 1192 * y;
                int r = (y1192 + 1634 * v);
                int g = (y1192 - 833 * v - 400 * u);
                int b = (y1192 + 2066 * u);
 
                if (r < 0) r = 0;
                else if (r > 262143) r = 262143;
                if (g < 0) g = 0;
                else if (g > 262143) g = 262143;
                if (b < 0) b = 0;
                else if (b > 262143) b = 262143;
 
                rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) &
                        0xff00) | ((b >> 10) & 0xff);
 
 
            }
        }
        return rgb;
    }


使用Bitmap.createBitmap转换为bitmap图片下,分析图片颜色的平均值,颜色都是16进制的,黑色的颜色 对应int = -16777216, 所以我们认为当前的平均值 / black(-16777216) 的比值小于等于1 同时大于0.95,就认为是弱光(这个值还可以调节)
 
  
  1.    private int getAverageColor(Bitmap bitmap) {
            int redBucket = 0;
            int greenBucket = 0;
            int blueBucket = 0;
            int pixelCount = 0;
     
            for (int y = 0; y < bitmap.getHeight(); y++) {
                for (int x = 0; x < bitmap.getWidth(); x++) {
                    int c = bitmap.getPixel(x, y);
     
                    pixelCount++;
                    redBucket += Color.red(c);
                    greenBucket += Color.green(c);
                    blueBucket += Color.blue(c);
                }
            }
     
            int averageColor = Color.rgb(redBucket / pixelCount, greenBucket
                    / pixelCount, blueBucket / pixelCount);
            return averageColor;
        }
    


我们分析的是矩形框内的图片,获取矩形框内图片的agb数组,将其转换为bitmap来分析其agb的平均值,分析完后直接释放当前帧bitmap(释放触发gc,会有内存抖动,效果上忽略不计)
private void analysisBitmapColor(byte[] data, int width, int height) {
        int[] rgb = decodeYUV420SP(data, width, height);
        Bitmap bmp = null;
        if (null != frameRect) {
            //取矩形扫描框frameRect的2分之一创建为bitmap来分析
            bmp = Bitmap.createBitmap(rgb, frameRect.left + (frameRect.right - frameRect.left) / 4, frameRect.width() / 2, frameRect.width() / 2, frameRect.height() / 2, Bitmap.Config.ARGB_4444);
        }
        if (bmp != null) {
            float color = getAverageColor(bmp);
            DecimalFormat decimalFormat1 = new DecimalFormat("0.00");
            String percent = decimalFormat1.format(color / -16777216);
            float floatPercent = Float.parseFloat(percent);
            Log.e(TAG, " color= " + color + " floatPercent= " + floatPercent + " bmp width= " + bmp.getWidth() + " bmp height= " + bmp.getHeight());
            Constants.isWeakLight = (color == -16777216 || (floatPercent >= 0.95 && floatPercent <= 1.00));
            bmp.recycle();
        }
    }


上述代码基本实现了弱光的检测,代码还可以进行优化,比如我在YUV转换为AGB时,我只转换矩形扫描框内的图片,只分析这块的图片AGB平均值,目前,这一帧的图片我全部转换了AGB,你会发现,color 永远不会等于-16777216(black),因为最优的做法就是只decode矩形扫描框内的图片将其转换为agb数组,然后再进行图片的agb分析,这样也仅仅有可能color会等于-16777216.


扫一扫自动放大的功能



二维码携带有坐标数据,根据坐标算出二维码的矩形大小并和当前frame边框的坐标进行比对,来进行放大,目前看微信好像也是这样实现的,不过弊端是什么,目前我是扫描出来这个界面结果后进行放大的,有点多此一举的感觉,
而微信我测试后,也是扫描后进行多次放大,我不知道他们为什么要这样做,已经decode到数据了,为什么还要放大?难道仅仅是为了体验,但是我感觉这样的体验很一般,但是他们微信用的自己的qbar,我反编译后 发现他们使用了自己的"libwechatQrmod.so"这个库,这里面肯定封装了扫描二维码的识别算法,目前这个算法,我还没有破解.目前来说 我也只能先这样,后续可以根据扫描时间来优化进行放大或修改吧.自动放大代码如下:
if (rawResult != null) {
            // Don't log the barcode contents for security.
            long end = System.currentTimeMillis();
            Log.d(TAG, "Found barcode in " + (end - start) + " ms");
            if (handler != null) {
                float point1X = rawResult.getResultPoints()[0].getX();
                float point1Y = rawResult.getResultPoints()[0].getY();
                float point2X = rawResult.getResultPoints()[1].getX();
                float point2Y = rawResult.getResultPoints()[1].getY();
                int len = (int) Math.sqrt(Math.abs(point1X - point2X) * Math.abs(point1X - point2X) + Math.abs(point1Y - point2Y) * Math.abs(point1Y - point2Y));
                if (frameRect != null) {
                    int frameWidth = frameRect.right - frameRect.left;
                    Camera camera = activity.getCameraManager().getCameraNotStatic();
                    Camera.Parameters parameters = camera.getParameters();
                    final int maxZoom = parameters.getMaxZoom();
                    int zoom = parameters.getZoom();
                    if (parameters.isZoomSupported()) {
                        if (len <= frameWidth / 4) {
                            if (zoom == 0) {
                                zoom = maxZoom / 3;
                            } else {
                                zoom = zoom + 10;
                            }
                            if (zoom > maxZoom) {
                                zoom = maxZoom;
                            }
                            parameters.setZoom(zoom);
                            camera.setParameters(parameters);
                            final Result finalRawResult = rawResult;
                            postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    Message message = Message.obtain(handler, R.id.decode_succeeded, finalRawResult);
                                    Bundle bundle = new Bundle();
                                    bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
                                    message.setData(bundle);
                                    message.sendToTarget();
                                }
                            }, 1000);
 
                        } else {
                            Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
                            Bundle bundle = new Bundle();
                            bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
                            message.setData(bundle);
                            message.sendToTarget();
                        }
                    }
                } else {
                    Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
                    Bundle bundle = new Bundle();
                    bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
                    message.setData(bundle);
                    message.sendToTarget();
                }
            }
        } else {
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_failed);
                message.sendToTarget();
//                if (!Constants.isWeakLight) {
//                    long failedTimeStamp = System.currentTimeMillis();
//                    if (failedTimeStamp - intervalTime > INTERVAL) {
//                        isResetTime = true;
//                        intervalTime = System.currentTimeMillis();
//                        Camera camera = activity.getCameraManager().getCameraNotStatic();
//                        Camera.Parameters parameters = camera.getParameters();
//                        final int maxZoom = parameters.getMaxZoom();
//                        int zoom = parameters.getZoom();
//                        if (parameters.isZoomSupported()) {
//                            if (zoom == 0) {
//                                zoom = maxZoom / 2;
//                            } else {
//                                zoom = zoom + 10;
//                            }
//                            if (zoom > maxZoom) {
//                                zoom = maxZoom;
//                            }
//                            parameters.setZoom(zoom);
//                            camera.setParameters(parameters);
//                        }
//                    }
//                }
            }
        }




猜你喜欢

转载自blog.csdn.net/fengyeNom1/article/details/80926811
今日推荐