解析发票二维码图片,前提是二维码图片拿到,其他文章都有,自己找。
步骤:
1、导入依赖
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.2</version>
</dependency>
<!-- OpenCV (bytedeco platform 包含本地库,方便跨平台) -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.7</version>
</dependency>
2、解析
package xxx.xxx.xxx;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.global.opencv_photo;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.Size;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Map;
/**
* 发票二维码解析工具
*/
public class DecodeInvoiceQRCodeUtil {
public static void main(String[] args) throws Exception {
// 替换为你的图片路径
String path = "xxx.jpg";
BufferedImage img = ImageIO.read(new File(path));
String result = decodeInvoiceQRCode(img);
System.out.println("解析结果: " + result);
}
/**
* 尝试多策略解析
* @param img
* @return
* @throws IOException
*/
public static String decodeInvoiceQRCode(BufferedImage img) {
// 1. 直接尝试(原图)
String r = tryDecodeZXing(img);
if (r != null) return r;
// 2. 放大后尝试
BufferedImage big = resize(img, img.getWidth()*2, img.getHeight()*2);
r = tryDecodeZXing(big);
if (r != null) return r;
// 3. 旋转若干角度尝试
for (int deg : new int[]{0, 90, 180, 270}) {
BufferedImage rot = rotateImage(big, deg);
r = tryDecodeZXing(rot);
if (r != null) return r;
}
// 4. OpenCV 预处理:去噪,二值化,多形态学,尝试
BufferedImage pre = preprocessWithOpenCV(img);
r = tryDecodeZXing(pre);
if (r != null) return r;
// 5. OpenCV 去 logo(inpaint)后尝试
BufferedImage inpainted = removeLogoByInpainting(img);
r = tryDecodeZXing(inpainted);
if (r != null) return r;
// 6. 组合尝试:放大 + inpaint + 锐化
BufferedImage inpaintedBig = resize(inpainted, inpainted.getWidth()*2, inpainted.getHeight()*2);
r = tryDecodeZXing(inpaintedBig);
if (r != null) return r;
return null; // 解析失败
}
/**
* ZXing 解码(尝试一些常用 hint)
* @param image
* @return
*/
public static String tryDecodeZXing(BufferedImage image) {
try {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
// 若二维码不是纯黑白
hints.put(DecodeHintType.PURE_BARCODE, Boolean.FALSE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, java.util.Arrays.asList(BarcodeFormat.QR_CODE));
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
com.google.zxing.Result result = new QRCodeReader().decode(bitmap, hints);
return result.getText();
} catch (NotFoundException nf) {
// 未找到
return null;
} catch (FormatException | ChecksumException e) {
return null;
} catch (Exception ex) {
return null;
}
}
/**
* 缩放
* @param img
* @param newW
* @param newH
* @return
*/
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
Image tmp = img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH);
BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = dimg.createGraphics();
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return dimg;
}
/**
* 旋转
* @param src
* @param degrees
* @return
*/
public static BufferedImage rotateImage(BufferedImage src, double degrees) {
int w = src.getWidth();
int h = src.getHeight();
BufferedImage dest = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = dest.createGraphics();
AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(degrees), w/2.0, h/2.0);
g2.setTransform(at);
g2.drawImage(src, 0, 0, null);
g2.dispose();
return dest;
}
/**
* OpenCV 预处理(灰度、双边滤波、阈值、形态学)
* @param input
* @return
*/
public static BufferedImage preprocessWithOpenCV(BufferedImage input) {
Mat src = bufferedImageToMat(input);
Mat gray = new Mat();
opencv_imgproc.cvtColor(src, gray, opencv_imgproc.COLOR_BGR2GRAY);
Mat denoise = new Mat();
opencv_imgproc.GaussianBlur(gray, denoise, new Size(3,3), 0);
// 自适应阈值
Mat thresh = new Mat();
opencv_imgproc.adaptiveThreshold(denoise, thresh, 255,
opencv_imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
opencv_imgproc.THRESH_BINARY, 25, 10);
// 形态学闭运算填补小洞
Mat kernel = opencv_imgproc.getStructuringElement(opencv_imgproc.MORPH_RECT, new Size(3,3));
opencv_imgproc.morphologyEx(thresh, thresh, opencv_imgproc.MORPH_CLOSE, kernel);
BufferedImage out = matToBufferedImage(thresh);
return out;
}
/**
* 尝试检测并 inpaint(去掉中间 logo)
* @param input
* @return
*/
public static BufferedImage removeLogoByInpainting(BufferedImage input) {
Mat src = bufferedImageToMat(input);
Mat gray = new Mat();
opencv_imgproc.cvtColor(src, gray, opencv_imgproc.COLOR_BGR2GRAY);
// 1. 找显著中心区域(简单方法:检测中心附近亮/非黑区域)
int w = gray.cols(), h = gray.rows();
// 选择中心区域一个方框 (约占 30% 大小),假设 logo 在中心
int cx = w/2, cy = h/2;
int rw = Math.max(20, w/5), rh = Math.max(20, h/5);
Rect centerRect = new Rect(Math.max(0, cx - rw/2), Math.max(0, cy - rh/2),
Math.min(rw, w), Math.min(rh, h));
Mat roi = new Mat(gray, centerRect);
// 2. 在中心区域做二值化,找出非二维码黑白模式(logo 通常有连续的亮色或复杂纹理)
Mat bin = new Mat();
opencv_imgproc.threshold(roi, bin, 0, 255, opencv_imgproc.THRESH_BINARY | opencv_imgproc.THRESH_OTSU);
// 3. 如果中心区域亮/暗变化明显,构造 mask(扩大并做膨胀)
Mat mask = Mat.zeros(gray.size(), opencv_core.CV_8UC1).asMat();
// 把 bin 写回 mask 的对应位置
Mat maskRoi = new Mat(mask, centerRect);
bin.copyTo(maskRoi);
// 膨胀使 mask 覆盖更大范围
Mat k = opencv_imgproc.getStructuringElement(opencv_imgproc.MORPH_ELLIPSE, new Size(25,25));
opencv_imgproc.dilate(mask, mask, k);
// 4. Inpaint 填充(TELEA)
Mat inpainted = new Mat();
opencv_photo.inpaint(src, mask, inpainted, 3, opencv_photo.INPAINT_TELEA);
return matToBufferedImage(inpainted);
}
/**
* 辅助:BufferedImage <-> Mat
* @param bi
* @return
*/
public static Mat bufferedImageToMat(BufferedImage bi) {
// Convert BufferedImage to Mat (BGR)
if (bi.getType() != BufferedImage.TYPE_3BYTE_BGR) {
BufferedImage converted = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = converted.createGraphics();
g.drawImage(bi, 0, 0, null);
g.dispose();
bi = converted;
}
byte[] pixels = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
Mat mat = new Mat(bi.getHeight(), bi.getWidth(), opencv_core.CV_8UC3);
mat.data().put(pixels);
// Convert RGB->BGR if needed (BufferedImage TYPE_3BYTE_BGR stores in BGR order already), so ok.
return mat;
}
/**
* Mat 转 BufferedImage
* @param mat
* @return
*/
public static BufferedImage matToBufferedImage(Mat mat) {
// Convert Mat (BGR or GRAY) to BufferedImage TYPE_3BYTE_BGR or TYPE_BYTE_GRAY
int type = BufferedImage.TYPE_BYTE_GRAY;
Mat mat2 = new Mat();
if (mat.channels() == 3) {
type = BufferedImage.TYPE_3BYTE_BGR;
mat2 = mat;
} else if (mat.channels() == 1) {
mat2 = mat;
} else if (mat.channels() == 4) {
opencv_imgproc.cvtColor(mat, mat2, opencv_imgproc.COLOR_BGRA2BGR);
type = BufferedImage.TYPE_3BYTE_BGR;
} else {
mat.convertTo(mat2, opencv_core.CV_8U);
}
int width = mat2.cols();
int height = mat2.rows();
int channels = mat2.channels();
byte[] data = new byte[width * height * channels];
mat2.data().get(data);
BufferedImage image = new BufferedImage(width, height, type);
final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(data, 0, targetPixels, 0, data.length);
return image;
}
}