From 086553b08169b4a8d3880769b6f20c3eb40baa6d Mon Sep 17 00:00:00 2001 From: hdka <823267011@qq.com> Date: Sat, 27 Sep 2025 19:37:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=88=AA=E5=9B=BE=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20=E5=8A=A0=E5=85=A5=E6=A8=A1=E5=9E=8B=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-video/pom.xml | 1 + .../com/ruoyi/video/common/ModelManager.java | 2 +- ...oDetector.java => OpenCvYoloDetector.java} | 75 +++++++++---------- .../main/resources/libs/models/models.json | 4 +- 4 files changed, 40 insertions(+), 42 deletions(-) rename ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/{OpenVinoYoloDetector.java => OpenCvYoloDetector.java} (77%) diff --git a/ruoyi-video/pom.xml b/ruoyi-video/pom.xml index 742784f..99ffcb2 100644 --- a/ruoyi-video/pom.xml +++ b/ruoyi-video/pom.xml @@ -38,6 +38,7 @@ 1.5.10 + com.fasterxml.jackson.core diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/common/ModelManager.java b/ruoyi-video/src/main/java/com/ruoyi/video/common/ModelManager.java index 253662a..3a2443d 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/common/ModelManager.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/common/ModelManager.java @@ -32,7 +32,7 @@ public final class ModelManager implements AutoCloseable { int rgb = palette[i % palette.length]; i++; int bgr = ((rgb & 0xFF) << 16) | (rgb & 0xFF00) | ((rgb >> 16) & 0xFF); - YoloDetector det = new OpenVinoYoloDetector(name, dir, w, h, backend, bgr); + YoloDetector det = new OpenCvYoloDetector(name, dir, w, h, backend, bgr); map.put(name, det); } } diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/OpenVinoYoloDetector.java b/ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/OpenCvYoloDetector.java similarity index 77% rename from ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/OpenVinoYoloDetector.java rename to ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/OpenCvYoloDetector.java index c7ea03e..eacba86 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/OpenVinoYoloDetector.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/thread/detector/OpenCvYoloDetector.java @@ -5,14 +5,21 @@ import org.bytedeco.javacpp.indexer.FloatRawIndexer; import org.bytedeco.opencv.opencv_core.*; import org.bytedeco.opencv.opencv_dnn.Net; -import java.nio.file.*; -import java.util.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -import static org.bytedeco.opencv.global.opencv_dnn.*; // DNN API(readNetFromModelOptimizer / blobFromImage 等) -import static org.bytedeco.opencv.global.opencv_core.*; // Mat/Size/Scalar/transpose 等 -import static org.bytedeco.opencv.global.opencv_imgproc.*; // cvtColor 等 +import static org.bytedeco.opencv.global.opencv_core.*; +import static org.bytedeco.opencv.global.opencv_dnn.*; +import static org.bytedeco.opencv.global.opencv_imgproc.*; -public final class OpenVinoYoloDetector implements YoloDetector { +/** + * 纯 OpenCV CPU 后端的 YOLO IR 检测器(.xml/.bin) + * 与原 OpenVinoYoloDetector 代码保持一致,只是固定:DNN_BACKEND_OPENCV + DNN_TARGET_CPU。 + */ +public final class OpenCvYoloDetector implements YoloDetector { private final String modelName; private final Net net; private final Size input; @@ -20,7 +27,15 @@ public final class OpenVinoYoloDetector implements YoloDetector { private final String[] classes; private final int colorBGR; - public OpenVinoYoloDetector(String name, Path dir, int inW, int inH, String backend, int colorBGR) throws Exception { + /** + * @param name 模型别名 + * @param dir 模型目录(含 model.xml / model.bin / classes.txt) + * @param inW 推理输入宽 + * @param inH 推理输入高 + * @param backend 兼容参数,忽略(统一使用 OpenCV CPU) + * @param colorBGR 画框颜色(BGR packed int) + */ + public OpenCvYoloDetector(String name, Path dir, int inW, int inH, String backend, int colorBGR) throws Exception { this.modelName = name; this.input = new Size(inW, inH); this.colorBGR = colorBGR; @@ -30,26 +45,18 @@ public final class OpenVinoYoloDetector implements YoloDetector { Path clsPath = dir.resolve("classes.txt"); if (Files.exists(clsPath)) { - this.classes = Files.readAllLines(clsPath).stream().map(String::trim) - .filter(s -> !s.isEmpty()).toArray(String[]::new); + this.classes = Files.readAllLines(clsPath).stream() + .map(String::trim).filter(s -> !s.isEmpty()) + .toArray(String[]::new); } else { this.classes = new String[0]; } this.net = readNetFromModelOptimizer(xml, bin); - boolean set = false; - if ("openvino".equalsIgnoreCase(backend)) { - try { - net.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE); - net.setPreferableTarget(DNN_TARGET_CPU); - set = true; - } catch (Throwable ignore) { /* 回退 */ } - } - if (!set) { - net.setPreferableBackend(DNN_BACKEND_OPENCV); - net.setPreferableTarget(DNN_TARGET_CPU); - } + // ✅ 固定使用 OpenCV CPU 后端(不再尝试 OpenVINO) + net.setPreferableBackend(DNN_BACKEND_OPENCV); + net.setPreferableTarget(DNN_TARGET_CPU); } @Override public String name() { return modelName; } @@ -58,7 +65,7 @@ public final class OpenVinoYoloDetector implements YoloDetector { public List detect(Mat bgr) { if (bgr == null || bgr.empty()) return Collections.emptyList(); - // 统一成 BGR 3 通道,避免 blobFromImage 断言失败 + // 保证 BGR 3通道 if (bgr.channels() != 3) { Mat tmp = new Mat(); if (bgr.channels() == 1) cvtColor(bgr, tmp, COLOR_GRAY2BGR); @@ -69,23 +76,19 @@ public final class OpenVinoYoloDetector implements YoloDetector { try (Mat blob = blobFromImage(bgr, 1.0/255.0, input, new Scalar(0.0), true, false, CV_32F)) { net.setInput(blob); - // ===== 多输出兼容(Bytedeco 正确写法)===== + + // 兼容单/多输出 org.bytedeco.opencv.opencv_core.StringVector outNames = net.getUnconnectedOutLayersNames(); List outs = new ArrayList<>(); if (outNames == null || outNames.size() == 0) { - // 只有一个默认输出 - Mat out = net.forward(); // ← 直接返回 Mat + Mat out = net.forward(); outs.add(out); } else { - // 多输出:用 MatVector 承接 org.bytedeco.opencv.opencv_core.MatVector outBlobs = new org.bytedeco.opencv.opencv_core.MatVector(outNames.size()); - net.forward(outBlobs, outNames); // ← 正确的重载 - - for (long i = 0; i < outBlobs.size(); i++) { - outs.add(outBlobs.get(i)); - } + net.forward(outBlobs, outNames); + for (long i = 0; i < outBlobs.size(); i++) outs.add(outBlobs.get(i)); } int fw = bgr.cols(), fh = bgr.rows(); @@ -98,7 +101,6 @@ public final class OpenVinoYoloDetector implements YoloDetector { } if (boxes.isEmpty()) return Collections.emptyList(); - // 纯 Java NMS,避免 MatOf* / Vector API 兼容问题 List keep = nmsIndices(boxes, scores, nmsTh); List result = new ArrayList<>(keep.size()); @@ -111,7 +113,6 @@ public final class OpenVinoYoloDetector implements YoloDetector { } return result; } catch (Throwable e) { - // 单帧失败不影响整体 return Collections.emptyList(); } } @@ -123,7 +124,6 @@ public final class OpenVinoYoloDetector implements YoloDetector { Mat m; if (dims == 2) { - // NxC 或 CxN if (out.cols() >= 6) { m = out; } else { @@ -132,7 +132,6 @@ public final class OpenVinoYoloDetector implements YoloDetector { m = tmp; } } else if (dims == 3) { - // [1,N,C] 或 [1,C,N] if (out.size(2) >= 6) { m = out.reshape(1, out.size(1)); // -> N×C } else { @@ -142,7 +141,6 @@ public final class OpenVinoYoloDetector implements YoloDetector { m = tmp; } } else if (dims == 4) { - // [1,1,N,C] 或 [1,1,C,N] int a = out.size(2), b = out.size(3); if (b >= 6) { m = out.reshape(1, a).clone(); // -> N×C @@ -153,7 +151,7 @@ public final class OpenVinoYoloDetector implements YoloDetector { m = tmp.clone(); } } else { - return; // 不支持的形状 + return; } int N = m.rows(), C = m.cols(); @@ -172,7 +170,7 @@ public final class OpenVinoYoloDetector implements YoloDetector { float conf = obj * bestScore; if (conf < confTh) continue; - // 默认假设归一化中心点格式 (cx,cy,w,h);若你的 IR 是 x1,y1,x2,y2,请把这里换算改掉 + // 假定 (cx,cy,w,h) 为归一化中心点格式;如是 x1,y1,x2,y2,请按需改这里 int bx = Math.max(0, Math.round(cx * fw - (w * fw) / 2f)); int by = Math.max(0, Math.round(cy * fh - (h * fh) / 2f)); int bw = Math.min(fw - bx, Math.round(w * fw)); @@ -189,7 +187,6 @@ public final class OpenVinoYoloDetector implements YoloDetector { private List nmsIndices(List boxes, List scores, float nmsThreshold) { List order = new ArrayList<>(boxes.size()); for (int i = 0; i < boxes.size(); i++) order.add(i); - // 按分数降序 order.sort((i, j) -> Float.compare(scores.get(j), scores.get(i))); List keep = new ArrayList<>(); diff --git a/ruoyi-video/src/main/resources/libs/models/models.json b/ruoyi-video/src/main/resources/libs/models/models.json index 8a19a3c..14a7b7c 100644 --- a/ruoyi-video/src/main/resources/libs/models/models.json +++ b/ruoyi-video/src/main/resources/libs/models/models.json @@ -1,4 +1,4 @@ [ - {"name":"smoke","path":"models/smoke","size":[640,640],"backend":"openvino"}, - {"name":"garbage","path":"models/garbage","size":[640,640],"backend":"openvino"} + {"name":"smoke","path":"libs/models/smoke","size":[640,640],"backend":"OpenCV"}, + {"name":"garbage","path":"libs/models/garbage","size":[640,640],"backend":"OpenCV"} ]