import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BinaryBitmap;
|
import com.google.zxing.DecodeHintType;
|
import com.google.zxing.LuminanceSource;
|
import com.google.zxing.MultiFormatReader;
|
import com.google.zxing.Result;
|
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
|
import com.google.zxing.common.HybridBinarizer;
|
|
import javax.imageio.ImageIO;
|
import java.awt.RenderingHints;
|
import java.awt.image.BufferedImage;
|
import java.io.File;
|
import java.util.EnumMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* 解码失败样本的成因判定
|
*
|
* 用法:java DiagnoseQrFailures <标签目录> <文件名1> [文件名2 ...]
|
*
|
* 思路:把图片按整数倍最近邻放大后重新解码。二维码是纯黑白方块,
|
* 最近邻放大不引入新信息,只是给解码器的采样网格更多余地。
|
* - 放大后能解出 → 二维码数据本身是对的,只是模块密度/采样位置过于临界
|
* (属解码端问题,但印刷尺寸偏小时真实扫码枪也可能读不出,应缩短二维码内容或加大标签)
|
* - 放大后仍解不出 → 该标签确有缺陷,需按废品处理
|
*/
|
public class DiagnoseQrFailures {
|
|
public static void main(String[] args) throws Exception {
|
File dir = new File(args[0]);
|
for (int i = 1; i < args.length; i++) {
|
File f = new File(dir, args[i]);
|
if (!f.isFile()) {
|
System.out.println(args[i] + " -> 文件不存在");
|
continue;
|
}
|
BufferedImage img = ImageIO.read(f);
|
String at1 = decode(img);
|
System.out.println("\n=== " + args[i] + " ===");
|
System.out.println(" 原始尺寸 : " + img.getWidth() + " x " + img.getHeight());
|
System.out.println(" 1x 解码 : " + (at1 == null ? "失败" : "成功 -> " + at1));
|
if (at1 != null) {
|
continue;
|
}
|
boolean rescued = false;
|
for (int scale : List.of(2, 3, 4, 6)) {
|
BufferedImage up = scaleNearest(img, scale);
|
String text = decode(up);
|
System.out.println(" " + scale + "x 放大解码 : " + (text == null ? "失败" : "成功 -> " + text));
|
if (text != null) {
|
rescued = true;
|
break;
|
}
|
}
|
System.out.println(" 结论 : " + (rescued
|
? "二维码数据有效,属模块密度临界(解码端),非废码"
|
: "放大后仍不可识读,标签确有缺陷"));
|
}
|
}
|
|
/** 整数倍最近邻放大:不插值、不引入新像素值,只放大采样空间 */
|
private static BufferedImage scaleNearest(BufferedImage src, int scale) {
|
BufferedImage out = new BufferedImage(
|
src.getWidth() * scale, src.getHeight() * scale, BufferedImage.TYPE_INT_RGB);
|
var g = out.createGraphics();
|
try {
|
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
g.drawImage(src, 0, 0, out.getWidth(), out.getHeight(), null);
|
} finally {
|
g.dispose();
|
}
|
return out;
|
}
|
|
private static String decode(BufferedImage image) {
|
LuminanceSource source = new BufferedImageLuminanceSource(image);
|
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
|
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
|
hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE));
|
try {
|
Result r = new MultiFormatReader().decode(
|
new BinaryBitmap(new HybridBinarizer(source)), hints);
|
return r == null ? null : r.getText();
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
}
|