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

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

原文地址

前言


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


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


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

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

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


需求分析


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 > ) r = ; if (g < 0) g = 0; else if (g > ) g = ; if (b < 0) b = 0; else if (b > ) b = ; rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff); } } return rgb; } 





使用Bitmap.createBitmap转换为bitmap图片下,分析图片颜色的平均值,颜色都是16进制的,黑色的颜色 对应int = -, 所以我们认为当前的平均值 / black(-) 的比值小于等于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 / -); float floatPercent = Float.parseFloat(percent); Log.e(TAG, " color= " + color + " floatPercent= " + floatPercent + " bmp width= " + bmp.getWidth() + " bmp height= " + bmp.getHeight()); Constants.isWeakLight = (color == - || (floatPercent >= 0.95 && floatPercent <= 1.00)); bmp.recycle(); } } 




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




扫一扫自动放大的功能




二维码携带有坐标数据,根据坐标算出二维码的矩形大小并和当前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); // } // } // } } } 




github传送门,欢迎star!




版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224281.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午12:17
下一篇 2026年3月17日 下午12:18


相关推荐

  • PHP+MySQL实现对一段时间内每天数据统计优化操作实例

    PHP+MySQL实现对一段时间内每天数据统计优化操作实例

    2021年10月19日
    37
  • javaEE和javaweb的区别

    javaEE和javaweb的区别JavaEEJavaEE 指的是一套规范 也可以认为是遵循 JavaEE 规范 使用 JavaSE 实现的技术和框架 例如 JavaSE 包含 socket 的内容 JavaEE 则是提出 JSR902 JSR903 规范 按照规范中的要求把 socket 封装成 Servlet 供直接使用 JavaSE 包含 annotation 和自定义注解 API PluggableAnn 的内容 JavaEE 则是提出 JSR269 规范 所有框架中定义的注解都必须符合该规范 典型的例如 lombok 中的

    2026年3月18日
    2
  • 照片切割器_切割图片的软件

    照片切割器_切割图片的软件两款图片切割工具ShoeBox:http://renderhjs.net/shoebox/BigShear:https://www.fancynode.com.cn/bigshear下面试下Sh

    2022年8月3日
    6
  • word怎么让页码在指定页面从1开始出来_word里页码怎么设置

    word怎么让页码在指定页面从1开始出来_word里页码怎么设置word排版的时候,因为一般文档都有封面、目录等,导致用默认的页码会使正文开始的时候不是第一页的尴尬情况如下图解决办法:1、先按默认的方法插入页码,插入–&amp;gt;页码2、在正文的前一页结尾处点布局–&amp;gt;分隔符–&amp;gt;下一页3、在正文页双击页码,在设计那里把链接到前一节给取消掉,接着点插入–&amp;gt;页码–&amp;gt;设置页码格式–&amp;gt;点起始页码–&amp;gt;设置为1

    2025年5月28日
    7
  • 机器学习之支持向量回归(SVR)

    机器学习之支持向量回归(SVR)简介支持向量机(SupportVectorMachine)是由Vapnik等人于1995年提出来的,之后随着统计理论的发展,支持向量机SVM也逐渐受到了各领域研究者的关注,在很短的时间就得到了很广泛的应用。支持向量机是被公认的比较优秀的分类模型。同时,在支持向量机的发展过程中,其理论方面的研究得到了同步的发展,为支持向量机的研究提供了强有力的理论支撑。本实训项目主要围绕支持向量机的原理和技术进行介绍,并基于实际案例进行实战实训。线性支持向量机#encoding=utf8fromsk

    2022年6月3日
    29
  • hexo博客搭建及主题优化(二)

    hexo博客搭建及主题优化(二)crystalBlog上篇hexo博客搭建及主题优化(一)主题优化二21.网站log设置主题目录下的_config.yml配置文件中:#配置网站favicon和网站LOGO##本地#favicon:/favicon.png#logo:/medias/logo.png#此处我用的CDN,也可以使用本地文件favicon:https://cdn.jsdelivr.net/gh/guixinchn/image/blog/favicon.pnglogo:https://cdn

    2026年1月30日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号