diff --git a/README.md b/README.md
index 97af8a4..d42be81 100644
--- a/README.md
+++ b/README.md
@@ -426,6 +426,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
### 2、Maven
在项目的 `pom.xml` 的 `dependencies` 中可以一次性引入全部功能(如下所示)。
+
⚠️ **注意:不推荐直接引入全部依赖**,更推荐根据实际需求,按功能模块单独引入,避免引入不必要的包。
详细引入方式请查看 [文档](http://doc.smartjavaai.cn/install.html)、或查看[示例代码](https://gitee.com/dengwenjie/SmartJavaAI/tree/master/examples)
@@ -434,7 +435,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
cn.smartjavaai
smartjavaai-all
- 1.0.24
+ 1.0.25
```
@@ -577,6 +578,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
| YOLOV8-SEG | OnnxRuntime | Ultralytics在COCO 数据集 上训练的模型 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) |
| YOLOV11-SEG | OnnxRuntime | Ultralytics在COCO 数据集 上训练的模型 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) |
| Mask R-CNN | MXNet | Mask R-CNN 是一种在目标检测基础上,同时为每个物体生成像素级分割区域的深度学习模型 | 无 |
+
---
#### OBB旋转框目标检测模型
@@ -733,6 +735,18 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
## 近期更新日志
+## [v1.0.25] - 2025-10-02
+- 【人脸检测】新增6个模型(MTCNN、YOLOV5、RetinaFace小尺寸版),大幅提升性能
+- 【人脸识别】新增Seetaface6轻量模型
+- 【目标检测】支持视频流目标检测(rtsp、视频文件等)
+- 【目标检测】支持tensorflow2目标检测模型
+- 【目标检测】新增行人检测模型(yolo-person)
+- 【通用视觉】新增4个动作识别模型
+- 【通用视觉】新增语义分割模型
+- 【通用视觉】新增5个实例分割模型(含yolov8-seg、yolov11-seg)
+- 【通用视觉】新增yolo-obb11旋转框检测(含yolov11-obb)
+- 【通用视觉】新增5个姿态估计模型(含yolov8-pose、yolov11-pose)
+
## [v1.0.24] - 2025-09-07
- 【人脸检测】新增6个模型(MTCNN、YOLOV5、RetinaFace小尺寸版),大幅提升性能
- 【人脸识别】新增Seetaface6轻量模型
diff --git a/all/pom.xml b/all/pom.xml
index 0e06207..5bb04f3 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -6,11 +6,11 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.24
+ 1.0.25
all
- 1.0.24
+ 1.0.25
${project.artifactId}
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/bom/pom.xml b/bom/pom.xml
index c73693c..697dc70 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -6,12 +6,12 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.24
+ 1.0.25
- 1.0.24
+ 1.0.25
bom
- sbom
+ bom
统一版本管理的 BOM 包,同时支持 import 和全量依赖
diff --git a/common/pom.xml b/common/pom.xml
index 24a57e4..06eabfa 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -6,7 +6,7 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.24
+ 1.0.25
common
diff --git a/common/src/main/java/cn/smartjavaai/common/cv/SmartImageFactory.java b/common/src/main/java/cn/smartjavaai/common/cv/SmartImageFactory.java
index 9f456a1..2d0a184 100644
--- a/common/src/main/java/cn/smartjavaai/common/cv/SmartImageFactory.java
+++ b/common/src/main/java/cn/smartjavaai/common/cv/SmartImageFactory.java
@@ -21,45 +21,132 @@ import org.opencv.imgproc.Imgproc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.file.Path;
+import java.nio.file.Paths;
/**
* 图片处理工厂类
* @author dwj
*/
-public class SmartImageFactory extends BufferedImageFactory {
+public class SmartImageFactory {
+ public enum Engine {
+ BUFFEREDIMAGE,
+ OPENCV
+ }
+
+ private static volatile Engine currentEngine = Engine.BUFFEREDIMAGE;
private static volatile SmartImageFactory instance;
- public static SmartImageFactory newInstance() {
+ public static synchronized void setEngine(Engine engine) {
+ if (engine == null || engine == currentEngine) {
+ return;
+ }
+ currentEngine = engine;
+ // 只在切换时注册全局
+ switch (currentEngine) {
+ case OPENCV:
+ ImageFactory.setImageFactory(new OpenCVImageFactory());
+ break;
+ case BUFFEREDIMAGE:
+ default:
+ ImageFactory.setImageFactory(new BufferedImageFactory());
+ }
+ }
+
+ public static synchronized SmartImageFactory getInstance() {
if (instance == null) {
- synchronized (SmartImageFactory.class) {
- if (instance == null) {
- instance = new SmartImageFactory();
- }
+ instance = new SmartImageFactory();
+ // 初始化全局 Engine
+ switch (currentEngine) {
+ case OPENCV:
+ ImageFactory.setImageFactory(new OpenCVImageFactory());
+ break;
+ case BUFFEREDIMAGE:
+ default:
+ ImageFactory.setImageFactory(new BufferedImageFactory());
}
}
return instance;
}
- public static SmartImageFactory getInstance(){
- return newInstance();
- }
public Image fromBufferedImage(BufferedImage sourceImage){
- return fromImage(OpenCVUtils.image2Mat(sourceImage));
+ if (sourceImage == null) {
+ throw new IllegalArgumentException("BufferedImage 不能为空");
+ }
+ Image image = null;
+ switch (currentEngine) {
+ case BUFFEREDIMAGE:
+ image = ImageFactory.getInstance().fromImage(sourceImage);
+ break;
+ case OPENCV:
+ // 先转 Mat
+ Mat mat = OpenCVUtils.image2Mat(sourceImage);
+ image = ImageFactory.getInstance().fromImage(mat);
+ break;
+ default:
+ throw new IllegalStateException("未知 Engine: " + currentEngine);
+ }
+ return image;
+ }
+
+ public Image fromMat(Mat mat){
+ if (mat == null) {
+ throw new IllegalArgumentException("mat 不能为空");
+ }
+ Image image = null;
+ switch (currentEngine) {
+ case OPENCV:
+ image = ImageFactory.getInstance().fromImage(mat);
+ break;
+ case BUFFEREDIMAGE:
+ // 先转 Mat
+ BufferedImage sourceImage = OpenCVUtils.mat2Image(mat);
+ image = ImageFactory.getInstance().fromImage(sourceImage);
+ break;
+ default:
+ throw new IllegalStateException("未知 Engine: " + currentEngine);
+ }
+ return image;
}
public Image fromBase64(String base64Image) throws IOException {
- return fromUrl(base64Image);
+ return ImageFactory.getInstance().fromUrl(base64Image);
}
- public Image fromBytes(byte[] imageData){
- return fromImage(new ByteArrayInputStream(imageData));
+ public Image fromBytes(byte[] imageData) throws IOException {
+ return ImageFactory.getInstance().fromInputStream(new ByteArrayInputStream(imageData));
}
+ public Image fromFile(File file) throws IOException {
+ return ImageFactory.getInstance().fromFile(file.toPath());
+ }
+
+ public Image fromFile(Path path) throws IOException {
+ return ImageFactory.getInstance().fromFile(path);
+ }
+
+ public Image fromFile(String filePath) throws IOException {
+ if (filePath == null || filePath.trim().isEmpty()) {
+ throw new IllegalArgumentException("filePath 不能为空");
+ }
+ return fromFile(Paths.get(filePath));
+ }
+
+ public Image fromPixels(int[] pixels, int width, int height){
+ return ImageFactory.getInstance().fromPixels(pixels, width, height);
+ }
+
+ public Image fromInputStream(InputStream inputStream) throws IOException {
+ return ImageFactory.getInstance().fromInputStream(inputStream);
+ }
+
+
+
}
diff --git a/common/src/main/java/cn/smartjavaai/common/entity/PolygonLabel.java b/common/src/main/java/cn/smartjavaai/common/entity/PolygonLabel.java
new file mode 100644
index 0000000..24d07a7
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/entity/PolygonLabel.java
@@ -0,0 +1,28 @@
+package cn.smartjavaai.common.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 多边形
+ * @author dwj
+ */
+@Data
+public class PolygonLabel {
+
+ private List points;
+ private String text;
+
+ public PolygonLabel(List points, String text) {
+ this.points = points;
+ this.text = text;
+ }
+
+ public PolygonLabel() {
+ }
+
+ public PolygonLabel(List points) {
+ this.points = points;
+ }
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/preprocess/BufferedImagePreprocessor.java b/common/src/main/java/cn/smartjavaai/common/preprocess/BufferedImagePreprocessor.java
index ec0b109..e4ce4e8 100644
--- a/common/src/main/java/cn/smartjavaai/common/preprocess/BufferedImagePreprocessor.java
+++ b/common/src/main/java/cn/smartjavaai/common/preprocess/BufferedImagePreprocessor.java
@@ -10,7 +10,7 @@ import java.awt.image.BufferedImage;
* @author dwj
* @date 2025/6/27
*/
-public class BufferedImagePreprocessor {
+public class BufferedImagePreprocessor implements ImagePreprocessor{
private BufferedImage image;
private DetectionRectangle rect;
diff --git a/common/src/main/java/cn/smartjavaai/common/preprocess/DJLImagePreprocessor.java b/common/src/main/java/cn/smartjavaai/common/preprocess/DJLImagePreprocessor.java
new file mode 100644
index 0000000..8cf1d30
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/preprocess/DJLImagePreprocessor.java
@@ -0,0 +1,84 @@
+package cn.smartjavaai.common.preprocess;
+
+import ai.djl.modality.cv.Image;
+import cn.smartjavaai.common.cv.SmartImageFactory;
+import cn.smartjavaai.common.entity.DetectionRectangle;
+import org.opencv.core.Mat;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * @author dwj
+ */
+public class DJLImagePreprocessor implements ImagePreprocessor{
+
+
+ private final ImagePreprocessor> delegate;
+ private Image input = null;
+
+ public DJLImagePreprocessor(Image image, DetectionRectangle rect) {
+ this.input = image;
+ if (input.getWrappedImage() instanceof BufferedImage) {
+ this.delegate = new BufferedImagePreprocessor((BufferedImage) input.getWrappedImage(), rect);
+ } else if (input.getWrappedImage() instanceof Mat) {
+ this.delegate = new OpenCVPreprocessor((Mat) input.getWrappedImage(), rect);
+ } else {
+ throw new IllegalArgumentException("Unsupported input type");
+ }
+ }
+
+ @Override
+ public DJLImagePreprocessor setExtendRatio(float ratio) {
+ delegate.setExtendRatio(ratio);
+ return this;
+ }
+
+ @Override
+ public DJLImagePreprocessor setTargetSize(int size) {
+ delegate.setTargetSize(size);
+ return this;
+ }
+
+ @Override
+ public DJLImagePreprocessor setCenterCropSize(int size) {
+ delegate.setCenterCropSize(size);
+ return this;
+ }
+
+ @Override
+ public DJLImagePreprocessor enableSquarePadding(boolean enable) {
+ delegate.enableSquarePadding(enable);
+ return this;
+ }
+
+ @Override
+ public DJLImagePreprocessor enableScaling(boolean enable) {
+ delegate.enableScaling(enable);
+ return this;
+ }
+
+ @Override
+ public DJLImagePreprocessor enableCenterCrop(boolean enable) {
+ delegate.enableCenterCrop(enable);
+ return this;
+ }
+
+ @Override
+ public ImagePreprocessor setPaddingColor(Color color) {
+ return delegate.setPaddingColor(color);
+ }
+
+
+ @Override
+ public Image process() {
+ Object result = delegate.process();
+ if (result instanceof BufferedImage) {
+ return SmartImageFactory.getInstance().fromBufferedImage((BufferedImage) result);
+ } else if (result instanceof Mat) {
+ return SmartImageFactory.getInstance().fromMat((Mat) result);
+ }
+ throw new IllegalStateException("Unsupported process result: " + result.getClass());
+ }
+
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/preprocess/ImagePreprocessor.java b/common/src/main/java/cn/smartjavaai/common/preprocess/ImagePreprocessor.java
new file mode 100644
index 0000000..396e080
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/preprocess/ImagePreprocessor.java
@@ -0,0 +1,28 @@
+package cn.smartjavaai.common.preprocess;
+
+import java.awt.*;
+
+/**
+ * 图片预处理
+ * @author dwj
+ */
+public interface ImagePreprocessor {
+
+
+ ImagePreprocessor setExtendRatio(float ratio);
+
+ ImagePreprocessor setTargetSize(int size);
+
+ ImagePreprocessor setCenterCropSize(int size);
+
+ ImagePreprocessor enableSquarePadding(boolean enable);
+
+ ImagePreprocessor enableScaling(boolean enable);
+
+ ImagePreprocessor enableCenterCrop(boolean enable);
+
+ ImagePreprocessor setPaddingColor(Color color);
+
+ T process();
+
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/preprocess/OpenCVPreprocessor.java b/common/src/main/java/cn/smartjavaai/common/preprocess/OpenCVPreprocessor.java
new file mode 100644
index 0000000..44dddf9
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/preprocess/OpenCVPreprocessor.java
@@ -0,0 +1,154 @@
+package cn.smartjavaai.common.preprocess;
+
+import cn.smartjavaai.common.entity.DetectionRectangle;
+import org.opencv.core.Mat;
+import org.opencv.core.*;
+import org.opencv.imgproc.Imgproc;
+
+/**
+ * @author dwj
+ */
+public class OpenCVPreprocessor implements ImagePreprocessor {
+
+ private Mat image;
+ private DetectionRectangle rect;
+ private float extendRatio = 1;
+ private int targetSize = 128;
+ private int centerCropSize = 80;
+
+ private Scalar paddingColor = new Scalar(127, 127, 127); // 默认灰色
+ private boolean enableSquarePadding = true;
+ private boolean enableScaling = true;
+ private boolean enableCenterCrop = false;
+
+ public OpenCVPreprocessor(Mat image, DetectionRectangle rect) {
+ this.image = image;
+ this.rect = rect;
+ }
+
+ @Override
+ public OpenCVPreprocessor setExtendRatio(float ratio) {
+ this.extendRatio = ratio;
+ return this;
+ }
+
+ @Override
+ public OpenCVPreprocessor setTargetSize(int size) {
+ this.targetSize = size;
+ return this;
+ }
+
+ @Override
+ public OpenCVPreprocessor setCenterCropSize(int size) {
+ this.centerCropSize = size;
+ return this;
+ }
+
+ @Override
+ public OpenCVPreprocessor enableSquarePadding(boolean enable) {
+ this.enableSquarePadding = enable;
+ return this;
+ }
+
+ @Override
+ public OpenCVPreprocessor enableScaling(boolean enable) {
+ this.enableScaling = enable;
+ return this;
+ }
+
+ @Override
+ public OpenCVPreprocessor enableCenterCrop(boolean enable) {
+ this.enableCenterCrop = enable;
+ return this;
+ }
+
+ @Override
+ public OpenCVPreprocessor setPaddingColor(java.awt.Color color) {
+ this.paddingColor = new Scalar(color.getBlue(), color.getGreen(), color.getRed());
+ return this;
+ }
+
+ @Override
+ public Mat process() {
+ // Step 1: 裁剪 + 扩展
+ Mat cropped = cropAndExtend();
+
+ // Step 2: 填充正方形
+ Mat squared = enableSquarePadding ? squarePadding(cropped) : cropped;
+
+ // Step 3: 缩放
+ Mat scaled = enableScaling ? scaleToTarget(squared) : squared;
+
+ // Step 4: CenterCrop
+ Mat finalResult = enableCenterCrop ? centerCrop(scaled) : scaled;
+
+ return finalResult;
+ }
+
+ /**
+ * 检测框扩展及裁剪
+ */
+ private Mat cropAndExtend() {
+ int x = rect.x;
+ int y = rect.y;
+ int width = rect.width;
+ int height = rect.height;
+
+ int extendX = Math.round(width * extendRatio);
+ int extendY = Math.round(height * extendRatio);
+
+ int left = Math.max(0, x - extendX);
+ int right = Math.min(image.width(), x + width + extendX);
+ int top = Math.max(0, y - extendY);
+ int bottom = Math.min(image.height(), y + height + extendY);
+
+ int origRoiWidth = right - left;
+ int origRoiHeight = bottom - top;
+ int longSide = Math.max(origRoiWidth, origRoiHeight);
+
+ // 计算可扩展空间(不超出原图边界)
+ int extendLeft = Math.min(left, (longSide - origRoiWidth) / 2);
+ int extendRight = Math.min(image.width() - right, (longSide - origRoiWidth + 1) / 2);
+ int extendTop = Math.min(top, (longSide - origRoiHeight) / 2);
+ int extendBottom = Math.min(image.height() - bottom, (longSide - origRoiHeight + 1) / 2);
+
+ int expandedLeft = left - extendLeft;
+ int expandedRight = right + extendRight;
+ int expandedTop = top - extendTop;
+ int expandedBottom = bottom + extendBottom;
+
+ Rect roi = new Rect(expandedLeft, expandedTop, expandedRight - expandedLeft, expandedBottom - expandedTop);
+ return new Mat(image, roi).clone(); // clone 避免与原图共享内存
+ }
+
+ /**
+ * 填充为正方形
+ */
+ private Mat squarePadding(Mat src) {
+ int longSide = Math.max(src.width(), src.height());
+ Mat squared = new Mat(new Size(longSide, longSide), src.type(), paddingColor);
+ int xOffset = (longSide - src.width()) / 2;
+ int yOffset = (longSide - src.height()) / 2;
+ src.copyTo(squared.submat(yOffset, yOffset + src.height(), xOffset, xOffset + src.width()));
+ return squared;
+ }
+
+ /**
+ * 缩放到目标大小
+ */
+ private Mat scaleToTarget(Mat src) {
+ Mat result = new Mat();
+ Imgproc.resize(src, result, new Size(targetSize, targetSize), 0, 0, Imgproc.INTER_AREA);
+ return result;
+ }
+
+ /**
+ * CenterCrop
+ */
+ private Mat centerCrop(Mat src) {
+ int startX = (src.width() - centerCropSize) / 2;
+ int startY = (src.height() - centerCropSize) / 2;
+ Rect roi = new Rect(startX, startY, centerCropSize, centerCropSize);
+ return new Mat(src, roi).clone();
+ }
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/utils/BufferedImageUtils.java b/common/src/main/java/cn/smartjavaai/common/utils/BufferedImageUtils.java
new file mode 100644
index 0000000..fd9cb8f
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/utils/BufferedImageUtils.java
@@ -0,0 +1,576 @@
+package cn.smartjavaai.common.utils;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.util.RandomUtils;
+import cn.smartjavaai.common.entity.*;
+import cn.smartjavaai.common.entity.Point;
+import cn.smartjavaai.common.entity.face.FaceAttribute;
+import cn.smartjavaai.common.entity.face.FaceSearchResult;
+import cn.smartjavaai.common.entity.face.HeadPose;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.apache.commons.lang3.StringUtils;
+import org.opencv.core.Mat;
+import org.opencv.core.Scalar;
+import org.opencv.imgproc.Imgproc;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentSampleModel;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author dwj
+ */
+public class BufferedImageUtils {
+
+ /**
+ * 拷贝图片
+ * @param src
+ * @return
+ */
+ public static BufferedImage copyBufferedImage(BufferedImage src) {
+ BufferedImage copy = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
+ Graphics2D g = copy.createGraphics();
+ g.drawImage(src, 0, 0, null);
+ g.dispose();
+ return copy;
+ }
+
+ /**
+ * 对图像解码返回BGR格式矩阵数据
+ *
+ * @param image
+ * @return
+ */
+ public static byte[] getMatrixBGR(BufferedImage image) {
+ byte[] matrixBGR;
+ if (isBGR3Byte(image)) {
+ matrixBGR = (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
+ } else {
+ // ARGB格式图像数据
+ int intrgb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
+ matrixBGR = new byte[image.getWidth() * image.getHeight() * 3];
+ // ARGB转BGR格式
+ for (int i = 0, j = 0; i < intrgb.length; ++i, j += 3) {
+ matrixBGR[j] = (byte) (intrgb[i] & 0xff);
+ matrixBGR[j + 1] = (byte) ((intrgb[i] >> 8) & 0xff);
+ matrixBGR[j + 2] = (byte) ((intrgb[i] >> 16) & 0xff);
+ }
+ }
+ return matrixBGR;
+ }
+
+ /**
+ * 推断图像是否为BGR格式
+ *
+ * @return
+ */
+ public static boolean isBGR3Byte(BufferedImage image) {
+ return equalBandOffsetWith3Byte(image, new int[]{0, 1, 2});
+ }
+
+ /**
+ * @param image
+ * @param bandOffset 用于推断通道顺序
+ * @return
+ */
+ private static boolean equalBandOffsetWith3Byte(BufferedImage image, int[] bandOffset) {
+ if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) {
+ if (image.getData().getSampleModel() instanceof ComponentSampleModel) {
+ ComponentSampleModel sampleModel = (ComponentSampleModel) image.getData().getSampleModel();
+ if (Arrays.equals(sampleModel.getBandOffsets(), bandOffset)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static BufferedImage bgrToBufferedImage(byte[] data, int width, int height) {
+ int type = BufferedImage.TYPE_3BYTE_BGR;
+ // bgr to rgb
+ byte b;
+ for (int i = 0; i < data.length; i = i + 3) {
+ b = data[i];
+ data[i] = data[i + 2];
+ data[i + 2] = b;
+ }
+ BufferedImage image = new BufferedImage(width, height, type);
+ image.getRaster().setDataElements(0, 0, width, height, data);
+ return image;
+ }
+
+ /**
+ * 检查图像是否有效
+ * @param image
+ * @return
+ */
+ public static boolean isImageValid(BufferedImage image) {
+ // 检查是否为 null 或尺寸异常(如宽高为0)
+ return image != null && image.getWidth() > 0 && image.getHeight() > 0;
+ }
+
+ /**
+ * 画检测框
+ *
+ * @param image
+ * @param x
+ * @param y
+ * @param width
+ * @param height
+ */
+ public static void drawRect(BufferedImage image, int x, int y, int width, int height) {
+ // 将绘制图像转换为Graphics2D
+ Graphics2D g = (Graphics2D) image.getGraphics();
+ try {
+ g.setColor(new Color(0, 255, 0));
+ // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
+ BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+ g.setStroke(bStroke);
+ g.drawRect(x, y, width, height);
+ } finally {
+ g.dispose();
+ }
+ }
+
+ /**
+ * 保存BufferedImage图片
+ * @param image
+ * @param outputPath
+ * @param formatName
+ * @throws IOException
+ */
+ public static void saveBufferedImage(BufferedImage image, String outputPath, String formatName) throws IOException {
+ if (image == null) {
+ throw new IllegalArgumentException("BufferedImage 不能为空");
+ }
+ if (outputPath == null || outputPath.isEmpty()) {
+ throw new IllegalArgumentException("输出路径不能为空");
+ }
+ if (formatName == null || formatName.isEmpty()) {
+ throw new IllegalArgumentException("格式不能为空");
+ }
+
+ Path path = Paths.get(outputPath);
+ Path parent = path.getParent();
+ if (parent != null && !Files.exists(parent)) {
+ Files.createDirectories(parent); // 自动创建父目录
+ }
+ File outFile = path.toFile();
+ boolean result = ImageIO.write(image, formatName, outFile);
+ if (!result) {
+ throw new IOException("保存图片失败,不支持的格式: " + formatName);
+ }
+ }
+
+
+ /**
+ * 默认保存图片格式为png
+ * @param image
+ * @param outputPath
+ * @throws IOException
+ */
+ public static void saveImage(BufferedImage image, String outputPath) throws IOException {
+ saveBufferedImage(image, outputPath, "png");
+ }
+
+
+
+ /**
+ * 画检测框(有倾斜角)
+ *
+ * @param image
+ * @param box
+ */
+ public static void drawRect(BufferedImage image, NDArray box) {
+ float[] points = box.toFloatArray();
+ int[] xPoints = new int[5];
+ int[] yPoints = new int[5];
+
+ for (int i = 0; i < 4; i++) {
+ xPoints[i] = (int) points[2 * i];
+ yPoints[i] = (int) points[2 * i + 1];
+ }
+ xPoints[4] = xPoints[0];
+ yPoints[4] = yPoints[0];
+
+ // 将绘制图像转换为Graphics2D
+ Graphics2D g = (Graphics2D) image.getGraphics();
+ try {
+ g.setColor(new Color(0, 255, 0));
+ // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
+ BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+ g.setStroke(bStroke);
+ g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
+ } finally {
+ g.dispose();
+ }
+ }
+
+ /**
+ * 画检测框(有倾斜角)和文本
+ *
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(BufferedImage image, NDArray box, String text) {
+ float[] points = box.toFloatArray();
+ int[] xPoints = new int[5];
+ int[] yPoints = new int[5];
+
+ for (int i = 0; i < 4; i++) {
+ xPoints[i] = (int) points[2 * i];
+ yPoints[i] = (int) points[2 * i + 1];
+ }
+ xPoints[4] = xPoints[0];
+ yPoints[4] = yPoints[0];
+
+ // 将绘制图像转换为Graphics2D
+ Graphics2D g = (Graphics2D) image.getGraphics();
+ try {
+ int fontSize = 32;
+ Font font = new Font("楷体", Font.PLAIN, fontSize);
+ g.setFont(font);
+ g.setColor(new Color(0, 0, 255));
+ // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
+ BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+ g.setStroke(bStroke);
+ g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
+ g.drawString(text, xPoints[0], yPoints[0]);
+ } finally {
+ g.dispose();
+ }
+ }
+
+
+ /**
+ * 显示文字
+ *
+ * @param image
+ * @param text
+ * @param x
+ * @param y
+ */
+ public static void drawImageText(BufferedImage image, String text, int x, int y) {
+ Graphics graphics = image.getGraphics();
+ int fontSize = 32;
+ Font font = new Font("楷体", Font.PLAIN, fontSize);
+ try {
+ graphics.setFont(font);
+ graphics.setColor(new Color(0, 0, 255));
+ int strWidth = graphics.getFontMetrics().stringWidth(text);
+ graphics.drawString(text, x, y);
+ } finally {
+ graphics.dispose();
+ }
+ }
+
+
+ /**
+ * 画检测框(有倾斜角)和文本
+ *
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(BufferedImage image, DetectionRectangle box, String text, Color color) {
+ // 将绘制图像转换为Graphics2D
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ try {
+ drawRectAndText(graphics, box, text, color);
+ } finally {
+ graphics.dispose();
+ }
+ }
+
+
+ /**
+ * 画检测框(有倾斜角)和文本
+ *
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(BufferedImage image, DetectionRectangle box, String text, int fontSize) {
+ Color color = new Color(255, 0, 0);
+ // 将绘制图像转换为Graphics2D
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ graphics.setFont(new Font("楷体", Font.PLAIN, fontSize));
+ try {
+ drawRectAndText(graphics, box, text, color);
+ } finally {
+ graphics.dispose();
+ }
+ }
+
+ /**
+ * 画检测框(有倾斜角)和文本
+ *
+ * @param graphics
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(Graphics2D graphics, DetectionRectangle box, String text, Color color) {
+ graphics.setStroke(new BasicStroke(2)); // 线宽2像素
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
+ int stroke = 2;
+ graphics.setColor(color);// 边框颜色
+ graphics.drawRect(box.getX(), box.getY(), box.getWidth(), box.getHeight());
+ Graphics2DUtils.drawText(graphics, text, box.getX(), box.getY(), stroke, 4);
+ }
+
+ /**
+ * 绘制检测框
+ * @param sourceImage
+ * @param detectionResponse
+ * @throws IOException
+ */
+ public static void drawFaceSearchResult(BufferedImage sourceImage, DetectionResponse detectionResponse, String displayField) {
+ if(!BufferedImageUtils.isImageValid(sourceImage)){
+ throw new IllegalArgumentException("图像无效");
+ }
+ if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
+ throw new IllegalArgumentException("无目标数据");
+ }
+ Graphics2D graphics = sourceImage.createGraphics();
+ graphics.setColor(Color.RED);// 边框颜色
+ graphics.setStroke(new BasicStroke(2)); // 线宽2像素
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
+ int stroke = 2;
+ for(DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()){
+ DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
+ graphics.setColor(Color.RED);// 边框颜色
+ graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
+ //绘制人脸关键点
+ if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
+ !detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
+ Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
+ //人脸查询结果
+ if(detectionInfo.getFaceInfo().getFaceSearchResults() != null){
+ for (FaceSearchResult faceSearchResult : detectionInfo.getFaceInfo().getFaceSearchResults()){
+ if(StringUtils.isNotBlank(faceSearchResult.getMetadata())){
+ JsonObject metadata = GsonUtils.parseToJsonObject(faceSearchResult.getMetadata());
+ JsonElement nameElement = metadata.get("name");
+ if(metadata.has("name")){
+ Graphics2DUtils.drawText(graphics, nameElement.getAsString(), rectangle.getX(), rectangle.getY(), stroke, 4);
+ }
+ }
+ }
+ }
+ }
+ }
+ graphics.dispose();
+ }
+
+
+
+
+
+ /**
+ * 绘制检测框
+ * @param sourceImage
+ * @param detectionResponse
+ * @throws IOException
+ */
+ public static void drawBoundingBoxes(BufferedImage sourceImage, DetectionResponse detectionResponse) {
+ if(!BufferedImageUtils.isImageValid(sourceImage)){
+ throw new IllegalArgumentException("图像无效");
+ }
+ if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
+ throw new IllegalArgumentException("无目标数据");
+ }
+ Graphics2D graphics = sourceImage.createGraphics();
+ graphics.setColor(Color.RED);// 边框颜色
+ graphics.setStroke(new BasicStroke(2)); // 线宽2像素
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
+ int stroke = 2;
+ for(DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()){
+ DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
+ graphics.setColor(Color.RED);// 边框颜色
+ graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
+ //绘制人脸关键点
+ if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
+ !detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
+ Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
+// Graphics2DUtils.drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
+ //人脸属性
+ if(detectionInfo.getFaceInfo().getFaceAttribute() != null){
+ FaceAttribute faceAttribute = detectionInfo.getFaceInfo().getFaceAttribute();
+ drawFaceAttribute(faceAttribute, rectangle, graphics);
+ }
+ }
+ //绘制目标检测信息
+ if(detectionInfo.getObjectDetInfo() != null){
+ String className = detectionInfo.getObjectDetInfo().getClassName();
+ Graphics2DUtils.drawText(graphics, className, rectangle.getX(), rectangle.getY(), stroke, 4);
+ }
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * 绘制矩形框和文字
+ *
+ * @param sourceImage
+ * @param detectionInfo
+ */
+ public static void drawRectAndText(BufferedImage sourceImage, DetectionInfo detectionInfo) {
+ if(!BufferedImageUtils.isImageValid(sourceImage)){
+ throw new IllegalArgumentException("图像无效");
+ }
+ if(Objects.isNull(detectionInfo)){
+ throw new IllegalArgumentException("无目标数据");
+ }
+ Graphics2D graphics = sourceImage.createGraphics();
+ graphics.setColor(Color.RED);// 边框颜色
+ graphics.setStroke(new BasicStroke(2)); // 线宽2像素
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
+ int stroke = 2;
+ DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
+ graphics.setColor(Color.RED);// 边框颜色
+ graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
+ //绘制人脸关键点
+ if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
+ !detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
+ Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
+// Graphics2DUtils.drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
+ //人脸属性
+ if(detectionInfo.getFaceInfo().getFaceAttribute() != null){
+ FaceAttribute faceAttribute = detectionInfo.getFaceInfo().getFaceAttribute();
+ drawFaceAttribute(faceAttribute, rectangle, graphics);
+ }
+ }
+ //绘制目标检测信息
+ if(detectionInfo.getObjectDetInfo() != null){
+ String className = detectionInfo.getObjectDetInfo().getClassName();
+ Graphics2DUtils.drawText(graphics, className, rectangle.getX(), rectangle.getY(), stroke, 4);
+ }
+ graphics.dispose();
+ }
+
+
+ /**
+ * 绘制人脸属性
+ * @param faceAttribute
+ * @param rectangle
+ * @param graphics
+ */
+ public static void drawFaceAttribute(FaceAttribute faceAttribute, DetectionRectangle rectangle, Graphics2D graphics){
+ List lines = new ArrayList<>();
+ if (faceAttribute.getGenderType() != null) {
+ lines.add("性别: " + faceAttribute.getGenderType().name());
+ }
+ if (faceAttribute.getAge() != null) {
+ lines.add("年龄: " + faceAttribute.getAge());
+ }
+ if (faceAttribute.getWearingMask() != null) {
+ lines.add("口罩: " + (faceAttribute.getWearingMask() ? "是" : "否"));
+ }
+ if (faceAttribute.getLeftEyeStatus() != null && faceAttribute.getRightEyeStatus() != null) {
+ lines.add("眼睛: " + faceAttribute.getLeftEyeStatus().name() + "/" + faceAttribute.getRightEyeStatus().name());
+ }
+ if (faceAttribute.getHeadPose() != null) {
+ HeadPose pose = faceAttribute.getHeadPose();
+ String pitch = pose.getPitch() != null ? String.valueOf(pose.getPitch().intValue()) : "-";
+ String yaw = pose.getYaw() != null ? String.valueOf(pose.getYaw().intValue()) : "-";
+ String roll = pose.getRoll() != null ? String.valueOf(pose.getRoll().intValue()) : "-";
+ lines.add("姿态: P=" + pitch + " Y=" + yaw + " R=" + roll);
+ }
+ if (!lines.isEmpty()) {
+ Graphics2DUtils.drawMultilineTextWithBackground(graphics, lines, rectangle.getX(), rectangle.getY()); // 适当偏移
+ }
+ }
+
+
+
+ public static void drawPolygonWithText(BufferedImage image,List polygonLabelList, int fontSize) {
+ Font font = new Font("楷体", Font.PLAIN, fontSize);
+ Stroke stroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+ Color color = new Color(0, 0, 255);
+ Graphics2D g = (Graphics2D) image.getGraphics();
+ for (PolygonLabel polygonLabel : polygonLabelList){
+ drawPolygonWithText(g, polygonLabel.getPoints(), polygonLabel.getText(), font, color, stroke);
+ }
+ }
+
+ /**
+ * 绘制多边形及文字
+ * @param g Graphics2D
+ * @param points 多边形顶点
+ * @param text 绘制的文字(可为空)
+ * @param font 字体
+ * @param color 颜色
+ * @param stroke 画笔样式
+ */
+ public static void drawPolygonWithText(Graphics2D g, List points,
+ String text, int fontSize) {
+ Font font = new Font("楷体", Font.PLAIN, fontSize);
+ Stroke stroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+ Color color = new Color(0, 0, 255);
+ drawPolygonWithText(g, points, text, font, color, stroke);
+ }
+
+
+ /**
+ * 绘制多边形及文字
+ * @param g Graphics2D
+ * @param points 多边形顶点
+ * @param text 绘制的文字(可为空)
+ * @param font 字体
+ * @param color 颜色
+ * @param stroke 画笔样式
+ */
+ public static void drawPolygonWithText(Graphics2D g, List points,
+ String text, Font font,
+ Color color, Stroke stroke) {
+ if (points == null || points.size() < 3) {
+ return;
+ }
+
+ int[] xPoints = points.stream().mapToInt(p -> (int) p.getX()).toArray();
+ int[] yPoints = points.stream().mapToInt(p -> (int) p.getY()).toArray();
+
+ g.setFont(font);
+ g.setColor(color);
+ g.setStroke(stroke);
+ // 绘制多边形
+ g.drawPolygon(xPoints, yPoints, points.size());
+ // 绘制文字(默认放在第一个点)
+ if (text != null && !text.isEmpty()) {
+ g.drawString(text, xPoints[0], yPoints[0]);
+ }
+ }
+
+ /**
+ * 绘制关键点
+ * @param graphics
+ * @param points
+ * @param color
+ */
+ public static void drawKeyPoints(Graphics2D graphics, List points, Color color){
+ if(points == null || points.isEmpty()){
+ return;
+ }
+ for (Point point : points){
+ //绘制关键点
+ graphics.setColor(color);
+ graphics.drawRect((int)point.getX(), (int)point.getY(), 2, 2);
+ }
+ }
+
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/utils/DJLCommonUtils.java b/common/src/main/java/cn/smartjavaai/common/utils/DJLCommonUtils.java
index 61caac2..98d1559 100644
--- a/common/src/main/java/cn/smartjavaai/common/utils/DJLCommonUtils.java
+++ b/common/src/main/java/cn/smartjavaai/common/utils/DJLCommonUtils.java
@@ -1,10 +1,17 @@
package cn.smartjavaai.common.utils;
+import ai.djl.modality.cv.output.BoundingBox;
+import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.output.Point;
import ai.djl.ndarray.NDArray;
import cn.smartjavaai.common.entity.R;
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -43,4 +50,113 @@ public class DJLCommonUtils {
}
+ /**
+ * float NDArray To float[][] Array
+ * @param ndArray
+ * @return
+ */
+ public static float[][] floatNDArrayToArray(NDArray ndArray) {
+ int rows = (int) (ndArray.getShape().get(0));
+ int cols = (int) (ndArray.getShape().get(1));
+ float[][] arr = new float[rows][cols];
+
+ float[] arrs = ndArray.toFloatArray();
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ arr[i][j] = arrs[i * cols + j];
+ }
+ }
+ return arr;
+ }
+
+
+ /**
+ * float NDArray To float[][] Array
+ * @param ndArray
+ * @param cvType
+ * @return
+ */
+ public static Mat floatNDArrayToMat(NDArray ndArray, int cvType) {
+ int rows = (int) (ndArray.getShape().get(0));
+ int cols = (int) (ndArray.getShape().get(1));
+ Mat mat = new Mat(rows, cols, cvType);
+
+ float[] arrs = ndArray.toFloatArray();
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ mat.put(i, j, arrs[i * cols + j]);
+ }
+ }
+ return mat;
+ }
+
+ /**
+ * float NDArray To Mat
+ * @param ndArray
+ * @return
+ */
+ public static Mat floatNDArrayToMat(NDArray ndArray) {
+ int rows = (int) (ndArray.getShape().get(0));
+ int cols = (int) (ndArray.getShape().get(1));
+ Mat mat = new Mat(rows, cols, CvType.CV_32F);
+
+ float[] arrs = ndArray.toFloatArray();
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ mat.put(i, j, arrs[i * cols + j]);
+ }
+ }
+
+ return mat;
+
+ }
+
+ /**
+ * uint8 NDArray To Mat
+ * @param ndArray
+ * @return
+ */
+ public static Mat uint8NDArrayToMat(NDArray ndArray) {
+ int rows = (int) (ndArray.getShape().get(0));
+ int cols = (int) (ndArray.getShape().get(1));
+ Mat mat = new Mat(rows, cols, CvType.CV_8U);
+
+ byte[] arrs = ndArray.toByteArray();
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ mat.put(i, j, arrs[i * cols + j]);
+ }
+ }
+ return mat;
+ }
+
+
+ /**
+ * List To Mat
+ * @param points
+ * @return
+ */
+ public static Mat toMat(List points) {
+ Mat mat = new Mat(points.size(), 2, CvType.CV_32F);
+ for (int i = 0; i < points.size(); i++) {
+ ai.djl.modality.cv.output.Point point = points.get(i);
+ mat.put(i, 0, (float) point.getX());
+ mat.put(i, 1, (float) point.getY());
+ }
+ return mat;
+ }
+
+ /**
+ * 构建一个空的 DetectedObjects 对象
+ * @return
+ */
+ public static DetectedObjects buildEmptyDetectedObjects(){
+ List classNames = new ArrayList<>();
+ List probabilities = new ArrayList<>();
+ List boxes = new ArrayList<>();
+ return new DetectedObjects(classNames, probabilities, boxes);
+ }
+
+
}
diff --git a/common/src/main/java/cn/smartjavaai/common/utils/Graphics2DUtils.java b/common/src/main/java/cn/smartjavaai/common/utils/Graphics2DUtils.java
new file mode 100644
index 0000000..4c50a1f
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/utils/Graphics2DUtils.java
@@ -0,0 +1,74 @@
+package cn.smartjavaai.common.utils;
+
+import cn.smartjavaai.common.entity.Point;
+
+import java.awt.*;
+import java.util.List;
+
+/**
+ * @author dwj
+ */
+public class Graphics2DUtils {
+
+
+ /**
+ * 绘制文本
+ * @param g
+ * @param text
+ * @param x
+ * @param y
+ * @param stroke
+ * @param padding
+ */
+ public static void drawText(Graphics2D g, String text, int x, int y, int stroke, int padding) {
+ FontMetrics metrics = g.getFontMetrics();
+ x += stroke / 2;
+ y += stroke / 2;
+ int width = metrics.stringWidth(text) + padding * 2 - stroke / 2;
+ int height = metrics.getHeight() + metrics.getDescent();
+ int ascent = metrics.getAscent();
+ y = Math.max(0, y - height);
+ java.awt.Rectangle background = new java.awt.Rectangle(x, y, width, height);
+ g.fill(background);
+ g.setPaint(Color.WHITE);
+ g.drawString(text, x + padding, y + ascent);
+ }
+
+ /**
+ * 绘制人脸关键点
+ * @param g
+ * @param keyPoints
+ */
+ public static void drawLandmarks(Graphics2D g, List keyPoints) {
+ g.setColor(new Color(246, 96, 0));
+ BasicStroke bStroke = new BasicStroke(4.0F, 0, 0);
+ g.setStroke(bStroke);
+ for (Point point : keyPoints){
+ g.drawRect((int)point.getX(), (int)point.getY(), 2, 2);
+ }
+ }
+
+
+ public static void drawMultilineTextWithBackground(Graphics2D g, List lines, int x, int y) {
+ Font font = new Font("SansSerif", Font.PLAIN, 14);
+ g.setFont(font);
+ FontMetrics fm = g.getFontMetrics();
+ int lineHeight = fm.getHeight();
+ int maxWidth = lines.stream().mapToInt(fm::stringWidth).max().orElse(0);
+
+ int padding = 4;
+ int boxWidth = maxWidth + padding * 2;
+ int boxHeight = lineHeight * lines.size() + padding * 2;
+
+ // 背景矩形
+ g.setColor(new Color(0, 0, 0, 128));
+ g.fillRoundRect(x, y, boxWidth, boxHeight, 8, 8);
+
+ // 绘制每一行文字
+ g.setColor(Color.WHITE);
+ for (int i = 0; i < lines.size(); i++) {
+ g.drawString(lines.get(i), x + padding, y + padding + (i + 1) * lineHeight - 4);
+ }
+ }
+
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/utils/GsonUtils.java b/common/src/main/java/cn/smartjavaai/common/utils/GsonUtils.java
new file mode 100644
index 0000000..3fa22ca
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/utils/GsonUtils.java
@@ -0,0 +1,69 @@
+package cn.smartjavaai.common.utils;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * @author dwj
+ */
+public class GsonUtils {
+
+ private static final Gson GSON = new Gson();
+
+ private GsonUtils() {
+ // 私有构造,防止实例化
+ }
+
+ /**
+ * 将 JSON 字符串安全转换为 JsonObject
+ *
+ * @param jsonStr JSON 字符串
+ * @return JsonObject,如果解析失败返回 null
+ */
+ public static JsonObject parseToJsonObject(String jsonStr) {
+ if (jsonStr == null || jsonStr.isEmpty()) {
+ return null;
+ }
+ try {
+ return JsonParser.parseString(jsonStr).getAsJsonObject();
+ } catch (JsonSyntaxException | IllegalStateException e) {
+ // 解析失败返回 null
+ return null;
+ }
+ }
+
+ /**
+ * 将 JSON 字符串转换为指定类型对象
+ *
+ * @param jsonStr JSON 字符串
+ * @param clazz 目标类型
+ * @param 类型参数
+ * @return 对象实例,如果解析失败返回 null
+ */
+ public static T fromJson(String jsonStr, Class clazz) {
+ if (jsonStr == null || jsonStr.isEmpty()) {
+ return null;
+ }
+ try {
+ return GSON.fromJson(jsonStr, clazz);
+ } catch (JsonSyntaxException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 将对象转换为 JSON 字符串
+ *
+ * @param obj 对象
+ * @return JSON 字符串,如果对象为 null 返回 null
+ */
+ public static String toJson(Object obj) {
+ if (obj == null) {
+ return null;
+ }
+ return GSON.toJson(obj);
+ }
+
+}
diff --git a/common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java b/common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java
index e925453..2eb206a 100644
--- a/common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java
+++ b/common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java
@@ -1,209 +1,38 @@
package cn.smartjavaai.common.utils;
-import ai.djl.modality.cv.BufferedImageFactory;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.CategoryMask;
import ai.djl.modality.cv.output.DetectedObjects;
+import ai.djl.modality.cv.util.NDImageUtils;
import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDManager;
+import ai.djl.opencv.OpenCVImageFactory;
import ai.djl.util.RandomUtils;
+import cn.hutool.core.codec.Base64;
import cn.smartjavaai.common.cv.SmartImageFactory;
+import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
+import cn.smartjavaai.common.entity.PolygonLabel;
import org.opencv.core.Mat;
-import org.opencv.core.Scalar;
-import org.opencv.core.Size;
-import org.opencv.imgproc.Imgproc;
-import javax.imageio.ImageIO;
-import java.awt.*;
import java.awt.image.BufferedImage;
//import java.awt.image.ColorConvertOp;
-import java.awt.image.ComponentSampleModel;
-import java.awt.image.DataBufferByte;
-import java.awt.image.ImageObserver;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
/**
* 图片处理工具类
*/
public class ImageUtils {
- /**
- * @param image
- * @param bandOffset 用于推断通道顺序
- * @return
- */
- private static boolean equalBandOffsetWith3Byte(BufferedImage image, int[] bandOffset) {
- if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) {
- if (image.getData().getSampleModel() instanceof ComponentSampleModel) {
- ComponentSampleModel sampleModel = (ComponentSampleModel) image.getData().getSampleModel();
- if (Arrays.equals(sampleModel.getBandOffsets(), bandOffset)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * 推断图像是否为BGR格式
- *
- * @return
- */
- public static boolean isBGR3Byte(BufferedImage image) {
- return equalBandOffsetWith3Byte(image, new int[]{0, 1, 2});
- }
-
- /**
- * 对图像解码返回BGR格式矩阵数据
- *
- * @param image
- * @return
- */
- public static byte[] getMatrixBGR(BufferedImage image) {
- byte[] matrixBGR;
- if (isBGR3Byte(image)) {
- matrixBGR = (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
- } else {
- // ARGB格式图像数据
- int intrgb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
- matrixBGR = new byte[image.getWidth() * image.getHeight() * 3];
- // ARGB转BGR格式
- for (int i = 0, j = 0; i < intrgb.length; ++i, j += 3) {
- matrixBGR[j] = (byte) (intrgb[i] & 0xff);
- matrixBGR[j + 1] = (byte) ((intrgb[i] >> 8) & 0xff);
- matrixBGR[j + 2] = (byte) ((intrgb[i] >> 16) & 0xff);
- }
- }
- return matrixBGR;
- }
-
-
- public static BufferedImage bgrToBufferedImage(byte[] data, int width, int height) {
- int type = BufferedImage.TYPE_3BYTE_BGR;
- // bgr to rgb
- byte b;
- for (int i = 0; i < data.length; i = i + 3) {
- b = data[i];
- data[i] = data[i + 2];
- data[i + 2] = b;
- }
- BufferedImage image = new BufferedImage(width, height, type);
- image.getRaster().setDataElements(0, 0, width, height, data);
- return image;
- }
-
- /**
- * 检查图像是否有效
- * @param image
- * @return
- */
- public static boolean isImageValid(BufferedImage image) {
- // 检查是否为 null 或尺寸异常(如宽高为0)
- return image != null && image.getWidth() > 0 && image.getHeight() > 0;
- }
-
- /**
- * 画检测框
- *
- * @param image
- * @param x
- * @param y
- * @param width
- * @param height
- */
- public static void drawImageRect(BufferedImage image, int x, int y, int width, int height) {
- // 将绘制图像转换为Graphics2D
- Graphics2D g = (Graphics2D) image.getGraphics();
- try {
- g.setColor(new Color(0, 255, 0));
- // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
- BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
- g.setStroke(bStroke);
- g.drawRect(x, y, width, height);
- } finally {
- g.dispose();
- }
- }
-
- /**
- * 画检测框
- *
- * @param image
- * @param x
- * @param y
- * @param width
- * @param height
- */
- public static void drawBufferedImageRect(Image image, int x, int y, int width, int height) {
- // 将绘制图像转换为Graphics2D
- BufferedImage bufferedImage = (BufferedImage)image.getWrappedImage();
- Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
- try {
- g.setColor(new Color(0, 255, 0));
- // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
- BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
- g.setStroke(bStroke);
- g.drawRect(x, y, width, height);
- } finally {
- g.dispose();
- }
- }
-
-
- /**
- * 保存BufferedImage图片
- *
- * @param img
- * @param name
- * @param path
- */
- public static void saveImage(BufferedImage img, String name, String path) {
- Mat mat = OpenCVUtils.image2Mat(img);
- Image djlImg = ImageFactory.getInstance().fromImage(mat); // 支持多种图片格式,自动适配
- Path outputDir = Paths.get(path);
- Path imagePath = outputDir.resolve(name);
- // OpenJDK 不能保存 jpg 图片的 alpha channel
- try {
- djlImg.save(Files.newOutputStream(imagePath), "png");
- } catch (IOException e) {
- e.printStackTrace();
- }
- mat.release();
- }
-
-
- /**
- * 保存BufferedImage图片
- *
- * @param img
- * @param path
- */
- public static void saveImage(BufferedImage img, String path) {
- Mat mat = OpenCVUtils.image2Mat(img);
- Image djlImg = ImageFactory.getInstance().fromImage(mat); // 支持多种图片格式,自动适配
- Path outputDir = Paths.get(path);
- // OpenJDK 不能保存 jpg 图片的 alpha channel
- try {
- djlImg.save(Files.newOutputStream(outputDir), "png");
- } catch (IOException e) {
- e.printStackTrace();
- }
- mat.release();
- }
-
-
-
-
-
/**
@@ -213,7 +42,7 @@ public class ImageUtils {
* @param name
* @param path
*/
- public static void saveImage(Image img, String name, String path) {
+ public static void save(Image img, String name, String path) {
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
@@ -224,6 +53,23 @@ public class ImageUtils {
}
}
+ /**
+ * 获取图片矩阵BGR
+ *
+ * @param img
+ * @return
+ */
+ public static byte[] getMatrixBGR(Image img) {
+ if (img.getWrappedImage() instanceof BufferedImage){
+ return BufferedImageUtils.getMatrixBGR((BufferedImage)img.getWrappedImage());
+ }else if (img.getWrappedImage() instanceof Mat){
+ return OpenCVUtils.getMatrixBGR((Mat)img.getWrappedImage());
+ }else {
+ throw new RuntimeException("不支持的图片类型");
+ }
+ }
+
+
/**
* 保存图片,含检测框
*
@@ -246,138 +92,6 @@ public class ImageUtils {
-
- /**
- * 画检测框(有倾斜角)
- *
- * @param image
- * @param box
- */
- public static void drawImageRect(BufferedImage image, NDArray box) {
- float[] points = box.toFloatArray();
- int[] xPoints = new int[5];
- int[] yPoints = new int[5];
-
- for (int i = 0; i < 4; i++) {
- xPoints[i] = (int) points[2 * i];
- yPoints[i] = (int) points[2 * i + 1];
- }
- xPoints[4] = xPoints[0];
- yPoints[4] = yPoints[0];
-
- // 将绘制图像转换为Graphics2D
- Graphics2D g = (Graphics2D) image.getGraphics();
- try {
- g.setColor(new Color(0, 255, 0));
- // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
- BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
- g.setStroke(bStroke);
- g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
- } finally {
- g.dispose();
- }
- }
-
- /**
- * 画检测框(有倾斜角)和文本
- *
- * @param image
- * @param box
- * @param text
- */
- public static void drawImageRectWithText(BufferedImage image, NDArray box, String text) {
- float[] points = box.toFloatArray();
- int[] xPoints = new int[5];
- int[] yPoints = new int[5];
-
- for (int i = 0; i < 4; i++) {
- xPoints[i] = (int) points[2 * i];
- yPoints[i] = (int) points[2 * i + 1];
- }
- xPoints[4] = xPoints[0];
- yPoints[4] = yPoints[0];
-
- // 将绘制图像转换为Graphics2D
- Graphics2D g = (Graphics2D) image.getGraphics();
- try {
- int fontSize = 32;
- Font font = new Font("楷体", Font.PLAIN, fontSize);
- g.setFont(font);
- g.setColor(new Color(0, 0, 255));
- // 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
- BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
- g.setStroke(bStroke);
- g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
- g.drawString(text, xPoints[0], yPoints[0]);
- } finally {
- g.dispose();
- }
- }
-
-
-
- /**
- * 显示文字
- *
- * @param image
- * @param text
- * @param x
- * @param y
- */
- public static void drawImageText(BufferedImage image, String text, int x, int y) {
- Graphics graphics = image.getGraphics();
- int fontSize = 32;
- Font font = new Font("楷体", Font.PLAIN, fontSize);
- try {
- graphics.setFont(font);
- graphics.setColor(new Color(0, 0, 255));
- int strWidth = graphics.getFontMetrics().stringWidth(text);
- graphics.drawString(text, x, y);
- } finally {
- graphics.dispose();
- }
- }
-
-
- /**
- * 画检测框(有倾斜角)和文本
- *
- * @param image
- * @param box
- * @param text
- */
- public static void drawImageRectWithText(BufferedImage image, DetectionRectangle box, String text, Color color) {
- // 将绘制图像转换为Graphics2D
- Graphics2D graphics = (Graphics2D) image.getGraphics();
- try {
- graphics.setColor(Color.RED);// 边框颜色
- graphics.setStroke(new BasicStroke(2)); // 线宽2像素
- graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
- int stroke = 2;
- graphics.setColor(color);// 边框颜色
- graphics.drawRect(box.getX(), box.getY(), box.getWidth(), box.getHeight());
- drawText(graphics, text, box.getX(), box.getY(), stroke, 4);
- graphics.dispose();
- } finally {
- graphics.dispose();
- }
- }
-
- public static void drawText(Graphics2D g, String text, int x, int y, int stroke, int padding) {
- FontMetrics metrics = g.getFontMetrics();
- x += stroke / 2;
- y += stroke / 2;
- int width = metrics.stringWidth(text) + padding * 2 - stroke / 2;
- int height = metrics.getHeight() + metrics.getDescent();
- int ascent = metrics.getAscent();
- y = Math.max(0, y - height);
- java.awt.Rectangle background = new java.awt.Rectangle(x, y, width, height);
- g.fill(background);
- g.setPaint(Color.WHITE);
- g.drawString(text, x + padding, y + ascent);
- }
-
/**
* 计算左上角,右下角坐标 (x0,y0,x1,y1)
* Get absolute coordinations
@@ -448,7 +162,7 @@ public class ImageUtils {
name.endsWith(".gif") || name.endsWith(".tiff") ||
name.endsWith(".webp")) {
- Image img = ImageFactory.getInstance().fromInputStream(Files.newInputStream(file.toPath()));
+ Image img = SmartImageFactory.getInstance().fromInputStream(Files.newInputStream(file.toPath()));
imageList.add(img);
}
}
@@ -476,46 +190,6 @@ public class ImageUtils {
return true;
}
- /**
- * 在图像上绘制带白色背景、黑色文字的文本
- */
- public static void putTextWithBackground(Mat image, String text, org.opencv.core.Point origin, Scalar textColor, Scalar backgroundColor, int padding) {
- // 默认字体
- int font = Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX;
- // 默认字体缩放大小
- double fontScale = 1.0;
- //线条粗细
- int thickness = 2;
- //获取文字大小
- int[] baseLine = new int[1];
- Size textSize = Imgproc.getTextSize(text, font, fontScale, thickness, baseLine);
- int textWidth = (int) textSize.width;
- int textHeight = (int) textSize.height;
-
- //计算带padding的背景框
- org.opencv.core.Point bgTopLeft = new org.opencv.core.Point(origin.x - padding, origin.y - textHeight - padding);
- org.opencv.core.Point bgBottomRight = new org.opencv.core.Point(origin.x + textWidth + padding, origin.y + baseLine[0] + padding);
-
- //绘制背景矩形
- Imgproc.rectangle(image, bgTopLeft, bgBottomRight, backgroundColor, Imgproc.FILLED);
-
- //绘制文字(黑色)
- Imgproc.putText(image, text, origin, font, fontScale, textColor, thickness);
- }
-
-
- /**
- * 拷贝图片
- * @param src
- * @return
- */
- public static BufferedImage copyBufferedImage(BufferedImage src) {
- BufferedImage copy = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
- Graphics2D g = copy.createGraphics();
- g.drawImage(src, 0, 0, null);
- g.dispose();
- return copy;
- }
/**
@@ -525,9 +199,9 @@ public class ImageUtils {
*/
public static Image copy(Image src) {
Object srcData = src.getWrappedImage();
- //当图片未BufferedImage,DJL的duplicate会有问题
+ //当图片是BufferedImage,DJL的duplicate会有问题
if (srcData instanceof BufferedImage) {
- return SmartImageFactory.getInstance().fromImage(copyBufferedImage((BufferedImage) srcData));
+ return SmartImageFactory.getInstance().fromBufferedImage(BufferedImageUtils.copyBufferedImage((BufferedImage) srcData));
}else{
return src.duplicate();
}
@@ -584,8 +258,241 @@ public class ImageUtils {
image.drawImage(maskImage, true);
}
+ /**
+ * 保存 Image 到指定路径,格式根据后缀自动推断
+ */
+ public static void save(Image image, Path path) throws IOException {
+ String fileName = path.getFileName().toString().toLowerCase();
+ String format = "png"; // 默认 png
+ if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
+ format = "jpg";
+ } else if (fileName.endsWith(".bmp")) {
+ format = "bmp";
+ } else if (fileName.endsWith(".webp")) {
+ format = "webp";
+ }
+ Files.createDirectories(path.getParent());
+ try (OutputStream os = Files.newOutputStream(path)) {
+ image.save(os, format);
+ }
+ }
+
+ /**
+ * 保存 Image 到指定路径,格式根据后缀自动推断
+ */
+ public static void save(Image image, Path path, String format) throws IOException {
+ Files.createDirectories(path.getParent());
+ try (OutputStream os = Files.newOutputStream(path)) {
+ image.save(os, format);
+ }
+ }
+
+ /**
+ * 保存 Image 到指定路径
+ */
+ public static void save(Image image, String imagePath) throws IOException {
+ Path path = Paths.get(imagePath);
+ Files.createDirectories(path.getParent());
+ try (OutputStream os = Files.newOutputStream(path)) {
+ image.save(os, "png");
+ }
+ }
+
+ /**
+ * 转换为 BufferedImage
+ */
+ public static BufferedImage toBufferedImage(Image image) {
+ Object wrapped = image.getWrappedImage();
+ if (wrapped instanceof BufferedImage) {
+ return (BufferedImage) wrapped;
+ } else if (wrapped instanceof Mat) {
+ Mat mat = (Mat) wrapped;
+ return OpenCVUtils.mat2Image(mat);
+ } else {
+ throw new IllegalArgumentException("Unsupported wrapped image type: " + wrapped.getClass());
+ }
+ }
+
+ /**
+ * 转换为 Mat
+ */
+ public static Mat toMat(Image image) {
+ Object wrapped = image.getWrappedImage();
+ if (wrapped instanceof BufferedImage) {
+ return OpenCVUtils.image2Mat((BufferedImage) wrapped);
+ } else if (wrapped instanceof Mat) {
+ return (Mat) wrapped;
+ } else {
+ throw new IllegalArgumentException("Unsupported wrapped image type: " + wrapped.getClass());
+ }
+ }
+
+ /**
+ * Image 转 byte[] (默认 png 格式)
+ */
+ public static byte[] toBytes(Image image, String format) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ image.save(baos, format);
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to convert Image to byte[]", e);
+ }
+ }
+
+ /**
+ * 保存 Image 到 OutputStream
+ *
+ * @param image 图像对象
+ * @param os 输出流(需要调用方负责关闭)
+ * @param format 保存格式(png/jpg/webp)
+ */
+ public static void toOutputStream(Image image, OutputStream os, String format) {
+ try {
+ image.save(os, format);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write image to OutputStream", e);
+ }
+ }
+
+ /**
+ * 转换 Image 为 Base64 字符串
+ *
+ * @param image 图像对象
+ * @param format 输出格式(png/jpg/webp)
+ * @return Base64 编码的字符串
+ */
+ public static String toBase64(Image image, String format) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ image.save(baos, format);
+ return Base64.encode(baos.toByteArray());
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to convert image to Base64", e);
+ }
+ }
+
+ /**
+ * 释放 OpenCV Mat
+ * @param image
+ */
+ public static void releaseOpenCVMat(Image image){
+ if (image != null && image.getWrappedImage() instanceof Mat){
+ ((Mat)image.getWrappedImage()).release();
+ }
+ }
+
+ /**
+ * 绘制检测结果
+ * @param sourceImage
+ * @param detectionResponse
+ * @return
+ */
+ public static Image drawBoundingBoxes(Image sourceImage, DetectionResponse detectionResponse){
+ Object srcData = sourceImage.getWrappedImage();
+ if (srcData instanceof BufferedImage) {
+ BufferedImage copyBufferedImage = BufferedImageUtils.copyBufferedImage((BufferedImage) srcData);
+ BufferedImageUtils.drawBoundingBoxes(copyBufferedImage, detectionResponse);
+ return SmartImageFactory.getInstance().fromBufferedImage(copyBufferedImage);
+ }else if (srcData instanceof Mat) {
+ Mat srcMat = ((Mat) srcData).clone();
+ OpenCVUtils.drawBoundingBoxes(srcMat, detectionResponse);
+ return SmartImageFactory.getInstance().fromMat(srcMat);
+ }else {
+ throw new IllegalArgumentException("Unsupported wrapped image type: " + srcData.getClass());
+ }
+ }
+
+ /**
+ * 逆时针旋转图片
+ *
+ * @param image
+ * @param times
+ * @return
+ */
+ public static Image rotateImg(Image image, int times) {
+ try (NDManager manager = NDManager.newBaseManager()) {
+ NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), times);
+ return OpenCVImageFactory.getInstance().fromNDArray(rotated);
+ }
+ }
+
+ /**
+ * 图片旋转
+ *
+ * @param manager
+ * @param image
+ * @return
+ */
+ public static Image rotateImg(NDManager manager, Image image) {
+ NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), 1);
+ return ImageFactory.getInstance().fromNDArray(rotated);
+ }
+
+
+ public static void drawPolygonWithText(Image image, List polygonLabelList, int fontSize) {
+ Object srcData = image.getWrappedImage();
+ if (srcData instanceof BufferedImage) {
+ BufferedImageUtils.drawPolygonWithText((BufferedImage) srcData, polygonLabelList, fontSize);
+ }else if (srcData instanceof Mat) {
+ Mat srcMat = (Mat) srcData;
+ OpenCVUtils.drawPolygonWithText(srcMat, polygonLabelList, fontSize);
+ }else {
+ throw new IllegalArgumentException("Unsupported wrapped image type: " + srcData.getClass());
+ }
+ }
+ /**
+ * 绘制矩形框
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(Image image, DetectionRectangle box, String text) {
+ Object srcData = image.getWrappedImage();
+ if (srcData instanceof BufferedImage) {
+ BufferedImageUtils.drawRectAndText((BufferedImage) srcData, box, text,12);
+ }else if (srcData instanceof Mat) {
+ Mat srcMat = (Mat) srcData;
+ OpenCVUtils.drawRectAndText(srcMat, box, text, 0.5);
+ }else {
+ throw new IllegalArgumentException();
+ }
+ }
+ /**
+ * 绘制矩形框
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(Image image, DetectionRectangle box, String text, double fontSize) {
+ Object srcData = image.getWrappedImage();
+ if (srcData instanceof BufferedImage) {
+ BufferedImageUtils.drawRectAndText((BufferedImage) srcData, box, text, (int)fontSize);
+ }else if (srcData instanceof Mat) {
+ Mat srcMat = (Mat) srcData;
+ OpenCVUtils.drawRectAndText(srcMat, box, text, fontSize);
+ }else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public static void drawRectAndText(Image image, DetectionInfo detectionInfo){
+ Object srcData = image.getWrappedImage();
+ if (srcData instanceof BufferedImage) {
+ BufferedImageUtils.drawRectAndText((BufferedImage) srcData, detectionInfo);
+ }else if (srcData instanceof Mat) {
+ Mat srcMat = (Mat) srcData;
+ OpenCVUtils.drawRectAndText(srcMat, detectionInfo);
+ }else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public static void drawRectAndText(Image image, List detectionInfoList){
+ for(DetectionInfo detectionInfo : detectionInfoList){
+ drawRectAndText(image, detectionInfo);
+ }
+ }
}
diff --git a/common/src/main/java/cn/smartjavaai/common/utils/OpenCVUtils.java b/common/src/main/java/cn/smartjavaai/common/utils/OpenCVUtils.java
index 909cf6a..bec5fb8 100644
--- a/common/src/main/java/cn/smartjavaai/common/utils/OpenCVUtils.java
+++ b/common/src/main/java/cn/smartjavaai/common/utils/OpenCVUtils.java
@@ -4,24 +4,36 @@ import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
+import ai.djl.opencv.OpenCVImageFactory;
import ai.djl.util.RandomUtils;
import cn.smartjavaai.common.entity.DetectionInfo;
+import cn.smartjavaai.common.entity.DetectionRectangle;
+import cn.smartjavaai.common.entity.DetectionResponse;
+import cn.smartjavaai.common.entity.PolygonLabel;
+import cn.smartjavaai.common.entity.face.FaceAttribute;
+import cn.smartjavaai.common.entity.face.HeadPose;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.opencv.core.*;
import org.opencv.core.Point;
+import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
/**
* OpenCV 工具类
*/
public class OpenCVUtils {
+
+
/**
* canny算法,边缘检测
*
@@ -153,7 +165,7 @@ public class OpenCVUtils {
* @param image
* @param detectionInfoList
*/
- public static void drawRectAndText(Image image, List detectionInfoList) {
+ public static void drawRectAndText(Mat image, List detectionInfoList) {
if(CollectionUtils.isEmpty(detectionInfoList))
return;
for(DetectionInfo detectionInfo : detectionInfoList){
@@ -162,42 +174,90 @@ public class OpenCVUtils {
}
+ /**
+ * 绘制矩形框和文字
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(Mat image, DetectionRectangle box, String text) {
+ if(image.empty())
+ return;
+ int x = box.getX();
+ int y = box.getY();
+ int width = box.getWidth();
+ int height = box.getHeight();
+ Scalar rectangleColor = new Scalar((double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178));
+ // 绘制矩形框
+ Point pt1 = new Point(x, y);
+ Point pt2 = new Point(x + width, y + height);
+ Imgproc.rectangle(image, pt1, pt2, rectangleColor, 2);
+ Scalar textColor = new Scalar(255.0, 255.0, 255.0);
+ putTextWithBackground(image, text, pt1, textColor, rectangleColor, 1);
+ }
+
+ /**
+ * 绘制矩形框和文字
+ * @param image
+ * @param box
+ * @param text
+ */
+ public static void drawRectAndText(Mat image, DetectionRectangle box, String text, double fontSize) {
+ if(image.empty())
+ return;
+ int x = box.getX();
+ int y = box.getY();
+ int width = box.getWidth();
+ int height = box.getHeight();
+ Scalar rectangleColor = new Scalar((double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178));
+ // 绘制矩形框
+ Point pt1 = new Point(x, y);
+ Point pt2 = new Point(x + width, y + height);
+ Imgproc.rectangle(image, pt1, pt2, rectangleColor, 2);
+ Scalar textColor = new Scalar(255.0, 255.0, 255.0);
+ putTextWithBackground(image, text, pt1, textColor, rectangleColor, 1, fontSize);
+ }
+
+
/**
* 绘制矩形框和文字
*
* @param image
* @param detectionInfo
*/
- public static void drawRectAndText(Image image, DetectionInfo detectionInfo) {
-
-
- Mat mat = (Mat)image.getWrappedImage();
+ public static void drawRectAndText(Mat image, DetectionInfo detectionInfo) {
if (image == null) return;
int x = detectionInfo.getDetectionRectangle().getX();
int y = detectionInfo.getDetectionRectangle().getY();
int width = detectionInfo.getDetectionRectangle().getWidth();
int height = detectionInfo.getDetectionRectangle().getHeight();
-
Scalar rectangleColor = new Scalar((double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178));
-
// 绘制矩形框
Point pt1 = new Point(x, y);
Point pt2 = new Point(x + width, y + height);
- Imgproc.rectangle(mat, pt1, pt2, rectangleColor, 2);
-
+ Imgproc.rectangle(image, pt1, pt2, rectangleColor, 2);
// 绘制文字
- if (Objects.nonNull(detectionInfo.getObjectDetInfo()) && StringUtils.isNotBlank(detectionInfo.getObjectDetInfo().getClassName())) {
- String className = detectionInfo.getObjectDetInfo().getClassName();
- Size size = Imgproc.getTextSize(className, 1, 1.3, 1, (int[])null);
- Point br = new Point((double)x + size.width + 4.0, (double)y + size.height + 4.0);
- Imgproc.rectangle(mat, pt1, br, rectangleColor, -1);
- Point point = new Point((double)x, (double)y + size.height + 2.0);
- Scalar color = new Scalar(255.0, 255.0, 255.0);
- Imgproc.putText(mat, className, point, 1, 1.3, color, 1);
+ String className = null;
+ Scalar textColor = new Scalar(255.0, 255.0, 255.0);
+ //目标检测信息
+ if(detectionInfo.getObjectDetInfo() != null){
+ className = detectionInfo.getObjectDetInfo().getClassName();
+ putTextWithBackground(image, className, pt1, textColor, rectangleColor, 1);
+ }
+ //人脸
+ if(detectionInfo.getFaceInfo() != null){
+ className = "face";
+ putTextWithBackground(image, className, pt1, textColor, rectangleColor, 1);
+ //绘制关键点
+ drawLandmarks(image, detectionInfo.getFaceInfo().getKeyPoints());
+ //绘制人脸属性
+ if(detectionInfo.getFaceInfo().getFaceAttribute() != null){
+ drawFaceAttribute(detectionInfo.getFaceInfo().getFaceAttribute(), detectionInfo.getDetectionRectangle(), image);
+ }
}
- image = ImageFactory.getInstance().fromImage(mat);
}
+
/**
* 在Mat上绘制矩形框和文字
*
@@ -262,4 +322,430 @@ public class OpenCVUtils {
// }
// return null;
// }
+
+
+ /**
+ * 从 OpenCV Mat 中获取 BGR 格式矩阵数据
+ *
+ * @param mat OpenCV Mat,需为 CV_8UC3 或可转换为 BGR 格式
+ * @return BGR 格式字节数组,按行连续存储
+ */
+ public static byte[] getMatrixBGR(Mat mat) {
+ if (mat == null || mat.empty()) {
+ throw new IllegalArgumentException("Mat 不能为空");
+ }
+
+ // 确保是三通道 BGR 格式
+ Mat bgrMat = new Mat();
+ if (mat.channels() == 3) {
+ mat.copyTo(bgrMat);
+ } else if (mat.channels() == 4) {
+ // RGBA 转 BGR
+ Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_RGBA2BGR);
+ } else if (mat.channels() == 1) {
+ // 灰度转 BGR
+ Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_GRAY2BGR);
+ } else {
+ throw new IllegalArgumentException("不支持的通道数: " + mat.channels());
+ }
+
+ int size = (int) (bgrMat.total() * bgrMat.channels());
+ byte[] data = new byte[size];
+ bgrMat.get(0, 0, data);
+ bgrMat.release(); // 释放临时 Mat
+ return data;
+ }
+
+ /**
+ * 在图像上绘制带白色背景、黑色文字的文本
+ */
+ public static void putTextWithBackground(Mat image, String text, org.opencv.core.Point origin, Scalar textColor, Scalar backgroundColor, int padding) {
+ // 默认字体
+ int font = Imgproc.FONT_HERSHEY_SIMPLEX;
+ // 默认字体缩放大小
+ double fontScale = 0.6;
+ //线条粗细
+ int thickness = 2;
+ //获取文字大小
+ int[] baseLine = new int[1];
+ Size textSize = Imgproc.getTextSize(text, font, fontScale, thickness, baseLine);
+ int textWidth = (int) textSize.width;
+ int textHeight = (int) textSize.height;
+
+ //计算带padding的背景框
+ org.opencv.core.Point bgTopLeft = new org.opencv.core.Point(origin.x - padding, origin.y - textHeight - padding);
+ org.opencv.core.Point bgBottomRight = new org.opencv.core.Point(origin.x + textWidth + padding, origin.y + baseLine[0] + padding);
+
+ //绘制背景矩形
+ Imgproc.rectangle(image, bgTopLeft, bgBottomRight, backgroundColor, Imgproc.FILLED);
+
+ //绘制文字(黑色)
+ Imgproc.putText(image, text, origin, font, fontScale, textColor, thickness);
+ }
+
+ /**
+ * 在图像上绘制带白色背景、黑色文字的文本
+ */
+ public static void putTextWithBackground(Mat image, String text, org.opencv.core.Point origin, Scalar textColor, Scalar backgroundColor, int padding, double fontScale) {
+ // 默认字体
+ int font = Imgproc.FONT_HERSHEY_SIMPLEX;
+ //线条粗细
+ int thickness = 1;
+ //获取文字大小
+ int[] baseLine = new int[1];
+ Size textSize = Imgproc.getTextSize(text, font, fontScale, thickness, baseLine);
+ int textWidth = (int) textSize.width;
+ int textHeight = (int) textSize.height;
+
+ //计算带padding的背景框
+ org.opencv.core.Point bgTopLeft = new org.opencv.core.Point(origin.x - padding, origin.y - textHeight - padding);
+ org.opencv.core.Point bgBottomRight = new org.opencv.core.Point(origin.x + textWidth + padding, origin.y + baseLine[0] + padding);
+
+ //绘制背景矩形
+ Imgproc.rectangle(image, bgTopLeft, bgBottomRight, backgroundColor, Imgproc.FILLED);
+
+ //绘制文字(黑色)
+ Imgproc.putText(image, text, origin, font, fontScale, textColor, thickness);
+ }
+
+ /**
+ * 绘制检测结果
+ * @param image 待绘制的图片
+ * @param detectionResponse 检测结果
+ * @return 绘制后的图片
+ */
+ public static void drawBoundingBoxes(Mat image, DetectionResponse detectionResponse){
+ if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
+ throw new IllegalArgumentException("无目标数据");
+ }
+ drawRectAndText(image, detectionResponse.getDetectionInfoList());
+ }
+
+ /**
+ * 在 Mat 上绘制关键点
+ * @param mat OpenCV 图像
+ * @param keyPoints 人脸关键点列表
+ */
+ public static void drawLandmarks(Mat mat, List keyPoints) {
+ // 设置颜色 (BGR 格式),这里是橙色 (0,96,246)
+ Scalar color = new Scalar(0, 96, 246);
+ // 遍历关键点,用圆来表示 (比矩形更自然)
+ for (cn.smartjavaai.common.entity.Point p : keyPoints) {
+ Point cvPoint = new Point(p.getX(), p.getY());
+ Imgproc.circle(mat, cvPoint, 2, color, 2, Imgproc.LINE_AA, 0);
+ }
+ }
+
+ /**
+ * 在 Mat 上绘制多行文字,并带背景
+ * @param mat OpenCV Mat
+ * @param lines 文字行
+ * @param x 起始 x
+ * @param y 起始 y
+ */
+ public static void drawMultilineTextWithBackground(Mat mat, List lines, int x, int y) {
+ int fontFace = Imgproc.FONT_HERSHEY_SIMPLEX;
+ double fontScale = 0.5; // 字体大小
+ int thickness = 1;
+ int baseline[] = {0};
+
+ // 逐行计算最大宽度 & 总高度
+ int maxWidth = 0;
+ int lineHeight = 0;
+ for (String line : lines) {
+ Size textSize = Imgproc.getTextSize(line, fontFace, fontScale, thickness, baseline);
+ maxWidth = Math.max(maxWidth, (int) textSize.width);
+ lineHeight = Math.max(lineHeight, (int) (textSize.height + baseline[0]));
+ }
+
+ int padding = 4;
+ int boxWidth = maxWidth + padding * 2;
+ int boxHeight = lineHeight * lines.size() + padding * 2;
+
+ // 绘制背景矩形 (半透明黑色在 OpenCV 里不好直接实现,只能画实色或用 addWeighted 合成)
+ Scalar bgColor = new Scalar(0, 0, 0); // BGR = 黑色
+ Point topLeft = new Point(x, y);
+ Point bottomRight = new Point(x + boxWidth, y + boxHeight);
+ Imgproc.rectangle(mat, topLeft, bottomRight, bgColor, -1); // -1 表示填充
+
+ // 逐行绘制文字 (白色)
+ Scalar textColor = new Scalar(255, 255, 255);
+ for (int i = 0; i < lines.size(); i++) {
+ int textY = y + padding + (i + 1) * lineHeight;
+ Imgproc.putText(mat, lines.get(i),
+ new Point(x + padding, textY),
+ fontFace, fontScale, textColor, thickness, Imgproc.LINE_AA, false);
+ }
+ }
+
+ /**
+ * 绘制人脸属性
+ * @param faceAttribute
+ * @param rectangle
+ * @param mat
+ */
+ public static void drawFaceAttribute(FaceAttribute faceAttribute, DetectionRectangle rectangle, Mat mat){
+ List lines = new ArrayList<>();
+ if (faceAttribute.getGenderType() != null) {
+ lines.add("gender: " + faceAttribute.getGenderType());
+ }
+ if (faceAttribute.getAge() != null) {
+ lines.add("age: " + faceAttribute.getAge());
+ }
+ if (faceAttribute.getWearingMask() != null) {
+ lines.add("mask: " + (faceAttribute.getWearingMask() ? "yes" : "no"));
+ }
+ if (faceAttribute.getLeftEyeStatus() != null && faceAttribute.getRightEyeStatus() != null) {
+ lines.add("eyes: " + faceAttribute.getLeftEyeStatus() + "/" + faceAttribute.getRightEyeStatus());
+ }
+ if (faceAttribute.getHeadPose() != null) {
+ HeadPose pose = faceAttribute.getHeadPose();
+ String pitch = pose.getPitch() != null ? String.valueOf(pose.getPitch().intValue()) : "-";
+ String yaw = pose.getYaw() != null ? String.valueOf(pose.getYaw().intValue()) : "-";
+ String roll = pose.getRoll() != null ? String.valueOf(pose.getRoll().intValue()) : "-";
+ lines.add("head pose: P=" + pitch + " Y=" + yaw + " R=" + roll);
+ }
+ if (!lines.isEmpty()) {
+ drawMultilineTextWithBackground(mat, lines, rectangle.getX(), rectangle.getY()); // 适当偏移
+ }
+ }
+
+
+ /**
+ * 透视变换 + 裁剪
+ * @param srcMat
+ * @param landMarks
+ * @return
+ */
+ public static Image transformAndCrop(Mat srcMat, List landMarks){
+ if (landMarks == null || landMarks.size() != 4) {
+ throw new IllegalArgumentException("必须提供4个关键点");
+ }
+
+ // 步骤 1:排序为 左上、右上、右下、左下
+ List ordered = PointUtils.orderPoints(landMarks);
+
+ ai.djl.modality.cv.output.Point lt = ordered.get(0);
+ ai.djl.modality.cv.output.Point rt = ordered.get(1);
+ ai.djl.modality.cv.output.Point rb = ordered.get(2);
+ ai.djl.modality.cv.output.Point lb = ordered.get(3);
+
+ // 步骤 2:计算目标图像尺寸(宽、高)
+ int img_crop_width = (int) Math.max(
+ PointUtils.distance(lt, rt),
+ PointUtils.distance(rb, lb)
+ );
+ int img_crop_height = (int) Math.max(
+ PointUtils.distance(lt, lb),
+ PointUtils.distance(rt, rb)
+ );
+
+ // 步骤 3:构造目标坐标点
+ List dstPoints = Arrays.asList(
+ new ai.djl.modality.cv.output.Point(0, 0),
+ new ai.djl.modality.cv.output.Point(img_crop_width, 0),
+ new ai.djl.modality.cv.output.Point(img_crop_width, img_crop_height),
+ new ai.djl.modality.cv.output.Point(0, img_crop_height)
+ );
+
+ // 步骤 4:透视变换
+ Mat srcPoint2f = DJLCommonUtils.toMat(ordered);
+ Mat dstPoint2f = DJLCommonUtils.toMat(dstPoints);
+ Mat cvMat = OpenCVUtils.perspectiveTransform(srcMat, srcPoint2f, dstPoint2f);
+
+ // 步骤 5:转为 DJL Image + 裁剪
+ Image subImg = OpenCVImageFactory.getInstance().fromImage(cvMat);
+ subImg = subImg.getSubImage(0, 0, img_crop_width, img_crop_height);
+
+ // 释放资源
+ cvMat.release();
+ srcPoint2f.release();
+ dstPoint2f.release();
+ return subImg;
+ }
+
+ /**
+ * Mat To MatOfPoint
+ * @param mat
+ * @return
+ */
+ public static MatOfPoint matToMatOfPoint(Mat mat) {
+ int rows = mat.rows();
+ MatOfPoint matOfPoint = new MatOfPoint();
+
+ List list = new ArrayList<>();
+ for (int i = 0; i < rows; i++) {
+ Point point = new Point((float) mat.get(i, 0)[0], (float) mat.get(i, 1)[0]);
+ list.add(point);
+ }
+ matOfPoint.fromList(list);
+
+ return matOfPoint;
+ }
+
+ /**
+ * Mat To double[][] Array
+ * @param mat
+ * @return
+ */
+ public static double[][] matToDoubleArray(Mat mat) {
+ int rows = mat.rows();
+ int cols = mat.cols();
+ double[][] doubles = new double[rows][cols];
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ doubles[i][j] = mat.get(i, j)[0];
+ }
+ }
+ return doubles;
+ }
+
+ /**
+ * Mat To float[][] Array
+ * @param mat
+ * @return
+ */
+ public static float[][] matToFloatArray(Mat mat) {
+ int rows = mat.rows();
+ int cols = mat.cols();
+
+ float[][] floats = new float[rows][cols];
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ floats[i][j] = (float) mat.get(i, j)[0];
+ }
+ }
+
+ return floats;
+ }
+
+ /**
+ * Mat To byte[][] Array
+ * @param mat
+ * @return
+ */
+ public static byte[][] matToUint8Array(Mat mat) {
+ int rows = mat.rows();
+ int cols = mat.cols();
+
+ byte[][] bytes = new byte[rows][cols];
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ bytes[i][j] = (byte) mat.get(i, j)[0];
+ }
+ }
+
+ return bytes;
+ }
+
+ /**
+ * float[][] Array To Mat
+ * @param arr
+ * @return
+ */
+ public static Mat floatArrayToMat(float[][] arr) {
+ int rows = arr.length;
+ int cols = arr[0].length;
+ Mat mat = new Mat(rows, cols, CvType.CV_32F);
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ mat.put(i, j, arr[i][j]);
+ }
+ }
+
+ return mat;
+ }
+
+ /**
+ * byte[][] Array To Mat
+ * @param arr
+ * @return
+ */
+ public static Mat uint8ArrayToMat(byte[][] arr) {
+ int rows = arr.length;
+ int cols = arr[0].length;
+ Mat mat = new Mat(rows, cols, CvType.CV_8U);
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ mat.put(i, j, arr[i][j]);
+ }
+ }
+
+ return mat;
+ }
+
+
+ /**
+ * 将自定义 Point 列表转换为 OpenCV Point 列表
+ * @param pointList 自定义 Point 列表
+ * @return OpenCV Point 列表
+ */
+ public static List toCvPointList(List pointList) {
+ if (pointList == null) {
+ return null;
+ }
+ return pointList.stream()
+ .map(p -> new Point(p.getX(), p.getY()))
+ .collect(Collectors.toList());
+ }
+
+ public static void drawPolygonWithText(Mat mat, List polygonLabelList, int fontSize) {
+ for (PolygonLabel polygonLabel : polygonLabelList){
+ List cvPointList = toCvPointList(polygonLabel.getPoints());
+ drawPolygonWithText(mat, cvPointList, polygonLabel.getText(), new Scalar(0, 255, 0), 2);
+ }
+ }
+
+
+ /**
+ * 在图像上绘制多边形(任意边数)
+ *
+ * @param mat 图像
+ * @param points 点的列表(至少2个点)
+ * @param color 颜色
+ * @param thickness 线宽
+ */
+ public static void drawPolygonWithText(Mat mat, List points, String text, Scalar color, int thickness) {
+ if (points == null || points.size() < 2) {
+ return;
+ }
+ // 连线
+ for (int i = 0; i < points.size(); i++) {
+ Point p1 = points.get(i);
+ Point p2 = points.get((i + 1) % points.size()); // 最后一个点连回第一个
+ Imgproc.line(mat, p1, p2, color, thickness);
+ }
+ if(StringUtils.isNotBlank(text)){
+ Scalar textScalar = new Scalar(0,0,0);
+ // 保证文字不超出矩形
+ Imgproc.putText(mat, text, points.get(0), Imgproc.FONT_HERSHEY_SIMPLEX, 1, textScalar, thickness);
+ }
+ }
+
+ public static Mat getSubImage(Mat image, int x, int y, int w, int h) {
+ return image.submat(new Rect(x, y, w, h));
+ }
+
+ /**
+ * 从本地路径读取图片并转为 Mat
+ *
+ * @param path 图片路径
+ * @return Mat 对象
+ */
+ public static Mat loadImage(String path) {
+ // 使用 imread 读取
+ Mat mat = Imgcodecs.imread(path);
+ // 判空,避免后续处理时报错
+ if (mat.empty()) {
+ throw new IllegalArgumentException("无法加载图片: " + path);
+ }
+ return mat;
+ }
+
+
+
}
diff --git a/examples/face-example/pom.xml b/examples/face-example/pom.xml
index 08db1dd..9191c33 100644
--- a/examples/face-example/pom.xml
+++ b/examples/face-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.24
+ 1.0.25
smartai.examples.face.facedet.FaceDetDemo
@@ -220,35 +220,6 @@
-
-
- org.bytedeco
- javacpp
- ${javacv.version}
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- ffmpeg
- 6.1.1-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- openblas
- 0.3.26-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- opencv
- 4.9.0-1.5.10
- ${javacv.platform.linux-arm64}
-
-
@@ -278,7 +249,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
central
https://repo1.maven.org/maven2/
diff --git a/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java
index d240f90..b019d93 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java
@@ -1,11 +1,14 @@
package smartai.examples.face.attribute;
+import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.entity.face.FaceAttribute;
import cn.smartjavaai.common.entity.face.FaceInfo;
+import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceAttributeConfig;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.enums.FaceAttributeModelEnum;
@@ -38,6 +41,8 @@ public class FaceAttributeDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -47,13 +52,13 @@ public class FaceAttributeDetDemo {
FaceAttributeConfig config = new FaceAttributeConfig();
config.setModelEnum(FaceAttributeModelEnum.SEETA_FACE6_MODEL);
//需替换为实际模型存储路径
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ config.setModelPath("C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models");
return FaceAttributeModelFactory.getInstance().getModel(config);
}
public FaceDetModel getFaceDetModel() {
//需替换为实际模型存储路径
- String modelPath = "C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models";
+ String modelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";
FaceDetConfig faceDetectModelConfig = new FaceDetConfig();
faceDetectModelConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetectModelConfig.setModelPath(modelPath);
@@ -68,10 +73,12 @@ public class FaceAttributeDetDemo {
public void testFaceAttributeDetect(){
try {
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
- DetectionResponse detectionResponse = faceAttributeModel.detect("src/main/resources/iu_1.jpg");
+ ////创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
+ DetectionResponse detectionResponse = faceAttributeModel.detect(image);
//绘制并导出人脸属性图片,小人脸仅有人脸框
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
- FaceUtils.drawBoxesWithFaceAttribute(image, detectionResponse,"C:/Users/Administrator/Downloads/double_person_.png");
+ BufferedImage bufferedImage = ImageUtils.toBufferedImage(image);
+ FaceUtils.drawBoxesWithFaceAttribute(bufferedImage, detectionResponse,"C:/Users/Administrator/Downloads/double_person_.png");
log.info("人脸属性检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
@@ -85,7 +92,9 @@ public class FaceAttributeDetDemo {
public void testFaceAttributeDetect2(){
try {
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
- FaceAttribute faceAttribute = faceAttributeModel.detectTopFace("src/main/resources/iu_1.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
+ FaceAttribute faceAttribute = faceAttributeModel.detectTopFace(image);
log.info("人脸属性检测结果:{}", JSONObject.toJSONString(faceAttribute));
} catch (Exception e) {
e.printStackTrace();
@@ -101,8 +110,8 @@ public class FaceAttributeDetDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
- //人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
diff --git a/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java b/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java
index b112dba..00435a4 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java
@@ -3,6 +3,7 @@ package smartai.examples.face.expression;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -11,6 +12,7 @@ import cn.smartjavaai.common.entity.face.ExpressionResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.FacialExpression;
import cn.smartjavaai.common.enums.face.LivenessStatus;
+import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
@@ -60,6 +62,8 @@ public class ExpressionRecDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -75,7 +79,7 @@ public class ExpressionRecDemo {
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/face_model");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
@@ -106,7 +110,9 @@ public class ExpressionRecDemo {
public void testExpressionDetect() {
try {
ExpressionModel model = getExpressionModel();
- R result = model.detectTopFace("src/main/resources/emotion/happy.png");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
+ R result = model.detectTopFace(image);
if(result.isSuccess()){
log.info("识别结果:{}", JSONObject.toJSONString(result.getData().getExpression().getDescription()));
}else{
@@ -125,7 +131,9 @@ public class ExpressionRecDemo {
public void testExpressionDetect2() {
try {
ExpressionModel model = getExpressionModel();
- R result = model.detect("src/main/resources/emotion/happy.png");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
+ R result = model.detect(image);
if(result.isSuccess()){
//log.info("识别结果:{}", JSONObject.toJSONString(result.getData()));
for (DetectionInfo detectionInfo : result.getData().getDetectionInfoList()) {
@@ -149,7 +157,8 @@ public class ExpressionRecDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
ExpressionModel model = getExpressionModel();
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/emotion/happy.png").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
R detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
R> result = model.detect(image, detResult.getData());
@@ -178,7 +187,8 @@ public class ExpressionRecDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
ExpressionModel model = getExpressionModel();
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/emotion/happy.png").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
R detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
for (DetectionInfo detectionInfo : detResult.getData().getDetectionInfoList()) {
@@ -204,15 +214,16 @@ public class ExpressionRecDemo {
public void testExpressionDetectAndDraw(){
try {
ExpressionModel model = getExpressionModel();
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/emotion/surprise.png").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/surprise.png");
R result = model.detect(image);
if(result.isSuccess()){
//log.info("识别结果:{}", JSONObject.toJSONString(result.getData()));
for (DetectionInfo detectionInfo : result.getData().getDetectionInfoList()) {
log.info("识别结果:{}", JSONObject.toJSONString(detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription()));
- ImageUtils.drawImageRectWithText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription(), Color.red);
+ ImageUtils.drawRectAndText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription());
}
- ImageUtils.saveImage(image, "output/detect.jpg");
+ ImageUtils.save(image, "output/detect.jpg");
}else{
log.info("识别失败:{}", result.getMessage());
}
@@ -225,7 +236,7 @@ public class ExpressionRecDemo {
* 摄像头表情识别
* 注意事项:如果视频比较卡,可以使用轻量的人脸检测模型
*/
- @Test
+// @Test
public void testExpressionDetectCamera(){
try {
ExpressionModel expressionModel = getExpressionModel();
@@ -264,7 +275,7 @@ public class ExpressionRecDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
- ImageFactory factory = ImageFactory.getInstance();
+ SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -273,19 +284,18 @@ public class ExpressionRecDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
- Image img = factory.fromImage(resizeImage);
- BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
- R detectedResult = expressionModel.detect(bufferedImage);
+ Image img = factory.fromMat(resizeImage);
+ R detectedResult = expressionModel.detect(img);
if(!detectedResult.isSuccess()){
log.debug("识别失败:{}", detectedResult.getMessage());
continue;
}
for(DetectionInfo detectionInfo : detectedResult.getData().getDetectionInfoList()){
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
- String text = detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription() + ":" + detectionInfo.getFaceInfo().getExpressionResult().getScore();
- ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, Color.red);
+ String text = detectionInfo.getFaceInfo().getExpressionResult().getExpression().getLabel() + ":" + detectionInfo.getFaceInfo().getExpressionResult().getScore();
+ ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
- frame.showImage(bufferedImage);
+ frame.showImage(ImageUtils.toBufferedImage(img));
}
capture.release();
diff --git a/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java
index 8fe5a89..2593894 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java
@@ -2,7 +2,9 @@ package smartai.examples.face.facedet;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
+import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -17,6 +19,7 @@ import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.model.liveness.LivenessDetModel;
+import cn.smartjavaai.face.utils.FaceUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
@@ -51,6 +54,8 @@ public class FaceDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -67,7 +72,7 @@ public class FaceDetDemo {
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/face_model");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
@@ -104,7 +109,7 @@ public class FaceDetDemo {
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.YOLOV5_FACE_320);
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
@@ -137,9 +142,16 @@ public class FaceDetDemo {
public void testFaceDetect(){
try {
FaceDetModel faceModel = getFaceDetModel();
- R detectedResult = faceModel.detect(imgPath);
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(imgPath);
+ R detectedResult = faceModel.detect(image);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
+ //裁剪人脸保存
+ for (DetectionInfo detectionInfo : detectedResult.getData().getDetectionInfoList()) {
+ Image faceImage = FaceUtils.cropFace(image, detectionInfo.getDetectionRectangle());
+ ImageUtils.save(faceImage, "output/face_" + detectionInfo.getDetectionRectangle().getX() + "_" + detectionInfo.getDetectionRectangle().getY() + ".jpg");
+ }
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
@@ -156,7 +168,12 @@ public class FaceDetDemo {
public void testFaceDetectAndDraw(){
try {
FaceDetModel faceModel = getFaceDetModel();
- faceModel.detectAndDraw("src/main/resources/largest_selfie.jpg","output/largest_selfie_detected.png");
+ R detectedResult = faceModel.detectAndDraw("src/main/resources/largest_selfie.jpg","output/largest_selfie_detected.png");
+ if(detectedResult.isSuccess()){
+ log.info("人脸检测成功:{}", JsonUtils.toJson(detectedResult.getData()));
+ }else{
+ log.info("人脸检测失败:{}", detectedResult.getMessage());
+ }
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -170,15 +187,14 @@ public class FaceDetDemo {
public void testFaceDetectAndDraw2(){
try {
FaceDetModel faceModel = getFaceDetModel();
- BufferedImage image = null;
- String imagePath = "src/main/resources/largest_selfie.jpg";
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
- //可以根据后续业务场景使用detectedImage
- R detectedImage = faceModel.detectAndDraw(image);
- if(detectedImage.isSuccess()){
- log.info("人脸检测成功");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(imgPath);
+ R detectionResponseR = faceModel.detectAndDraw(image);
+ if(detectionResponseR.isSuccess()){
+ log.info("人脸检测成功:{}", JsonUtils.toJson(detectionResponseR.getData()));
+ ImageUtils.save(detectionResponseR.getData().getDrawnImage(), "output/iu_1_detect.png");
}else{
- log.info("人脸检测失败:{}", detectedImage.getMessage());
+ log.info("人脸检测失败:{}", detectionResponseR.getMessage());
}
} catch (Exception e) {
throw new RuntimeException(e);
@@ -187,31 +203,6 @@ public class FaceDetDemo {
}
- /**
- * 人脸检测(GPU模式)
- */
- @Test
- public void testDetectFaceGPU(){
- try {
- FaceDetConfig config = new FaceDetConfig();
- //人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
- config.setModelEnum(FaceDetModelEnum.MTCNN);
- //下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/face_model");
- //只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
- config.setConfidenceThreshold(0.5f);
- config.setDevice(DeviceEnum.GPU);
- FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel(config);
- R detectedResult = faceModel.detect(imgPath);
- if(detectedResult.isSuccess()){
- log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
- }else{
- log.info("人脸检测失败:{}", detectedResult.getMessage());
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
/**
* 人脸检测(Seetaface6)
@@ -221,7 +212,9 @@ public class FaceDetDemo {
public void testFaceDetectSeetaface6(){
try {
FaceDetModel faceModel = getSeetaface6DetModel();
- R detectedResult = faceModel.detect(imgPath);
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(imgPath);
+ R detectedResult = faceModel.detect(image);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
}else{
@@ -276,7 +269,7 @@ public class FaceDetDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
- ImageFactory factory = ImageFactory.getInstance();
+ SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -285,9 +278,8 @@ public class FaceDetDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
- Image img = factory.fromImage(resizeImage);
- BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
- R detectedResult = faceModel.detect(bufferedImage);
+ Image img = factory.fromMat(resizeImage);
+ R detectedResult = faceModel.detect(img);
if(!detectedResult.isSuccess()){
log.debug("识别失败:{}", detectedResult.getMessage());
continue;
@@ -298,11 +290,10 @@ public class FaceDetDemo {
if(detectionInfo.getScore() > 0){
text = detectionInfo.getScore() + "";
}
- ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, Color.red);
+ ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
- frame.showImage(bufferedImage);
+ frame.showImage(ImageUtils.toBufferedImage(img));
}
-
capture.release();
System.exit(0);
} catch (Exception e) {
diff --git a/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java b/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java
index 8cf5c5d..6c6ead1 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java
@@ -1,10 +1,13 @@
package smartai.examples.face.facerec;
-import cn.smartjavaai.common.config.Config;
+import ai.djl.modality.cv.Image;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.entity.face.FaceSearchResult;
import cn.smartjavaai.common.enums.DeviceEnum;
+import cn.smartjavaai.common.utils.BufferedImageUtils;
+import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.config.FaceRecConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
@@ -18,7 +21,6 @@ import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.factory.FaceRecModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.model.facerec.FaceRecModel;
-import cn.smartjavaai.face.utils.SimilarityUtil;
import cn.smartjavaai.face.vector.config.MilvusConfig;
import cn.smartjavaai.face.vector.config.SQLiteConfig;
import cn.smartjavaai.face.vector.entity.FaceVector;
@@ -28,6 +30,7 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
+import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
@@ -45,6 +48,8 @@ public class FaceRecDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -61,7 +66,7 @@ public class FaceRecDemo {
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/face_model");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
@@ -116,19 +121,19 @@ public class FaceRecDemo {
* 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
* @return
*/
- public FaceRecModel getHighAccuracyFaceRecModel(){
+ public FaceRecModel getFaceRecModel(){
FaceRecConfig config = new FaceRecConfig();
//高精度模型,速度慢
- config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);
+ config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL);
//模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/model/elasticface.pt");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_ir_se50.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(true);
config.setDevice(device);
//指定人脸检测模型
- config.setDetectModel(getProFaceDetModel());
+ config.setDetectModel(getFaceDetModel());
return FaceRecModelFactory.getInstance().getModel(config);
}
@@ -141,9 +146,9 @@ public class FaceRecDemo {
public FaceRecModel getHighSpeedFaceRecModel(){
FaceRecConfig config = new FaceRecConfig();
//模型枚举
- config.setModelEnum(FaceRecModelEnum.SEETA_FACE6_LIGHT_MODEL);
+ config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_MOBILE_FACENET_MODEL);
//模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/xxx/Documents/develop/model/sf3.0_models");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_mobilefacenet.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
@@ -161,8 +166,9 @@ public class FaceRecDemo {
public FaceRecModel getFaceRecModelWithDbConfig(){
FaceRecConfig config = new FaceRecConfig();
//高精度模型,速度慢,追求速度请更换高速模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
- config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);//人脸识别模型
- config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
+ config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL);
+ //模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_ir_se50.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
@@ -175,9 +181,9 @@ public class FaceRecDemo {
MilvusConfig vectorDBConfig = new MilvusConfig();
vectorDBConfig.setHost("127.0.0.1");
vectorDBConfig.setPort(19530);
- //vectorDBConfig.setUsername("root");
- //vectorDBConfig.setPassword("Milvus");
- //vectorDBConfig.setCollectionName("face5");
+// vectorDBConfig.setUsername("root");
+// vectorDBConfig.setPassword("Milvus");
+// vectorDBConfig.setCollectionName("face6");
//ID策略:自动生成
vectorDBConfig.setIdStrategy(IdStrategy.AUTO);
//索引类型:内积 (Inner Product) 不建议修改
@@ -193,8 +199,9 @@ public class FaceRecDemo {
public FaceRecModel getFaceRecModelWithSQLiteConfig(){
FaceRecConfig config = new FaceRecConfig();
//高精度模型,速度慢, 追求速度请更换高速模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
- config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);//人脸检测模型
- config.setModelPath("/Users/wenjie/Documents/develop/model/elasticface.pt");
+ config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL);
+ //模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_ir_se50.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
@@ -221,9 +228,11 @@ public class FaceRecDemo {
public void testExtractFeatures(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
- FaceRecModel faceRecModel = getHighAccuracyFaceRecModel();
+ FaceRecModel faceRecModel = getFaceRecModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//提取图片中所有人脸特征
- R faceResult = faceRecModel.extractFeatures("src/main/resources/iu_1.jpg");
+ R faceResult = faceRecModel.extractFeatures(image);
if(faceResult.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(faceResult.getData()));
}else{
@@ -246,15 +255,59 @@ public class FaceRecDemo {
public void featureComparison(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
- FaceRecModel faceRecModel = getHighAccuracyFaceRecModel();
+ FaceRecModel faceRecModel = getFaceRecModel();
//基于图像直接比对人脸特征
R similarResult = faceRecModel.featureComparison("src/main/resources/iu_1.jpg","src/main/resources/iu_2.jpg");
if(similarResult.isSuccess()){
//相似度阈值不同模型不同,具体参看文档
log.info("人脸比对相似度:{}", JSONObject.toJSONString(similarResult.getData()));
+ //不同模型的相似度标准不同。当前阈值仅适用于 insight_face 模型,切换模型时请相应调整阈值,详情请参考文档。
+ if(similarResult.getData() >= 0.62f){
+ log.info("识别为同一人");
+ }else{
+ log.info("识别为不同人");
+ }
}else{
log.info("人脸比对失败:{}", similarResult.getMessage());
}
+
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 人脸比对1:1(基于图像直接比对)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。(接口内自动完成)
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
+ * @throws Exception
+ */
+ @Test
+ public void featureComparison3(){
+ try {
+ //高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
+ FaceRecModel faceRecModel = getFaceRecModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image1 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
+ Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
+ //基于图像直接比对人脸特征
+ R similarResult = faceRecModel.featureComparison(image1, image2);
+ if(similarResult.isSuccess()){
+ //相似度阈值不同模型不同,具体参看文档
+ log.info("人脸比对相似度:{}", JSONObject.toJSONString(similarResult.getData()));
+ //不同模型的相似度标准不同。当前阈值仅适用于 insight_face 模型,切换模型时请相应调整阈值,详情请参考文档。
+ if(similarResult.getData() >= 0.62f){
+ log.info("识别为同一人");
+ }else{
+ log.info("识别为不同人");
+ }
+ }else{
+ log.info("人脸比对失败:{}", similarResult.getMessage());
+ }
+
}
catch (Exception e){
e.printStackTrace();
@@ -273,9 +326,11 @@ public class FaceRecDemo {
public void featureComparison2(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
- FaceRecModel faceRecModel = getHighAccuracyFaceRecModel();
+ FaceRecModel faceRecModel = getFaceRecModel();
//特征提取(提取分数最高人脸特征),适用于单人脸场景
- R featureResult1 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image1 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
+ R featureResult1 = faceRecModel.extractTopFaceFeature(image1);
if(featureResult1.isSuccess()){
log.info("图片1人脸特征提取成功:{}", JSONObject.toJSONString(featureResult1.getData()));
}else{
@@ -283,7 +338,8 @@ public class FaceRecDemo {
return;
}
//特征提取(提取分数最高人脸特征),适用于单人脸场景
- R featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_2.jpg");
+ Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
+ R featureResult2 = faceRecModel.extractTopFaceFeature(image2);
if(featureResult2.isSuccess()){
log.info("图片2人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
}else{
@@ -293,6 +349,12 @@ public class FaceRecDemo {
//计算相似度
float similar = faceRecModel.calculSimilar(featureResult1.getData(), featureResult2.getData());
log.info("相似度:{}", similar);
+ //不同模型的相似度标准不同。当前阈值仅适用于 insight_face 模型,切换模型时请相应调整阈值,详情请参考文档。
+ if(similar >= 0.62f){
+ log.info("识别为同一人");
+ }else{
+ log.info("识别为不同人");
+ }
}
catch (Exception e){
e.printStackTrace();
@@ -318,8 +380,10 @@ public class FaceRecDemo {
Thread.sleep(100);
}
log.info("====================人脸注册==========================");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
- R featureResult = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
+ R featureResult = faceRecModel.extractTopFaceFeature(image);
if(featureResult.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
}else{
@@ -341,21 +405,23 @@ public class FaceRecDemo {
}else{
log.info("注册失败:{}", registerResult.getMessage());
}
- /*log.info("====================人脸更新==========================");
+ log.info("====================人脸更新==========================");
//更新人脸 只支持自定义ID:vectorDBConfig.setIdStrategy(IdStrategy.CUSTOM);
- FaceRegisterInfo updateInfo = new FaceRegisterInfo();
- //设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
- JSONObject metadataJsonUpdate = new JSONObject();
- metadataJsonUpdate.put("name", "iu_update");
- metadataJsonUpdate.put("age", "25");
- updateInfo.setMetadata(metadataJsonUpdate.toJSONString());
- //更新必须设置ID,只有
- updateInfo.setId(registerResult.getData());
- faceRecModel.upsertFace(updateInfo, "src/main/resources/iu_2.jpg");
- log.info("更新人脸成功");*/
+// FaceRegisterInfo updateInfo = new FaceRegisterInfo();
+// //设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
+// JSONObject metadataJsonUpdate = new JSONObject();
+// metadataJsonUpdate.put("name", "iu_update");
+// metadataJsonUpdate.put("age", "25");
+// updateInfo.setMetadata(metadataJsonUpdate.toJSONString());
+// //更新必须设置ID,只有
+// updateInfo.setId(registerResult.getData());
+// Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
+// faceRecModel.upsertFace(updateInfo, image2);
+// log.info("更新人脸成功");
log.info("====================人脸查询==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
- R featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_3.jpg");
+ Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
+ R featureResult2 = faceRecModel.extractTopFaceFeature(image3);
if(featureResult2.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
}else{
@@ -364,8 +430,7 @@ public class FaceRecDemo {
}
FaceSearchParams faceSearchParams = new FaceSearchParams();
faceSearchParams.setTopK(1);
- faceSearchParams.setThreshold(0.8f);
-
+// faceSearchParams.setThreshold(0.62f);
List faceSearchResults = faceRecModel.search(featureResult2.getData(), faceSearchParams);
// R faceSearchResults = faceModel.search("src/main/resources/face/iu_3.jpg", faceSearchParams);
log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
@@ -397,7 +462,9 @@ public class FaceRecDemo {
}
log.info("====================人脸注册==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
- R featureResult = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
+ R featureResult = faceRecModel.extractTopFaceFeature(image);
if(featureResult.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
}else{
@@ -429,11 +496,13 @@ public class FaceRecDemo {
updateInfo.setMetadata(metadataJsonUpdate.toJSONString());
//更新必须设置ID,只有
updateInfo.setId(registerResult.getData());
- faceRecModel.upsertFace(updateInfo, "src/main/resources/iu_2.jpg");
+ Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
+ faceRecModel.upsertFace(updateInfo, image2);
log.info("更新人脸成功");
log.info("====================人脸查询==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
- R featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_3.jpg");
+ Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
+ R featureResult2 = faceRecModel.extractTopFaceFeature(image3);
if(featureResult2.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
}else{
@@ -442,7 +511,7 @@ public class FaceRecDemo {
}
FaceSearchParams faceSearchParams = new FaceSearchParams();
faceSearchParams.setTopK(1);
- faceSearchParams.setThreshold(0.8f);
+ //faceSearchParams.setThreshold(0.62f);
List faceSearchResults = faceRecModel.search(featureResult2.getData(), faceSearchParams);
log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
log.info("====================人脸删除==========================");
@@ -454,6 +523,57 @@ public class FaceRecDemo {
}
}
+ /**
+ * 人脸查询及绘制
+ *
+ * @throws Exception
+ */
+ @Test
+ public void searchFace3(){
+ try {
+ //高精度模型,速度慢, 追求速度请更换高速模型
+ FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig();
+ //等待加载人脸库结束
+ while (!faceRecModel.isLoadFaceCompleted()){
+ Thread.sleep(100);
+ }
+ log.info("====================人脸注册==========================");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
+ //人脸注册信息
+ FaceRegisterInfo faceRegisterInfo = new FaceRegisterInfo();
+ //设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
+ JSONObject metadataJson = new JSONObject();
+ metadataJson.put("name", "iu");
+ metadataJson.put("age", "25");
+ faceRegisterInfo.setMetadata(metadataJson.toJSONString());
+ //可自定义 ID,若未设置则自动生成。
+ //faceRegisterInfo.setId("00001");
+ //人脸注册,返回人脸库ID
+ R registerResult = faceRecModel.register(faceRegisterInfo, image);
+ if(registerResult.isSuccess()){
+ log.info("注册成功:ID-{}", registerResult.getData());
+ }else{
+ log.info("注册失败:{}", registerResult.getMessage());
+ }
+ log.info("====================人脸查询==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
+ FaceSearchParams faceSearchParams = new FaceSearchParams();
+ faceSearchParams.setTopK(1);
+ //faceSearchParams.setThreshold(0.62f);
+ //图片中只会显示Metadata信息中name的字段
+ Image drawSearchResult = faceRecModel.drawSearchResult(image3, faceSearchParams, "name");
+ ImageUtils.save(drawSearchResult, "output/search_result.jpg");
+ log.info("====================人脸删除==========================");
+ faceRecModel.removeRegister(registerResult.getData());
+ log.info("人脸删除成功");
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
/**
* 获取人脸信息
@@ -486,7 +606,7 @@ public class FaceRecDemo {
public void listFaces(){
//使用ID获取人脸信息
try {
- FaceRecModel faceRecModel = getFaceRecModelWithDbConfig();
+ FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
Thread.sleep(100);
diff --git a/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java
index 8950c9d..e6854e2 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java
@@ -4,6 +4,7 @@ import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import cn.hutool.core.lang.UUID;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -65,6 +66,8 @@ public class LivenessDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -104,9 +107,9 @@ public class LivenessDetDemo {
config.setModelEnum(LivenessModelEnum.MINI_VISION_MODEL);
config.setDevice(device);
//模型1路径:需替换为实际模型存储路径
- config.setModelPath("/Users/xxx/Documents/develop/model/live/2.7_80x80_MiniFASNetV2.onnx");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/live/2.7_80x80_MiniFASNetV2.onnx");
//SE模型路径:需替换为实际模型存储路径
- config.putCustomParam("seModelPath", "/Users/xxx/Documents/develop/model/live/4_0_0_80x80_MiniFASNetV1SE.onnx");
+ config.putCustomParam("seModelPath", "/Users/wenjie/Documents/develop/model/live/4_0_0_80x80_MiniFASNetV1SE.onnx");
//人脸活体阈值,可选,超过阈值则认为是真人,低于阈值是非活体
config.setRealityThreshold(0.5f);
/*视频检测帧数,可选,默认10,输出帧数超过这个number之后,就可以输出识别结果。
@@ -132,7 +135,7 @@ public class LivenessDetDemo {
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
- config.setModelPath("/Users/wenjie/Documents/develop/face_model");
+ config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
@@ -149,10 +152,12 @@ public class LivenessDetDemo {
public void testLivenessDetect(){
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
- R response = livenessDetModel.detect("src/main/resources/liveness/1.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
+ R response = livenessDetModel.detect(image);
if(response.isSuccess()){
for (DetectionInfo detectionInfo : response.getData().getDetectionInfoList()){
- log.info("活体检测结果:{}", JSONObject.toJSONString(detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription()));
+ log.info("活体检测结果:{}", JSONObject.toJSONString(detectionInfo));
}
}else{
log.info("活体检测失败:{}", response.getMessage());
@@ -169,18 +174,18 @@ public class LivenessDetDemo {
public void testLivenessDetectAndDraw(){
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/liveness/1.jpg").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
R response = livenessDetModel.detect(image);
if(response.isSuccess()){
for (DetectionInfo detectionInfo : response.getData().getDetectionInfoList()){
log.info("活体检测结果:{}", JSONObject.toJSONString(detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription()));
- Color color = detectionInfo.getFaceInfo().getLivenessStatus().getStatus() == LivenessStatus.LIVE ? Color.GREEN : Color.RED;
- ImageUtils.drawImageRectWithText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription(), color);
+ ImageUtils.drawRectAndText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getLivenessStatus().getStatus().toString());
+ ImageUtils.save(image, "output/detect.jpg");
}
}else{
log.info("活体检测失败:{}", response.getMessage());
}
- ImageUtils.saveImage(image, "output/detect.jpg");
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -194,10 +199,12 @@ public class LivenessDetDemo {
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
//指定文件夹路径
- File dir = new File("face-example/src/main/resources/liveness");
+ File dir = new File("src/main/resources/liveness");
File[] files = dir.listFiles();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ SmartImageFactory imageFactory = SmartImageFactory.getInstance();
for (File file : files) {
- R response = livenessDetModel.detectTopFace(ImageIO.read(file));
+ R response = livenessDetModel.detectTopFace(imageFactory.fromFile(file));
if(response.isSuccess()){
log.info("{}活体检测结果:{},分数:{}", file.getName(), response.getData().getStatus().getDescription(), response.getData().getScore());
}else{
@@ -218,8 +225,8 @@ public class LivenessDetDemo {
try {
FaceDetModel faceDetectModel = getFaceDetModel();
LivenessDetModel livenessDetModel = getLivenessDetModel();
- // 将图片路径转换为 BufferedImage
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/liveness/1.jpg").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
//人脸检测
R detectionResponse = faceDetectModel.detect(image);
if(detectionResponse.isSuccess()){
@@ -251,8 +258,8 @@ public class LivenessDetDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
LivenessDetModel livenessDetModel = getMiniVisionLivenessDetModel();
- // 将图片路径转换为 BufferedImage
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/liveness/1.jpg").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
R detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
for (DetectionInfo detectionInfo : detResult.getData().getDetectionInfoList()) {
@@ -281,7 +288,7 @@ public class LivenessDetDemo {
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
//视频路径
- R livenessStatus = livenessDetModel.detectVideo("video.mp4");
+ R livenessStatus = livenessDetModel.detectVideo("/Users/wenjie/Documents/idea_workplace/SmartJavaAI-Demo/src/main/resources/girl.mp4");
if (livenessStatus.isSuccess()){
log.info("识别结果:{}", JSONObject.toJSONString(livenessStatus.getData()));
}else{
@@ -296,7 +303,7 @@ public class LivenessDetDemo {
* 摄像头活体检测
* 注意事项:如果视频比较卡,可以使用轻量的人脸检测模型
*/
- @Test
+// @Test
public void testLivenessDetectCamera(){
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
@@ -335,7 +342,7 @@ public class LivenessDetDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
- ImageFactory factory = ImageFactory.getInstance();
+ SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -344,9 +351,8 @@ public class LivenessDetDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
- Image img = factory.fromImage(resizeImage);
- BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
- R detectedResult = livenessDetModel.detect(bufferedImage);
+ Image img = factory.fromMat(resizeImage);
+ R detectedResult = livenessDetModel.detect(img);
if(!detectedResult.isSuccess()){
log.debug("识别失败:{}", detectedResult.getMessage());
continue;
@@ -355,11 +361,10 @@ public class LivenessDetDemo {
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
Color color = detectionInfo.getFaceInfo().getLivenessStatus().getStatus() == LivenessStatus.LIVE ? Color.GREEN : Color.RED;
String text = detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription() + ":" + detectionInfo.getFaceInfo().getLivenessStatus().getScore();
- ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, color);
+ ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
- frame.showImage(bufferedImage);
+ frame.showImage(ImageUtils.toBufferedImage(img));
}
-
capture.release();
System.exit(0);
} catch (Exception e) {
diff --git a/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java
index 10b6ed0..a297908 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java
@@ -1,6 +1,8 @@
package smartai.examples.face.quality;
+import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
@@ -47,6 +49,8 @@ public class FaceQualityDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -62,7 +66,7 @@ public class FaceQualityDetDemo {
QualityConfig config = new QualityConfig();
config.setModelEnum(QualityModelEnum.SEETA_FACE6_MODEL);
//需替换为实际模型存储路径
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ config.setModelPath("C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models");
config.setDevice(device);
return FaceQualityModelFactory.getInstance().getModel(config);
}
@@ -74,7 +78,7 @@ public class FaceQualityDetDemo {
*/
public FaceDetModel getFaceDetModel() {
//需替换为实际模型存储路径
- String modelPath = "C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models";
+ String modelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";
FaceDetConfig faceDetectModelConfig = new FaceDetConfig();
faceDetectModelConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetectModelConfig.setModelPath(modelPath);
@@ -91,8 +95,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -124,8 +129,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -157,8 +163,8 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
- //人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -190,8 +196,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -224,8 +231,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -258,8 +266,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
- BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
diff --git a/examples/ocr-examples/output/ocr_4_recognized.jpg b/examples/ocr-examples/output/ocr_4_recognized.jpg
index 9d5dfa1..f3de69f 100644
Binary files a/examples/ocr-examples/output/ocr_4_recognized.jpg and b/examples/ocr-examples/output/ocr_4_recognized.jpg differ
diff --git a/examples/ocr-examples/output/plate_recognized2.jpg b/examples/ocr-examples/output/plate_recognized2.jpg
index 468a416..d0ef832 100644
Binary files a/examples/ocr-examples/output/plate_recognized2.jpg and b/examples/ocr-examples/output/plate_recognized2.jpg differ
diff --git a/examples/ocr-examples/output/table_ch2_result.jpg b/examples/ocr-examples/output/table_ch2_result.jpg
index fe84cb2..999445d 100644
Binary files a/examples/ocr-examples/output/table_ch2_result.jpg and b/examples/ocr-examples/output/table_ch2_result.jpg differ
diff --git a/examples/ocr-examples/pom.xml b/examples/ocr-examples/pom.xml
index 646a98f..3266564 100644
--- a/examples/ocr-examples/pom.xml
+++ b/examples/ocr-examples/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.24
+ 1.0.25
smartai.examples.ocr.common.OcrRecognizeDemo
@@ -219,39 +219,6 @@
-
-
- org.bytedeco
- javacpp
- ${javacv.version}
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- ffmpeg
- 6.1.1-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- openblas
- 0.3.26-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- opencv
- 4.9.0-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
-
-
-
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java
index 0d05cb2..1b70918 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java
@@ -2,6 +2,7 @@ package smartai.examples.ocr.common;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -43,6 +44,7 @@ public class OcrDetectionDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -56,7 +58,7 @@ public class OcrDetectionDemo {
//指定检测模型,切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
+ config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -73,7 +75,9 @@ public class OcrDetectionDemo {
public void detect(){
try {
OcrCommonDetModel model = getDetectionModel();
- List boxes = model.detect("src/main/resources/ocr_1.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
+ List boxes = model.detect(image);
log.info("OCR检测结果:{}", JSONObject.toJSONString(boxes));
} catch (Exception e) {
e.printStackTrace();
@@ -97,6 +101,26 @@ public class OcrDetectionDemo {
}
}
+ /**
+ * 文本检测并绘制结果
+ * 检测图像中的文本区域,仅检测文本框位置,不识别文字内容
+ * 注意事项:
+ * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。
+ * 2、模型文件需要放在单独文件夹
+ */
+ @Test
+ public void detectAndDraw2(){
+ try {
+ OcrCommonDetModel model = getDetectionModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
+ Image resultImage = model.detectAndDraw(image);
+ ImageUtils.save(resultImage, "output/ocr_1_detected2.jpg");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
/**
* 批量文本检测:批量检测要求图片宽高一致
@@ -110,7 +134,7 @@ public class OcrDetectionDemo {
try {
OcrCommonDetModel model = getDetectionModel();
//批量检测要求图片宽高一致
- String folderPath = "/Users/xxx/Downloads/testing33";
+ String folderPath = "/Users/wenjie/Downloads/testing33";
//读取文件夹中所有图片
List images = ImageUtils.readImagesFromFolder(folderPath);
List> ocrResult = model.batchDetectDJLImage(images);
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java
index 6175653..e665fae 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java
@@ -1,7 +1,10 @@
package smartai.examples.ocr.common;
+import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.enums.DeviceEnum;
+import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
@@ -46,7 +49,7 @@ public class OcrDirectionDetDemo {
//指定行文本方向检测模型,切换模型需要同时修改modelEnum及modelPath
directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25);
//指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
- directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
+ directionModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setDevice(device);
directionModelConfig.setTextDetModel(getDetectionModel());
return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
@@ -61,7 +64,7 @@ public class OcrDirectionDetDemo {
//指定检测模型,切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
+ config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -78,7 +81,9 @@ public class OcrDirectionDetDemo {
public void detect(){
try {
OcrDirectionModel directionModel = getDirectionModel();
- List itemList = directionModel.detect("src/main/resources/ocr_1.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
+ List itemList = directionModel.detect(image);
log.info("OCR方向检测结果1:{}", JSONObject.toJSONString(itemList));
} catch (Exception e) {
e.printStackTrace();
@@ -102,6 +107,25 @@ public class OcrDirectionDetDemo {
}
}
+ /**
+ * 文本检测并绘制结果
+ * 流程:文本检测 -> 方向分类
+ * 检测图像中的文本区域,仅检测文本框位置,不识别文字内容
+ * 模型需要放在单独文件夹
+ */
+ @Test
+ public void detectAndDraw2(){
+ try {
+ OcrDirectionModel directionModel = getDirectionModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
+ Image resultImage = directionModel.detectAndDraw(image);
+ ImageUtils.save(resultImage, "output/ocr_1_detected4.jpg");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
}
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java
index e938ac4..45d5b47 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java
@@ -5,7 +5,9 @@ import ai.djl.util.JsonUtils;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.enums.DeviceEnum;
+import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
@@ -48,34 +50,70 @@ public class OcrRecognizeDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
//Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
- * 获取通用识别模型(不带方向矫正)
+ * 获取通用识别模型(高精确度模型)
+ * 注意事项:高精度模型,识别准确度高,速度慢
* @return
*/
- public OcrCommonRecModel getRecModel(){
+ public OcrCommonRecModel getProRecModel(){
OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
//指定文本识别模型,切换模型需要同时修改modelEnum及modelPath
- recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
+ recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_SERVER_REC_MODEL);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx");
recModelConfig.setDevice(device);
- recModelConfig.setTextDetModel(getDetectionModel());
+ recModelConfig.setTextDetModel(getProDetectionModel());
+ recModelConfig.setDirectionModel(getDirectionModel());
return OcrModelFactory.getInstance().getRecModel(recModelConfig);
}
/**
- * 获取文本检测模型
+ * 获取通用识别模型(极速模型)
+ * 注意事项:极速模型,识别准确度低,速度快
* @return
*/
- public OcrCommonDetModel getDetectionModel() {
+ public OcrCommonRecModel getFastRecModel(){
+ OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
+ //指定文本识别模型,切换模型需要同时修改modelEnum及modelPath
+ recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
+ //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
+ recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
+ recModelConfig.setDevice(device);
+ recModelConfig.setTextDetModel(getFastDetectionModel());
+ return OcrModelFactory.getInstance().getRecModel(recModelConfig);
+ }
+
+
+ /**
+ * 获取文本检测模型(极速模型)
+ * 注意事项:极速模型,识别准确度低,速度快
+ * @return
+ */
+ public OcrCommonDetModel getFastDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
//指定检测模型,切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
+ config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
+ config.setDevice(device);
+ return OcrModelFactory.getInstance().getDetModel(config);
+ }
+
+ /**
+ * 获取文本检测模型(高精确度模型)
+ * 注意事项:高精度模型,识别准确度高,速度慢
+ * @return
+ */
+ public OcrCommonDetModel getProDetectionModel() {
+ OcrDetModelConfig config = new OcrDetModelConfig();
+ //指定检测模型,切换模型需要同时修改modelEnum及modelPath
+ config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_SERVER_DET_MODEL);
+ //指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
@@ -90,28 +128,12 @@ public class OcrRecognizeDemo {
//指定行文本方向检测模型,切换模型需要同时修改modelEnum及modelPath
directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25);
//指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
- directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
+ directionModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setDevice(device);
return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
}
- /**
- * 获取通用识别模型(带方向矫正)
- * @return
- */
- public OcrCommonRecModel getRecModelWithDirection() {
- OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
- //指定文本识别模型,切换模型需要同时修改modelEnum及modelPath
- recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
- //指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
- recModelConfig.setDevice(device);
- recModelConfig.setTextDetModel(getDetectionModel());
- recModelConfig.setDirectionModel(getDirectionModel());
- return OcrModelFactory.getInstance().getRecModel(recModelConfig);
- }
-
/**
* 文本识别
@@ -124,10 +146,12 @@ public class OcrRecognizeDemo {
@Test
public void recognize(){
try {
- OcrCommonRecModel recModel = getRecModel();
+ OcrCommonRecModel recModel = getFastRecModel();
//不带方向矫正,分行返回文本
OcrRecOptions options = new OcrRecOptions(false, true);
- OcrInfo ocrInfo = recModel.recognize("/Users/wenjie/Downloads/49421755855753_.pic_hd.jpg",options);
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
+ OcrInfo ocrInfo = recModel.recognize(image, options);
log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo));
} catch (Exception e) {
e.printStackTrace();
@@ -146,8 +170,10 @@ public class OcrRecognizeDemo {
@Test
public void recognizeHandWriting(){
try {
- OcrCommonRecModel recModel = getRecModel();
- OcrInfo ocrInfo = recModel.recognize("src/main/resources/handwriting_1.jpg",new OcrRecOptions());
+ OcrCommonRecModel recModel = getFastRecModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/handwriting_1.jpg");
+ OcrInfo ocrInfo = recModel.recognize(image, new OcrRecOptions());
log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo));
} catch (Exception e) {
e.printStackTrace();
@@ -166,10 +192,12 @@ public class OcrRecognizeDemo {
@Test
public void recognize2(){
try {
- OcrCommonRecModel recModel = getRecModelWithDirection();
+ OcrCommonRecModel recModel = getFastRecModel();
//带方向矫正,分行返回文本
OcrRecOptions options = new OcrRecOptions(true, true);
- OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_3.jpg",options);
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_3.jpg");
+ OcrInfo ocrInfo = recModel.recognize(image, options);
log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo));
} catch (Exception e) {
e.printStackTrace();
@@ -189,7 +217,7 @@ public class OcrRecognizeDemo {
@Test
public void recognizeAndDraw(){
try {
- OcrCommonRecModel recModel = getRecModelWithDirection();
+ OcrCommonRecModel recModel = getFastRecModel();
int fontSize = 18;
recModel.recognizeAndDraw("src/main/resources/general_ocr_002.png", "output/ocr_4_recognized.jpg", fontSize, new OcrRecOptions());
} catch (Exception e) {
@@ -200,55 +228,24 @@ public class OcrRecognizeDemo {
@Test
public void recognizeAndDraw2(){
try {
- OcrCommonRecModel recModel = getRecModel();
+ OcrCommonRecModel recModel = getFastRecModel();
int fontSize = 18;
//创建保存路径
Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
- Path imageOutputPath = Paths.get("output/ocr_4_recognized.jpg");
- BufferedImage image = null;
- image = ImageIO.read(new File(inputImagePath.toAbsolutePath().toString()));
- BufferedImage resultImage = recModel.recognizeAndDraw(image, fontSize, new OcrRecOptions());
- ImageUtils.saveImage(resultImage, imageOutputPath.toAbsolutePath().toString());
+ Path imageOutputPath = Paths.get("output/ocr_5_recognized.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(inputImagePath);
+ OcrInfo ocrInfo = recModel.recognizeAndDraw(image, fontSize, new OcrRecOptions());
+ log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo));
+ //保存绘制结果
+ if(ocrInfo != null && ocrInfo.getDrawnImage() != null){
+ ImageUtils.save(ocrInfo.getDrawnImage(), imageOutputPath.toAbsolutePath().toString());
+ }
} catch (Exception e) {
e.printStackTrace();
}
}
- /**
- * 文本识别并绘制结果(返回base64)
- */
- @Test
- public void recognizeAndDrawToBase64(){
- try {
- OcrCommonRecModel recModel = getRecModel();
- int fontSize = 18;
- //创建保存路径
- Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
- byte[] imageBytes = FileUtil.readBytes(inputImagePath);
- String base64 = recModel.recognizeAndDrawToBase64(imageBytes, fontSize, new OcrRecOptions());
- log.info("base64:{}", base64);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 文本识别并绘制结果(返回OcrInfo,OcrInfo中包含base64)
- */
- @Test
- public void recognizeAndDraw3(){
- try {
- OcrCommonRecModel recModel = getRecModel();
- int fontSize = 18;
- //创建保存路径
- Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
- byte[] imageBytes = FileUtil.readBytes(inputImagePath);
- OcrInfo ocrInfo = recModel.recognizeAndDraw(imageBytes, fontSize, new OcrRecOptions());
- log.info("ocrInfo:{}", JsonUtils.toJson(ocrInfo));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
/**
* 批量识别
@@ -259,7 +256,7 @@ public class OcrRecognizeDemo {
@Test
public void batchRecognize(){
try {
- OcrCommonRecModel recModel = getRecModelWithDirection();
+ OcrCommonRecModel recModel = getFastRecModel();
//批量检测要求图片宽高一致
String folderPath = "/Users/xxx/Downloads/testing33";
//读取文件夹中所有图片
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java
index b9319b7..0dd1947 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java
@@ -1,7 +1,9 @@
package smartai.examples.ocr.plate;
+import ai.djl.modality.cv.Image;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -38,6 +40,7 @@ public class PlateRecDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -68,6 +71,7 @@ public class PlateRecDemo {
recModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/plate/plate_rec_color.onnx");
//指定车牌检测模型
recModelConfig.setPlateDetModel(getPlateDetModel());
+ recModelConfig.setDevice(device);
return PlateModelFactory.getInstance().getRecModel(recModelConfig);
}
@@ -75,10 +79,12 @@ public class PlateRecDemo {
* 车牌识别
*/
@Test
- public void testDetect() {
+ public void testDetect() throws IOException {
PlateRecModel plateRecModel = getPlateRecModel();
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/plate/Quicker_20220930_180856.png");
//识别车号
- R> result = plateRecModel.recognize("src/main/resources/plate/Quicker_20220930_180856.png");
+ R> result = plateRecModel.recognize(image);
if(result.isSuccess()){
log.info("车牌识别结果:{}", JsonUtils.toJson(result.getData()));
}else{
@@ -109,14 +115,14 @@ public class PlateRecDemo {
public void recognizeAndDraw2() {
try {
PlateRecModel plateRecModel = getPlateRecModel();
- BufferedImage image = null;
String imagePath = "src/main/resources/plate/Quicker_20220930_180856.png";
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(imagePath);
//可以根据后续业务场景使用detectedImage
- R detectedImage = plateRecModel.recognizeAndDraw(image);
+ R detectedImage = plateRecModel.recognizeAndDraw(image);
if(detectedImage.isSuccess()){
log.info("车牌识别成功");
- ImageUtils.saveImage(detectedImage.getData(), "output/plate_recognized2.jpg");
+ ImageUtils.save(detectedImage.getData(), "output/plate_recognized3.jpg");
}else{
log.error("车牌识别失败:{}", detectedImage.getMessage());
}
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java
index c50ad68..942d1bc 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java
@@ -3,6 +3,7 @@ package smartai.examples.ocr.table;
import ai.djl.modality.cv.Image;
import cn.hutool.core.io.FileUtil;
import cn.smartjavaai.common.config.Config;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -47,6 +48,7 @@ public class TableRecDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -79,7 +81,6 @@ public class TableRecDemo {
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
-// config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -115,45 +116,6 @@ public class TableRecDemo {
- /**
- * 表格识别
- * 仅支持简单表格
- * 流程:表格结构识别 -> 文本检测 -> 文本识别 -> 合成html table
- * 注意事项:
- * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。
- * 2、模型文件需要放在单独文件夹
- */
- @Test
- public void recognize(){
- try {
- TableStructureModel tableStructureModel = getTableStructureModel();
- OcrCommonDetModel detModel = getDetectionModel();
- OcrCommonRecModel recModel = getRecModel();
- OcrDirectionModel directionModel = getDirectionModel();
- //创建表格识别器
- TableRecognizer tableRecognizer = TableRecognizer.builder()
- .withStructureModel(tableStructureModel)
- .withTextDetModel(detModel)
-// .withDirectionModel(getDirectionModel()) //如果表格中存在旋转的文字,可以使用方向分类模型
- .withTextRecModel(recModel).build();
- String imagePath = "src/main/resources/table/table_ch1.png";
- BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
- R result = tableRecognizer.recognize(image);
- if(result.isSuccess()){
- log.info("result: {}", result.getData().getHtml());
- //导出html内容到文件
- Path outputPath = Paths.get("output/table_ch2_result.html");
- FileUtil.writeUtf8String(result.getData().getHtml(), outputPath.toAbsolutePath().toString());
- //绘制表格结构
- tableRecognizer.drawTable(result.getData(), image, "output/table_ch2_result.jpg");
- //导出excel,如果导出失败,可能是因为表格结果识别的结果是错乱的
- tableRecognizer.exportExcel(result.getData().getHtml(), "output/table_ch2_result.xls");
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
/**
* 表格识别
@@ -177,7 +139,8 @@ public class TableRecDemo {
// .withDirectionModel(getDirectionModel()) //如果表格中存在旋转的文字,可以使用方向分类模型
.withTextRecModel(recModel).build();
String imagePath = "src/main/resources/table/table_ch1.png";
- BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(imagePath);
R result = tableRecognizer.recognize(image);
if(result.isSuccess()){
log.info("result: {}", result.getData().getHtml());
@@ -185,8 +148,8 @@ public class TableRecDemo {
Path outputPath = Paths.get("output/table_ch2_result.html");
FileUtil.writeUtf8String(result.getData().getHtml(), outputPath.toAbsolutePath().toString());
//绘制表格结构
- BufferedImage resultImage = tableRecognizer.drawTable(result.getData(), image);
- ImageUtils.saveImage(resultImage, "output/table_ch2_result.jpg");
+ Image resultImage = tableRecognizer.drawTable(result.getData(), image);
+ ImageUtils.save(resultImage, "output/table_ch2_result.jpg");
//导出excel,如果导出失败,可能是因为表格结果识别的结果是错乱的
try (OutputStream out = Files.newOutputStream(Paths.get("output/table_ch2_result2.xls"))) {
tableRecognizer.exportExcel(result.getData().getHtml(), out);
diff --git a/examples/speech-examples/pom.xml b/examples/speech-examples/pom.xml
index 6fb00b8..6b5d89f 100644
--- a/examples/speech-examples/pom.xml
+++ b/examples/speech-examples/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.24
+ 1.0.25
smartai.examples.speech.asr.common.OcrRecognizeDemo
diff --git a/examples/translation-example/pom.xml b/examples/translation-example/pom.xml
index 656484f..2c7e935 100644
--- a/examples/translation-example/pom.xml
+++ b/examples/translation-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.24
+ 1.0.25
smartai.examples.nlp.translation.TranslationDemo
diff --git a/examples/vision-example/pom.xml b/examples/vision-example/pom.xml
index 79f6a4d..706928a 100644
--- a/examples/vision-example/pom.xml
+++ b/examples/vision-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.24
+ 1.0.25
smartai.examples.vision.ObjectDetectionDemo
@@ -272,36 +272,6 @@
-
-
- org.bytedeco
- javacpp
- ${javacv.version}
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- ffmpeg
- 6.1.1-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- openblas
- 0.3.26-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
- org.bytedeco
- opencv
- 4.9.0-1.5.10
- ${javacv.platform.linux-arm64}
-
-
-
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/ActionRecognizeDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/ActionRecognizeDemo.java
index 7566d34..e912f1f 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/ActionRecognizeDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/ActionRecognizeDemo.java
@@ -32,6 +32,8 @@ public class ActionRecognizeDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/InstanceSegDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/InstanceSegDemo.java
index 923252d..dc2caa1 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/InstanceSegDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/InstanceSegDemo.java
@@ -41,6 +41,8 @@ public class InstanceSegDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -119,7 +121,7 @@ public class InstanceSegDemo {
if(result.isSuccess()){
log.info("实例分割结果:{}", JSONObject.toJSONString(result.getData()));
//保存图片
- ImageUtils.saveImage(result.getData().getDrawnImage(), "dog_bike_car_detected.png", "output");
+ ImageUtils.save(result.getData().getDrawnImage(), "dog_bike_car_detected2.png", "output");
}else{
log.info("实例分割失败:{}", result.getMessage());
}
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/ObbDetDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/ObbDetDemo.java
index 08a2703..18559c2 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/ObbDetDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/ObbDetDemo.java
@@ -36,6 +36,8 @@ public class ObbDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -115,7 +117,7 @@ public class ObbDetDemo {
if(result.isSuccess()){
log.info("旋转框检测结果:{}", JSONObject.toJSONString(result.getData()));
//保存图片
- ImageUtils.saveImage(result.getData().getDrawnImage(), "boats_obb_detected.png", "output");
+ ImageUtils.save(result.getData().getDrawnImage(), "output/boats_obb_detected2.png");
}else{
log.info("旋转框检测失败:{}", result.getMessage());
}
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/ObjectDetectionDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/ObjectDetectionDemo.java
index e70c61f..50fd93c 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/ObjectDetectionDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/ObjectDetectionDemo.java
@@ -5,6 +5,7 @@ import ai.djl.modality.cv.ImageFactory;
import ai.djl.util.JsonUtils;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.UUID;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -56,6 +57,8 @@ public class ObjectDetectionDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -90,7 +93,9 @@ public class ObjectDetectionDemo {
public void objectDetection(){
try {
DetectorModel detectorModel = getModel();
- DetectionResponse detectionResponse = detectorModel.detect("src/main/resources/object_detection.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/object_detection.jpg");
+ DetectionResponse detectionResponse = detectorModel.detect(image);
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
@@ -118,10 +123,14 @@ public class ObjectDetectionDemo {
try {
DetectorModel detectorModel = getModel();
String imagePath = "src/main/resources/object_detection.jpg";
- BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile(imagePath);
//可以根据后续业务场景使用detectedImage
- BufferedImage detectedImage = detectorModel.detectAndDraw(image);
- Assert.assertNotNull("detectedImage null", detectedImage);
+ DetectionResponse detectionResponse = detectorModel.detectAndDraw(image);
+ log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
+ if(detectionResponse != null && detectionResponse.getDrawnImage() != null){
+ ImageUtils.save(detectionResponse.getDrawnImage(), "output/object_detection_detected2.png");
+ }
} catch (Exception e) {
e.printStackTrace();
}
@@ -181,9 +190,9 @@ public class ObjectDetectionDemo {
config.setTopK(100);
config.setDevice(device);
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
- DetectionResponse detectionResponse = detectorModel.detect("src/main/resources/dog_bike_car.jpg");
- //检测并保存绘制结果
- detectorModel.detectAndDraw("src/main/resources/dog_bike_car.jpg", "output/dog_bike_car_detect.jpg");
+ //创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
+ Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/dog_bike_car.jpg");
+ DetectionResponse detectionResponse = detectorModel.detect(image);
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
@@ -218,11 +227,11 @@ public class ObjectDetectionDemo {
log.info("时间:" + LocalDateTimeUtil.now().toString());
log.info("检测结果:{}", JsonUtils.toJson(detectionInfoList));
//绘制检测结果
- OpenCVUtils.drawRectAndText(image, detectionInfoList);
+ ImageUtils.drawRectAndText(image, detectionInfoList);
//保存图片
- ImageUtils.saveImage(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
+ ImageUtils.save(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
if (image != null){
- ((Mat)image.getWrappedImage()).release();
+ ImageUtils.releaseOpenCVMat(image);
}
}
@@ -268,9 +277,12 @@ public class ObjectDetectionDemo {
log.info("时间:" + LocalDateTimeUtil.now().toString());
log.info("检测结果:{}", JsonUtils.toJson(detectionInfoList));
//绘制检测结果
- OpenCVUtils.drawRectAndText(image, detectionInfoList);
+ ImageUtils.drawRectAndText(image, detectionInfoList);
//保存图片
- ImageUtils.saveImage(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
+ ImageUtils.save(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
+ if (image != null){
+ ImageUtils.releaseOpenCVMat(image);
+ }
}
@Override
@@ -316,9 +328,12 @@ public class ObjectDetectionDemo {
log.info("时间:" + LocalDateTimeUtil.now().toString());
log.info("检测结果:{}", JsonUtils.toJson(detectionInfoList));
//绘制检测结果
- OpenCVUtils.drawRectAndText(image, detectionInfoList);
+ ImageUtils.drawRectAndText(image, detectionInfoList);
//保存图片
- ImageUtils.saveImage(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
+ ImageUtils.save(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
+ if (image != null){
+ ImageUtils.releaseOpenCVMat(image);
+ }
}
@Override
@@ -385,7 +400,7 @@ public class ObjectDetectionDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
- ImageFactory factory = ImageFactory.getInstance();
+ SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -394,9 +409,8 @@ public class ObjectDetectionDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
- Image img = factory.fromImage(resizeImage);
- BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
- DetectionResponse detectedResult = detectorModel.detect(bufferedImage);
+ Image img = factory.fromMat(resizeImage);
+ DetectionResponse detectedResult = detectorModel.detect(img);
if (Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getDetectionInfoList()) || detectedResult.getDetectionInfoList().size() == 0){
log.debug("未检测到物体");
continue;
@@ -404,11 +418,10 @@ public class ObjectDetectionDemo {
for(DetectionInfo detectionInfo : detectedResult.getDetectionInfoList()){
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
String text = detectionInfo.getObjectDetInfo().getClassName();
- ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, Color.RED);
+ ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
- frame.showImage(bufferedImage);
+ frame.showImage(ImageUtils.toBufferedImage(img));
}
-
capture.release();
System.exit(0);
} catch (Exception e) {
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/PersonDetectDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/PersonDetectDemo.java
index 26eb4bf..b1125f3 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/PersonDetectDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/PersonDetectDemo.java
@@ -32,6 +32,8 @@ public class PersonDetectDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -108,7 +110,7 @@ public class PersonDetectDemo {
if(result.isSuccess()){
log.info("行人检测结果:{}", JSONObject.toJSONString(result.getData()));
//保存图片
- ImageUtils.saveImage(result.getData().getDrawnImage(), "person_result.png", "output");
+ ImageUtils.save(result.getData().getDrawnImage(), "person_result.png", "output");
}else{
log.info("行人检测失败:{}", result.getMessage());
}
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/PoseDetDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/PoseDetDemo.java
index 1101868..e49fd33 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/PoseDetDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/PoseDetDemo.java
@@ -33,6 +33,8 @@ public class PoseDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -105,7 +107,7 @@ public class PoseDetDemo {
//可以根据后续业务场景使用detectedImage
Image drawImage = detectorModel.detectAndDraw(image);
//保存图片
- ImageUtils.saveImage(drawImage, "pose_detected.png", "output");
+ ImageUtils.save(drawImage, "pose_detected2.png", "output");
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/SemSegDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/SemSegDemo.java
index a786085..56cd50c 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/SemSegDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/SemSegDemo.java
@@ -36,6 +36,8 @@ public class SemSegDemo {
@BeforeClass
public static void beforeAll() throws IOException {
+ //将图片处理的底层引擎切换为 OpenCV
+ SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -109,7 +111,7 @@ public class SemSegDemo {
//可以根据后续业务场景使用detectedImage
Image dretectedImage = detectorModel.detectAndDraw(image);
//保存
- ImageUtils.saveImage(dretectedImage, "dog_bike_car_detected.png", "output");
+ ImageUtils.save(dretectedImage, "dog_bike_car_detected2.png", "output");
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/face/pom.xml b/face/pom.xml
index fac0167..41e0da9 100644
--- a/face/pom.xml
+++ b/face/pom.xml
@@ -6,11 +6,11 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.24
+ 1.0.25
face
- 1.0.24
+ 1.0.25
face
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/face/src/main/java/cn/smartjavaai/face/enums/FaceRecModelEnum.java b/face/src/main/java/cn/smartjavaai/face/enums/FaceRecModelEnum.java
index 562d1fc..49155d8 100644
--- a/face/src/main/java/cn/smartjavaai/face/enums/FaceRecModelEnum.java
+++ b/face/src/main/java/cn/smartjavaai/face/enums/FaceRecModelEnum.java
@@ -6,21 +6,61 @@ package cn.smartjavaai.face.enums;
*/
public enum FaceRecModelEnum {
- FACENET_MODEL("FaceNetModel"),
- SEETA_FACE6_MODEL("SeetaFace6Model"),
- SEETA_FACE6_LIGHT_MODEL("SeetaFace6Model"),
- INSIGHT_FACE_IRSE50_MODEL("InsightFaceIRSE50Model"),
- INSIGHT_FACE_MOBILE_FACENET_MODEL("InsightFaceMobilefacenetModel"),
- ELASTIC_FACE_MODEL("ElasticFaceModel");
+ FACENET_MODEL("PyTorch", 112, 112, 0.7f),
+ SEETA_FACE6_MODEL("c++", 0, 0, 0.62f),
+ SEETA_FACE6_LIGHT_MODEL("c++", 0, 0, 0.62f),
+ INSIGHT_FACE_IRSE50_MODEL("PyTorch", 112, 112, 0.62f),
+ INSIGHT_FACE_MOBILE_FACENET_MODEL("PyTorch", 112, 112, 0.64f),
+ ELASTIC_FACE_MODEL("PyTorch", 112, 112, 0.61f),
+ SPHERE_FACE_20A_ONNX("OnnxRuntime", 96, 112, 0.7f),
+ SPHERE_FACE_20A_PT("PyTorch", 96, 112, 0.7f),
+ DREAM_IJBA_RES18_NAIVE("OnnxRuntime", 224, 224, 0.74f),
+ EVOLVE_FACE_IR50("PyTorch", 112, 112, 0.62f),
+ EVOLVE_FACE_IR50_ASIA("PyTorch", 112, 112, 0.62f),
+ EVOLVE_FACE_IR152("PyTorch", 112, 112, 0.62f),
+ VGG_FACE("PyTorch", 224, 224, 0.75f);
- private final String modelClassName;
+ /**
+ * 模型输入尺寸:宽
+ */
+ private final int inputWidth;
- FaceRecModelEnum(String modelClassName) {
- this.modelClassName = modelClassName;
+ /**
+ * 模型输入尺寸:高
+ */
+ private final int inputHeight;
+
+ /**
+ * 模型引擎
+ */
+ private final String engine;
+
+ /**
+ * 相似度阈值
+ */
+ private final float threshold;
+
+ FaceRecModelEnum(String engine, int inputWidth, int inputHeight, float threshold) {
+ this.inputWidth = inputWidth;
+ this.inputHeight = inputHeight;
+ this.engine = engine;
+ this.threshold = threshold;
}
- public String getModelClassName() {
- return modelClassName;
+ public int getInputWidth() {
+ return inputWidth;
+ }
+
+ public int getInputHeight() {
+ return inputHeight;
+ }
+
+ public String getEngine() {
+ return engine;
+ }
+
+ public float getThreshold() {
+ return threshold;
}
/**
diff --git a/face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java b/face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java
index d5327dd..ac48b57 100644
--- a/face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java
+++ b/face/src/main/java/cn/smartjavaai/face/factory/ExpressionModelFactory.java
@@ -90,6 +90,7 @@ public class ExpressionModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
+ model.setFromFactory(true);
return model;
}
@@ -101,4 +102,26 @@ public class ExpressionModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
+ /**
+ * 关闭所有已加载的模型
+ */
+ public void closeAll() {
+ modelMap.values().forEach(model -> {
+ try {
+ model.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ modelMap.clear();
+ }
+
+ /**
+ * 移除缓存的模型
+ * @param modelEnum
+ */
+ public static void removeFromCache(ExpressionModelEnum modelEnum) {
+ modelMap.remove(modelEnum);
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/factory/FaceAttributeModelFactory.java b/face/src/main/java/cn/smartjavaai/face/factory/FaceAttributeModelFactory.java
index 695bd27..dae9506 100644
--- a/face/src/main/java/cn/smartjavaai/face/factory/FaceAttributeModelFactory.java
+++ b/face/src/main/java/cn/smartjavaai/face/factory/FaceAttributeModelFactory.java
@@ -2,6 +2,8 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.FaceAttributeConfig;
+import cn.smartjavaai.face.enums.ExpressionModelEnum;
+import cn.smartjavaai.face.enums.FaceAttributeModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.attribute.Seetaface6FaceAttributeModel;
import lombok.extern.slf4j.Slf4j;
@@ -21,12 +23,12 @@ public class FaceAttributeModelFactory {
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
private static volatile FaceAttributeModelFactory instance;
- private static final ConcurrentHashMap modelMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
- private static final Map> registry =
+ private static final Map> registry =
new ConcurrentHashMap<>();
@@ -48,8 +50,8 @@ public class FaceAttributeModelFactory {
* @param name
* @param clazz
*/
- private static void registerModel(String name, Class extends FaceAttributeModel> clazz) {
- registry.put(name.toLowerCase(), clazz);
+ private static void registerModel(FaceAttributeModelEnum faceAttributeModelEnum, Class extends FaceAttributeModel> clazz) {
+ registry.put(faceAttributeModelEnum, clazz);
}
@@ -62,7 +64,7 @@ public class FaceAttributeModelFactory {
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
throw new FaceException("未配置活体检测模型");
}
- return modelMap.computeIfAbsent(config.getModelEnum().name(), k -> {
+ return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceModel(config);
});
}
@@ -73,9 +75,9 @@ public class FaceAttributeModelFactory {
* @return
*/
private FaceAttributeModel createFaceModel(FaceAttributeConfig config) {
- Class> clazz = registry.get(config.getModelEnum().getModelClassName().toLowerCase());
+ Class> clazz = registry.get(config.getModelEnum());
if(clazz == null){
- throw new FaceException("Unsupported algorithm");
+ throw new FaceException("Unsupported model");
}
FaceAttributeModel model = null;
try {
@@ -84,14 +86,37 @@ public class FaceAttributeModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
+ model.setFromFactory(true);
return model;
}
// 初始化默认算法
static {
- registerModel("seetaface6model", Seetaface6FaceAttributeModel.class);
+ registerModel(FaceAttributeModelEnum.SEETA_FACE6_MODEL, Seetaface6FaceAttributeModel.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
+ /**
+ * 关闭所有已加载的模型
+ */
+ public void closeAll() {
+ modelMap.values().forEach(model -> {
+ try {
+ model.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ modelMap.clear();
+ }
+
+ /**
+ * 移除缓存的模型
+ * @param modelEnum
+ */
+ public static void removeFromCache(FaceAttributeModelEnum modelEnum) {
+ modelMap.remove(modelEnum);
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/factory/FaceDetModelFactory.java b/face/src/main/java/cn/smartjavaai/face/factory/FaceDetModelFactory.java
index 1e0f0a2..2150b53 100644
--- a/face/src/main/java/cn/smartjavaai/face/factory/FaceDetModelFactory.java
+++ b/face/src/main/java/cn/smartjavaai/face/factory/FaceDetModelFactory.java
@@ -3,6 +3,7 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
+import cn.smartjavaai.face.enums.FaceAttributeModelEnum;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.facedect.CommonFaceDetModel;
@@ -93,16 +94,17 @@ public class FaceDetModelFactory {
private FaceDetModel createFaceDetModel(FaceDetConfig config) {
Class> clazz = registry.get(config.getModelEnum());
if(clazz == null){
- throw new FaceException("Unsupported algorithm");
+ throw new FaceException("Unsupported model");
}
- FaceDetModel algorithm = null;
+ FaceDetModel model = null;
try {
- algorithm = (FaceDetModel) clazz.newInstance();
+ model = (FaceDetModel) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new FaceException(e);
}
- algorithm.loadModel(config);
- return algorithm;
+ model.loadModel(config);
+ model.setFromFactory(true);
+ return model;
}
@@ -133,4 +135,26 @@ public class FaceDetModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
+ /**
+ * 关闭所有已加载的模型
+ */
+ public void closeAll() {
+ modelMap.values().forEach(model -> {
+ try {
+ model.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ modelMap.clear();
+ }
+
+ /**
+ * 移除缓存的模型
+ * @param modelEnum
+ */
+ public static void removeFromCache(FaceDetModelEnum modelEnum) {
+ modelMap.remove(modelEnum);
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/factory/FaceQualityModelFactory.java b/face/src/main/java/cn/smartjavaai/face/factory/FaceQualityModelFactory.java
index ac0d902..5b030b2 100644
--- a/face/src/main/java/cn/smartjavaai/face/factory/FaceQualityModelFactory.java
+++ b/face/src/main/java/cn/smartjavaai/face/factory/FaceQualityModelFactory.java
@@ -2,6 +2,8 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.QualityConfig;
+import cn.smartjavaai.face.enums.FaceDetModelEnum;
+import cn.smartjavaai.face.enums.QualityModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.quality.FaceQualityModel;
import cn.smartjavaai.face.model.quality.Seetaface6QualityModel;
@@ -21,12 +23,12 @@ public class FaceQualityModelFactory {
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
private static volatile FaceQualityModelFactory instance;
- private static final ConcurrentHashMap modelMap = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
- private static final Map> registry =
+ private static final Map> registry =
new ConcurrentHashMap<>();
@@ -45,11 +47,11 @@ public class FaceQualityModelFactory {
/**
* 注册模型
- * @param name
+ * @param qualityModelEnum
* @param clazz
*/
- private static void registerModel(String name, Class extends FaceQualityModel> clazz) {
- registry.put(name.toLowerCase(), clazz);
+ private static void registerModel(QualityModelEnum qualityModelEnum, Class extends FaceQualityModel> clazz) {
+ registry.put(qualityModelEnum, clazz);
}
@@ -62,7 +64,7 @@ public class FaceQualityModelFactory {
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
throw new FaceException("未配置质量评估模型");
}
- return modelMap.computeIfAbsent(config.getModelEnum().name(), k -> {
+ return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceModel(config);
});
}
@@ -73,7 +75,7 @@ public class FaceQualityModelFactory {
* @return
*/
private FaceQualityModel createFaceModel(QualityConfig config) {
- Class> clazz = registry.get(config.getModelEnum().getModelClassName().toLowerCase());
+ Class> clazz = registry.get(config.getModelEnum());
if(clazz == null){
throw new FaceException("Unsupported algorithm");
}
@@ -84,14 +86,37 @@ public class FaceQualityModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
+ model.setFromFactory(true);
return model;
}
// 初始化默认算法
static {
- registerModel("seetaface6model", Seetaface6QualityModel.class);
+ registerModel(QualityModelEnum.SEETA_FACE6_MODEL, Seetaface6QualityModel.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
+ /**
+ * 关闭所有已加载的模型
+ */
+ public void closeAll() {
+ modelMap.values().forEach(model -> {
+ try {
+ model.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ modelMap.clear();
+ }
+
+ /**
+ * 移除缓存的模型
+ * @param modelEnum
+ */
+ public static void removeFromCache(QualityModelEnum modelEnum) {
+ modelMap.remove(modelEnum);
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/factory/FaceRecModelFactory.java b/face/src/main/java/cn/smartjavaai/face/factory/FaceRecModelFactory.java
index 25cf08c..99c815d 100644
--- a/face/src/main/java/cn/smartjavaai/face/factory/FaceRecModelFactory.java
+++ b/face/src/main/java/cn/smartjavaai/face/factory/FaceRecModelFactory.java
@@ -4,6 +4,7 @@ import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.FaceRecConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
+import cn.smartjavaai.face.enums.QualityModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.facerec.*;
import lombok.extern.slf4j.Slf4j;
@@ -79,14 +80,15 @@ public class FaceRecModelFactory {
if(clazz == null){
throw new FaceException("Unsupported model");
}
- FaceRecModel algorithm = null;
+ FaceRecModel model = null;
try {
- algorithm = (FaceRecModel) clazz.newInstance();
+ model = (FaceRecModel) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new FaceException(e);
}
- algorithm.loadModel(config);
- return algorithm;
+ model.loadModel(config);
+ model.setFromFactory(true);
+ return model;
}
@@ -98,7 +100,36 @@ public class FaceRecModelFactory {
registerAlgorithm(FaceRecModelEnum.ELASTIC_FACE_MODEL, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.SEETA_FACE6_MODEL, SeetaFace6FaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.SEETA_FACE6_LIGHT_MODEL, SeetaFace6FaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.DREAM_IJBA_RES18_NAIVE, CommonFaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.VGG_FACE, CommonFaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.SPHERE_FACE_20A_ONNX, CommonFaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.SPHERE_FACE_20A_PT, CommonFaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.EVOLVE_FACE_IR50, CommonFaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.EVOLVE_FACE_IR50_ASIA, CommonFaceRecModel.class);
+ registerAlgorithm(FaceRecModelEnum.EVOLVE_FACE_IR152, CommonFaceRecModel.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
+ /**
+ * 关闭所有已加载的模型
+ */
+ public void closeAll() {
+ modelMap.values().forEach(model -> {
+ try {
+ model.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ modelMap.clear();
+ }
+
+ /**
+ * 移除缓存的模型
+ * @param modelEnum
+ */
+ public static void removeFromCache(FaceRecModelEnum modelEnum) {
+ modelMap.remove(modelEnum);
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/factory/LivenessModelFactory.java b/face/src/main/java/cn/smartjavaai/face/factory/LivenessModelFactory.java
index 6d2525a..84bcabc 100644
--- a/face/src/main/java/cn/smartjavaai/face/factory/LivenessModelFactory.java
+++ b/face/src/main/java/cn/smartjavaai/face/factory/LivenessModelFactory.java
@@ -2,6 +2,7 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.LivenessConfig;
+import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.enums.LivenessModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.liveness.CommonLivenessModel;
@@ -87,6 +88,7 @@ public class LivenessModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
+ model.setFromFactory(true);
return model;
}
@@ -99,4 +101,26 @@ public class LivenessModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
+ /**
+ * 关闭所有已加载的模型
+ */
+ public void closeAll() {
+ modelMap.values().forEach(model -> {
+ try {
+ model.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ modelMap.clear();
+ }
+
+ /**
+ * 移除缓存的模型
+ * @param modelEnum
+ */
+ public static void removeFromCache(LivenessModelEnum modelEnum) {
+ modelMap.remove(modelEnum);
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/attribute/FaceAttributeModel.java b/face/src/main/java/cn/smartjavaai/face/model/attribute/FaceAttributeModel.java
index df00753..4e8f341 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/attribute/FaceAttributeModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/attribute/FaceAttributeModel.java
@@ -1,5 +1,6 @@
package cn.smartjavaai.face.model.attribute;
+import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.face.FaceAttribute;
@@ -22,12 +23,12 @@ public interface FaceAttributeModel extends AutoCloseable{
void loadModel(FaceAttributeConfig config); // 加载模型
-
/**
* 人脸属性识别(多人脸)
* @param imagePath 图片路径
* @return
*/
+ @Deprecated
default DetectionResponse detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -37,6 +38,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
+ @Deprecated
default DetectionResponse detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -46,6 +48,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imageData 图片字节流
* @return
*/
+ @Deprecated
default DetectionResponse detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -56,6 +59,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default List detect(String imagePath, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,6 +70,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default FaceAttribute detect(String imagePath, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -76,6 +81,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default List detect(byte[] imageData,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -86,6 +92,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default FaceAttribute detect(byte[] imageData, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -97,6 +104,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default List detect(BufferedImage image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -107,6 +115,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default FaceAttribute detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -117,6 +126,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param image
* @return
*/
+ @Deprecated
default FaceAttribute detectTopFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -127,6 +137,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imagePath
* @return
*/
+ @Deprecated
default FaceAttribute detectTopFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -136,6 +147,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imageData
* @return
*/
+ @Deprecated
default FaceAttribute detectTopFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -145,6 +157,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param image
* @return
*/
+ @Deprecated
default FaceAttribute detectCropedFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -154,6 +167,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imagePath
* @return
*/
+ @Deprecated
default FaceAttribute detectCropedFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -163,17 +177,69 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imageData
* @return
*/
+ @Deprecated
default FaceAttribute detectCropedFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
+ /**
+ * 人脸属性识别(多人脸)
+ * @param image
+ * @return
+ */
+ default DetectionResponse detect(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 人脸属性识别(多人脸)
+ * @param image
+ * @param faceDetectionResponse 人脸检测结果
+ * @return
+ */
+ default List detect(Image image, DetectionResponse faceDetectionResponse){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 人脸属性识别(单人脸)
+ * @param image
+ * @param faceDetectionRectangle 人脸检测结果-人脸框
+ * @return
+ */
+ default FaceAttribute detect(Image image, DetectionRectangle faceDetectionRectangle, List keyPoints){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 人脸属性识别(分数最高人脸)
+ * @param image
+ * @return
+ */
+ default FaceAttribute detectTopFace(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
+ /**
+ * 人脸属性识别(裁剪后的人脸)
+ * @param image
+ * @return
+ */
+ default FaceAttribute detectCropedFace(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
+ default void setFromFactory(boolean fromFactory){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
-
-
-
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java b/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java
index c2a92c3..29f6bfa 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java
@@ -1,12 +1,15 @@
package cn.smartjavaai.face.model.attribute;
import ai.djl.engine.Engine;
+import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.entity.*;
+import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.face.FaceAttribute;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.entity.face.HeadPose;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.EyeStatus;
+import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.PoolUtils;
@@ -14,8 +17,10 @@ import cn.smartjavaai.face.config.FaceAttributeConfig;
import cn.smartjavaai.common.enums.face.GenderType;
import cn.smartjavaai.face.context.PredictorContext;
import cn.smartjavaai.face.exception.FaceException;
+import cn.smartjavaai.face.factory.FaceAttributeModelFactory;
import cn.smartjavaai.face.seetaface.NativeLoader;
import cn.smartjavaai.face.utils.FaceUtils;
+import cn.smartjavaai.face.utils.Seetaface6Utils;
import com.seeta.pool.*;
import com.seeta.sdk.*;
import lombok.extern.slf4j.Slf4j;
@@ -150,7 +155,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public DetectionResponse detect(BufferedImage image) {
- if(!ImageUtils.isImageValid(image)){
+ if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
//创建推力器上下文
@@ -168,7 +173,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
- imageData.data = ImageUtils.getMatrixBGR(image);
+ imageData.data = BufferedImageUtils.getMatrixBGR(image);
//检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){
@@ -182,7 +187,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
faceAttributeList.add(faceAttribute);
}
- return FaceUtils.convertToFaceAttributeResponse(seetaResult, seetaPointFSList, faceAttributeList);
+ return Seetaface6Utils.convertToFaceAttributeResponse(seetaResult, seetaPointFSList, faceAttributeList);
} catch (Exception e) {
throw new FaceException("人脸属性检测错误", e);
} finally {
@@ -212,15 +217,15 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
if (config.isEnableGender()){
GenderPredictor.GENDER[] gender = new GenderPredictor.GENDER[1];
boolean isSuccess = predictorContext.genderPredictor.PredictGenderWithCrop(imageData, landmarks, gender);
- genderType = isSuccess ? FaceUtils.convertToGenderType(gender[0]) : GenderType.UNKNOWN;
+ genderType = isSuccess ? Seetaface6Utils.convertToGenderType(gender[0]) : GenderType.UNKNOWN;
}
//眼睛状态检测
EyeStatus leftEyeStatus = null;
EyeStatus rightEyeStatus = null;
if (config.isEnableEyeStatus()){
EyeStateDetector.EYE_STATE[] eyeState = predictorContext.eyeStateDetector.detect(imageData, landmarks);
- leftEyeStatus = FaceUtils.convertToEyeStatus(eyeState[0]);
- rightEyeStatus = FaceUtils.convertToEyeStatus(eyeState[1]);
+ leftEyeStatus = Seetaface6Utils.convertToEyeStatus(eyeState[0]);
+ rightEyeStatus = Seetaface6Utils.convertToEyeStatus(eyeState[1]);
}
//年龄检测
Integer age = 0;
@@ -278,7 +283,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public List detect(BufferedImage image, DetectionResponse faceDetectionResponse) {
- if(!ImageUtils.isImageValid(image)){
+ if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
@@ -297,8 +302,8 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
- imageData.data = ImageUtils.getMatrixBGR(image);
- SeetaRect seetaRect = FaceUtils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
+ imageData.data = BufferedImageUtils.getMatrixBGR(image);
+ SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
SeetaPointF[] landmarks = null;
FaceInfo faceInfo = detectionInfo.getFaceInfo();
//如果没有人脸标识,则提取人脸标识
@@ -307,7 +312,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
landmarks = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, landmarks);
}else{
- landmarks = FaceUtils.convertToSeetaPointF(faceInfo.getKeyPoints());
+ landmarks = Seetaface6Utils.convertToSeetaPointF(faceInfo.getKeyPoints());
}
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
@@ -365,7 +370,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public FaceAttribute detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List keyPoints) {
- if(!ImageUtils.isImageValid(image)){
+ if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
if(Objects.isNull(faceDetectionRectangle)){
@@ -380,13 +385,13 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
- imageData.data = ImageUtils.getMatrixBGR(image);
- SeetaRect seetaRect = FaceUtils.convertToSeetaRect(faceDetectionRectangle);
+ imageData.data = BufferedImageUtils.getMatrixBGR(image);
+ SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(faceDetectionRectangle);
SeetaPointF[] landmarks = null;
if(keyPoints == null || keyPoints.isEmpty()){
throw new FaceException("人脸关键点keyPoints为空");
}
- landmarks = FaceUtils.convertToSeetaPointF(keyPoints);
+ landmarks = Seetaface6Utils.convertToSeetaPointF(keyPoints);
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
return faceAttribute;
@@ -431,10 +436,196 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public FaceAttribute detectTopFace(BufferedImage image) {
- if(!ImageUtils.isImageValid(image)){
+ if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
+ FaceLandmarker faceLandmarker = null;
+ FaceDetector detectPredictor = null;
+ //创建推力器上下文
+ PredictorContext predictorContext = new PredictorContext();
+ try {
+ detectPredictor = faceDetectorPool.borrowObject();
+ faceLandmarker = faceLandmarkerPool.borrowObject();
+ predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
+ predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
+ predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
+ predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
+ predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = BufferedImageUtils.getMatrixBGR(image);
+ //检测人脸
+ SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
+ if(Objects.isNull(seetaResult)){
+ throw new FaceException("无人脸数据");
+ }
+ SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
+ faceLandmarker.mark(imageData, seetaResult[0], landmarks);
+ //人脸属性检测
+ FaceAttribute faceAttribute = detect(imageData, seetaResult[0], landmarks, predictorContext);
+ return faceAttribute;
+ } catch (Exception e) {
+ throw new FaceException("活体检测错误", e);
+ } finally {
+ if (detectPredictor != null) {
+ try {
+ faceDetectorPool.returnObject(detectPredictor);
+ } catch (Exception e) {
+ log.warn("归还Predictor失败", e);
+ }
+ }
+
+ if (faceLandmarker != null) {
+ try {
+ faceLandmarkerPool.returnObject(faceLandmarker);
+ } catch (Exception e) {
+ log.warn("归还Predictor失败", e);
+ }
+ }
+ PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
+ PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
+ PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
+ PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
+ PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
+ }
+ }
+
+ @Override
+ public DetectionResponse detect(Image image) {
+ //创建推力器上下文
+ PredictorContext predictorContext = new PredictorContext();
+ FaceLandmarker faceLandmarker = null;
+ FaceDetector detectPredictor = null;
+ List seetaPointFSList = new ArrayList();
+ List faceAttributeList = new ArrayList();
+ try {
+ detectPredictor = faceDetectorPool.borrowObject();
+ faceLandmarker = faceLandmarkerPool.borrowObject();
+ predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
+ predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
+ predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
+ predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
+ predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ //检测人脸
+ SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
+ if(Objects.isNull(seetaResult)){
+ throw new FaceException("无人脸数据");
+ }
+ for(SeetaRect seetaRect : seetaResult){
+ SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
+ faceLandmarker.mark(imageData, seetaRect, landmarks);
+ seetaPointFSList.add(landmarks);
+ //人脸属性检测
+ FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
+ faceAttributeList.add(faceAttribute);
+ }
+ return Seetaface6Utils.convertToFaceAttributeResponse(seetaResult, seetaPointFSList, faceAttributeList);
+ } catch (Exception e) {
+ throw new FaceException("人脸属性检测错误", e);
+ } finally {
+ // 统一归还所有 Predictor 到池
+ PoolUtils.returnToPool(faceDetectorPool, detectPredictor);
+ PoolUtils.returnToPool(faceLandmarkerPool, faceLandmarker);
+ PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
+ PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
+ PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
+ PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
+ PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
+ }
+ }
+
+ @Override
+ public List detect(Image image, DetectionResponse faceDetectionResponse) {
+ if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
+ throw new FaceException("无人脸数据");
+ }
+ //创建推力器上下文
+ PredictorContext predictorContext = new PredictorContext();
+ FaceLandmarker faceLandmarker = null;
+ List faceAttributeList = new ArrayList();
+ try {
+ faceLandmarker = faceLandmarkerPool.borrowObject();
+ predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
+ predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
+ predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
+ predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
+ predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
+ for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
+ SeetaPointF[] landmarks = null;
+ FaceInfo faceInfo = detectionInfo.getFaceInfo();
+ //如果没有人脸标识,则提取人脸标识
+ if(faceInfo == null || faceInfo.getKeyPoints() == null || faceInfo.getKeyPoints().isEmpty()){
+ //提取人脸的5点人脸标识
+ landmarks = new SeetaPointF[faceLandmarker.number()];
+ faceLandmarker.mark(imageData, seetaRect, landmarks);
+ }else{
+ landmarks = Seetaface6Utils.convertToSeetaPointF(faceInfo.getKeyPoints());
+ }
+ //人脸属性检测
+ FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
+ faceAttributeList.add(faceAttribute);
+ }
+ } catch (Exception e) {
+ throw new FaceException("活体检测错误", e);
+ } finally {
+ if (faceLandmarker != null) {
+ try {
+ faceLandmarkerPool.returnObject(faceLandmarker);
+ } catch (Exception e) {
+ log.warn("归还Predictor失败", e);
+ }
+ }
+ PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
+ PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
+ PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
+ PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
+ PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
+ }
+ return faceAttributeList;
+ }
+
+ @Override
+ public FaceAttribute detect(Image image, DetectionRectangle faceDetectionRectangle, List keyPoints) {
+ if(Objects.isNull(faceDetectionRectangle)){
+ throw new FaceException("无人脸数据");
+ }
+ //创建推力器上下文
+ PredictorContext predictorContext = new PredictorContext();
+ try {
+ predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
+ predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
+ predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
+ predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
+ predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(faceDetectionRectangle);
+ SeetaPointF[] landmarks = null;
+ if(keyPoints == null || keyPoints.isEmpty()){
+ throw new FaceException("人脸关键点keyPoints为空");
+ }
+ landmarks = Seetaface6Utils.convertToSeetaPointF(keyPoints);
+ //人脸属性检测
+ FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
+ return faceAttribute;
+ } catch (Exception e) {
+ throw new FaceException("活体检测错误", e);
+ } finally {
+ PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
+ PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
+ PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
+ PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
+ PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
+ }
+ }
+
+ @Override
+ public FaceAttribute detectTopFace(Image image) {
FaceLandmarker faceLandmarker = null;
FaceDetector detectPredictor = null;
//创建推力器上下文
@@ -485,7 +676,6 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
}
}
-
public FaceDetectorPool getFaceDetectorPool() {
return faceDetectorPool;
}
@@ -514,8 +704,20 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
return poseEstimatorPool;
}
+ private boolean fromFactory = false;
+
+ public void setFromFactory(boolean fromFactory) {
+ this.fromFactory = fromFactory;
+ }
+ public boolean isFromFactory() {
+ return fromFactory;
+ }
+
@Override
public void close() throws Exception {
+ if (fromFactory) {
+ FaceAttributeModelFactory.removeFromCache(config.getModelEnum());
+ }
if(Objects.nonNull(faceDetectorPool)){
faceDetectorPool.close();
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/expression/CommonEmotionModel.java b/face/src/main/java/cn/smartjavaai/face/model/expression/CommonEmotionModel.java
index 06ef818..bcc242f 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/expression/CommonEmotionModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/expression/CommonEmotionModel.java
@@ -1,47 +1,33 @@
package cn.smartjavaai.face.model.expression;
-import ai.djl.Device;
import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.Classifications;
import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.ImageFactory;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
-import ai.djl.training.util.ProgressBar;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.ExpressionResult;
import cn.smartjavaai.common.entity.face.FaceInfo;
-import cn.smartjavaai.common.entity.face.LivenessResult;
-import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.FacialExpression;
import cn.smartjavaai.common.pool.PredictorFactory;
-import cn.smartjavaai.common.utils.Base64ImageUtils;
-import cn.smartjavaai.common.utils.FileUtils;
-import cn.smartjavaai.common.utils.ImageUtils;
-import cn.smartjavaai.common.utils.OpenCVUtils;
+import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.FaceExpressionConfig;
import cn.smartjavaai.face.exception.FaceException;
+import cn.smartjavaai.face.factory.ExpressionModelFactory;
import cn.smartjavaai.face.model.expression.criterial.EmotionCriteriaFactory;
-import cn.smartjavaai.face.model.expression.translator.DenseNetEmotionTranslator;
-import cn.smartjavaai.face.preprocess.DJLImagePreprocessor;
+import cn.smartjavaai.face.preprocess.DJLImageFacePreprocessor;
import cn.smartjavaai.face.utils.FaceUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
-import org.opencv.core.Mat;
-import org.opencv.face.Face;
-import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.File;
import java.io.IOException;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -68,6 +54,9 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(config.getModelPath())){
throw new FaceException("modelPath为空");
}
+ if(Objects.isNull(config.getDetectModel())){
+ throw new FaceException("未指定人脸检测模型");
+ }
this.config = config;
@@ -92,7 +81,7 @@ public class CommonEmotionModel implements ExpressionModel{
Predictor predictor = null;
try (NDManager manager = model.getNDManager().newSubManager()){
predictor = predictorPool.borrowObject();
- DJLImagePreprocessor imagePreprocessor = new DJLImagePreprocessor(image, manager);
+ DJLImageFacePreprocessor imagePreprocessor = new DJLImageFacePreprocessor(image, manager);
Image faceImg = image;
if(config.isAlign()){
//仿射变换
@@ -131,40 +120,24 @@ public class CommonEmotionModel implements ExpressionModel{
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
- // 将图片路径转换为 BufferedImage
- BufferedImage image = null;
+ Image image = null;
try {
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ image = SmartImageFactory.getInstance().fromFile(imagePath);
+ R detectionResponseR = detect(image);
+ return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
}
- return detect(image);
}
@Override
public R detect(BufferedImage image) {
- if(Objects.isNull(config.getDetectModel())){
- return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
- }
- R faceDetectionResponse = config.getDetectModel().detect(image);
- if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
- return R.fail(R.Status.NO_FACE_DETECTED);
- }
- Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
- for(DetectionInfo detectionInfo : faceDetectionResponse.getData().getDetectionInfoList()){
- FaceInfo faceInfo = detectionInfo.getFaceInfo();
- if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
- return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
- }
- Classifications classifications = detectCore(djlImage, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
- Classifications.Classification bestClass = classifications.best();
- FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
- ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
- result.setClassifications(classifications);
- faceInfo.setExpressionResult(result);
- }
- ((Mat)djlImage.getWrappedImage()).release();
- return faceDetectionResponse;
+ Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
+ R detectionResponseR = detect(imageDjl);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
}
@Override
@@ -172,11 +145,15 @@ public class CommonEmotionModel implements ExpressionModel{
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
+ Image imageDjl = null;
try {
- return detect(ImageIO.read(new ByteArrayInputStream(imageData)));
+ imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
- throw new FaceException("错误的图像", e);
+ throw new RuntimeException(e);
}
+ R detectionResponseR = detect(imageDjl);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
}
@Override
@@ -184,8 +161,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
- byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
- return detect(imageData);
+ Image image = null;
+ try {
+ image = SmartImageFactory.getInstance().fromBase64(base64Image);
+ R detectionResponseR = detect(image);
+ return detectionResponseR;
+ } catch (IOException e) {
+ throw new FaceException("无效图片", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
+ }
}
@Override
@@ -193,14 +178,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
- // 将图片路径转换为 BufferedImage
- BufferedImage image = null;
+ Image image = null;
try {
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ image = SmartImageFactory.getInstance().fromFile(imagePath);
+ R> detectionResponseR = detect(image, faceDetectionResponse);
+ return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
}
- return detect(image, faceDetectionResponse);
}
@Override
@@ -208,37 +195,23 @@ public class CommonEmotionModel implements ExpressionModel{
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
+ Image imageDjl = null;
try {
- return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionResponse);
+ imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
- throw new FaceException("错误的图像", e);
+ throw new RuntimeException(e);
}
+ R> detectionResponseR = detect(imageDjl, faceDetectionResponse);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
}
@Override
public R> detect(BufferedImage image, DetectionResponse faceDetectionResponse) {
- if(!ImageUtils.isImageValid(image)){
- R.fail(R.Status.INVALID_IMAGE);
- }
- if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
- R.fail(R.Status.NO_FACE_DETECTED);
- }
- Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
- List expressionResults = new ArrayList<>();
- for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
- FaceInfo faceInfo = detectionInfo.getFaceInfo();
- if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
- return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
- }
- Classifications classifications = detectCore(djlImage, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
- Classifications.Classification bestClass = classifications.best();
- FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
- ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
- result.setClassifications(classifications);
- expressionResults.add(result);
- }
- ((Mat)djlImage.getWrappedImage()).release();
- return R.ok(expressionResults);
+ Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
+ R> detectionResponseR = detect(imageDjl, faceDetectionResponse);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
}
@Override
@@ -246,8 +219,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
- byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
- return detect(imageData, faceDetectionResponse);
+ Image image = null;
+ try {
+ image = SmartImageFactory.getInstance().fromBase64(base64Image);
+ R> detectionResponseR = detect(image, faceDetectionResponse);
+ return detectionResponseR;
+ } catch (IOException e) {
+ throw new FaceException("无效图片", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
+ }
}
@Override
@@ -255,14 +236,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
- // 将图片路径转换为 BufferedImage
- BufferedImage image = null;
+ Image image = null;
try {
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ image = SmartImageFactory.getInstance().fromFile(imagePath);
+ R detectionResponseR = detect(image, faceDetectionRectangle, keyPoints);
+ return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
}
- return detect(image, faceDetectionRectangle, keyPoints);
}
@Override
@@ -270,26 +253,26 @@ public class CommonEmotionModel implements ExpressionModel{
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
+ Image imageDjl = null;
try {
- return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionRectangle, keyPoints);
+ imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
- throw new FaceException("错误的图像", e);
+ throw new RuntimeException(e);
}
+ R detectionResponseR = detect(imageDjl, faceDetectionRectangle, keyPoints);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
}
@Override
public R detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List keyPoints) {
- if(!ImageUtils.isImageValid(image)){
+ if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
- Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
- Classifications classifications = detectCore(djlImage, faceDetectionRectangle, keyPoints);
- Classifications.Classification bestClass = classifications.best();
- FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
- ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
- result.setClassifications(classifications);
- ((Mat)djlImage.getWrappedImage()).release();
- return R.ok(result);
+ Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
+ R detectionResponseR = detect(imageDjl, faceDetectionRectangle, keyPoints);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
}
@@ -298,15 +281,134 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
- byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
- return detect(imageData, faceDetectionRectangle, keyPoints);
+ Image image = null;
+ try {
+ image = SmartImageFactory.getInstance().fromBase64(base64Image);
+ R detectionResponseR = detect(image, faceDetectionRectangle, keyPoints);
+ return detectionResponseR;
+ } catch (IOException e) {
+ throw new FaceException("无效图片", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
+ }
}
@Override
public R detectTopFace(BufferedImage image) {
+ Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
+ R detectionResponseR = detectTopFace(imageDjl);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
+ }
+
+ @Override
+ public R detectTopFace(String imagePath) {
+ if(!FileUtils.isFileExists(imagePath)){
+ return R.fail(R.Status.FILE_NOT_FOUND);
+ }
+ Image image = null;
+ try {
+ image = SmartImageFactory.getInstance().fromFile(imagePath);
+ R detectionResponseR = detectTopFace(image);
+ return detectionResponseR;
+ } catch (IOException e) {
+ throw new FaceException("无效图片路径", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
+ }
+ }
+
+ @Override
+ public R detectTopFace(byte[] imageData) {
+ if(Objects.isNull(imageData)){
+ return R.fail(R.Status.INVALID_IMAGE);
+ }
+ Image imageDjl = null;
+ try {
+ imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ R detectionResponseR = detectTopFace(imageDjl);
+ ImageUtils.releaseOpenCVMat(imageDjl);
+ return detectionResponseR;
+ }
+
+ @Override
+ public R detectTopFaceBase64(String base64Image) {
+ if(StringUtils.isBlank(base64Image)){
+ return R.fail(R.Status.INVALID_IMAGE);
+ }
+ Image image = null;
+ try {
+ image = SmartImageFactory.getInstance().fromBase64(base64Image);
+ R detectionResponseR = detectTopFace(image);
+ return detectionResponseR;
+ } catch (IOException e) {
+ throw new FaceException("无效图片", e);
+ }finally {
+ ImageUtils.releaseOpenCVMat(image);
+ }
+ }
+
+
+ @Override
+ public R detect(Image image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
+ R faceDetectionResponse = config.getDetectModel().detect(image);
+ if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
+ return R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ for(DetectionInfo detectionInfo : faceDetectionResponse.getData().getDetectionInfoList()){
+ FaceInfo faceInfo = detectionInfo.getFaceInfo();
+ if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
+ return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
+ }
+ Classifications classifications = detectCore(image, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
+ Classifications.Classification bestClass = classifications.best();
+ FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
+ ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
+ result.setClassifications(classifications);
+ faceInfo.setExpressionResult(result);
+ }
+ return faceDetectionResponse;
+ }
+
+ @Override
+ public R> detect(Image image, DetectionResponse faceDetectionResponse) {
+ if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
+ R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ List expressionResults = new ArrayList<>();
+ for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
+ FaceInfo faceInfo = detectionInfo.getFaceInfo();
+ if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
+ return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
+ }
+ Classifications classifications = detectCore(image, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
+ Classifications.Classification bestClass = classifications.best();
+ FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
+ ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
+ result.setClassifications(classifications);
+ expressionResults.add(result);
+ }
+ return R.ok(expressionResults);
+ }
+
+ @Override
+ public R detect(Image image, DetectionRectangle faceDetectionRectangle, List keyPoints) {
+ Classifications classifications = detectCore(image, faceDetectionRectangle, keyPoints);
+ Classifications.Classification bestClass = classifications.best();
+ FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
+ ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
+ result.setClassifications(classifications);
+ return R.ok(result);
+ }
+
+ @Override
+ public R detectTopFace(Image image) {
R faceDetectionResponse = config.getDetectModel().detect(image);
if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
@@ -319,49 +421,26 @@ public class CommonEmotionModel implements ExpressionModel{
return detect(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getKeyPoints());
}
- @Override
- public R detectTopFace(String imagePath) {
- if(!FileUtils.isFileExists(imagePath)){
- return R.fail(R.Status.FILE_NOT_FOUND);
- }
- // 将图片路径转换为 BufferedImage
- BufferedImage image = null;
- try {
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
- } catch (IOException e) {
- throw new FaceException("无效图片路径", e);
- }
- return detectTopFace(image);
- }
-
- @Override
- public R detectTopFace(byte[] imageData) {
- if(Objects.isNull(imageData)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- try {
- return detectTopFace(ImageIO.read(new ByteArrayInputStream(imageData)));
- } catch (IOException e) {
- throw new FaceException("错误的图像", e);
- }
- }
-
- @Override
- public R detectTopFaceBase64(String base64Image) {
- if(StringUtils.isBlank(base64Image)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
- return detectTopFace(imageData);
- }
-
@Override
public GenericObjectPool> getPool() {
return predictorPool;
}
+ private boolean fromFactory = false;
+
+ @Override
+ public void setFromFactory(boolean fromFactory) {
+ this.fromFactory = fromFactory;
+ }
+ public boolean isFromFactory() {
+ return fromFactory;
+ }
+
@Override
public void close() {
+ if (fromFactory) {
+ ExpressionModelFactory.removeFromCache(config.getModelEnum());
+ }
try {
if (predictorPool != null) {
predictorPool.close();
diff --git a/face/src/main/java/cn/smartjavaai/face/model/expression/ExpressionModel.java b/face/src/main/java/cn/smartjavaai/face/model/expression/ExpressionModel.java
index d5e4e16..6160b99 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/expression/ExpressionModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/expression/ExpressionModel.java
@@ -35,6 +35,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
+ @Deprecated
default R detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -44,6 +45,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
+ @Deprecated
default R detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -53,6 +55,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imageData 图片字节流
* @return
*/
+ @Deprecated
default R detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -63,6 +66,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param base64Image
* @return
*/
+ @Deprecated
default R detectBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -74,6 +78,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default R> detect(String imagePath, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -85,6 +90,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default R> detect(byte[] imageData,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -95,6 +101,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default R> detect(BufferedImage image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -105,6 +112,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
+ @Deprecated
default R> detectBase64(String base64Image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -116,6 +124,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default R detect(String imagePath, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -127,6 +136,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default R detect(byte[] imageData, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -140,6 +150,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default R detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -150,6 +161,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
+ @Deprecated
default R detectBase64(String base64Image, DetectionRectangle faceDetectionRectangle, List keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -160,6 +172,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param image
* @return
*/
+ @Deprecated
default R detectTopFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -170,6 +183,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imagePath
* @return
*/
+ @Deprecated
default R detectTopFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -179,6 +193,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imageData
* @return
*/
+ @Deprecated
default R detectTopFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -189,15 +204,59 @@ public interface ExpressionModel extends AutoCloseable{
* @param base64Image
* @return
*/
+ @Deprecated
default R detectTopFaceBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
+ /**
+ * 表情识别(多人脸)
+ * @param image
+ * @return
+ */
+ default R detect(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
+ /**
+ * 表情识别(多人脸)
+ * @param image
+ * @param faceDetectionResponse 人脸检测结果
+ * @return
+ */
+ default R> detect(Image image, DetectionResponse faceDetectionResponse){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 表情识别(单人脸)
+ * @param image
+ * @param faceDetectionRectangle 人脸检测结果-人脸框
+ * @return
+ */
+ default R detect(Image image, DetectionRectangle faceDetectionRectangle, List keyPoints){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 表情识别(分数最高人脸)
+ * @param image
+ * @return
+ */
+ default R detectTopFace(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
default GenericObjectPool> getPool(){
throw new UnsupportedOperationException("默认不支持该功能");
}
+ default void setFromFactory(boolean fromFactory){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java
index b79710d..a1dc181 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java
@@ -9,15 +9,15 @@ import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.pool.PredictorFactory;
-import cn.smartjavaai.common.utils.Base64ImageUtils;
-import cn.smartjavaai.common.utils.FileUtils;
-import cn.smartjavaai.common.utils.ImageUtils;
-import cn.smartjavaai.common.utils.OpenCVUtils;
+import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException;
+import cn.smartjavaai.face.factory.ExpressionModelFactory;
+import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.criterial.FaceDetCriteriaFactory;
import cn.smartjavaai.face.utils.FaceUtils;
import lombok.extern.slf4j.Slf4j;
@@ -48,6 +48,8 @@ public class CommonFaceDetModel implements FaceDetModel{
private ZooModel model;
+ private FaceDetConfig config;
+
/**
* 加载模型
@@ -57,6 +59,7 @@ public class CommonFaceDetModel implements FaceDetModel{
public void loadModel(FaceDetConfig config){
Criteria criteria = FaceDetCriteriaFactory.createCriteria(config);
try {
+ this.config = config;
model = criteria.loadModel();
this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model));
int predictorPoolSize = config.getPredictorPoolSize();
@@ -72,14 +75,13 @@ public class CommonFaceDetModel implements FaceDetModel{
}
}
+ @Override
+ public R detect(Image image) {
+ DetectedObjects detection = detectCore(image);
+ return R.ok(FaceUtils.convertToDetectionResponse(detection, image));
+ }
- /**
- * 检测人脸
- * @param imagePath 图片路径
- * @return
- * @throws Exception
- */
@Override
public R detect(String imagePath){
if(!FileUtils.isFileExists(imagePath)){
@@ -87,13 +89,17 @@ public class CommonFaceDetModel implements FaceDetModel{
}
Image img = null;
try {
- img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
- DetectedObjects detection = detect(img);
- return R.ok(FaceUtils.convertToDetectionResponse(detection,img));
+ img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
+ DetectedObjects detection = detectCore(img);
+ DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detection, img);
+ if(detectionResponse == null){
+ return R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ return R.ok(detectionResponse);
} catch (IOException e) {
throw new FaceException("无效的图片", e);
} finally {
- if (img != null) {
+ if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -113,13 +119,17 @@ public class CommonFaceDetModel implements FaceDetModel{
}
Image img = null;
try {
- img = ImageFactory.getInstance().fromInputStream(imageInputStream);
- DetectedObjects detection = detect(img);
- return R.ok(FaceUtils.convertToDetectionResponse(detection,img));
+ img = SmartImageFactory.getInstance().fromInputStream(imageInputStream);
+ DetectedObjects detection = detectCore(img);
+ DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detection,img);
+ if(detectionResponse == null){
+ return R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ return R.ok(detectionResponse);
} catch (IOException e) {
throw new FaceException("无效图片输入流", e);
} finally {
- if (img != null) {
+ if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -128,18 +138,22 @@ public class CommonFaceDetModel implements FaceDetModel{
@Override
public R detect(BufferedImage image) {
- if(!ImageUtils.isImageValid(image)){
+ if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
- img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
- DetectedObjects detection = detect(img);
- return R.ok(FaceUtils.convertToDetectionResponse(detection,img));
+ img = SmartImageFactory.getInstance().fromBufferedImage(image);
+ DetectedObjects detection = detectCore(img);
+ DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detection,img);
+ if(detectionResponse == null){
+ return R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ return R.ok(detectionResponse);
} catch (Exception e) {
throw new FaceException(e);
} finally {
- if (img != null) {
+ if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -164,14 +178,27 @@ public class CommonFaceDetModel implements FaceDetModel{
}
@Override
- public R detectAndDraw(String imagePath, String outputPath) {
+ public R detectAndDraw(Image image) {
+ DetectedObjects detectedObjects = detectCore(image);
+ if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
+ return R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ Image drawnImage = ImageUtils.copy(image);
+ drawnImage.drawBoundingBoxes(detectedObjects);
+ DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detectedObjects, drawnImage);
+ detectionResponse.setDrawnImage(drawnImage);
+ return R.ok(detectionResponse);
+ }
+
+ @Override
+ public R detectAndDraw(String imagePath, String outputPath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image img = null;
try {
- img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
- DetectedObjects detectedObjects = detect(img);
+ img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
+ DetectedObjects detectedObjects = detectCore(img);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -179,11 +206,15 @@ public class CommonFaceDetModel implements FaceDetModel{
Path output = Paths.get(outputPath);
log.debug("Saving to {}", output.toAbsolutePath().toString());
img.save(Files.newOutputStream(output), "png");
- return R.ok();
+ DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detectedObjects,img);
+ if(detectionResponse == null){
+ return R.fail(R.Status.NO_FACE_DETECTED);
+ }
+ return R.ok(detectionResponse);
} catch (IOException e) {
throw new FaceException(e);
} finally {
- if (img != null){
+ if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -191,11 +222,11 @@ public class CommonFaceDetModel implements FaceDetModel{
@Override
public R detectAndDraw(BufferedImage sourceImage) {
- if(!ImageUtils.isImageValid(sourceImage)){
+ if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
- Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
- DetectedObjects detectedObjects = detect(img);
+ Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
+ DetectedObjects detectedObjects = detectCore(img);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -210,18 +241,21 @@ public class CommonFaceDetModel implements FaceDetModel{
} catch (IOException e) {
throw new FaceException("导出图片失败", e);
} finally {
- if (img != null){
+ if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
}
+
+
/**
* 人脸检测
* @param image
* @return
*/
- public DetectedObjects detect(Image image){
+ @Override
+ public DetectedObjects detectCore(Image image){
Predictor predictor = null;
try {
predictor = predictorPool.borrowObject();
@@ -250,8 +284,22 @@ public class CommonFaceDetModel implements FaceDetModel{
return predictorPool;
}
+ private boolean fromFactory = false;
+
+ @Override
+ public void setFromFactory(boolean fromFactory) {
+ this.fromFactory = fromFactory;
+ }
+ public boolean isFromFactory() {
+ return fromFactory;
+ }
+
+
@Override
public void close() {
+ if (fromFactory) {
+ FaceDetModelFactory.removeFromCache(config.getModelEnum());
+ }
try {
if (predictorPool != null) {
predictorPool.close();
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java
index c10aab2..228c4b3 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java
@@ -30,6 +30,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
+ @Deprecated
default R detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -39,6 +40,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param imageInputStream 图片输入流
* @return
*/
+ @Deprecated
default R detect(InputStream imageInputStream){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -48,6 +50,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
+ @Deprecated
default R detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -57,6 +60,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param imageData
* @return
*/
+ @Deprecated
default R detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,16 +70,46 @@ public interface FaceDetModel extends AutoCloseable{
* @param base64Image
* @return
*/
+ @Deprecated
default R detectBase64(String base64Image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
+ /**
+ * 人脸检测
+ * @param image
+ * @return
+ */
+ default DetectedObjects detectCore(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 人脸检测
+ * @param image
+ * @return
+ */
+ default R detect(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
+ /**
+ * 检测并绘制人脸
+ * @param image
+ * @return
+ */
+ default R detectAndDraw(Image image){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
/**
* 检测并绘制人脸
* @param imagePath 图片输入路径(包含文件名称)
* @param outputPath 图片输出路径(包含文件名称)
*/
- default R detectAndDraw(String imagePath, String outputPath){
+ default R detectAndDraw(String imagePath, String outputPath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -84,6 +118,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param sourceImage
* @return
*/
+ @Deprecated
default R detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -93,4 +128,7 @@ public interface FaceDetModel extends AutoCloseable{
}
+ default void setFromFactory(boolean fromFactory){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java
index 3311d44..e878b12 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java
@@ -5,7 +5,6 @@ import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
-import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.ndarray.NDArray;
@@ -16,6 +15,7 @@ import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import ai.djl.translate.NoopTranslator;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.face.FaceInfo;
@@ -24,6 +24,7 @@ import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException;
+import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.criterial.FaceDetCriteriaFactory;
import cn.smartjavaai.face.model.facedect.mtcnn.*;
import cn.smartjavaai.face.utils.FaceUtils;
@@ -53,7 +54,7 @@ import java.util.Objects;
* @author dwj
*/
@Slf4j
-public class MtcnnFaceDetModel implements FaceDetModel{
+public class MtcnnFaceDetModel extends CommonFaceDetModel{
public ZooModel pNetModel;
@@ -65,6 +66,8 @@ public class MtcnnFaceDetModel implements FaceDetModel{
private GenericObjectPool> rnetPredictorPool;
private GenericObjectPool> onetPredictorPool;
+ private FaceDetConfig config;
+
/**
* 加载模型
@@ -72,6 +75,7 @@ public class MtcnnFaceDetModel implements FaceDetModel{
*/
@Override
public void loadModel(FaceDetConfig config){
+ this.config = config;
if(StringUtils.isBlank(config.getModelPath())){
throw new FaceException("modelPath is null");
}
@@ -130,146 +134,13 @@ public class MtcnnFaceDetModel implements FaceDetModel{
}
-
- /**
- * 检测人脸
- * @param imagePath 图片路径
- * @return
- * @throws Exception
- */
- @Override
- public R detect(String imagePath){
- if(!FileUtils.isFileExists(imagePath)){
- return R.fail(R.Status.FILE_NOT_FOUND);
- }
- Image img = null;
- try {
- img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
- return detect(img);
- } catch (IOException e) {
- throw new FaceException("无效的图片", e);
- } finally {
- if (img != null) {
- ((Mat)img.getWrappedImage()).release();
- }
- }
-
- }
-
- /**
- * 检测人脸
- * @param imageInputStream 图片流
- * @return
- * @throws Exception
- */
- @Override
- public R detect(InputStream imageInputStream){
- if(Objects.isNull(imageInputStream)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- Image img = null;
- try {
- img = ImageFactory.getInstance().fromInputStream(imageInputStream);
- return detect(img);
- } catch (IOException e) {
- throw new FaceException("无效图片输入流", e);
- } finally {
- if (img != null) {
- ((Mat)img.getWrappedImage()).release();
- }
- }
- }
-
- @Override
- public R detect(BufferedImage image) {
- if(!ImageUtils.isImageValid(image)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- Image img = null;
- try {
- img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
- return detect(img);
- } catch (Exception e) {
- throw new FaceException(e);
- } finally {
- if (img != null) {
- ((Mat)img.getWrappedImage()).release();
- }
- }
-
- }
-
- @Override
- public R detect(byte[] imageData) {
- if(Objects.isNull(imageData)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- return detect(new ByteArrayInputStream(imageData));
- }
-
- @Override
- public R detectBase64(String base64Image) {
- if(StringUtils.isBlank(base64Image)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
- return detect(imageData);
- }
-
- @Override
- public R detectAndDraw(String imagePath, String outputPath) {
- if(!FileUtils.isFileExists(imagePath)){
- return R.fail(R.Status.FILE_NOT_FOUND);
- }
- Image img = null;
- try {
- img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
- R detectionResponseR = detect(img);
- if(!detectionResponseR.isSuccess()){
- return R.fail(detectionResponseR.getCode(), detectionResponseR.getMessage());
- }
- if(Objects.isNull(detectionResponseR.getData()) ||
- CollectionUtils.isEmpty(detectionResponseR.getData().getDetectionInfoList())){
- return R.fail(R.Status.NO_FACE_DETECTED);
- }
- BufferedImage sourceImage = OpenCVUtils.mat2Image((Mat)img.getWrappedImage());
- FaceUtils.drawBoundingBoxes(sourceImage, detectionResponseR.getData(), outputPath);
- return R.ok();
- } catch (IOException e) {
- throw new FaceException(e);
- } finally {
- if (img != null){
- ((Mat)img.getWrappedImage()).release();
- }
- }
- }
-
- @Override
- public R detectAndDraw(BufferedImage sourceImage) {
- if(!ImageUtils.isImageValid(sourceImage)){
- return R.fail(R.Status.INVALID_IMAGE);
- }
- try {
- R detectionResponseR = detect(sourceImage);
- if(!detectionResponseR.isSuccess()){
- return R.fail(detectionResponseR.getCode(), detectionResponseR.getMessage());
- }
- if(Objects.isNull(detectionResponseR.getData()) ||
- CollectionUtils.isEmpty(detectionResponseR.getData().getDetectionInfoList())){
- return R.fail(R.Status.NO_FACE_DETECTED);
- }
- return R.ok(FaceUtils.drawBoundingBoxes(sourceImage, detectionResponseR.getData()));
- } catch (IOException e) {
- throw new FaceException("导出图片失败", e);
- }
- }
-
/**
* 人脸检测
* @param image
* @return
*/
- public R detect(Image image){
+ @Override
+ public DetectedObjects detectCore(Image image){
Predictor pNetPredictor = null;
Predictor rNetPredictor = null;
Predictor oNetPredictor = null;
@@ -281,35 +152,33 @@ public class MtcnnFaceDetModel implements FaceDetModel{
int w = image.getWidth();
//第一阶段
NDList outputPnet = PNetModel.firstStage(manager, pNetPredictor, image);
+
if(CollectionUtils.isEmpty(outputPnet)){
- return R.fail(R.Status.NO_FACE_DETECTED);
+ return DJLCommonUtils.buildEmptyDetectedObjects();
}
NDArray boxes = outputPnet.get(0);
NDArray image_inds = outputPnet.get(1);
NDArray imgs = outputPnet.get(2);
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_inds) || DJLCommonUtils.isNDArrayEmpty(imgs)){
- return R.fail(R.Status.NO_FACE_DETECTED);
+ return DJLCommonUtils.buildEmptyDetectedObjects();
}
NDList pad = MtcnnUtils.pad(boxes, w, h);
//第二阶段
- NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs,boxes,pad, image_inds);
+ NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs, boxes, pad, image_inds);
if(CollectionUtils.isEmpty(outputRnet)){
- return R.fail(R.Status.NO_FACE_DETECTED);
+ return DJLCommonUtils.buildEmptyDetectedObjects();
}
NDArray image_indsFiltered = outputRnet.get(0);
NDArray scoresFiltered = outputRnet.get(1);
boxes = outputRnet.get(2);
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_indsFiltered) || DJLCommonUtils.isNDArrayEmpty(scoresFiltered)){
- return R.fail(R.Status.NO_FACE_DETECTED);
+ return DJLCommonUtils.buildEmptyDetectedObjects();
}
//第三阶段
- MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs,boxes, w, h, scoresFiltered, image_indsFiltered);
- DetectionResponse detectionResponse = convertToDetectionResponse(oNetResult);
- if(Objects.isNull(detectionResponse)){
- return R.fail(R.Status.NO_FACE_DETECTED);
- }
- return R.ok(detectionResponse);
+ MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs, boxes, w, h, scoresFiltered, image_indsFiltered);
+ return FaceUtils.toDetectedObjects(oNetResult, w, h);
} catch (Exception e) {
+ e.printStackTrace();
throw new RuntimeException(e);
} finally {
if (pNetPredictor != null) {
@@ -354,50 +223,50 @@ public class MtcnnFaceDetModel implements FaceDetModel{
- /**
- * 转换为FaceDetectedResult
- * @param mtcnnBatchResult
- * @return
- */
- public static DetectionResponse convertToDetectionResponse(MtcnnBatchResult mtcnnBatchResult){
- if(Objects.isNull(mtcnnBatchResult) || CollectionUtils.isEmpty(mtcnnBatchResult.boxes)
- || CollectionUtils.isEmpty(mtcnnBatchResult.points)
- || CollectionUtils.isEmpty(mtcnnBatchResult.probs)){
- return null;
- }
- DetectionResponse detectionResponse = new DetectionResponse();
- List detectionInfoList = new ArrayList();
-
- NDArray boxes = mtcnnBatchResult.boxes.get(0);
- NDArray probs = mtcnnBatchResult.probs.get(0);
- NDArray points = mtcnnBatchResult.points.get(0);
-
- if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){
- return null;
- }
- long numBoxes = boxes.getShape().get(0);
- for (int i = 0; i < numBoxes; i++) {
- float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
- float score = probs.getFloat(i);
- NDArray pointND = points.get(i); // shape [5,2]
- float[] flatPoints = pointND.toFloatArray(); // 一维长度 10
- List keyPoints = new ArrayList();
- for (int p = 0; p < 5; p++) {
- keyPoints.add(new Point(flatPoints[p * 2], flatPoints[p * 2 + 1]));
- }
- int x = Math.round(boxCoords[0]);
- int y = Math.round(boxCoords[1]);
- int w = Math.round(boxCoords[2] - boxCoords[0]);
- int h = Math.round(boxCoords[3] - boxCoords[1]);
-
- DetectionRectangle rectangle = new DetectionRectangle(x, y, w, h);
- FaceInfo faceInfo = new FaceInfo(keyPoints);
- DetectionInfo detectionInfo = new DetectionInfo(rectangle, score, faceInfo);
- detectionInfoList.add(detectionInfo);
- }
- detectionResponse.setDetectionInfoList(detectionInfoList);
- return detectionResponse;
- }
+// /**
+// * 转换为FaceDetectedResult
+// * @param mtcnnBatchResult
+// * @return
+// */
+// public static DetectionResponse convertToDetectionResponse(MtcnnBatchResult mtcnnBatchResult){
+// if(Objects.isNull(mtcnnBatchResult) || CollectionUtils.isEmpty(mtcnnBatchResult.boxes)
+// || CollectionUtils.isEmpty(mtcnnBatchResult.points)
+// || CollectionUtils.isEmpty(mtcnnBatchResult.probs)){
+// return null;
+// }
+// DetectionResponse detectionResponse = new DetectionResponse();
+// List detectionInfoList = new ArrayList();
+//
+// NDArray boxes = mtcnnBatchResult.boxes.get(0);
+// NDArray probs = mtcnnBatchResult.probs.get(0);
+// NDArray points = mtcnnBatchResult.points.get(0);
+//
+// if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){
+// return null;
+// }
+// long numBoxes = boxes.getShape().get(0);
+// for (int i = 0; i < numBoxes; i++) {
+// float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
+// float score = probs.getFloat(i);
+// NDArray pointND = points.get(i); // shape [5,2]
+// float[] flatPoints = pointND.toFloatArray(); // 一维长度 10
+// List keyPoints = new ArrayList();
+// for (int p = 0; p < 5; p++) {
+// keyPoints.add(new Point(flatPoints[p * 2], flatPoints[p * 2 + 1]));
+// }
+// int x = Math.round(boxCoords[0]);
+// int y = Math.round(boxCoords[1]);
+// int w = Math.round(boxCoords[2] - boxCoords[0]);
+// int h = Math.round(boxCoords[3] - boxCoords[1]);
+//
+// DetectionRectangle rectangle = new DetectionRectangle(x, y, w, h);
+// FaceInfo faceInfo = new FaceInfo(keyPoints);
+// DetectionInfo detectionInfo = new DetectionInfo(rectangle, score, faceInfo);
+// detectionInfoList.add(detectionInfo);
+// }
+// detectionResponse.setDetectionInfoList(detectionInfoList);
+// return detectionResponse;
+// }
@@ -413,8 +282,22 @@ public class MtcnnFaceDetModel implements FaceDetModel{
return onetPredictorPool;
}
+
+ private boolean fromFactory = false;
+
+ @Override
+ public void setFromFactory(boolean fromFactory) {
+ this.fromFactory = fromFactory;
+ }
+ public boolean isFromFactory() {
+ return fromFactory;
+ }
+
@Override
public void close() {
+ if (fromFactory) {
+ FaceDetModelFactory.removeFromCache(config.getModelEnum());
+ }
try {
if (pnetPredictorPool != null) {
pnetPredictorPool.close();
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java
index bdde257..ef275c4 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java
@@ -1,14 +1,18 @@
package cn.smartjavaai.face.model.facedect;
import ai.djl.engine.Engine;
+import ai.djl.modality.cv.Image;
+import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.Base64ImageUtils;
+import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException;
+import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.seetaface.NativeLoader;
import cn.smartjavaai.face.utils.FaceUtils;
import com.seeta.pool.*;
@@ -81,6 +85,59 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
}
+ @Override
+ public R detect(Image image) {
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ FaceDetector predictor = null;
+ FaceLandmarker faceLandmarker = null;
+ try {
+ predictor = faceDetectorPool.borrowObject();
+ predictor.set(FaceDetector.Property.PROPERTY_THRESHOLD, config.getConfidenceThreshold() > 0 ? config.getConfidenceThreshold() : THRESHOLD);
+ faceLandmarker = faceLandmarkerPool.borrowObject();
+ SeetaRect[] seetaResult = predictor.Detect(imageData);
+ List