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