背景
最近有個應用需要在服務端解析圖片中的二維碼,網上查了一下,幾乎全是用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 "";
}
}
}
