diff --git a/README.md b/README.md
index c80fbc8..7e0acbd 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# LxCameraApi
-蓝芯和hik工业摄像头java实现
\ No newline at end of file
+蓝芯和hik工业摄像头java实现
+
+采用opencvWechatQrcode 需要将D:\git\测试\lxCameraApi\src\main\resources\wechatqrcode放到指定位置!!!
\ No newline at end of file
diff --git a/logs/sys-error.log b/logs/sys-error.log
deleted file mode 100644
index e69de29..0000000
diff --git a/pom.xml b/pom.xml
index 19dd8f6..869a8be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,24 +36,24 @@
org.bytedeco
javacpp
- 1.5.7
+ 1.5.11
windows-x86_64
org.bytedeco
openblas
- 0.3.19-1.5.7
+ 0.3.28-1.5.11
windows-x86_64
org.bytedeco
opencv
- 4.5.5-1.5.7
+ 4.10.0-1.5.11
org.bytedeco
opencv
- 4.5.5-1.5.7
+ 4.10.0-1.5.11
windows-x86_64
@@ -73,27 +73,17 @@
org.bytedeco
javacpp
- 1.5.7
+ 1.5.11
windows-x86_64
org.bytedeco
openblas
- 0.3.19-1.5.7
- windows-x86_64
-
-
- org.bytedeco
- opencv
- 4.5.5-1.5.7
-
-
- org.bytedeco
- opencv
- 4.5.5-1.5.7
+ 0.3.28-1.5.11
windows-x86_64
+
com.microsoft.onnxruntime
onnxruntime
@@ -228,6 +218,23 @@
+
+
+ copy-wechatqrcode-models
+ generate-resources
+
+ copy-resources
+
+
+
+ ${user.home}/.lxCameraApi/models/wechatqrcode
+
+
+ ${project.basedir}/src/main/resources/wechatqrcode
+
+
+
+
diff --git a/src/main/java/com/example/lxcameraapi/controller/QtCodeController.java b/src/main/java/com/example/lxcameraapi/controller/QtCodeController.java
index 4fc5bbd..58cb11c 100644
--- a/src/main/java/com/example/lxcameraapi/controller/QtCodeController.java
+++ b/src/main/java/com/example/lxcameraapi/controller/QtCodeController.java
@@ -234,8 +234,9 @@ public class QtCodeController {
boxCountResponse.setSuccess(false);
return boxCountResponse;
}
+// 默认3次
- int count = request.getCount() != null ? request.getCount() : 1;
+ int count = request.getCount() != null ? request.getCount() : 3;
int intervalMs = request.getIntervalMs() != null ? request.getIntervalMs() : 500;
// 拍照并收集路径
@@ -417,10 +418,25 @@ public class QtCodeController {
// System.out.println(deCode(img));
// }
+ private static String getModelPath(String filename) {
+ // 从classpath资源加载模型文件
+ String basePath = "wechatqrcode/";
+ try {
+ File resourceFile = new File(Thread.currentThread().getContextClassLoader().getResource(basePath + filename).toURI());
+ return resourceFile.getAbsolutePath();
+ } catch (Exception e) {
+ return basePath + filename;
+ }
+ }
+
private static String deCode(Mat img) {
- // 微信二维码对象,要返回二维码坐标前2个参数必传;后2个在二维码小或不清晰时必传。
- WeChatQRCode we = new WeChatQRCode();
-// List points = new ArrayList();
+ // 微信二维码对象,需要传入模型文件路径
+ WeChatQRCode we = new WeChatQRCode(
+ getModelPath("detect.prototxt"),
+ getModelPath("detect.caffemodel"),
+ getModelPath("sr.prototxt"),
+ getModelPath("sr.caffemodel")
+ );
// 微信二维码引擎解码,返回的valList中存放的是解码后的数据,points中Mat存放的是二维码4个角的坐标
StringVector stringVector = we.detectAndDecode(img);
if (stringVector.empty()) {
diff --git a/src/main/java/com/example/lxcameraapi/service/AsyncProcessingService.java b/src/main/java/com/example/lxcameraapi/service/AsyncProcessingService.java
index 926b2e4..a6fd13f 100644
--- a/src/main/java/com/example/lxcameraapi/service/AsyncProcessingService.java
+++ b/src/main/java/com/example/lxcameraapi/service/AsyncProcessingService.java
@@ -250,6 +250,12 @@ public class AsyncProcessingService {
BoundingBox box = detectResults.get(j);
try {
BufferedImage croppedImage = cropBoundingBox(originalImage, box, 30);
+
+ // 保存裁剪的原始图片用于调试
+ String debugPath = path + "_crop_" + i + "_" + j + ".jpg";
+ ImageIO.write(croppedImage, "jpg", new File(debugPath));
+ log.info("DEBUG: 裁剪图片保存到: {}, 尺寸: {}x{}, 置信度: {}",
+ debugPath, croppedImage.getWidth(), croppedImage.getHeight(), box.getConfidence());
// 最小尺寸放大
int minSize = 320;
@@ -257,6 +263,7 @@ public class AsyncProcessingService {
int newWidth = Math.max(croppedImage.getWidth(), minSize);
int newHeight = Math.max(croppedImage.getHeight(), minSize);
croppedImage = resizeImage(croppedImage, newWidth, newHeight);
+ log.info("DEBUG: 图片已放大到: {}x{}", newWidth, newHeight);
}
// 解码
@@ -265,8 +272,6 @@ public class AsyncProcessingService {
if (qrCodeContent != null) {
qrCodeResults.add(qrCodeContent);
validBoxes.add(box);
- } else {
- ImageIO.write(croppedImage, "jpg", new File(path + "_" + j + ".jpg"));
}
} catch (Exception e) {
log.error("第{}张图片解码异常,box: {}", i + 1, box, e);
@@ -401,54 +406,65 @@ public class AsyncProcessingService {
// 2. 尝试灰度化
try {
+ log.info("DEBUG: 灰度化开始, image类型={}, 尺寸={}x{}", image.getType(), image.getWidth(), image.getHeight());
Mat mat = opencvService.bufferedImageToMat(image);
+ log.info("DEBUG: bufferedImageToMat完成, mat类型={}, channels={}, depth={}",
+ mat.type(), mat.channels(), mat.depth());
Mat gray = opencvService.toGrayscale(mat);
+ log.info("DEBUG: toGrayscale完成, gray channels={}, depth={}", gray.channels(), gray.depth());
BufferedImage grayImage = opencvService.matToBufferedImage(gray);
+ log.info("DEBUG: matToBufferedImage完成, grayImage类型={}", grayImage.getType());
mat.release();
gray.release();
result = tryDecode(grayImage, confidence, retryIndex, "灰度");
if (result != null) return result;
} catch (Exception e) {
- log.debug("灰度预处理失败: {}", e.getMessage());
+ log.error("灰度预处理失败: type={}, msg={}", e.getClass().getName(), e.getMessage(), e);
}
// 3. 尝试CLAHE增强
try {
+ log.info("DEBUG: CLAHE开始");
Mat mat = opencvService.bufferedImageToMat(image);
Mat clahe = opencvService.clahe(mat);
+ log.info("DEBUG: clahe完成, clahe channels={}, depth={}", clahe.channels(), clahe.depth());
BufferedImage claheImage = opencvService.matToBufferedImage(clahe);
mat.release();
clahe.release();
result = tryDecode(claheImage, confidence, retryIndex, "CLAHE");
if (result != null) return result;
} catch (Exception e) {
- log.debug("CLAHE预处理失败: {}", e.getMessage());
+ log.error("CLAHE预处理失败: type={}, msg={}", e.getClass().getName(), e.getMessage(), e);
}
// 4. 尝试二值化
try {
+ log.info("DEBUG: 二值化开始");
Mat mat = opencvService.bufferedImageToMat(image);
Mat binary = opencvService.adaptiveThreshold(mat);
+ log.info("DEBUG: adaptiveThreshold完成, binary channels={}, depth={}", binary.channels(), binary.depth());
BufferedImage binaryImage = opencvService.matToBufferedImage(binary);
mat.release();
binary.release();
result = tryDecode(binaryImage, confidence, retryIndex, "二值化");
if (result != null) return result;
} catch (Exception e) {
- log.debug("二值化预处理失败: {}", e.getMessage());
+ log.error("二值化预处理失败: type={}, msg={}", e.getClass().getName(), e.getMessage(), e);
}
// 5. 尝试组合预处理(去噪+灰度+CLAHE+二值化)
try {
+ log.info("DEBUG: 组合预处理开始");
Mat mat = opencvService.bufferedImageToMat(image);
Mat processed = opencvService.preprocessForQrCode(mat);
+ log.info("DEBUG: preprocessForQrCode完成, processed channels={}, depth={}", processed.channels(), processed.depth());
BufferedImage processedImage = opencvService.matToBufferedImage(processed);
mat.release();
processed.release();
result = tryDecode(processedImage, confidence, retryIndex, "组合预处理");
if (result != null) return result;
} catch (Exception e) {
- log.debug("组合预处理失败: {}", e.getMessage());
+ log.error("组合预处理失败: type={}, msg={}", e.getClass().getName(), e.getMessage(), e);
}
return null;
@@ -509,7 +525,7 @@ public class AsyncProcessingService {
* 调整图片大小
*/
private BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
- BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
+ BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2d = resizedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);
diff --git a/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/QrCode/WeChatDeCode.java b/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/QrCode/WeChatDeCode.java
index f3034f4..77994a7 100644
--- a/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/QrCode/WeChatDeCode.java
+++ b/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/QrCode/WeChatDeCode.java
@@ -21,6 +21,7 @@ import javax.imageio.ImageIO;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.opencv.core.CvType.CV_8UC3;
+import org.bytedeco.javacpp.Loader;
/**
*
@@ -28,7 +29,7 @@ import static org.opencv.core.CvType.CV_8UC3;
public class WeChatDeCode {
public static void main(String... args) {
- String imagePath = "D:\\data\\media\\2026-04-17\\1\\1776408379598.png_0.jpg";
+ String imagePath = "D:\\data\\media\\2026-05-20\\1\\2026-05-20-12-20-05-578_1.png_crop_1_6.jpg";
Mat img = imread(imagePath);
System.out.println(deCode(img));
@@ -167,9 +168,59 @@ public class WeChatDeCode {
mat.data().put(data);
return mat;
}
+ private static String getModelPath(String filename) {
+ // 对于 detect 和 sr 文件,优先尝试 _2021nov 版本
+ String altFilename = filename
+ .replace("detect.", "detect_2021nov.")
+ .replace("sr.", "sr_2021nov.");
+
+ // 模型文件路径(不含中文)
+ String modelBasePath = "D:/ccd/wechatqrcode/";
+ File altFile = new File(modelBasePath + altFilename);
+ File origFile = new File(modelBasePath + filename);
+
+ if (altFile.exists()) {
+ System.out.println("Loading model: " + altFile.getAbsolutePath());
+ return altFile.getAbsolutePath();
+ } else if (origFile.exists()) {
+ System.out.println("Loading model: " + origFile.getAbsolutePath());
+ return origFile.getAbsolutePath();
+ }
+
+ System.out.println("Model file not found: " + modelBasePath + altFilename);
+ return modelBasePath + altFilename;
+ }
+
public static String deCode(Mat img) {
- // 微信二维码对象,要返回二维码坐标前2个参数必传;后2个在二维码小或不清晰时必传。
- WeChatQRCode we = new WeChatQRCode();
+ // 获取模型文件路径
+ String detectPrototxt = getModelPath("detect_2021nov.prototxt");
+ String detectCaffemodel = getModelPath("detect_2021nov.caffemodel");
+ String srPrototxt = getModelPath("sr_2021nov.prototxt");
+ String srCaffemodel = getModelPath("sr_2021nov.caffemodel");
+
+ // 验证所有文件都存在
+ if (!new File(detectPrototxt).exists()) {
+ throw new RuntimeException("文件不存在: " + detectPrototxt);
+ }
+ if (!new File(detectCaffemodel).exists()) {
+ throw new RuntimeException("文件不存在: " + detectCaffemodel);
+ }
+ if (!new File(srPrototxt).exists()) {
+ throw new RuntimeException("文件不存在: " + srPrototxt);
+ }
+ if (!new File(srCaffemodel).exists()) {
+ throw new RuntimeException("文件不存在: " + srCaffemodel);
+ }
+
+ System.out.println("=== 模型文件路径 ===");
+ System.out.println("detectPrototxt: " + detectPrototxt + " (" + new File(detectPrototxt).length() + " bytes)");
+ System.out.println("detectCaffemodel: " + detectCaffemodel + " (" + new File(detectCaffemodel).length() + " bytes)");
+ System.out.println("srPrototxt: " + srPrototxt + " (" + new File(srPrototxt).length() + " bytes)");
+ System.out.println("srCaffemodel: " + srCaffemodel + " (" + new File(srCaffemodel).length() + " bytes)");
+ System.out.println("====================");
+
+ // 微信二维码对象,需要传入模型文件路径
+ WeChatQRCode we = new WeChatQRCode(detectPrototxt, detectCaffemodel, srPrototxt, srCaffemodel);
// 使用 MatVector 存储坐标点
org.bytedeco.opencv.opencv_core.MatVector points = new org.bytedeco.opencv.opencv_core.MatVector();
diff --git a/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/camera/lx/config/MultiImageRequest.java b/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/camera/lx/config/MultiImageRequest.java
index def20ad..bf88849 100644
--- a/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/camera/lx/config/MultiImageRequest.java
+++ b/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/camera/lx/config/MultiImageRequest.java
@@ -25,7 +25,7 @@ public class MultiImageRequest {
/**
* 拍照数量,默认1
*/
- private Integer count = 1;
+ private Integer count = 3;
/**
* 拍照间隔(ms),默认500
diff --git a/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/opencv/OpencvService.java b/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/opencv/OpencvService.java
index 086fb66..ddc2f03 100644
--- a/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/opencv/OpencvService.java
+++ b/src/main/java/com/example/lxcameraapi/service/IndustrialCamera/opencv/OpencvService.java
@@ -11,7 +11,9 @@ import org.springframework.stereotype.Service;
import java.awt.*;
import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -233,15 +235,17 @@ public class OpencvService {
* BufferedImage 转 Mat
*/
public Mat bufferedImageToMat(BufferedImage image) {
- if (image.getType() == BufferedImage.TYPE_INT_RGB) {
- return matify(image);
+ // 统一转换为 TYPE_3BYTE_BGR 以避免 DataBufferInt 问题
+ BufferedImage bgrImage;
+ if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) {
+ bgrImage = image;
} else {
- BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
- Graphics2D g = rgbImage.createGraphics();
+ bgrImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = bgrImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
- return matify(rgbImage);
}
+ return matify(bgrImage);
}
private Mat matify(BufferedImage image) {
@@ -255,23 +259,47 @@ public class OpencvService {
* Mat 转 BufferedImage
*/
public BufferedImage matToBufferedImage(Mat matrix) {
- int type = BufferedImage.TYPE_BYTE_GRAY;
- if (matrix.channels() > 1) {
- type = BufferedImage.TYPE_3BYTE_BGR;
+ // 16位图像转8位
+ Mat byteMat = matrix;
+ if (matrix.depth() == CvType.CV_16U || matrix.depth() == CvType.CV_16S) {
+ Mat normalized = new Mat();
+ Core.normalize(matrix, normalized, 0, 255, Core.NORM_MINMAX, CvType.CV_8UC(matrix.channels()));
+ byteMat = normalized;
}
- int bufferSize = matrix.channels() * matrix.cols() * matrix.rows();
- byte[] buffer = new byte[bufferSize];
- matrix.get(0, 0, buffer);
- BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(), type);
- final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
- System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);
+
+ // 转换为 BGR 或灰度格式
+ Mat bgrMat = new Mat();
+ if (byteMat.channels() > 1) {
+ Imgproc.cvtColor(byteMat, bgrMat, Imgproc.COLOR_BGR2RGB);
+ } else {
+ Imgproc.cvtColor(byteMat, bgrMat, Imgproc.COLOR_GRAY2RGB);
+ }
+ if (byteMat != matrix) {
+ byteMat.release();
+ }
+
+ // 使用 JavaCV/ImageIO 方式转换
+ int width = bgrMat.cols();
+ int height = bgrMat.rows();
+ int channels = bgrMat.channels();
+
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
+ byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
+
+ bgrMat.get(0, 0, pixels);
+ bgrMat.release();
+
return image;
}
/**
- * 图像预处理 - 灰度化
+ * 图像预处理 - 灰度化(自动处理不同通道数)
*/
public Mat toGrayscale(Mat src) {
+ // 如果已经是灰度图,直接返回副本
+ if (src.channels() == 1) {
+ return src.clone();
+ }
Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
return gray;
@@ -304,16 +332,11 @@ public class OpencvService {
* 图像预处理 - 锐化
*/
public Mat sharpen(Mat src) {
- Mat kernel = new Mat(3, 3, CvType.CV_32F);
- float[] data = {
- 0, -1, 0,
- -1, 5, -1,
- 0, -1, 0
- };
- kernel.put(0, 0, data);
+ Mat gray = toGrayscale(src);
Mat sharpened = new Mat();
- Imgproc.filter2D(src, sharpened, -1, kernel);
- kernel.release();
+ Imgproc.Laplacian(gray, sharpened, CvType.CV_8U, 3);
+ Core.convertScaleAbs(sharpened, sharpened);
+ gray.release();
return sharpened;
}
@@ -339,6 +362,7 @@ public class OpencvService {
* 图像预处理 - CLAHE(对比度限制自适应直方图均衡化)
*/
public Mat clahe(Mat src) {
+ // toGrayscale已自动处理单通道图
Mat gray = toGrayscale(src);
CLAHE clahe = Imgproc.createCLAHE(2.0, new Size(8, 8));
Mat result = new Mat();
diff --git a/src/main/resources/wechatqrcode/detect_2021nov.caffemodel b/src/main/resources/wechatqrcode/detect_2021nov.caffemodel
new file mode 100644
index 0000000..453126c
Binary files /dev/null and b/src/main/resources/wechatqrcode/detect_2021nov.caffemodel differ
diff --git a/src/main/resources/wechatqrcode/detect.prototxt b/src/main/resources/wechatqrcode/detect_2021nov.prototxt
similarity index 100%
rename from src/main/resources/wechatqrcode/detect.prototxt
rename to src/main/resources/wechatqrcode/detect_2021nov.prototxt
diff --git a/src/main/resources/wechatqrcode/sr.caffemodel b/src/main/resources/wechatqrcode/sr_2021nov.caffemodel
similarity index 100%
rename from src/main/resources/wechatqrcode/sr.caffemodel
rename to src/main/resources/wechatqrcode/sr_2021nov.caffemodel
diff --git a/src/main/resources/wechatqrcode/sr.prototxt b/src/main/resources/wechatqrcode/sr_2021nov.prototxt
similarity index 100%
rename from src/main/resources/wechatqrcode/sr.prototxt
rename to src/main/resources/wechatqrcode/sr_2021nov.prototxt