背景
最近有个应用需要在服务端解析图片中的二维码,网上查了一下,几乎全是用google zxing的实现。但是测试过程中发现,这玩意比较坑,很多在微信里长按能识别的图片,使用zxing却识别不出来。 于是乎开始了纠结的过程!!!! 搞不懂为啥网上一直没解决方法~~~~~(虽然很多人在问!! 国外也有人在问!!!!),大家都是不求甚解,只能copy代码?? 最终还是自己动手吧!!
给张测试图
过程
- google一下
google了很久, 在stackoveflow上找到了一个描述!!大概是说“图片不能超过某个大小”, 结果误导了我!! 将2448x3264的图片缩小到了1500x2000的尺寸,结果还真识别出来了。 于是乎我以为问题解决了,识别时我加了一个超出尺寸缩放的逻辑!! 过了一天,再测试,发现还是很多图片识别不出来!! 看来歪果仁也不靠谱
- 怀疑色差导致的,于是将图片转成灰阶,将偏黑色的全置成“黑色”,其它的全置为白色,这样就得到了一个“黑白图片”。 失败!!!!
private static BufferedImage toGrayImage(BufferedImage image) { BufferedImage result = image; if (BufferedImage.TYPE_BYTE_GRAY != image.getType()) { BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); newImage.getGraphics().drawImage(image, 0, 0, null); result = newImage; } Raster raster = result.getRaster(); DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); byte[] data = buffer.getData(); for (int i = 0; i < data.length; i++) { byte value = 0; if (data[i] < 32) { value = -1; } buffer.setElem(i, value); } return result; }
- 怀疑是zxing本身算法问题。
https://zxing.org/w/decode.jspx 这个网站可以直接上传图片,再使用zxing提取结果。 发现这个网站也不行!!! 难道真的有问题? 然后尝试使用其它的一些API,结果还真没找出靠谱的API来。
- 确认zxing没问题
偶然间下载了一个使用zxing的android app,该app可以调用摄像头扫描二维码。 而这个app扫描电脑上显示的图片是没有问题的, 这间接证明了zxing可能是没有 问题的。 有了对的例子参照就好,照着改就行了。 然后看了一下Android调用摄像头扫描的代码,按照里面的参数都调整了一下,还是失败 !!!!
-
- 于是放了一个大招!!! 仿APP的扫描逻辑, 按不同比例、一块地遍历图片扫描! 有GIS的底子,写这些算法还是挺快的 ^_^ 结果还是失败!
- 接着将android扫描的结果保存成了图片,对比了一下,也没发现所以然来。快要崩溃了!!
- FINAL:突然想到,Android里扫描的图片其实和真实的图片是有差别的, Android里是摄像头拍屏幕“得到”的,拍到的结果肯定比实际的图片“模糊”!!
然后我将灰阶处理过的黑白图放大后仔细分析了一下,发现下面的图片其实有很多“杂点”,而手机扫描时,这些“杂点”却被模糊看不出来了。 ,思路出来了!!!! 需要将图片里面影响识别的“杂点”给模糊处理! 再联想到stackoverflow上的一次成功例子,,模糊嘛,将图片缩小不就模糊了。
最终处理方法
zxing在识别高清的手机图时,将图片逐步缩小,并扫描!! 就这么简单!!!!!
下面是代码,测试了好几个图片都可以识别的, 过程有些纠结哎!!!
/** * QR 二维码工具。 * * @author liuyixin */ public class QRCodeUtil { private static final int MAX_QRCODE_SIXE = 1500; public static String readToString(BufferedImage sourceImage) { BufferedImage image = toGrayImage(sourceImage); if (sourceImage.getWidth() > MAX_QRCODE_SIXE && sourceImage.getHeight() > MAX_QRCODE_SIXE) {// second image = resizeToMaxSize(sourceImage); } String result = readDirectly(image); if (StringUtils.isNotBlank(result)) { return result; } int minSize = 170; int imgSize = Math.min(image.getWidth(), image.getHeight()); int level = 1; while (imgSize > minSize) { BufferedImage newImage = new BufferedImage((int) (image.getWidth() * Math.pow(0.9, level)), (int) (image.getHeight() * Math.pow(0.9, level)), image.getType()); newImage.getGraphics().drawImage(image, 0, 0, newImage.getWidth(), newImage.getHeight(), 0, 0, image.getWidth(), image.getHeight(), null); result = readDirectly(newImage); if (StringUtils.isNotBlank(result)) { return result; } imgSize = Math.min(newImage.getWidth(), newImage.getHeight()); level++; } return ""; } /** * 将图片转成灰阶。 * * @param image * @return */ private static BufferedImage toGrayImage(BufferedImage image) { BufferedImage result = image; if (BufferedImage.TYPE_BYTE_GRAY != image.getType()) { BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); newImage.getGraphics().drawImage(image, 0, 0, null); result = newImage; } /*黑白处理 Raster raster = result.getRaster(); DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); byte[] data = buffer.getData(); for (int i = 0; i < data.length; i++) { byte value = 0; if (data[i] < 32) { value = -1; } buffer.setElem(i, value); }*/ return result; } /** * 图片若过大,则缩放图片。 * * @param image * @return */ private static BufferedImage resizeToMaxSize(BufferedImage image) { int height = MAX_QRCODE_SIXE; int width = MAX_QRCODE_SIXE; if (image.getWidth() > image.getHeight()) { width = (int) (height * (((double) image.getWidth()) / image.getHeight())); } else { height = (int) (width * (((double) image.getHeight()) / image.getWidth())); } BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); newImage.getGraphics().drawImage(image, 0, 0, newImage.getWidth() + 1, newImage.getHeight() + 1, 0, 0, image.getWidth(), image.getHeight(), null); return newImage; } private static String readDirectly(BufferedImage image) { LuminanceSource source = new BufferedImageLuminanceSource(image); Binarizer binarizer = new HybridBinarizer(source); BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer); Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>(); hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); try { return new MultiFormatReader().decode(binaryBitmap, hints).getText(); } catch (NotFoundException e) { return ""; } } }