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 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; } } }