【人脸识别】 新增多种人脸识别模型

【底层优化】 支持自由选择 OpenCV 或 BufferedImage 作为图像引擎

【通用图像】 全部模型启用 Image 输入,支持各类图片格式与 Image 的互转

【模型管理】 优化模型生命周期,关闭后可重新创建

【人脸识别】 支持在人脸查询结果中绘制姓名标注

【人脸检测】 新增人脸裁剪功能

【修复】 修复若干已知问题,提升系统稳定性
This commit is contained in:
dengwenjie
2025-10-02 16:26:42 +08:00
parent 1b50e2b943
commit dfa8cf9bb4
133 changed files with 6635 additions and 3532 deletions

View File

@@ -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 开发者打造的一个功能丰富、开箱即用的
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-all</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</dependency>
```
@@ -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轻量模型

View File

@@ -6,11 +6,11 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</parent>
<artifactId>all</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
<name>${project.artifactId}</name>
<description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</parent>
<version>1.0.24</version>
<version>1.0.25</version>
<artifactId>bom</artifactId>
<name>sbom</name>
<name>bom</name>
<description>统一版本管理的 BOM 包,同时支持 import 和全量依赖</description>
<properties>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</parent>
<name>common</name>

View File

@@ -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);
}
}

View File

@@ -0,0 +1,28 @@
package cn.smartjavaai.common.entity;
import lombok.Data;
import java.util.List;
/**
* 多边形
* @author dwj
*/
@Data
public class PolygonLabel {
private List<Point> points;
private String text;
public PolygonLabel(List<Point> points, String text) {
this.points = points;
this.text = text;
}
public PolygonLabel() {
}
public PolygonLabel(List<Point> points) {
this.points = points;
}
}

View File

@@ -10,7 +10,7 @@ import java.awt.image.BufferedImage;
* @author dwj
* @date 2025/6/27
*/
public class BufferedImagePreprocessor {
public class BufferedImagePreprocessor implements ImagePreprocessor<BufferedImage>{
private BufferedImage image;
private DetectionRectangle rect;

View File

@@ -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<Image>{
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());
}
}

View File

@@ -0,0 +1,28 @@
package cn.smartjavaai.common.preprocess;
import java.awt.*;
/**
* 图片预处理
* @author dwj
*/
public interface ImagePreprocessor<T> {
ImagePreprocessor<T> setExtendRatio(float ratio);
ImagePreprocessor<T> setTargetSize(int size);
ImagePreprocessor<T> setCenterCropSize(int size);
ImagePreprocessor<T> enableSquarePadding(boolean enable);
ImagePreprocessor<T> enableScaling(boolean enable);
ImagePreprocessor<T> enableCenterCrop(boolean enable);
ImagePreprocessor<T> setPaddingColor(Color color);
T process();
}

View File

@@ -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<Mat> {
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();
}
}

View File

@@ -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<String> 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<PolygonLabel> 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<Point> 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<Point> 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<Point> 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);
}
}
}

View File

@@ -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<Point> 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<String> classNames = new ArrayList<>();
List<Double> probabilities = new ArrayList<>();
List<BoundingBox> boxes = new ArrayList<>();
return new DetectedObjects(classNames, probabilities, boxes);
}
}

View File

@@ -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<cn.smartjavaai.common.entity.Point> 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<String> 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);
}
}
}

View File

@@ -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 <T> 类型参数
* @return 对象实例,如果解析失败返回 null
*/
public static <T> T fromJson(String jsonStr, Class<T> 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);
}
}

View File

@@ -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();
//当图片BufferedImageDJL的duplicate会有问题
//当图片BufferedImageDJL的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<PolygonLabel> 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<DetectionInfo> detectionInfoList){
for(DetectionInfo detectionInfo : detectionInfoList){
drawRectAndText(image, detectionInfo);
}
}
}

View File

@@ -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<DetectionInfo> detectionInfoList) {
public static void drawRectAndText(Mat image, List<DetectionInfo> 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<cn.smartjavaai.common.entity.Point> 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<String> 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<String> 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<ai.djl.modality.cv.output.Point> landMarks){
if (landMarks == null || landMarks.size() != 4) {
throw new IllegalArgumentException("必须提供4个关键点");
}
// 步骤 1排序为 左上、右上、右下、左下
List<ai.djl.modality.cv.output.Point> 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<ai.djl.modality.cv.output.Point> 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<Point> 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<Point> toCvPointList(List<cn.smartjavaai.common.entity.Point> 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<PolygonLabel> polygonLabelList, int fontSize) {
for (PolygonLabel polygonLabel : polygonLabelList){
List<Point> 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<Point> 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;
}
}

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.24</smartjavaai.version>
<smartjavaai.version>1.0.25</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.face.facedet.FaceDetDemo</exec.mainClass>
@@ -220,35 +220,6 @@
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
</dependencies>
@@ -278,7 +249,21 @@
</plugins>
</build>
<repositories>
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>阿里云公共仓库</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>

View File

@@ -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> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));

View File

@@ -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<ExpressionResult> 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<ExpressionResult> 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<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
R<List<ExpressionResult>> 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<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> detectedResult = expressionModel.detect(bufferedImage);
Image img = factory.fromMat(resizeImage);
R<DetectionResponse> 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();

View File

@@ -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<DetectionResponse> detectedResult = faceModel.detect(imgPath);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imgPath);
R<DetectionResponse> 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<DetectionResponse> 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<BufferedImage> detectedImage = faceModel.detectAndDraw(image);
if(detectedImage.isSuccess()){
log.info("人脸检测成功");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imgPath);
R<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> detectedResult = faceModel.detect(imgPath);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imgPath);
R<DetectionResponse> 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<DetectionResponse> detectedResult = faceModel.detect(bufferedImage);
Image img = factory.fromMat(resizeImage);
R<DetectionResponse> 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) {

View File

@@ -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<DetectionResponse> faceResult = faceRecModel.extractFeatures("src/main/resources/iu_1.jpg");
R<DetectionResponse> 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<Float> 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();
}
}
/**
* 人脸比对11基于图像直接比对
* 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。(接口内自动完成)
* 注意事项:
* 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<Float> 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<float[]> 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<float[]> featureResult1 = faceRecModel.extractTopFaceFeature(image1);
if(featureResult1.isSuccess()){
log.info("图片1人脸特征提取成功{}", JSONObject.toJSONString(featureResult1.getData()));
}else{
@@ -283,7 +338,8 @@ public class FaceRecDemo {
return;
}
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_2.jpg");
Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
R<float[]> 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<float[]> featureResult = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
R<float[]> 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("====================人脸更新==========================");
//更新人脸 只支持自定义IDvectorDBConfig.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<float[]> featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_3.jpg");
Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
R<float[]> 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<FaceSearchResult> faceSearchResults = faceRecModel.search(featureResult2.getData(), faceSearchParams);
// R<DetectionResponse> 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<float[]> 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<float[]> 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<float[]> featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_3.jpg");
Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
R<float[]> 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<FaceSearchResult> 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<String> 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);

View File

@@ -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<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> 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<LivenessResult> response = livenessDetModel.detectTopFace(ImageIO.read(file));
R<LivenessResult> 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> 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<DetectionResponse> 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<LivenessResult> livenessStatus = livenessDetModel.detectVideo("video.mp4");
R<LivenessResult> 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<DetectionResponse> detectedResult = livenessDetModel.detect(bufferedImage);
Image img = factory.fromMat(resizeImage);
R<DetectionResponse> 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) {

View File

@@ -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> 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> 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> 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> 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> 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> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 KiB

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 273 KiB

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.24</smartjavaai.version>
<smartjavaai.version>1.0.25</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.ocr.common.OcrRecognizeDemo</exec.mainClass>
@@ -219,39 +219,6 @@
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
</dependencies>
<build>

View File

@@ -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<OcrBox> 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<OcrBox> 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<Image> images = ImageUtils.readImagesFromFolder(folderPath);
List<List<OcrBox>> ocrResult = model.batchDetectDJLImage(images);

View File

@@ -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<OcrItem> 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<OcrItem> 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();
}
}
}

View File

@@ -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";
//读取文件夹中所有图片

View File

@@ -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<List<PlateInfo>> result = plateRecModel.recognize("src/main/resources/plate/Quicker_20220930_180856.png");
R<List<PlateInfo>> 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<BufferedImage> detectedImage = plateRecModel.recognizeAndDraw(image);
R<Image> 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());
}

View File

@@ -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<TableStructureResult> 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<TableStructureResult> 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);

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.24</smartjavaai.version>
<smartjavaai.version>1.0.25</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.speech.asr.common.OcrRecognizeDemo</exec.mainClass>

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.24</smartjavaai.version>
<smartjavaai.version>1.0.25</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.nlp.translation.TranslationDemo</exec.mainClass>

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.24</smartjavaai.version>
<smartjavaai.version>1.0.25</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.vision.ObjectDetectionDemo</exec.mainClass>
@@ -272,36 +272,6 @@
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
</dependencies>

View File

@@ -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");
}

View File

@@ -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());
}

View File

@@ -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());
}

View File

@@ -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) {

View File

@@ -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());
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -6,11 +6,11 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</parent>
<artifactId>face</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
<name>face</name>
<description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -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;
}
/**

View File

@@ -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);
}
}

View File

@@ -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<String, FaceAttributeModel> modelMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<FaceAttributeModelEnum, FaceAttributeModel> modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
private static final Map<String, Class<? extends FaceAttributeModel>> registry =
private static final Map<FaceAttributeModelEnum, Class<? extends FaceAttributeModel>> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<String, FaceQualityModel> modelMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<QualityModelEnum, FaceQualityModel> modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
private static final Map<String, Class<? extends FaceQualityModel>> registry =
private static final Map<QualityModelEnum, Class<? extends FaceQualityModel>> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<FaceAttribute> 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<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -76,6 +81,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default List<FaceAttribute> 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<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -97,6 +104,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default List<FaceAttribute> 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<Point> 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<FaceAttribute> detect(Image image, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸属性识别(单人脸)
* @param image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default FaceAttribute detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> 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("默认不支持该功能");
}
}

View File

@@ -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<FaceAttribute> 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<Point> 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<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
List<FaceAttribute> faceAttributeList = new ArrayList<FaceAttribute>();
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<FaceAttribute> 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<FaceAttribute> faceAttributeList = new ArrayList<FaceAttribute>();
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<Point> 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();
}

View File

@@ -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<Image, Classifications> 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<DetectionResponse> detectionResponseR = detect(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return detect(image);
}
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> 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<List<ExpressionResult>> 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<List<ExpressionResult>> detectionResponseR = detect(imageDjl, faceDetectionResponse);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<List<ExpressionResult>> 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<ExpressionResult> 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<List<ExpressionResult>> 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<List<ExpressionResult>> 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<ExpressionResult> 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<ExpressionResult> detectionResponseR = detect(imageDjl, faceDetectionRectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<ExpressionResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> 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<ExpressionResult> 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<ExpressionResult> detectionResponseR = detect(image, faceDetectionRectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public R<ExpressionResult> detectTopFace(BufferedImage image) {
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<ExpressionResult> detectionResponseR = detectTopFace(imageDjl);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<ExpressionResult> 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<ExpressionResult> detectionResponseR = detectTopFace(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public R<ExpressionResult> 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<ExpressionResult> detectionResponseR = detectTopFace(imageDjl);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<ExpressionResult> detectTopFaceBase64(String base64Image) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image image = null;
try {
image = SmartImageFactory.getInstance().fromBase64(base64Image);
R<ExpressionResult> detectionResponseR = detectTopFace(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public R<DetectionResponse> detect(Image image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> 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<List<ExpressionResult>> detect(Image image, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
R.fail(R.Status.NO_FACE_DETECTED);
}
List<ExpressionResult> 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<ExpressionResult> detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> 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<ExpressionResult> detectTopFace(Image image) {
R<DetectionResponse> 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<ExpressionResult> 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<ExpressionResult> 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<ExpressionResult> 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<Predictor<Image, Classifications>> 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();

View File

@@ -35,6 +35,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<DetectionResponse> detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -44,6 +45,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<DetectionResponse> detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -53,6 +55,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imageData 图片字节流
* @return
*/
@Deprecated
default R<DetectionResponse> detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -63,6 +66,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<DetectionResponse> detectBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -74,6 +78,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default R<List<ExpressionResult>> detect(String imagePath, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -85,6 +90,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default R<List<ExpressionResult>> detect(byte[] imageData,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -95,6 +101,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default R<List<ExpressionResult>> detect(BufferedImage image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -105,6 +112,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default R<List<ExpressionResult>> detectBase64(String base64Image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -116,6 +124,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default R<ExpressionResult> detect(String imagePath, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -127,6 +136,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default R<ExpressionResult> detect(byte[] imageData, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -140,6 +150,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default R<ExpressionResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -150,6 +161,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default R<ExpressionResult> detectBase64(String base64Image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -160,6 +172,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param image
* @return
*/
@Deprecated
default R<ExpressionResult> detectTopFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -170,6 +183,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imagePath
* @return
*/
@Deprecated
default R<ExpressionResult> detectTopFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -179,6 +193,7 @@ public interface ExpressionModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default R<ExpressionResult> detectTopFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -189,15 +204,59 @@ public interface ExpressionModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<ExpressionResult> detectTopFaceBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 表情识别(多人脸)
* @param image
* @return
*/
default R<DetectionResponse> detect(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 表情识别(多人脸)
* @param image
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default R<List<ExpressionResult>> detect(Image image, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 表情识别(单人脸)
* @param image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<ExpressionResult> detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 表情识别(分数最高人脸)
* @param image
* @return
*/
default R<ExpressionResult> detectTopFace(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, Classifications>> getPool(){
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -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<Image, DetectedObjects> model;
private FaceDetConfig config;
/**
* 加载模型
@@ -57,6 +59,7 @@ public class CommonFaceDetModel implements FaceDetModel{
public void loadModel(FaceDetConfig config){
Criteria<Image, DetectedObjects> 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<DetectionResponse> detect(Image image) {
DetectedObjects detection = detectCore(image);
return R.ok(FaceUtils.convertToDetectionResponse(detection, image));
}
/**
* 检测人脸
* @param imagePath 图片路径
* @return
* @throws Exception
*/
@Override
public R<DetectionResponse> 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<DetectionResponse> 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<Void> detectAndDraw(String imagePath, String outputPath) {
public R<DetectionResponse> 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<DetectionResponse> 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<BufferedImage> 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<Image, DetectedObjects> 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();

View File

@@ -30,6 +30,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<DetectionResponse> detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -39,6 +40,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param imageInputStream 图片输入流
* @return
*/
@Deprecated
default R<DetectionResponse> detect(InputStream imageInputStream){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -48,6 +50,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<DetectionResponse> detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -57,6 +60,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default R<DetectionResponse> detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,16 +70,46 @@ public interface FaceDetModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<DetectionResponse> detectBase64(String base64Image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸检测
* @param image
* @return
*/
default DetectedObjects detectCore(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸检测
* @param image
* @return
*/
default R<DetectionResponse> detect(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制人脸
* @param image
* @return
*/
default R<DetectionResponse> detectAndDraw(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制人脸
* @param imagePath 图片输入路径(包含文件名称)
* @param outputPath 图片输出路径(包含文件名称)
*/
default R<Void> detectAndDraw(String imagePath, String outputPath){
default R<DetectionResponse> detectAndDraw(String imagePath, String outputPath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -84,6 +118,7 @@ public interface FaceDetModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<BufferedImage> detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -93,4 +128,7 @@ public interface FaceDetModel extends AutoCloseable{
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -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<NDList, NDList> pNetModel;
@@ -65,6 +66,8 @@ public class MtcnnFaceDetModel implements FaceDetModel{
private GenericObjectPool<Predictor<NDList, NDList>> rnetPredictorPool;
private GenericObjectPool<Predictor<NDList, NDList>> 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<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> 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<DetectionResponse> detect(byte[] imageData) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
return detect(new ByteArrayInputStream(imageData));
}
@Override
public R<DetectionResponse> 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<Void> 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<DetectionResponse> 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<BufferedImage> detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
R<DetectionResponse> 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<DetectionResponse> detect(Image image){
@Override
public DetectedObjects detectCore(Image image){
Predictor<NDList, NDList> pNetPredictor = null;
Predictor<NDList, NDList> rNetPredictor = null;
Predictor<NDList, NDList> 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<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
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<Point> keyPoints = new ArrayList<Point>();
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<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
//
// 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<Point> keyPoints = new ArrayList<Point>();
// 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();

View File

@@ -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<DetectionResponse> 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<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
for(SeetaRect seetaRect : seetaResult){
//提取人脸的5点人脸标识
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, pointFS);
seetaPointFSList.add(pointFS);
}
return R.ok(FaceUtils.convertToDetectionResponse(seetaResult, seetaPointFSList));
} catch (Exception e) {
throw new FaceException("目标检测错误", e);
}finally {
if (predictor != null) {
try {
faceDetectorPool.returnObject(predictor); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceLandmarker != null) {
try {
faceLandmarkerPool.returnObject(faceLandmarker); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<DetectionResponse> detectAndDraw(Image image) {
R<DetectionResponse> result = detect(image);
if(result.getCode() != R.Status.SUCCESS.getCode()){
return R.fail(result.getCode(), result.getMessage());
}
if(Objects.isNull(result.getData()) || Objects.isNull(result.getData().getDetectionInfoList()) || result.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
Image drawnImage = ImageUtils.drawBoundingBoxes(image, result.getData());
result.getData().setDrawnImage(drawnImage);
return result;
}
@Override
public R<DetectionResponse> detect(String imagePath) {
if(!FileUtils.isFileExists(imagePath)){
@@ -112,11 +169,11 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
FaceDetector predictor = null;
FaceLandmarker faceLandmarker = null;
try {
@@ -174,19 +231,14 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
}
@Override
public R<Void> detectAndDraw(String imagePath, String outputPath) {
public R<DetectionResponse> detectAndDraw(String imagePath, String outputPath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image image = null;
Image drawImage = null;
try {
//创建保存路径
Path imageOutputPath = Paths.get(outputPath);
BufferedImage image = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}
image = SmartImageFactory.getInstance().fromFile(imagePath);
R<DetectionResponse> result = detect(image);
if(result.getCode() != R.Status.SUCCESS.getCode()){
return R.fail(result.getCode(), result.getMessage());
@@ -195,16 +247,20 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return R.fail(R.Status.NO_FACE_DETECTED);
}
//绘制人脸框
FaceUtils.drawBoundingBoxes(image, result.getData(), imageOutputPath.toAbsolutePath().toString());
return R.ok();
drawImage = ImageUtils.drawBoundingBoxes(image, result.getData());
ImageUtils.save(drawImage, Paths.get(outputPath), "png");
return result;
} catch (IOException e) {
throw new FaceException(e);
throw new FaceException("保存图片失败", e);
} finally {
ImageUtils.releaseOpenCVMat(image);
ImageUtils.releaseOpenCVMat(drawImage);
}
}
@Override
public R<BufferedImage> detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
R<DetectionResponse> result = detect(sourceImage);
@@ -215,14 +271,14 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return R.fail(R.Status.NO_FACE_DETECTED);
}
//绘制人脸框
try {
return R.ok(FaceUtils.drawBoundingBoxes(sourceImage, result.getData()));
} catch (IOException e) {
throw new RuntimeException(e);
}
BufferedImage drawnImage = BufferedImageUtils.copyBufferedImage(sourceImage);
BufferedImageUtils.drawBoundingBoxes(drawnImage, result.getData());
return R.ok(drawnImage);
}
public FaceDetectorPool getFaceDetectorPool() {
return faceDetectorPool;
}
@@ -231,8 +287,23 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return faceLandmarkerPool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
@Override
public void close() throws Exception {
if (fromFactory) {
FaceDetModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (faceDetectorPool != null) {
faceDetectorPool.close();

View File

@@ -4,31 +4,27 @@ 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.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import cn.hutool.core.lang.UUID;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.entity.face.FaceSearchResult;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.config.FaceRecConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
import cn.smartjavaai.face.entity.FaceRegisterInfo;
import cn.smartjavaai.face.entity.FaceSearchParams;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.enums.SimilarityType;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.factory.FaceRecModelFactory;
import cn.smartjavaai.face.model.facerec.criteria.FaceRecCriteriaFactory;
import cn.smartjavaai.face.preprocess.DJLImagePreprocessor;
import cn.smartjavaai.face.preprocess.DJLImageFacePreprocessor;
import cn.smartjavaai.face.utils.*;
import cn.smartjavaai.face.vector.config.MilvusConfig;
import cn.smartjavaai.face.vector.config.SQLiteConfig;
@@ -39,8 +35,8 @@ import cn.smartjavaai.face.vector.entity.FaceVector;
import cn.smartjavaai.face.vector.exception.VectorDBException;
import io.milvus.param.MetricType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.opencv.core.Mat;
@@ -95,7 +91,7 @@ public class CommonFaceRecModel implements FaceRecModel{
throw new FaceException("config为null");
}
if(Objects.isNull(config.getDetectModel())){
config.setDetectModel(getDefaultDetModel());
throw new FaceException("请指定人脸检测模型");
}
this.config = config;
Criteria<Image, float[]> faceFeatureCriteria = FaceRecCriteriaFactory.createCriteria(config);
@@ -217,7 +213,7 @@ public class CommonFaceRecModel implements FaceRecModel{
@Override
public R<Float> featureComparison(BufferedImage sourceImage1, BufferedImage sourceImag2) {
if(!ImageUtils.isImageValid(sourceImage1) || !ImageUtils.isImageValid(sourceImag2)){
if(!BufferedImageUtils.isImageValid(sourceImage1) || !BufferedImageUtils.isImageValid(sourceImag2)){
throw new FaceException("图像无效");
}
R<float[]> feature1 = extractTopFaceFeature(sourceImage1);
@@ -246,19 +242,6 @@ public class CommonFaceRecModel implements FaceRecModel{
}
}
/**
* 获取默认人脸检测模型
* @return
*/
private FaceDetModel getDefaultDetModel() {
FaceDetConfig detectModelConfig = new FaceDetConfig();
detectModelConfig.setModelEnum(FaceDetModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE);
detectModelConfig.setConfidenceThreshold(0.98);
log.debug("创建默认人脸检测模型ULTRA_LIGHT_FAST_GENERIC_FACE");
FaceDetModel detectModel = FaceDetModelFactory.getInstance().getModel(detectModelConfig);
return detectModel;
}
@Override
public R<DetectionResponse> extractFeatures(BufferedImage image) {
R<DetectionResponse> detectedResult = config.getDetectModel().detect(image);
@@ -268,9 +251,9 @@ public class CommonFaceRecModel implements FaceRecModel{
if(Objects.isNull(detectedResult.getData()) || Objects.isNull(detectedResult.getData().getDetectionInfoList()) || detectedResult.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image djlImage = SmartImageFactory.getInstance().fromBufferedImage(image);
try (NDManager manager = model.getNDManager().newSubManager()) {
DJLImagePreprocessor djlImagePreprocessor = new DJLImagePreprocessor(djlImage, manager);
DJLImageFacePreprocessor djlImagePreprocessor = new DJLImageFacePreprocessor(djlImage, manager);
for (DetectionInfo detectionInfo : detectedResult.getData().getDetectionInfoList()){
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
FaceInfo faceInfo = detectionInfo.getFaceInfo();
@@ -289,12 +272,17 @@ public class CommonFaceRecModel implements FaceRecModel{
subImage = djlImagePreprocessor.process();
}
}
features = featureExtraction(subImage);
if (subImage != null && subImage.getWrappedImage() instanceof Mat) {
((Mat)subImage.getWrappedImage()).release();
}
faceInfo.setFeature(features);
}
}finally {
if (djlImage != null && djlImage.getWrappedImage() instanceof Mat) {
((Mat)djlImage.getWrappedImage()).release();
}
}
((Mat)djlImage.getWrappedImage()).release();
return detectedResult;
}
@@ -335,10 +323,10 @@ public class CommonFaceRecModel implements FaceRecModel{
if(Objects.isNull(detectedResult.getData()) || Objects.isNull(detectedResult.getData().getDetectionInfoList()) || detectedResult.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image djlImage = SmartImageFactory.getInstance().fromBufferedImage(image);
float[] features = null;
try (NDManager manager = model.getNDManager().newSubManager()) {
DJLImagePreprocessor djlImagePreprocessor = new DJLImagePreprocessor(djlImage, manager);
DJLImageFacePreprocessor djlImagePreprocessor = new DJLImageFacePreprocessor(djlImage, manager);
//只取第一个人脸
DetectionInfo detectionInfo = detectedResult.getData().getDetectionInfoList().get(0);
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
@@ -358,8 +346,14 @@ public class CommonFaceRecModel implements FaceRecModel{
}
}
features = featureExtraction(subImage);
if (subImage != null && subImage.getWrappedImage() instanceof Mat) {
((Mat)subImage.getWrappedImage()).release();
}
}finally {
if (djlImage != null && djlImage.getWrappedImage() instanceof Mat) {
((Mat)djlImage.getWrappedImage()).release();
}
}
((Mat)djlImage.getWrappedImage()).release();
return Objects.isNull(features) ? R.fail(R.Status.Unknown) : R.ok(features);
}
@@ -596,7 +590,7 @@ public class CommonFaceRecModel implements FaceRecModel{
throw new FaceException("人脸查询参数为空");
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? FaceDetectConstant.FACENET_DEFAULT_SIMILARITY_THRESHOLD : params.getThreshold();
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
@@ -629,13 +623,16 @@ public class CommonFaceRecModel implements FaceRecModel{
return detectionResponse;
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? FaceDetectConstant.FACENET_DEFAULT_SIMILARITY_THRESHOLD : params.getThreshold();
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
for (DetectionInfo detectionInfo : detectionResponse.getData().getDetectionInfoList()){
if(Objects.nonNull(detectionInfo.getFaceInfo()) && Objects.nonNull(detectionInfo.getFaceInfo().getFeature())){
List<FaceSearchResult> searchResults = vectorDBClient.search(detectionInfo.getFaceInfo().getFeature(), searchParams);
if (CollectionUtils.isEmpty(searchResults)){
return R.fail(1000, "未找到匹配结果");
}
detectionInfo.getFaceInfo().setFaceSearchResults(searchResults);
}
}
@@ -686,9 +683,184 @@ public class CommonFaceRecModel implements FaceRecModel{
vectorDBClient.releaseFaceFeatures();
}
@Override
public R<Float> featureComparison(Image image1, Image image2) {
R<float[]> feature1 = extractTopFaceFeature(image1);
if (!feature1.isSuccess()){
return R.fail(feature1.getCode(), feature1.getMessage());
}
R<float[]> feature2 = extractTopFaceFeature(image2);
if (!feature2.isSuccess()){
return R.fail(feature2.getCode(), feature2.getMessage());
}
float ret = calculSimilar(feature1.getData(), feature2.getData());
return R.ok(ret);
}
@Override
public R<String> register(FaceRegisterInfo faceRegisterInfo, Image image) {
if(vectorDBClient == null){
throw new VectorDBException("向量数据库未初始化成功");
}
//提取最大人脸特征
R<float[]> featureResponse = extractTopFaceFeature(image);
if(!featureResponse.isSuccess()){
return R.fail(featureResponse.getCode(), featureResponse.getMessage());
}
return register(faceRegisterInfo, featureResponse.getData());
}
@Override
public void upsertFace(FaceRegisterInfo faceRegisterInfo, Image image) {
if(vectorDBClient == null){
throw new VectorDBException("向量数据库未初始化成功");
}
if(Objects.isNull(faceRegisterInfo)){
throw new FaceException("注册信息为空");
}
if(StringUtils.isBlank(faceRegisterInfo.getId())){
throw new FaceException("注册信息中ID为空");
}
//提取最大人脸特征
R<float[]> featureResponse = extractTopFaceFeature(image);
if(!featureResponse.isSuccess()){
throw new FaceException(featureResponse.getMessage());
}
upsertFace(faceRegisterInfo, featureResponse.getData());
}
@Override
public R<DetectionResponse> search(Image image, FaceSearchParams params) {
if(vectorDBClient == null){
throw new VectorDBException("向量数据库未初始化成功");
}
//提取所有人脸特征
R<DetectionResponse> detectionResponse = extractFeatures(image);
if(!detectionResponse.isSuccess()){
return detectionResponse;
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
for (DetectionInfo detectionInfo : detectionResponse.getData().getDetectionInfoList()){
if(Objects.nonNull(detectionInfo.getFaceInfo()) && Objects.nonNull(detectionInfo.getFaceInfo().getFeature())){
List<FaceSearchResult> searchResults = vectorDBClient.search(detectionInfo.getFaceInfo().getFeature(), searchParams);
if (CollectionUtils.isEmpty(searchResults)){
return R.fail(1000, "未找到匹配结果");
}
detectionInfo.getFaceInfo().setFaceSearchResults(searchResults);
}
}
return detectionResponse;
}
@Override
public R<List<FaceSearchResult>> searchByTopFace(Image image, FaceSearchParams params) {
if(vectorDBClient == null){
return R.fail(1000, "向量数据库未初始化成功");
}
//提取最大人脸特征
R<float[]> featureResponse = extractTopFaceFeature(image);
if(!featureResponse.isSuccess()){
return R.fail(featureResponse.getCode(), featureResponse.getMessage());
}
return R.ok(search(featureResponse.getData(), params));
}
@Override
public R<DetectionResponse> extractFeatures(Image image) {
R<DetectionResponse> detectedResult = config.getDetectModel().detect(image);
if(!detectedResult.isSuccess()){
return detectedResult;
}
if(Objects.isNull(detectedResult.getData()) || Objects.isNull(detectedResult.getData().getDetectionInfoList()) || detectedResult.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
try (NDManager manager = model.getNDManager().newSubManager()) {
DJLImageFacePreprocessor djlImagePreprocessor = new DJLImageFacePreprocessor(image, manager);
for (DetectionInfo detectionInfo : detectedResult.getData().getDetectionInfoList()){
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
FaceInfo faceInfo = detectionInfo.getFaceInfo();
float[] features = null;
Image subImage = null;
//人脸对齐
if(config.isAlign()){
//人脸对齐
double[][] pointsArray = FaceUtils.facePoints(faceInfo.getKeyPoints());
djlImagePreprocessor.enableCrop(rectangle).enableAffine(pointsArray, 96, 112);
subImage = djlImagePreprocessor.process();
}else{
//裁剪
djlImagePreprocessor.enableCrop(rectangle);
if(config.isCropFace()){
subImage = djlImagePreprocessor.process();
}
}
features = featureExtraction(subImage);
if (subImage != null && subImage.getWrappedImage() instanceof Mat) {
((Mat)subImage.getWrappedImage()).release();
}
faceInfo.setFeature(features);
}
}
return detectedResult;
}
@Override
public R<float[]> extractTopFaceFeature(Image image) {
R<DetectionResponse> detectedResult = config.getDetectModel().detect(image);
if(!detectedResult.isSuccess()){
return R.fail(detectedResult.getCode(), detectedResult.getMessage());
}
if(Objects.isNull(detectedResult.getData()) || Objects.isNull(detectedResult.getData().getDetectionInfoList()) || detectedResult.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
float[] features = null;
try (NDManager manager = model.getNDManager().newSubManager()) {
DJLImageFacePreprocessor djlImagePreprocessor = new DJLImageFacePreprocessor(image, manager);
//只取第一个人脸
DetectionInfo detectionInfo = detectedResult.getData().getDetectionInfoList().get(0);
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
FaceInfo faceInfo = detectionInfo.getFaceInfo();
Image subImage = null;
//人脸对齐
if(config.isAlign()){
//人脸对齐
double[][] pointsArray = FaceUtils.facePoints(faceInfo.getKeyPoints());
djlImagePreprocessor.enableCrop(rectangle).enableAffine(pointsArray, 96, 112);
subImage = djlImagePreprocessor.process();
}else{
//裁剪
djlImagePreprocessor.enableCrop(rectangle);
if(config.isCropFace()){
subImage = djlImagePreprocessor.process();
}
}
features = featureExtraction(subImage);
if (subImage != null && subImage.getWrappedImage() instanceof Mat) {
((Mat)subImage.getWrappedImage()).release();
}
}
return Objects.isNull(features) ? R.fail(R.Status.Unknown) : R.ok(features);
}
@Override
public Image drawSearchResult(Image image, FaceSearchParams params, String displayField) {
R<DetectionResponse> detectionResponse = search(image, params);
Image drawImage = ImageUtils.copy(image);
BufferedImage bufferedImage = ImageUtils.toBufferedImage(drawImage);
BufferedImageUtils.drawFaceSearchResult(bufferedImage, detectionResponse.getData(), displayField);
return SmartImageFactory.getInstance().fromBufferedImage(bufferedImage);
}
@Override
public void close() {
if (fromFactory) {
FaceRecModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -717,4 +889,15 @@ public class CommonFaceRecModel implements FaceRecModel{
public GenericObjectPool<Predictor<Image, float[]>> getPool() {
return predictorPool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -38,22 +38,38 @@ public interface FaceRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征比较
* @param image1 图1
* @param image2 图2
* @return
*/
default R<Float> featureComparison(Image image1, Image image2){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征比较
* @param imagePath1 图1路径
* @param imagePath2 图2路径
* @return
*/
@Deprecated
default R<Float> featureComparison(String imagePath1, String imagePath2){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征比较
* @param sourceImage1 图1BufferedImage
* @param sourceImag2 图2BufferedImage
* @return
*/
@Deprecated
default R<Float> featureComparison(BufferedImage sourceImage1, BufferedImage sourceImag2){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -65,10 +81,22 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData2
* @return
*/
@Deprecated
default R<Float> featureComparison(byte[] imageData1, byte[] imageData2){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 注册人脸
* 提取分数最高人脸进行注册
* @param faceRegisterInfo 注册人脸信息
* @param image
* @return
*/
default R<String> register(FaceRegisterInfo faceRegisterInfo, Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 注册人脸
* 提取分数最高人脸进行注册
@@ -76,6 +104,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<String> register(FaceRegisterInfo faceRegisterInfo, String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -87,6 +116,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param inputStream
* @return
*/
@Deprecated
default R<String> register(FaceRegisterInfo faceRegisterInfo, InputStream inputStream){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -98,6 +128,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<String> register(FaceRegisterInfo faceRegisterInfo, BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -110,6 +141,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default R<String> register(FaceRegisterInfo faceRegisterInfo, byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -125,6 +157,17 @@ public interface FaceRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 更新或注册人脸
* 自动提取分数最高人脸进行更新
* @param faceRegisterInfo 注册人脸信息
* @param image
* @return
*/
default void upsertFace(FaceRegisterInfo faceRegisterInfo, Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 更新或注册人脸
* 自动提取分数最高人脸进行更新
@@ -132,6 +175,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imagePath
* @return
*/
@Deprecated
default void upsertFace(FaceRegisterInfo faceRegisterInfo, String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -144,6 +188,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default void upsertFace(FaceRegisterInfo faceRegisterInfo, BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -167,10 +212,21 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default void upsertFace(FaceRegisterInfo faceRegisterInfo, byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 查询人脸(查询图片中所有人脸)
* @param image
* @param params 人脸查询参数
* @return
*/
default R<DetectionResponse> search(Image image, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 查询人脸(查询图片中所有人脸)
@@ -178,6 +234,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param params 人脸查询参数
* @return
*/
@Deprecated
default R<DetectionResponse> search(String imagePath, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -189,6 +246,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<DetectionResponse> search(BufferedImage sourceImage, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -199,6 +257,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default R<DetectionResponse> search(byte[] imageData, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -213,6 +272,18 @@ public interface FaceRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 查询人脸
* 从图像中提取分数最高的人脸特征,并在人脸库中进行 1:N 查询
* 适用于单人脸场景
* @param image
* @param params 人脸查询参数
* @return
*/
default R<List<FaceSearchResult>> searchByTopFace(Image image, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 查询人脸
* 从图像中提取分数最高的人脸特征,并在人脸库中进行 1:N 查询
@@ -221,6 +292,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param params 人脸查询参数
* @return
*/
@Deprecated
default R<List<FaceSearchResult>> searchByTopFace(String imagePath, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -233,6 +305,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<List<FaceSearchResult>> searchByTopFace(BufferedImage sourceImage, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -244,6 +317,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default R<List<FaceSearchResult>> searchByTopFace(byte[] imageData, FaceSearchParams params){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -286,12 +360,24 @@ public interface FaceRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征提取(所有人脸)
* 适用于多人脸场景
* @param image
* @return
*/
default R<DetectionResponse> extractFeatures(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征提取(所有人脸)
* 适用于多人脸场景
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<DetectionResponse> extractFeatures(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -302,6 +388,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData 图片字节流
* @return
*/
@Deprecated
default R<DetectionResponse> extractFeatures(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -312,10 +399,21 @@ public interface FaceRecModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<DetectionResponse> extractFeatures(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征提取(提取分数最高人脸特征)
* 适用于单人脸场景
* @param image
* @return
*/
default R<float[]> extractTopFaceFeature(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 特征提取(提取分数最高人脸特征)
@@ -323,6 +421,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<float[]> extractTopFaceFeature(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -333,6 +432,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<float[]> extractTopFaceFeature(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -343,6 +443,7 @@ public interface FaceRecModel extends AutoCloseable{
* @param imageData 图片字节流
* @return
*/
@Deprecated
default R<float[]> extractTopFaceFeature(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -370,9 +471,23 @@ public interface FaceRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 绘制人脸搜索结果
* @param image
* @param params
* @param displayField
*/
default Image drawSearchResult(Image image, FaceSearchParams params, String displayField){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, float[]>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -0,0 +1,114 @@
package cn.smartjavaai.face.model.facerec;
import ai.djl.modality.cv.Image;
import lombok.Data;
/**
* 人脸识别参数
* @author dwj
* @date 2025/9/18
*/
@Data
public class FaceRecPreprocessConfig {
private int inputWidth;
private int inputHeight;
/**
* 像素类型
*/
private Image.Flag imageFlag;
/**
* 是否使用管道
*/
private boolean usePipeline;
/**
* 是否归一化
*/
private boolean normalize;
/**
* 归一化 mean
*/
private float[] mean;
/**
* 归一化 std
*/
private float[] std;
/**
* 输出索引(默认取第 0 个)
*/
private int outputIndex;
private FaceRecPreprocessConfig(Builder builder) {
this.inputWidth = builder.inputWidth;
this.inputHeight = builder.inputHeight;
this.imageFlag = builder.imageFlag;
this.usePipeline = builder.usePipeline;
this.normalize = builder.normalize;
this.mean = builder.mean;
this.std = builder.std;
this.outputIndex = builder.outputIndex;
}
// ========= Builder =========
public static class Builder {
private int inputWidth = 112; // 默认值
private int inputHeight = 112; // 默认值
private Image.Flag imageFlag = Image.Flag.COLOR; // 默认彩色
private boolean usePipeline = true;
private boolean normalize = true;
private float[] mean = new float[]{0.5F, 0.5F, 0.5F};
private float[] std = new float[]{0.5F, 0.5F, 0.5F};
private int outputIndex;
public Builder inputSize(int width, int height) {
this.inputWidth = width;
this.inputHeight = height;
return this;
}
public Builder imageFlag(Image.Flag flag) {
this.imageFlag = flag;
return this;
}
public Builder usePipeline(boolean usePipeline) {
this.usePipeline = usePipeline;
return this;
}
public Builder normalize(boolean normalize) {
this.normalize = normalize;
return this;
}
public Builder mean(float... mean) {
this.mean = mean;
return this;
}
public Builder std(float... std) {
this.std = std;
return this;
}
public Builder outputIndex(int outputIndex) {
this.outputIndex = outputIndex;
return this;
}
public FaceRecPreprocessConfig build() {
return new FaceRecPreprocessConfig(this);
}
}
}

View File

@@ -0,0 +1,7 @@
package cn.smartjavaai.face.model.facerec;
/**
* @author dwj
*/
public class FaceRecTranslatorBuilder {
}

View File

@@ -1,11 +1,14 @@
package cn.smartjavaai.face.model.facerec;
import ai.djl.engine.Engine;
import ai.djl.modality.cv.Image;
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.FaceInfo;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceRecConfig;
@@ -16,7 +19,10 @@ import cn.smartjavaai.face.entity.FaceSearchParams;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.enums.SimilarityType;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.factory.FaceRecModelFactory;
import cn.smartjavaai.face.utils.FaceUtils;
import cn.smartjavaai.face.utils.Seetaface6Utils;
import cn.smartjavaai.face.vector.config.MilvusConfig;
import cn.smartjavaai.face.vector.config.SQLiteConfig;
import cn.smartjavaai.face.vector.core.VectorDBClient;
@@ -29,6 +35,7 @@ import com.seeta.sdk.*;
import cn.smartjavaai.face.seetaface.NativeLoader;
import io.milvus.param.MetricType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import javax.imageio.ImageIO;
@@ -213,7 +220,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
@Override
public R<Float> featureComparison(BufferedImage image1, BufferedImage image2) {
if(!ImageUtils.isImageValid(image1) || !ImageUtils.isImageValid(image2)){
if(!BufferedImageUtils.isImageValid(image1) || !BufferedImageUtils.isImageValid(image2)){
return R.fail(R.Status.INVALID_IMAGE);
}
R<float[]> feature1 = extractTopFaceFeature(image1);
@@ -263,7 +270,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
@Override
public R<String> register(FaceRegisterInfo faceRegisterInfo, BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
//提取特征向量
@@ -376,7 +383,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
@Override
public R<DetectionResponse> search(BufferedImage image, FaceSearchParams params) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
//提取所有人脸特征
@@ -385,13 +392,16 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
return detectionResponse;
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? FaceDetectConstant.SEETAFACE_DEFAULT_SIMILARITY_THRESHOLD : params.getThreshold();
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
for (DetectionInfo detectionInfo : detectionResponse.getData().getDetectionInfoList()){
if(Objects.nonNull(detectionInfo.getFaceInfo()) && Objects.nonNull(detectionInfo.getFaceInfo().getFeature())){
List<FaceSearchResult> searchResults = vectorDBClient.search(detectionInfo.getFaceInfo().getFeature(), searchParams);
if (CollectionUtils.isEmpty(searchResults)){
return R.fail(1000, "未找到匹配结果");
}
detectionInfo.getFaceInfo().setFaceSearchResults(searchResults);
}
}
@@ -425,7 +435,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
throw new FaceException("人脸查询参数为空");
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? FaceDetectConstant.SEETAFACE_DEFAULT_SIMILARITY_THRESHOLD : params.getThreshold();
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
@@ -450,7 +460,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
@Override
public R<List<FaceSearchResult>> searchByTopFace(BufferedImage sourceImage, FaceSearchParams params) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
//提取分数最高人脸特征
@@ -459,7 +469,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
return R.fail(featureResponse.getCode(), featureResponse.getMessage());
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? FaceDetectConstant.SEETAFACE_DEFAULT_SIMILARITY_THRESHOLD : params.getThreshold();
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
@@ -586,7 +596,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
@Override
public R<DetectionResponse> extractFeatures(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
FaceDetector faceDetector = null;
@@ -594,7 +604,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
FaceRecognizer faceRecognizer = null;
try {
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
faceRecognizer = faceRecognizerPool.borrowObject();
//默认使用Seetaface6检测模型
if(Objects.isNull(config.getDetectModel())){
@@ -637,7 +647,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
}
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(faceInfo.getKeyPoints());
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(faceInfo.getKeyPoints());
//CropFaceV2 + ExtractCroppedFace 已包含裁剪+人脸对齐
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
if(!isSuccess){
@@ -679,12 +689,12 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
@Override
public R<float[]> extractTopFaceFeature(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
float[] features = null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
FaceDetector faceDetector = null;
FaceLandmarker faceLandmarker = null;
FaceRecognizer faceRecognizer = null;
@@ -712,7 +722,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
return R.fail(R.Status.NO_FACE_DETECTED);
}
DetectionInfo detectionInfo = detectResponse.getData().getDetectionInfoList().get(0);
pointFS = FaceUtils.convertToSeetaPointF(detectionInfo.getFaceInfo().getKeyPoints());
pointFS = Seetaface6Utils.convertToSeetaPointF(detectionInfo.getFaceInfo().getKeyPoints());
}
//提取特征
features = new float[faceRecognizer.GetExtractFeatureSize()];
@@ -761,7 +771,7 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
try {
faceRecognizer = faceRecognizerPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
//提取特征
float[] features = new float[faceRecognizer.GetExtractFeatureSize()];
faceRecognizer.ExtractCroppedFace(imageData, features);
@@ -904,8 +914,264 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
return R.ok(vectorDBClient.listFaces(pageNum, pageSize));
}
@Override
public R<Float> featureComparison(Image image1, Image image2) {
R<float[]> feature1 = extractTopFaceFeature(image1);
if(!feature1.isSuccess()){
return R.fail(feature1.getCode(), feature1.getMessage());
}
R<float[]> feature2 = extractTopFaceFeature(image2);
if(!feature2.isSuccess()){
return R.fail(feature2.getCode(), feature2.getMessage());
}
return R.ok(calculSimilar(feature1.getData(), feature2.getData()));
}
@Override
public R<String> register(FaceRegisterInfo faceRegisterInfo, Image image) {
//提取特征向量
R<float[]> featureResponse = extractTopFaceFeature(image);
if(!featureResponse.isSuccess()){
return R.fail(featureResponse.getCode(), featureResponse.getMessage());
}
return register(faceRegisterInfo, featureResponse.getData());
}
@Override
public void upsertFace(FaceRegisterInfo faceRegisterInfo, Image image) {
if(vectorDBClient == null){
throw new VectorDBException("向量数据库未初始化成功");
}
if(Objects.isNull(faceRegisterInfo)){
throw new FaceException("注册信息为空");
}
if(StringUtils.isBlank(faceRegisterInfo.getId())){
throw new FaceException("注册信息中ID为空");
}
//提取最大人脸特征
R<float[]> featureResponse = extractTopFaceFeature(image);
if(!featureResponse.isSuccess()){
throw new FaceException(featureResponse.getMessage());
}
upsertFace(faceRegisterInfo, featureResponse.getData());
}
@Override
public R<DetectionResponse> search(Image image, FaceSearchParams params) {
//提取所有人脸特征
R<DetectionResponse> detectionResponse = extractFeatures(image);
if(!detectionResponse.isSuccess()){
return detectionResponse;
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
for (DetectionInfo detectionInfo : detectionResponse.getData().getDetectionInfoList()){
if(Objects.nonNull(detectionInfo.getFaceInfo()) && Objects.nonNull(detectionInfo.getFaceInfo().getFeature())){
List<FaceSearchResult> searchResults = vectorDBClient.search(detectionInfo.getFaceInfo().getFeature(), searchParams);
if (CollectionUtils.isEmpty(searchResults)){
return R.fail(1000, "未找到匹配结果");
}
detectionInfo.getFaceInfo().setFaceSearchResults(searchResults);
}
}
return detectionResponse;
}
@Override
public R<List<FaceSearchResult>> searchByTopFace(Image image, FaceSearchParams params) {
//提取分数最高人脸特征
R<float[]> featureResponse = extractTopFaceFeature(image);
if(!featureResponse.isSuccess()){
return R.fail(featureResponse.getCode(), featureResponse.getMessage());
}
//设置默认值
float threshold = Objects.isNull(params.getThreshold()) ? config.getModelEnum().getThreshold() : params.getThreshold();
int topK = Objects.isNull(params.getTopK()) ? 1 : params.getTopK();
boolean normalize = Objects.isNull(params.getNormalizeSimilarity()) ? NORMALIZE_SIMILARITY : params.getNormalizeSimilarity();
FaceSearchParams searchParams = new FaceSearchParams(topK, threshold, normalize);
List<FaceSearchResult> searchResults = vectorDBClient.search(featureResponse.getData(), searchParams);
return R.ok(searchResults);
}
@Override
public R<DetectionResponse> extractFeatures(Image image) {
FaceDetector faceDetector = null;
FaceLandmarker faceLandmarker = null;
FaceRecognizer faceRecognizer = null;
try {
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
faceRecognizer = faceRecognizerPool.borrowObject();
//默认使用Seetaface6检测模型
if(Objects.isNull(config.getDetectModel())){
List<float[]> featureList = new ArrayList<float[]>();
List<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
faceDetector = faceDetectorPool.borrowObject();
faceLandmarker = faceLandmarkerPool.borrowObject();
//检测人脸
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
for(SeetaRect seetaRect : seetaResult){
//提取人脸的5点人脸标识
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, pointFS);
//提取特征
float[] features = new float[faceRecognizer.GetExtractFeatureSize()];
//CropFaceV2 + ExtractCroppedFace 已包含裁剪+人脸对齐
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
if(!isSuccess){
return R.fail(R.Status.Unknown.getCode(), "人脸特征提取失败");
}
featureList.add(features);
seetaPointFSList.add(pointFS);
}
return R.ok(FaceUtils.featuresConvertToResponse(seetaResult, seetaPointFSList, featureList));
}else{
R<DetectionResponse> detectResponse = config.getDetectModel().detect(image);
if(!detectResponse.isSuccess()){
return detectResponse;
}
if(Objects.isNull(detectResponse.getData()) || Objects.isNull(detectResponse.getData().getDetectionInfoList()) || detectResponse.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
for(DetectionInfo detectionInfo : detectResponse.getData().getDetectionInfoList()){
//提取特征
float[] features = new float[faceRecognizer.GetExtractFeatureSize()];
FaceInfo faceInfo = detectionInfo.getFaceInfo();
if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
}
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(faceInfo.getKeyPoints());
//CropFaceV2 + ExtractCroppedFace 已包含裁剪+人脸对齐
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
if(!isSuccess){
return R.fail(R.Status.Unknown.getCode(), "人脸特征提取失败");
}
faceInfo.setFeature(features);
}
return detectResponse;
}
} catch (FaceException e) {
throw e;
} catch (Exception e) {
throw new FaceException("人脸特征提取异常", e);
}finally {
if (faceDetector != null) {
try {
faceDetectorPool.returnObject(faceDetector); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceLandmarker != null) {
try {
faceLandmarkerPool.returnObject(faceLandmarker); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceRecognizer != null) {
try {
faceRecognizerPool.returnObject(faceRecognizer); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<float[]> extractTopFaceFeature(Image image) {
float[] features = null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
FaceDetector faceDetector = null;
FaceLandmarker faceLandmarker = null;
FaceRecognizer faceRecognizer = null;
try {
faceRecognizer = faceRecognizerPool.borrowObject();
//提取人脸的5点人脸标识
SeetaPointF[] pointFS = null;
//默认使用Seetaface6检测模型
if(Objects.isNull(config.getDetectModel())){
faceDetector = faceDetectorPool.borrowObject();
faceLandmarker = faceLandmarkerPool.borrowObject();
//检测人脸
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
pointFS = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaResult[0], pointFS);
}else{
R<DetectionResponse> detectResponse = config.getDetectModel().detect(image);
if(!detectResponse.isSuccess()){
return R.fail(detectResponse.getCode(), detectResponse.getMessage());
}
if(Objects.isNull(detectResponse.getData()) || Objects.isNull(detectResponse.getData().getDetectionInfoList()) || detectResponse.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
DetectionInfo detectionInfo = detectResponse.getData().getDetectionInfoList().get(0);
pointFS = Seetaface6Utils.convertToSeetaPointF(detectionInfo.getFaceInfo().getKeyPoints());
}
//提取特征
features = new float[faceRecognizer.GetExtractFeatureSize()];
//CropFaceV2 + ExtractCroppedFace 已包含裁剪+人脸对齐
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
if(!isSuccess){
return R.fail(R.Status.Unknown.getCode(), "人脸特征提取失败");
}
return R.ok(features);
} catch (FaceException e) {
throw e;
} catch (Exception e) {
throw new FaceException("目标检测错误", e);
}finally {
if (faceDetector != null) {
try {
faceDetectorPool.returnObject(faceDetector); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceLandmarker != null) {
try {
faceLandmarkerPool.returnObject(faceLandmarker); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceRecognizer != null) {
try {
faceRecognizerPool.returnObject(faceRecognizer); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public Image drawSearchResult(Image image, FaceSearchParams params, String displayField) {
R<DetectionResponse> detectionResponse = search(image, params);
Image drawImage = ImageUtils.copy(image);
BufferedImage bufferedImage = ImageUtils.toBufferedImage(drawImage);
BufferedImageUtils.drawFaceSearchResult(bufferedImage, detectionResponse.getData(), displayField);
return SmartImageFactory.getInstance().fromBufferedImage(bufferedImage);
}
@Override
public void close() throws Exception {
if (fromFactory) {
FaceRecModelFactory.removeFromCache(config.getModelEnum());
}
if(Objects.nonNull(faceDetectorPool)){
faceDetectorPool.close();
}
@@ -923,6 +1189,9 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
}
}
@Override
public boolean isLoadFaceCompleted() {
return isLoadCompleted;
@@ -944,4 +1213,15 @@ public class SeetaFace6FaceRecModel implements FaceRecModel{
public FaceDatabasePool getFaceDatabasePool() {
return faceDatabasePool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -4,18 +4,20 @@ import ai.djl.Device;
import ai.djl.modality.cv.Image;
import ai.djl.repository.zoo.Criteria;
import ai.djl.training.util.ProgressBar;
import ai.djl.translate.Translator;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.face.config.FaceRecConfig;
import cn.smartjavaai.face.constant.FaceNetConstant;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.model.facerec.translator.FaceFeatureTranslator;
import cn.smartjavaai.face.model.facerec.translator.FaceNetRecTranslator;
import cn.smartjavaai.face.model.facerec.FaceRecPreprocessConfig;
import cn.smartjavaai.face.model.facerec.translator.*;
import org.apache.commons.lang3.StringUtils;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
@@ -29,73 +31,40 @@ public class FaceRecCriteriaFactory {
if(!Objects.isNull(config.getDevice())){
device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId());
}
Criteria<Image, float[]> criteria = null;
if(config.getModelEnum() == FaceRecModelEnum.FACENET_MODEL){
criteria =
Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelName("face_feature") // specify model file prefix
.optModelUrls(StringUtils.isNotBlank(config.getModelPath()) ? null :
FaceNetConstant.MODEL_URL)
.optModelPath(StringUtils.isNotBlank(config.getModelPath()) ? Paths.get(config.getModelPath()) : null)
.optTranslator(new FaceNetRecTranslator())
.optDevice(device)
.optEngine("PyTorch") // Use PyTorch engine
.optProgress(new ProgressBar())
.build();
}else if (config.getModelEnum() == FaceRecModelEnum.INSIGHT_FACE_MOBILE_FACENET_MODEL){
if(StringUtils.isBlank(config.getModelPath())){
throw new RuntimeException("请指定模型路径");
}
List<Float> mean = Arrays.asList(0.5f,0.5f,0.5f,0.5f,0.5f,0.5f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
criteria = Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelPath(Paths.get(config.getModelPath()))
// .optTranslatorFactory(new ImageFeatureExtractorFactory())
// .optArgument("normalize", normalize)
// .optArgument("resize", "112,112")
.optTranslator(new FaceFeatureTranslator())
.optEngine("PyTorch") // Use PyTorch engine
.optProgress(new ProgressBar())
.optDevice(device)
.build();
}else if (config.getModelEnum() == FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL){
if(StringUtils.isBlank(config.getModelPath())){
throw new RuntimeException("请指定模型路径");
}
List<Float> mean = Arrays.asList(0.5f,0.5f,0.5f,0.5f,0.5f,0.5f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
criteria = Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelPath(Paths.get(config.getModelPath()))
// .optTranslatorFactory(new ImageFeatureExtractorFactory())
// .optArgument("normalize", normalize)
// .optArgument("resize", "112,112")
.optTranslator(new FaceFeatureTranslator())
.optEngine("PyTorch") // Use PyTorch engine
.optDevice(device)
.optProgress(new ProgressBar())
.build();
}else if (config.getModelEnum() == FaceRecModelEnum.ELASTIC_FACE_MODEL){
if(StringUtils.isBlank(config.getModelPath())){
throw new RuntimeException("请指定模型路径");
}
List<Float> mean = Arrays.asList(0.5f,0.5f,0.5f,0.5f,0.5f,0.5f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
criteria = Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelPath(Paths.get(config.getModelPath()))
// .optTranslatorFactory(new ImageFeatureExtractorFactory())
// .optArgument("normalize", normalize)
// .optArgument("resize", "112,112")
.optTranslator(new FaceFeatureTranslator())
.optEngine("PyTorch") // Use PyTorch engine
.optDevice(device)
.optProgress(new ProgressBar())
.build();
}
Translator<Image, float[]> translator = getFaceRecTranslator(config);
Criteria<Image, float[]> criteria =
Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelUrls(StringUtils.isNotBlank(config.getModelPath()) ? null :
FaceNetConstant.MODEL_URL)
.optModelPath(StringUtils.isNotBlank(config.getModelPath()) ? Paths.get(config.getModelPath()) : null)
.optTranslator(translator)
.optDevice(device)
.optEngine(config.getModelEnum().getEngine())
.optProgress(new ProgressBar())
.build();
return criteria;
}
/**
* 获取人脸识别模型Translator
* @param config
* @return
*/
public static Translator<Image, float[]> getFaceRecTranslator(FaceRecConfig config) {
FaceRecPreprocessConfig preprocessConfig = new FaceRecPreprocessConfig.Builder()
.inputSize(config.getModelEnum().getInputWidth(), config.getModelEnum().getInputHeight())
.build();
switch (config.getModelEnum()) {
case VGG_FACE:
preprocessConfig = new FaceRecPreprocessConfig.Builder()
.inputSize(config.getModelEnum().getInputWidth(), config.getModelEnum().getInputHeight())
.usePipeline(false)
.normalize(false)
.build();
break;
}
return new CommonFaceRecTranslator(preprocessConfig);
}
}

View File

@@ -0,0 +1,77 @@
package cn.smartjavaai.face.model.facerec.translator;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.transform.Normalize;
import ai.djl.modality.cv.transform.Resize;
import ai.djl.modality.cv.transform.ToTensor;
import ai.djl.modality.cv.util.NDImageUtils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.types.DataType;
import ai.djl.translate.Batchifier;
import ai.djl.translate.Pipeline;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
import cn.smartjavaai.face.model.facerec.FaceRecPreprocessConfig;
/**
* 通用人脸识别模型转换器
* @author dwj
*/
public class CommonFaceRecTranslator implements Translator<Image, float[]> {
private FaceRecPreprocessConfig preprocessConfig;
public CommonFaceRecTranslator(FaceRecPreprocessConfig preprocessConfig) {
this.preprocessConfig = preprocessConfig;
}
/**
* {@inheritDoc}
*/
@Override
public NDList processInput(TranslatorContext ctx, Image input) {
NDArray array = input.toNDArray(ctx.getNDManager(), preprocessConfig.getImageFlag());
NDList ndList = null;
if(preprocessConfig.isUsePipeline()){
Pipeline pipeline = new Pipeline();
if(input.getWidth() != preprocessConfig.getInputWidth() || input.getHeight() != preprocessConfig.getInputHeight()){
pipeline.add(new Resize(preprocessConfig.getInputWidth(), preprocessConfig.getInputHeight()));
}
pipeline.add(new ToTensor());
if(preprocessConfig.isNormalize()){
pipeline.add(new Normalize(
preprocessConfig.getMean(),
preprocessConfig.getStd()));
}
ndList = pipeline.transform(new NDList(array));
}else{
if(input.getWidth() != preprocessConfig.getInputWidth() || input.getHeight() != preprocessConfig.getInputHeight()){
array = NDImageUtils.resize(array, preprocessConfig.getInputWidth(), preprocessConfig.getInputHeight());
}
array = array.toType(DataType.FLOAT32, false);
if (preprocessConfig.isNormalize()){
array = array.sub(preprocessConfig.getMean()[0]).div(preprocessConfig.getStd()[0]);
}
array = array.transpose(2, 0, 1);
return new NDList(array);
}
return ndList;
}
/**
* {@inheritDoc}
*/
@Override
public float[] processOutput(TranslatorContext ctx, NDList list) {
NDArray embedding = list.get(preprocessConfig.getOutputIndex());
embedding = embedding.div(embedding.norm()); // L2归一化
return embedding.toFloatArray();
}
@Override
public Batchifier getBatchifier() {
return Batchifier.STACK;
}
}

View File

@@ -1,59 +0,0 @@
package cn.smartjavaai.face.model.facerec.translator;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.transform.Normalize;
import ai.djl.modality.cv.transform.Resize;
import ai.djl.modality.cv.transform.ToTensor;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.translate.Batchifier;
import ai.djl.translate.Pipeline;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
/**
* facenet人脸特征提取Translator
* @author dwj
* @date 2025/3/31
*/
public final class FaceFeatureTranslator implements Translator<Image, float[]> {
public FaceFeatureTranslator() {
}
/**
* {@inheritDoc}
*/
@Override
public NDList processInput(TranslatorContext ctx, Image input) {
NDArray array = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
Pipeline pipeline = new Pipeline();
if(input.getWidth() != 112 || input.getHeight() != 112){
pipeline.add(new Resize(112,112));
}
pipeline
.add(new ToTensor())
.add(new Normalize(
new float[]{0.5F, 0.5F, 0.5F},
new float[]{0.5F, 0.5F, 0.5F}));
return pipeline.transform(new NDList(array));
}
/**
* {@inheritDoc}
*/
@Override
public float[] processOutput(TranslatorContext ctx, NDList list) {
NDArray embedding = list.singletonOrThrow();
embedding = embedding.div(embedding.norm()); // L2归一化
return embedding.toFloatArray();
}
@Override
public Batchifier getBatchifier() {
return Batchifier.STACK;
}
}

View File

@@ -1,57 +0,0 @@
package cn.smartjavaai.face.model.facerec.translator;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.transform.Normalize;
import ai.djl.modality.cv.transform.Resize;
import ai.djl.modality.cv.transform.ToTensor;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.translate.Batchifier;
import ai.djl.translate.Pipeline;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
/**
* facenet人脸特征提取Translator
* @author dwj
* @date 2025/3/31
*/
public final class FaceNetRecTranslator implements Translator<Image, float[]> {
public FaceNetRecTranslator() {
}
/**
* {@inheritDoc}
*/
@Override
public NDList processInput(TranslatorContext ctx, Image input) {
NDArray array = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
Pipeline pipeline = new Pipeline();
pipeline
//.add(new Resize(112))
.add(new ToTensor())
.add(new Normalize(
new float[]{0.5F, 0.5F, 0.5F},
new float[]{0.5F, 0.5F, 0.5F}));
return pipeline.transform(new NDList(array));
}
/**
* {@inheritDoc}
*/
@Override
public float[] processOutput(TranslatorContext ctx, NDList list) {
NDArray embedding = list.singletonOrThrow();
embedding = embedding.div(embedding.norm()); // L2归一化
return embedding.toFloatArray();
}
@Override
public Batchifier getBatchifier() {
return Batchifier.STACK;
}
}

View File

@@ -11,31 +11,35 @@ import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
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.LivenessStatus;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.preprocess.BufferedImagePreprocessor;
import cn.smartjavaai.common.preprocess.DJLImagePreprocessor;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.LivenessConfig;
import cn.smartjavaai.face.constant.MiniVisionConstant;
import cn.smartjavaai.face.entity.FaceQualityResult;
import cn.smartjavaai.face.enums.LivenessModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.factory.LivenessModelFactory;
import cn.smartjavaai.face.model.liveness.criterial.LivenessCriteriaFactory;
import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator;
import com.seeta.sdk.FaceAntiSpoofing;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.opencv.core.Mat;
import javax.imageio.ImageIO;
@@ -56,11 +60,19 @@ import java.util.*;
@Slf4j
public class CommonLivenessModel implements LivenessDetModel{
static {
//视频功能需要
OpenCV.loadLocally();
}
protected GenericObjectPool<Predictor<Image, Float>> predictorPool;
protected LivenessConfig config;
protected ZooModel<Image, Float> model;
private OpenCVFrameConverter.ToOrgOpenCvCoreMat converterToMat = null;
@Override
public void loadModel(LivenessConfig config) {
if(Objects.isNull(config)){
@@ -69,6 +81,9 @@ public class CommonLivenessModel implements LivenessDetModel{
if(StringUtils.isBlank(config.getModelPath())){
throw new FaceException("modelPath不能为空");
}
if(Objects.isNull(config.getDetectModel())){
throw new FaceException("未指定检测模型");
}
this.config = config;
//设置真人阈值
Float realityThreshold = Objects.isNull(config.getRealityThreshold()) ? MiniVisionConstant.REALITY_THRESHOLD : config.getRealityThreshold();
@@ -92,254 +107,6 @@ public class CommonLivenessModel implements LivenessDetModel{
}
@Override
public R<LivenessResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle) {
if(!ImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
Predictor<Image, Float> predictor = null;
Image djlImage = null;
try {
predictor = predictorPool.borrowObject();
//预处理图片
BufferedImage processedImage = image;
if(config.getModelEnum() == LivenessModelEnum.IIC_FL_MODEL){
processedImage = new BufferedImagePreprocessor(image, faceDetectionRectangle)
.setExtendRatio(96f / 112f)
.enableSquarePadding(true)
.enableScaling(true)
.setTargetSize(128)
.enableCenterCrop(true)
.setCenterCropSize(112)
.process();
}
djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(processedImage));
Float result = predictor.predict(djlImage);
if(result >= config.getRealityThreshold()){
return R.ok(new LivenessResult(LivenessStatus.LIVE, result));
}else{
float nonLiveScore = BigDecimal.ONE.subtract(new BigDecimal(result)).floatValue();
return R.ok(new LivenessResult(LivenessStatus.NON_LIVE, nonLiveScore));
}
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
}finally {
if (predictor != null) {
try {
predictorPool.returnObject(predictor); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
try {
predictor.close(); // 归还失败才销毁
} catch (Exception ex) {
log.error("关闭Predictor失败", ex);
}
}
}
if (djlImage != null){
((Mat)djlImage.getWrappedImage()).release();
}
}
}
@Override
public R<LivenessResult> detect(String imagePath, DetectionRectangle faceDetectionRectangle) {
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 detect(image, faceDetectionRectangle);
}
@Override
public R<LivenessResult> detect(byte[] imageData, DetectionRectangle faceDetectionRectangle) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionRectangle);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
@Override
public R<LivenessResult> detectBase64(String base64Image, DetectionRectangle faceDetectionRectangle) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detect(imageData, faceDetectionRectangle);
}
@Override
public R<List<LivenessResult>> detect(String imagePath, DetectionResponse faceDetectionResponse) {
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 detect(image, faceDetectionResponse);
}
@Override
public R<List<LivenessResult>> detect(byte[] imageData, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionResponse);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
@Override
public R<List<LivenessResult>> 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);
}
List<LivenessResult> livenessStatusList = new ArrayList<LivenessResult>();
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
R<LivenessResult> result = detect(image, detectionInfo.getDetectionRectangle());
if(!result.isSuccess()){
return R.fail(result.getCode(), result.getMessage());
}
livenessStatusList.add(result.getData());
}
return R.ok(livenessStatusList);
}
@Override
public R<List<LivenessResult>> detectBase64(String base64Image, DetectionResponse faceDetectionResponse) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detect(imageData, faceDetectionResponse);
}
@Override
public R<LivenessResult> detectTopFace(BufferedImage image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> 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);
}
return detect(image, faceDetectionResponse.getData().getDetectionInfoList().get(0).getDetectionRectangle());
}
@Override
public R<LivenessResult> 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<LivenessResult> 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<LivenessResult> detectTopFaceBase64(String base64Image) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detectTopFace(imageData);
}
@Override
public R<DetectionResponse> detect(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 detect(image);
}
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> 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()){
R<LivenessResult> result = detect(image, detectionInfo.getDetectionRectangle());
if(!result.isSuccess()){
return R.fail(result.getCode(), result.getMessage());
}
if(Objects.isNull(detectionInfo.getFaceInfo())){
detectionInfo.setFaceInfo(new FaceInfo());
}
detectionInfo.getFaceInfo().setLivenessStatus(result.getData());
}
return faceDetectionResponse;
}
@Override
public R<DetectionResponse> detect(byte[] imageData) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)));
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
@Override
public R<DetectionResponse> 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<LivenessResult> detectVideo(InputStream videoInputStream) {
@@ -376,20 +143,18 @@ public class CommonLivenessModel implements LivenessDetModel{
// 获取当前帧
Frame frame = grabber.grabImage();
if (frame != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
R<LivenessResult> livenessStatus = detectTopFace(bufferedImage);
if(!livenessStatus.isSuccess()){
log.debug("" + frameIndex + "帧处理失败:" + livenessStatus.getMessage());
if(converterToMat == null){
converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
}
Mat mat = converterToMat.convert(frame);
R<LivenessResult> livenessScore = detectTopFace(SmartImageFactory.getInstance().fromMat(mat));
mat.release();
if(!livenessScore.isSuccess()){
log.debug("" + frameIndex + "帧处理失败:" + livenessScore.getMessage());
continue;
}else{
log.debug("" + frameIndex + "帧活体检测结果:" + JsonUtils.toJson(livenessStatus));
float liveScore = 0;
if(livenessStatus.getData().getStatus() == LivenessStatus.LIVE){
liveScore = livenessStatus.getData().getScore();
}else{
liveScore = BigDecimal.ONE.subtract(BigDecimal.valueOf(livenessStatus.getData().getScore())).floatValue();
}
scoreWindow.add(liveScore);
log.debug("" + frameIndex + "帧活体检测结果:" + livenessScore);
scoreWindow.add(livenessScore.getData().getScore());
}
// 如果累计检测帧数 >= 配置值,开始判断
if (scoreWindow.size() >= config.getFrameCount()) {
@@ -398,14 +163,9 @@ public class CommonLivenessModel implements LivenessDetModel{
.average()
.orElse(0.0);
log.debug("滑动窗口平均得分: {}", avgScore);
if (avgScore >= config.getRealityThreshold()) {
grabber.stop();
return R.ok(new LivenessResult(LivenessStatus.LIVE, avgScore));
} else {
grabber.stop();
float nonLiveScore = BigDecimal.ONE.subtract(BigDecimal.valueOf(avgScore)).floatValue();
return R.ok(new LivenessResult(LivenessStatus.NON_LIVE, nonLiveScore));
}
grabber.stop();
LivenessStatus livenessStatus = avgScore > config.getRealityThreshold() ? LivenessStatus.LIVE : LivenessStatus.NON_LIVE;
return R.ok(new LivenessResult(livenessStatus, avgScore));
}
}
}
@@ -420,6 +180,97 @@ public class CommonLivenessModel implements LivenessDetModel{
}
@Override
public R<DetectionResponse> detect(Image image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> 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()){
R<LivenessResult> result = detect(image, detectionInfo.getDetectionRectangle());
if(!result.isSuccess()){
return R.fail(result.getCode(), result.getMessage());
}
if(Objects.isNull(detectionInfo.getFaceInfo())){
detectionInfo.setFaceInfo(new FaceInfo());
}
detectionInfo.getFaceInfo().setLivenessStatus(result.getData());
}
return faceDetectionResponse;
}
@Override
public R<List<LivenessResult>> detect(Image image, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
R.fail(R.Status.NO_FACE_DETECTED);
}
List<LivenessResult> livenessStatusList = new ArrayList<LivenessResult>();
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
R<LivenessResult> result = detect(image, detectionInfo.getDetectionRectangle());
if(!result.isSuccess()){
return R.fail(result.getCode(), result.getMessage());
}
livenessStatusList.add(result.getData());
}
return R.ok(livenessStatusList);
}
@Override
public R<LivenessResult> detect(Image image, DetectionRectangle faceDetectionRectangle) {
Predictor<Image, Float> predictor = null;
//预处理图片
Image processedImage = null;
try {
predictor = predictorPool.borrowObject();
if(config.getModelEnum() == LivenessModelEnum.IIC_FL_MODEL){
processedImage = new DJLImagePreprocessor(image, faceDetectionRectangle)
.setExtendRatio(96f / 112f)
.enableSquarePadding(true)
.enableScaling(true)
.setTargetSize(128)
.enableCenterCrop(true)
.setCenterCropSize(112)
.process();
}
Float result = null;
if(processedImage != null){
result = predictor.predict(processedImage);
ImageUtils.releaseOpenCVMat(processedImage);
}else{
result = predictor.predict(image);
}
LivenessStatus status = result >= config.getRealityThreshold() ? LivenessStatus.LIVE : LivenessStatus.NON_LIVE;
return R.ok(new LivenessResult(status, result));
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
}finally {
if (predictor != null) {
try {
predictorPool.returnObject(predictor); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
try {
predictor.close(); // 归还失败才销毁
} catch (Exception ex) {
log.error("关闭Predictor失败", ex);
}
}
}
}
}
@Override
public R<LivenessResult> detectTopFace(Image image) {
R<DetectionResponse> 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);
}
return detect(image, faceDetectionResponse.getData().getDetectionInfoList().get(0).getDetectionRectangle());
}
@Override
public GenericObjectPool<Predictor<Image, Float>> getPool() {
return predictorPool;
@@ -427,6 +278,9 @@ public class CommonLivenessModel implements LivenessDetModel{
@Override
public void close() throws Exception {
if (fromFactory) {
LivenessModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -441,6 +295,15 @@ public class CommonLivenessModel implements LivenessDetModel{
} catch (Exception e) {
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -26,254 +26,6 @@ public interface LivenessDetModel extends AutoCloseable{
*/
void loadModel(LivenessConfig config); // 加载模型
/**
* 活体检测(多人脸)
* @param imagePath 图片路径
* @return
*/
default R<DetectionResponse> detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param image BufferedImage
* @return
*/
default R<DetectionResponse> detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param imageData 图片字节流
* @return
*/
default R<DetectionResponse> detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param base64Image
* @return
*/
default R<DetectionResponse> detectBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param imagePath 图片路径
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default R<List<LivenessResult>> detect(String imagePath, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param imageData 图片数据
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default R<List<LivenessResult>> detect(byte[] imageData,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param image BufferedImage
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default R<List<LivenessResult>> detect(BufferedImage image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param base64Image
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default R<List<LivenessResult>> detectBase64(String base64Image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param imagePath 图片路径
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(String imagePath, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param imageData
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(byte[] imageData, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param image BufferedImage
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param base64Image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detectBase64(String base64Image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param imagePath 图片路径
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(String imagePath, DetectionRectangle faceDetectionRectangle){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param imageData
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(byte[] imageData, DetectionRectangle faceDetectionRectangle){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param image BufferedImage
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param base64Image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detectBase64(String base64Image, DetectionRectangle faceDetectionRectangle){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(分数最高人脸)
* @param image
* @return
*/
default R<LivenessResult> detectTopFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(分数最高人脸)
* @param imagePath
* @return
*/
default R<LivenessResult> detectTopFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(分数最高人脸)
* @param imageData
* @return
*/
default R<LivenessResult> detectTopFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(分数最高人脸)
* @param base64Image
* @return
*/
default R<LivenessResult> detectTopFaceBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 视频活体检测(逐帧检测)
* @param frameImage
* @param faceDetectionRectangle
* @return
*/
// default R<LivenessResult> detectVideoByFrame(BufferedImage frameImage, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
// throw new UnsupportedOperationException("默认不支持该功能");
// }
/**
* 视频活体检测(逐帧检测)
* @param frameData
* @param faceDetectionRectangle
* @return
*/
// default R<LivenessResult> detectVideoByFrame(byte[] frameData, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
// throw new UnsupportedOperationException("默认不支持该功能");
// }
/**
* 视频活体检测(逐帧检测)
* @param frameImageData
* @return
*/
// default R<LivenessResult> detectVideoByFrame(byte[] frameImageData){
// throw new UnsupportedOperationException("默认不支持该功能");
// }
/**
* 视频活体检测(逐帧检测)
* @param frameImageData
* @return
*/
// default R<LivenessResult> detectVideoByFrame(BufferedImage frameImageData){
// throw new UnsupportedOperationException("默认不支持该功能");
// }
/**
* 视频活体检测
* @param videoInputStream
@@ -293,6 +45,53 @@ public interface LivenessDetModel extends AutoCloseable{
}
/**
* 活体检测(多人脸)
* @param image
* @return
*/
default R<DetectionResponse> detect(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(多人脸)
* @param image
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default R<List<LivenessResult>> detect(Image image, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(单人脸)
* @param image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default R<LivenessResult> detect(Image image, DetectionRectangle faceDetectionRectangle){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 活体检测(分数最高人脸)
* @param image
* @return
*/
default R<LivenessResult> detectTopFace(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, Float>> getPool() {
@@ -300,5 +99,10 @@ public interface LivenessDetModel extends AutoCloseable{
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -12,20 +12,18 @@ import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.entity.face.LivenessResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.LivenessStatus;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.preprocess.BufferedImagePreprocessor;
import cn.smartjavaai.common.utils.ArrayUtils;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.preprocess.DJLImagePreprocessor;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.LivenessConfig;
import cn.smartjavaai.face.constant.MiniVisionConstant;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.LivenessModelFactory;
import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator;
import cn.smartjavaai.common.utils.OpenCVUtils;
import com.seeta.sdk.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -151,10 +149,7 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
@Override
public R<LivenessResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle) {
if(!ImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
public R<LivenessResult> detect(Image image, DetectionRectangle faceDetectionRectangle) {
Predictor<Image, float[]> predictor = null;
Predictor<Image, float[]> sePredictor = null;
try {
@@ -162,30 +157,27 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
float[] seResult = null;
if(Objects.nonNull(predictorPool)){
//预处理图片
BufferedImage processedImage = new BufferedImagePreprocessor(image, faceDetectionRectangle)
Image processedImage = new DJLImagePreprocessor(image, faceDetectionRectangle)
.setExtendRatio(2.7f)
.enableSquarePadding(true)
.enableScaling(true)
.setTargetSize(80)
.process();
predictor = predictorPool.borrowObject();
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(processedImage));
result = predictor.predict(djlImage);
((Mat)djlImage.getWrappedImage()).release();
result = predictor.predict(processedImage);
ImageUtils.releaseOpenCVMat(processedImage);
}
if(Objects.nonNull(sePredictorPool)){
//预处理图片
BufferedImage processedImage = new BufferedImagePreprocessor(image, faceDetectionRectangle)
Image processedImage = new DJLImagePreprocessor(image, faceDetectionRectangle)
.setExtendRatio(4)
.enableSquarePadding(true)
.enableScaling(true)
.setTargetSize(80)
.process();
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(processedImage));
sePredictor = sePredictorPool.borrowObject();
seResult = sePredictor.predict(djlImage);
((Mat)djlImage.getWrappedImage()).release();
seResult = sePredictor.predict(processedImage);
ImageUtils.releaseOpenCVMat(processedImage);
}
if(Objects.isNull(result) && Objects.isNull(seResult)){
throw new FaceException("活体检测错误");
@@ -195,15 +187,12 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
BigDecimal score = Objects.isNull(result) ? BigDecimal.ZERO : BigDecimal.valueOf(result[maxIndex]);
BigDecimal seScore = Objects.isNull(seResult) ? BigDecimal.ZERO : BigDecimal.valueOf(seResult[maxIndex]);
BigDecimal avgSocre = score.add(seScore).divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP);
//活体
if(maxIndex == 1){
if(avgSocre.floatValue() >= config.getRealityThreshold()){
return R.ok(new LivenessResult(LivenessStatus.LIVE, avgSocre.floatValue()));
}else{
float nonLiveScore = BigDecimal.ONE.subtract(avgSocre).floatValue();
return R.ok(new LivenessResult(LivenessStatus.NON_LIVE, nonLiveScore));
}
}else{
return R.ok(new LivenessResult(LivenessStatus.NON_LIVE, avgSocre.floatValue()));
LivenessStatus livenessStatus = avgSocre.floatValue() > config.getRealityThreshold() ? LivenessStatus.LIVE : LivenessStatus.NON_LIVE;
return R.ok(new LivenessResult(livenessStatus, avgSocre.floatValue()));
}else{//非活体
return R.ok(new LivenessResult(LivenessStatus.NON_LIVE, BigDecimal.ONE.subtract(avgSocre).floatValue()));
}
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
@@ -245,6 +234,9 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
@Override
public void close() throws Exception {
if (fromFactory) {
LivenessModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -284,4 +276,14 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
MINIFASNET_V1_SE,
FUSION // 融合模型
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -1,21 +1,27 @@
package cn.smartjavaai.face.model.liveness;
import ai.djl.engine.Engine;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.entity.face.LivenessResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.LivenessConfig;
import cn.smartjavaai.common.enums.face.LivenessStatus;
import cn.smartjavaai.face.constant.LivenessConstant;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.LivenessModelFactory;
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;
import nu.pattern.OpenCV;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
@@ -44,6 +50,11 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
private LivenessConfig config;
static {
//视频功能需要
OpenCV.loadLocally();
}
@Override
public void loadModel(LivenessConfig config) {
if(StringUtils.isBlank(config.getModelPath())){
@@ -129,10 +140,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
}
private R<LivenessResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints, boolean isImage) {
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
private R<LivenessResult> detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints, boolean isImage) {
if(Objects.isNull(faceDetectionRectangle)){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -145,8 +153,8 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
faceAntiSpoofing = faceAntiSpoofingPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(faceDetectionRectangle);
SeetaPointF[] landmarks = FaceUtils.convertToSeetaPointF(keyPoints);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(faceDetectionRectangle);
SeetaPointF[] landmarks = Seetaface6Utils.convertToSeetaPointF(keyPoints);
//检测图片
if(isImage){
status = faceAntiSpoofing.Predict(imageData, seetaRect, landmarks);
@@ -154,7 +162,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
//检测视频
status = faceAntiSpoofing.PredictVideo(imageData, seetaRect, landmarks);
}
return R.ok(new LivenessResult(FaceUtils.convertToLivenessStatus(status)));
return R.ok(new LivenessResult(Seetaface6Utils.convertToLivenessStatus(status)));
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
} finally {
@@ -168,38 +176,61 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
}
}
@Override
public R<DetectionResponse> detect(String imagePath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
// 将图片路径转换为 BufferedImage
BufferedImage image = null;
private R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
FaceAntiSpoofing faceAntiSpoofing = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
faceAntiSpoofing = faceAntiSpoofingPool.borrowObject();
//重置视频
faceAntiSpoofing.ResetVideo();
grabber.start();
// 获取视频总帧数
int totalFrames = grabber.getLengthInFrames();
int videoFrameCountConfig = faceAntiSpoofing.GetVideoFrameCount();
log.debug("视频总帧数:{},检测帧数:{}", totalFrames, videoFrameCountConfig);
if(totalFrames < videoFrameCountConfig){
return R.fail(1001, "视频帧数低于检测帧数");
}
// 逐帧处理视频
for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
if(frameIndex >= config.getMaxVideoDetectFrames()){
return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames());
}
// 获取当前帧
Frame frame = grabber.grabImage();
if (frame != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
Image image = SmartImageFactory.getInstance().fromBufferedImage(bufferedImage);
R<LivenessResult> livenessStatus = detectTopFace(image, false);
if(!livenessStatus.isSuccess()){
log.debug("" + frameIndex + "帧处理失败:" + livenessStatus.getMessage());
continue;
}
//满足检测帧数之后停止检测
if(livenessStatus.getData().getStatus() != LivenessStatus.DETECTING){
return livenessStatus;
}
}
}
grabber.stop();
} catch (FFmpegFrameGrabber.Exception e) {
throw new FaceException(e);
} catch (Exception e) {
throw new FaceException(e);
} finally {
if (faceAntiSpoofing != null) {
try {
faceAntiSpoofingPool.returnObject(faceAntiSpoofing);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
return detect(image);
return R.fail(1000, "有效帧数量不足,无法完成活体检测");
}
@Override
public R<DetectionResponse> detect(byte[] imageData) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)));
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
public R<DetectionResponse> detect(Image image) {
FaceAntiSpoofing faceAntiSpoofing = null;
FaceLandmarker faceLandmarker = null;
FaceDetector detectPredictor = null;
@@ -224,9 +255,9 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
seetaPointFSList.add(landmarks);
//检测图片
FaceAntiSpoofing.Status status = faceAntiSpoofing.Predict(imageData, seetaRect, landmarks);
livenessStatusList.add(FaceUtils.convertToLivenessStatus(status));
livenessStatusList.add(Seetaface6Utils.convertToLivenessStatus(status));
}
return R.ok(FaceUtils.convertToDetectionResponse(seetaResult, seetaPointFSList, livenessStatusList));
return R.ok(Seetaface6Utils.convertToDetectionResponse(seetaResult, seetaPointFSList, livenessStatusList));
}else{
R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image);
if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
@@ -272,26 +303,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
}
@Override
public R<List<LivenessResult>> detect(String imagePath, DetectionResponse faceDetectionResponse) {
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 detect(image, faceDetectionResponse);
}
@Override
public R<List<LivenessResult>> detect(BufferedImage image, DetectionResponse faceDetectionResponse) {
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
public R<List<LivenessResult>> detect(Image image, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -310,73 +322,17 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
return R.ok(livenessStatusList);
}
@Override
public R<List<LivenessResult>> detect(byte[] imageData, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionResponse);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
@Override
public R<LivenessResult> detect(String imagePath, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
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 detect(image, faceDetectionRectangle, keyPoints);
}
@Override
public R<LivenessResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
public R<LivenessResult> detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
return detect(image, faceDetectionRectangle, keyPoints, true);
}
@Override
public R<LivenessResult> detect(byte[] imageData, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionRectangle, keyPoints);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
public R<LivenessResult> detectTopFace(Image image) {
return detectTopFace(image, true);
}
@Override
public R<LivenessResult> 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);
}
private R<LivenessResult> detectTopFace(BufferedImage image, boolean isImage) {
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
private R<LivenessResult> detectTopFace(Image image, boolean isImage) {
FaceAntiSpoofing faceAntiSpoofing = null;
FaceLandmarker faceLandmarker = null;
FaceDetector detectPredictor = null;
@@ -402,14 +358,15 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
}else{
status = faceAntiSpoofing.PredictVideo(imageData, seetaResult[0], landmarks);
}
return R.ok(new LivenessResult(FaceUtils.convertToLivenessStatus(status)));
return R.ok(new LivenessResult(Seetaface6Utils.convertToLivenessStatus(status)));
}else{
R<DetectionResponse> 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);
}
DetectionInfo detectionInfo = faceDetectionResponse.getData().getDetectionInfoList().get(0);
return detect(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getKeyPoints(), isImage);
R<LivenessResult> detectionResponseR = detect(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getKeyPoints(), isImage);
return detectionResponseR;
}
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
@@ -438,58 +395,6 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
}
}
@Override
public R<LivenessResult> detectTopFace(BufferedImage image) {
return detectTopFace(image, true);
}
@Override
public R<LivenessResult> 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);
}
}
public R<LivenessResult> detectVideoByFrame(BufferedImage frameImage, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
if(!ImageUtils.isImageValid(frameImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
return detect(frameImage,faceDetectionRectangle, keyPoints,false);
}
public R<LivenessResult> detectVideoByFrame(byte[] frameData, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
if(Objects.isNull(frameData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detect(ImageIO.read(new ByteArrayInputStream(frameData)), faceDetectionRectangle, keyPoints, false);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
public R<LivenessResult> detectVideoByFrame(byte[] frameImageData) {
if(Objects.isNull(frameImageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detectVideoByFrame(ImageIO.read(new ByteArrayInputStream(frameImageData)));
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
public R<LivenessResult> detectVideoByFrame(BufferedImage frameImageData) {
return detectTopFace(frameImageData, false);
}
@Override
public R<LivenessResult> detectVideo(InputStream videoInputStream) {
if(Objects.isNull(videoInputStream)){
@@ -506,57 +411,6 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
return detectVideo(new FFmpegFrameGrabber(videoPath));
}
private R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
FaceAntiSpoofing faceAntiSpoofing = null;
try {
faceAntiSpoofing = faceAntiSpoofingPool.borrowObject();
//重置视频
faceAntiSpoofing.ResetVideo();
grabber.start();
// 获取视频总帧数
int totalFrames = grabber.getLengthInFrames();
int videoFrameCountConfig = faceAntiSpoofing.GetVideoFrameCount();
log.debug("视频总帧数:{},检测帧数:{}", totalFrames, videoFrameCountConfig);
if(totalFrames < videoFrameCountConfig){
return R.fail(1001, "视频帧数低于检测帧数");
}
// 逐帧处理视频
for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
if(frameIndex >= config.getMaxVideoDetectFrames()){
return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames());
}
// 获取当前帧
Frame frame = grabber.grabImage();
if (frame != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
R<LivenessResult> livenessStatus = detectVideoByFrame(bufferedImage);
if(!livenessStatus.isSuccess()){
log.debug("" + frameIndex + "帧处理失败:" + livenessStatus.getMessage());
continue;
}
//满足检测帧数之后停止检测
if(livenessStatus.getData().getStatus() != LivenessStatus.DETECTING){
return livenessStatus;
}
}
}
grabber.stop();
} catch (FFmpegFrameGrabber.Exception e) {
throw new FaceException(e);
} catch (Exception e) {
throw new FaceException(e);
} finally {
if (faceAntiSpoofing != null) {
try {
faceAntiSpoofingPool.returnObject(faceAntiSpoofing);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
return R.fail(1000, "有效帧数量不足,无法完成活体检测");
}
public FaceDetectorPool getFaceDetectorPool() {
return faceDetectorPool;
}
@@ -571,6 +425,9 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
@Override
public void close() throws Exception {
if (fromFactory) {
LivenessModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (faceDetectorPool != null) {
faceDetectorPool.close();
@@ -593,4 +450,14 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
log.warn("关闭 predictorPool 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -12,7 +12,6 @@ import cn.smartjavaai.face.config.LivenessConfig;
import cn.smartjavaai.face.constant.FaceNetConstant;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.enums.LivenessModelEnum;
import cn.smartjavaai.face.model.facerec.translator.FaceNetRecTranslator;
import cn.smartjavaai.face.model.liveness.translator.IicFrTranslator;
import org.apache.commons.lang3.StringUtils;

View File

@@ -1,5 +1,6 @@
package cn.smartjavaai.face.model.quality;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.R;
@@ -31,6 +32,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateBrightness(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -42,6 +44,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateBrightness(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -53,6 +56,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateBrightness(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -65,6 +69,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateClarity(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -76,6 +81,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateClarity(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -87,6 +93,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateClarity(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -98,6 +105,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateCompleteness(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -109,6 +117,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateCompleteness(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -120,6 +129,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateCompleteness(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -131,6 +141,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluatePose(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -142,6 +153,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluatePose(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -153,6 +165,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluatePose(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -164,6 +177,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateResolution(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -175,6 +189,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateResolution(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -186,6 +201,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualityResult> evaluateResolution(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -198,6 +214,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualitySummary> evaluateAll(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -209,6 +226,7 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualitySummary> evaluateAll(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -221,9 +239,80 @@ public interface FaceQualityModel extends AutoCloseable{
* @param keyPoints
* @return
*/
@Deprecated
default R<FaceQualitySummary> evaluateAll(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 亮度评估
* @param image
* @param rectangle
* @param keyPoints
* @return
*/
default R<FaceQualityResult> evaluateBrightness(Image image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 清晰度评估
* @param image
* @param rectangle
* @param keyPoints
* @return
*/
default R<FaceQualityResult> evaluateClarity(Image image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 完整度评估
* @param image
* @param rectangle
* @param keyPoints
* @return
*/
default R<FaceQualityResult> evaluateCompleteness(Image image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸姿态评估
* @param image
* @param rectangle
* @param keyPoints
* @return
*/
default R<FaceQualityResult> evaluatePose(Image image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸分辨率评估
* @param image
* @param rectangle
* @param keyPoints
* @return
*/
default R<FaceQualityResult> evaluateResolution(Image image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 评估所有
* @param image
* @param rectangle
* @param keyPoints
* @return
*/
default R<FaceQualitySummary> evaluateAll(Image image, DetectionRectangle rectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -1,8 +1,12 @@
package cn.smartjavaai.face.model.quality;
import ai.djl.engine.Engine;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.ExpressionResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.PoolUtils;
@@ -11,9 +15,12 @@ import cn.smartjavaai.face.entity.FaceQualityResult;
import cn.smartjavaai.face.entity.FaceQualitySummary;
import cn.smartjavaai.face.enums.QualityGrade;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceQualityModelFactory;
import cn.smartjavaai.face.factory.LivenessModelFactory;
import cn.smartjavaai.face.seetaface.ClarityDLResult;
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;
@@ -98,42 +105,10 @@ public class Seetaface6QualityModel implements FaceQualityModel {
@Override
public R<FaceQualityResult> evaluateBrightness(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfBrightness qualityOfBrightness = null;
try {
if(Objects.isNull(this.qualityOfBrightnessPool)){
this.qualityOfBrightnessPool = new QualityOfBrightnessPool(new SeetaConfSetting());
qualityOfBrightnessPool.setMaxTotal(predictorPoolSize);
}
qualityOfBrightness = qualityOfBrightnessPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfBrightness.QualityLevel level = qualityOfBrightness.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("亮度评估错误", e);
} finally {
if (qualityOfBrightness != null) {
try {
qualityOfBrightnessPool.returnObject(qualityOfBrightness);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<FaceQualityResult> detectionResponseR = evaluateBrightness(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -141,14 +116,16 @@ public class Seetaface6QualityModel implements FaceQualityModel {
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<FaceQualityResult> detectionResponseR = evaluateBrightness(image, rectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return evaluateBrightness(image, rectangle, keyPoints);
}
@Override
@@ -156,51 +133,23 @@ public class Seetaface6QualityModel implements FaceQualityModel {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return evaluateBrightness(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<FaceQualityResult> detectionResponseR = evaluateBrightness(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<FaceQualityResult> evaluateClarity(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfClarity qualityOfClarity = null;
try {
if(Objects.isNull(this.qualityOfClarityPool)){
this.qualityOfClarityPool = new QualityOfClarityPool(new SeetaConfSetting());
qualityOfClarityPool.setMaxTotal(predictorPoolSize);
}
qualityOfClarity = qualityOfClarityPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfClarity.QualityLevel level = qualityOfClarity.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("清晰度评估错误", e);
} finally {
if (qualityOfClarity != null) {
try {
qualityOfClarityPool.returnObject(qualityOfClarity);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<FaceQualityResult> detectionResponseR = evaluateClarity(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -208,14 +157,16 @@ public class Seetaface6QualityModel implements FaceQualityModel {
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<FaceQualityResult> detectionResponseR = evaluateClarity(image, rectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return evaluateClarity(image, rectangle, keyPoints);
}
@Override
@@ -223,51 +174,23 @@ public class Seetaface6QualityModel implements FaceQualityModel {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return evaluateClarity(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<FaceQualityResult> detectionResponseR = evaluateClarity(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<FaceQualityResult> evaluateCompleteness(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfIntegrity qualityOfIntegrity = null;
try {
if(Objects.isNull(this.qualityOfIntegrityPool)){
this.qualityOfIntegrityPool = new QualityOfIntegrityPool(new SeetaConfSetting());
qualityOfIntegrityPool.setMaxTotal(predictorPoolSize);
}
qualityOfIntegrity = qualityOfIntegrityPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfIntegrity.QualityLevel level = qualityOfIntegrity.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("完整度评估错误", e);
} finally {
if (qualityOfIntegrity != null) {
try {
qualityOfIntegrityPool.returnObject(qualityOfIntegrity);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<FaceQualityResult> detectionResponseR = evaluateCompleteness(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -275,14 +198,16 @@ public class Seetaface6QualityModel implements FaceQualityModel {
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<FaceQualityResult> detectionResponseR = evaluateCompleteness(image, rectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return evaluateCompleteness(image, rectangle, keyPoints);
}
@Override
@@ -290,51 +215,23 @@ public class Seetaface6QualityModel implements FaceQualityModel {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return evaluateCompleteness(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<FaceQualityResult> detectionResponseR = evaluateCompleteness(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<FaceQualityResult> evaluatePose(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfPose qualityOfPose = null;
try {
if(Objects.isNull(this.qualityOfPosePool)){
this.qualityOfPosePool = new QualityOfPosePool(new SeetaConfSetting());
qualityOfPosePool.setMaxTotal(predictorPoolSize);
}
qualityOfPose = qualityOfPosePool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfPose.QualityLevel level = qualityOfPose.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("姿态评估错误", e);
} finally {
if (qualityOfPose != null) {
try {
qualityOfPosePool.returnObject(qualityOfPose);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<FaceQualityResult> detectionResponseR = evaluatePose(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -342,14 +239,16 @@ public class Seetaface6QualityModel implements FaceQualityModel {
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<FaceQualityResult> detectionResponseR = evaluatePose(image, rectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return evaluatePose(image, rectangle, keyPoints);
}
@Override
@@ -357,51 +256,23 @@ public class Seetaface6QualityModel implements FaceQualityModel {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return evaluatePose(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<FaceQualityResult> detectionResponseR = evaluatePose(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<FaceQualityResult> evaluateResolution(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfResolution qualityOfResolution = null;
try {
if(Objects.isNull(this.qualityOfResolutionPool)){
this.qualityOfResolutionPool = new QualityOfResolutionPool(new SeetaConfSetting());
qualityOfResolutionPool.setMaxTotal(predictorPoolSize);
}
qualityOfResolution = qualityOfResolutionPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfResolution.QualityLevel level = qualityOfResolution.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("姿态评估错误", e);
} finally {
if (qualityOfResolution != null) {
try {
qualityOfResolutionPool.returnObject(qualityOfResolution);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<FaceQualityResult> detectionResponseR = evaluateResolution(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -409,14 +280,16 @@ public class Seetaface6QualityModel implements FaceQualityModel {
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<FaceQualityResult> detectionResponseR = evaluateResolution(image, rectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return evaluateResolution(image, rectangle, keyPoints);
}
@Override
@@ -424,17 +297,21 @@ public class Seetaface6QualityModel implements FaceQualityModel {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return evaluateResolution(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<FaceQualityResult> detectionResponseR = evaluateResolution(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
public R<ClarityDLResult> evaluateClarityWithDL(BufferedImage image, List<Point> keyPoints) {
public R<ClarityDLResult> evaluateClarityWithDL(Image image, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
@@ -456,7 +333,7 @@ public class Seetaface6QualityModel implements FaceQualityModel {
qualityOfLBN = qualityOfLBNPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
int[] light = new int[1];
int[] blur = new int[1];
int[] noise = new int[1];
@@ -476,34 +353,9 @@ public class Seetaface6QualityModel implements FaceQualityModel {
}
public R<ClarityDLResult> evaluateClarityWithDL(String imagePath, List<Point> keyPoints) {
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 evaluateClarityWithDL(image, keyPoints);
}
public R<ClarityDLResult> evaluateClarityWithDL(byte[] imageData, List<Point> keyPoints) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return evaluateClarityWithDL(ImageIO.read(new ByteArrayInputStream(imageData)), keyPoints);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
public R<FaceQualityResult> evaluatePoseWithDL(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
public R<FaceQualityResult> evaluatePoseWithDL(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
@@ -528,8 +380,8 @@ public class Seetaface6QualityModel implements FaceQualityModel {
qualityOfPoseEx = qualityOfPoseExPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
float[] scores = new float[1];
QualityOfPoseEx.QualityLevel level = qualityOfPoseEx.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
@@ -547,30 +399,6 @@ public class Seetaface6QualityModel implements FaceQualityModel {
}
}
public R<FaceQualityResult> evaluatePoseWithDL(String imagePath, DetectionRectangle rectangle, List<Point> keyPoints) {
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 evaluatePoseWithDL(image, rectangle, keyPoints);
}
public R<FaceQualityResult> evaluatePoseWithDL(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return evaluatePoseWithDL(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
/**
* 获取清晰度模型配置(深度学习)
@@ -613,20 +441,246 @@ public class Seetaface6QualityModel implements FaceQualityModel {
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<FaceQualitySummary> detectionResponseR = evaluateAll(image, rectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return evaluateAll(image, rectangle, keyPoints);
}
@Override
public R<FaceQualitySummary> evaluateAll(BufferedImage image, DetectionRectangle rectangle, List<Point> keyPoints) {
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<FaceQualitySummary> detectionResponseR = evaluateAll(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<FaceQualitySummary> evaluateAll(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints) {
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<FaceQualitySummary> detectionResponseR = evaluateAll(imageDjl, rectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<FaceQualityResult> evaluateBrightness(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(!ImageUtils.isImageValid(image)){
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfBrightness qualityOfBrightness = null;
try {
if(Objects.isNull(this.qualityOfBrightnessPool)){
this.qualityOfBrightnessPool = new QualityOfBrightnessPool(new SeetaConfSetting());
qualityOfBrightnessPool.setMaxTotal(predictorPoolSize);
}
qualityOfBrightness = qualityOfBrightnessPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfBrightness.QualityLevel level = qualityOfBrightness.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("亮度评估错误", e);
} finally {
if (qualityOfBrightness != null) {
try {
qualityOfBrightnessPool.returnObject(qualityOfBrightness);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<FaceQualityResult> evaluateClarity(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfClarity qualityOfClarity = null;
try {
if(Objects.isNull(this.qualityOfClarityPool)){
this.qualityOfClarityPool = new QualityOfClarityPool(new SeetaConfSetting());
qualityOfClarityPool.setMaxTotal(predictorPoolSize);
}
qualityOfClarity = qualityOfClarityPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfClarity.QualityLevel level = qualityOfClarity.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("清晰度评估错误", e);
} finally {
if (qualityOfClarity != null) {
try {
qualityOfClarityPool.returnObject(qualityOfClarity);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<FaceQualityResult> evaluateCompleteness(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfIntegrity qualityOfIntegrity = null;
try {
if(Objects.isNull(this.qualityOfIntegrityPool)){
this.qualityOfIntegrityPool = new QualityOfIntegrityPool(new SeetaConfSetting());
qualityOfIntegrityPool.setMaxTotal(predictorPoolSize);
}
qualityOfIntegrity = qualityOfIntegrityPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfIntegrity.QualityLevel level = qualityOfIntegrity.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("完整度评估错误", e);
} finally {
if (qualityOfIntegrity != null) {
try {
qualityOfIntegrityPool.returnObject(qualityOfIntegrity);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<FaceQualityResult> evaluatePose(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfPose qualityOfPose = null;
try {
if(Objects.isNull(this.qualityOfPosePool)){
this.qualityOfPosePool = new QualityOfPosePool(new SeetaConfSetting());
qualityOfPosePool.setMaxTotal(predictorPoolSize);
}
qualityOfPose = qualityOfPosePool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfPose.QualityLevel level = qualityOfPose.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("姿态评估错误", e);
} finally {
if (qualityOfPose != null) {
try {
qualityOfPosePool.returnObject(qualityOfPose);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<FaceQualityResult> evaluateResolution(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
return R.fail(R.Status.PARAM_ERROR.getCode(), "rectangle为空");
}
if(Objects.isNull(keyPoints) || keyPoints.isEmpty()){
return R.fail(R.Status.PARAM_ERROR.getCode(), "keyPoints为空");
}
QualityOfResolution qualityOfResolution = null;
try {
if(Objects.isNull(this.qualityOfResolutionPool)){
this.qualityOfResolutionPool = new QualityOfResolutionPool(new SeetaConfSetting());
qualityOfResolutionPool.setMaxTotal(predictorPoolSize);
}
qualityOfResolution = qualityOfResolutionPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
float[] scores = new float[1];
QualityOfResolution.QualityLevel level = qualityOfResolution.check(imageData, seetaRect, pointFS, scores);
FaceQualityResult result = new FaceQualityResult(scores[0], QualityGrade.valueOf(level.name()));
return R.ok(result);
} catch (Exception e) {
throw new FaceException("姿态评估错误", e);
} finally {
if (qualityOfResolution != null) {
try {
qualityOfResolutionPool.returnObject(qualityOfResolution);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<FaceQualitySummary> evaluateAll(Image image, DetectionRectangle rectangle, List<Point> keyPoints) {
//参数检查
if(Objects.isNull(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(rectangle)){
@@ -669,8 +723,8 @@ public class Seetaface6QualityModel implements FaceQualityModel {
qualityOfResolution = qualityOfResolutionPool.borrowObject();
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = FaceUtils.convertToSeetaPointF(keyPoints);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(rectangle);
SeetaPointF[] pointFS = Seetaface6Utils.convertToSeetaPointF(keyPoints);
float[] scoresBrightness = new float[1];
QualityOfBrightness.QualityLevel level = qualityOfBrightness.check(imageData, seetaRect, pointFS, scoresBrightness);
summary.setBrightness(new FaceQualityResult(scoresBrightness[0], QualityGrade.valueOf(level.name())));
@@ -698,18 +752,6 @@ public class Seetaface6QualityModel implements FaceQualityModel {
}
}
@Override
public R<FaceQualitySummary> evaluateAll(byte[] imageData, DetectionRectangle rectangle, List<Point> keyPoints) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return evaluateAll(ImageIO.read(new ByteArrayInputStream(imageData)), rectangle, keyPoints);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
public QualityOfBrightnessPool getQualityOfBrightnessPool() {
return qualityOfBrightnessPool;
}
@@ -740,6 +782,9 @@ public class Seetaface6QualityModel implements FaceQualityModel {
@Override
public void close() throws Exception {
if (fromFactory) {
FaceQualityModelFactory.removeFromCache(config.getModelEnum());
}
if(Objects.nonNull(qualityOfBrightnessPool)){
qualityOfBrightnessPool.close();
}
@@ -762,4 +807,14 @@ public class Seetaface6QualityModel implements FaceQualityModel {
qualityOfResolutionPool.close();
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -3,13 +3,14 @@ package cn.smartjavaai.face.preprocess;
import ai.djl.modality.cv.Image;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.opencv.OpenCVImageFactory;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.face.utils.FaceAlignUtils;
import cn.smartjavaai.face.utils.FaceUtils;
import org.opencv.core.Mat;
import java.awt.image.BufferedImage;
import java.util.Objects;
/**
@@ -17,7 +18,7 @@ import java.util.Objects;
* @author dwj
* @date 2025/6/27
*/
public class DJLImagePreprocessor {
public class DJLImageFacePreprocessor {
private Image image;
@@ -33,20 +34,20 @@ public class DJLImagePreprocessor {
private int affineTargetWidth;
private int affineTargetHeight;
public DJLImagePreprocessor(Image image, NDManager manager) {
public DJLImageFacePreprocessor(Image image, NDManager manager) {
this.image = image;
this.manager = manager;
}
// 启用裁剪
public DJLImagePreprocessor enableCrop(DetectionRectangle rect) {
public DJLImageFacePreprocessor enableCrop(DetectionRectangle rect) {
this.enableCrop = true;
this.cropRect = rect;
return this;
}
// 启用仿射变换
public DJLImagePreprocessor enableAffine(double[][] keyPoints, int targetWidth, int targetHeight) {
public DJLImageFacePreprocessor enableAffine(double[][] keyPoints, int targetWidth, int targetHeight) {
if(Objects.isNull(keyPoints)){
throw new IllegalArgumentException("keyPoints must be not null");
}
@@ -83,8 +84,11 @@ public class DJLImagePreprocessor {
}
// 5点仿射变换
Mat affine_matrix = OpenCVUtils.toOpenCVMat(manager, srcPoints, dstPoints);
Mat mat = FaceAlignUtils.warpAffine((Mat) image.getWrappedImage(), affine_matrix, width, height);
Image alignedImg = OpenCVImageFactory.getInstance().fromImage(mat);
Object imageObj = image.getWrappedImage();
Mat src = image.getWrappedImage() instanceof Mat ? (Mat) imageObj : OpenCVUtils.image2Mat((BufferedImage) imageObj);
Mat mat = FaceAlignUtils.warpAffine(src, affine_matrix, width, height);
Image alignedImg = SmartImageFactory.getInstance().fromMat(mat);
affine_matrix.release();
return alignedImg;
}

View File

@@ -4,6 +4,7 @@ import ai.djl.modality.cv.Image;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.opencv.OpenCVImageFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import com.seeta.sdk.SeetaImageData;
@@ -56,7 +57,7 @@ public class FaceAlignUtils {
public static SeetaImageData faceAlign(BufferedImage sourceImage, SeetaPointF[] pointFS) {
NDManager manager = NDManager.newBaseManager();
//获取子图中人脸关键点坐标
double[][] pointsArray = FaceUtils.facePoints(pointFS);
double[][] pointsArray = Seetaface6Utils.facePoints(pointFS);
NDArray srcPoints = manager.create(pointsArray);
NDArray dstPoints = FaceUtils.faceTemplate512x512(manager);
// 5点仿射变换
@@ -64,7 +65,7 @@ public class FaceAlignUtils {
Mat mat = FaceAlignUtils.warpAffine(OpenCVUtils.image2Mat(sourceImage), affine_matrix);
BufferedImage alignImage = OpenCVUtils.mat2Image(mat);
SeetaImageData imageData = new SeetaImageData(alignImage.getWidth(), alignImage.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(alignImage);
imageData.data = BufferedImageUtils.getMatrixBGR(alignImage);
return imageData;
}
}

View File

@@ -3,8 +3,10 @@ package cn.smartjavaai.face.utils;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.output.Landmark;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.ndarray.types.DataType;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.face.FaceAttribute;
@@ -13,9 +15,12 @@ import cn.smartjavaai.common.entity.face.HeadPose;
import cn.smartjavaai.common.entity.face.LivenessResult;
import cn.smartjavaai.common.enums.face.EyeStatus;
import cn.smartjavaai.common.enums.face.GenderType;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.Graphics2DUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.enums.face.LivenessStatus;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.facedect.mtcnn.MtcnnBatchResult;
import com.seeta.sdk.*;
import javax.imageio.ImageIO;
@@ -158,101 +163,7 @@ public class FaceUtils {
return new DetectionResponse(detectionInfoList);
}
/**
* 绘制人脸框
* @param sourceImage
* @param detectionResponse
* @param savePath
* @throws IOException
*/
public static void drawBoundingBoxes(BufferedImage sourceImage, DetectionResponse detectionResponse, String savePath) throws IOException {
if(!ImageUtils.isImageValid(sourceImage)){
throw new FaceException("图像无效");
}
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
throw new FaceException("无目标数据");
}
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());
String className = "face";
if (detectionInfo.getScore() > 0){
int percent = (int) Math.round(detectionInfo.getScore() * 100);
className = "face " + percent + "%";
}
drawText(graphics, className , rectangle.getX(), rectangle.getY(), stroke, 4);
//绘制人脸关键点
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
}
}
graphics.dispose();
ImageIO.write(sourceImage, "png", new File(savePath));
}
/**
* 绘制人脸框
* @param sourceImage
* @param detectionResponse
* @throws IOException
*/
public static BufferedImage drawBoundingBoxes(BufferedImage sourceImage, DetectionResponse detectionResponse) throws IOException {
if(!ImageUtils.isImageValid(sourceImage)){
throw new FaceException("图像无效");
}
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
throw new FaceException("无目标数据");
}
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());
drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
//绘制人脸关键点
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
}
}
graphics.dispose();
return sourceImage;
}
/**
* 绘制文字
* @param g
* @param text
* @param x
* @param y
* @param stroke
* @param padding
*/
private 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();
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);
}
/**
* 修正检测框
@@ -306,28 +217,7 @@ public class FaceUtils {
return pointsArray;
}
/**
* 子图中人脸关键点坐标 - Coordinates of key points in the image
*
* @param pointFS
* @return
*/
public static double[][] facePoints(SeetaPointF[] pointFS) {
// 图中关键点坐标 - Coordinates of key points in the image
// 1. left_eye_x , left_eye_y
// 2. right_eye_x , right_eye_y
// 3. nose_x , nose_y
// 4. left_mouth_x , left_mouth_y
// 5. right_mouth_x , right_mouth_y
double[][] pointsArray = new double[5][2]; // 保存人脸关键点 - Save facial key points
int i = 0;
for (SeetaPointF point : pointFS) {
pointsArray[i][0] = point.getX();
pointsArray[i][1] = point.getY();
i++;
}
return pointsArray;
}
/**
* 512x512的目标点 - Target point of 512x512
@@ -386,180 +276,6 @@ public class FaceUtils {
return points;
}
/**
* bgr转图片
* @return 图片
*/
public static BufferedImage toBufferedImage(SeetaImageData seetaImageData) {
int type = BufferedImage.TYPE_3BYTE_BGR;
BufferedImage image = new BufferedImage(seetaImageData.width, seetaImageData.height, type);
image.getRaster().setDataElements(0, 0, seetaImageData.width, seetaImageData.height, seetaImageData.data);
return image;
}
/**
* 绘制人脸关键点
* @param g
* @param keyPoints
*/
private static void drawLandmarks(Graphics2D g, List<Point> 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);
}
}
/**
* 将DetectionRectangle转换为SeetaRect
* @param detectionRectangle
* @return
*/
public static SeetaRect convertToSeetaRect(DetectionRectangle detectionRectangle){
SeetaRect seetaRect = new SeetaRect();
seetaRect.x = detectionRectangle.getX();
seetaRect.y = detectionRectangle.getY();
seetaRect.width = detectionRectangle.getWidth();
seetaRect.height = detectionRectangle.getHeight();
return seetaRect;
}
/**
* 将PointList转换为SeetaPointF[]
* @param pointList
* @return
*/
public static SeetaPointF[] convertToSeetaPointF(List<Point> pointList){
return pointList.stream()
.map(p -> {
SeetaPointF sp = new SeetaPointF();
sp.x = p.getX();
sp.y = p.getY();
return sp;
})
.toArray(SeetaPointF[]::new);
}
/**
* 将SeetaAntiSpoofing.Status转换为LivenessStatus
* @param status
* @return
*/
public static LivenessStatus convertToLivenessStatus(FaceAntiSpoofing.Status status){
if(status == null){
return LivenessStatus.UNKNOWN;
}
switch (status) {
case REAL:
return LivenessStatus.LIVE;
case SPOOF:
return LivenessStatus.NON_LIVE;
case FUZZY:
return LivenessStatus.UNKNOWN;
case DETECTING:
return LivenessStatus.DETECTING;
default:
return LivenessStatus.UNKNOWN; // 默认返回未知
}
}
/**
* 转为GenderType
* @param gender
* @return
*/
public static GenderType convertToGenderType(GenderPredictor.GENDER gender){
if(gender == null){
return GenderType.UNKNOWN;
}
switch (gender) {
case MALE:
return GenderType.MALE;
case FEMALE:
return GenderType.FEMALE;
default:
return GenderType.UNKNOWN; // 默认返回未知
}
}
/**
* 转为EyeStatus
* @param eyeState
* @return
*/
public static EyeStatus convertToEyeStatus(EyeStateDetector.EYE_STATE eyeState){
if(eyeState == null){
return EyeStatus.UNKNOWN;
}
switch (eyeState) {
case EYE_OPEN:
return EyeStatus.OPEN;
case EYE_CLOSE:
return EyeStatus.CLOSED;
case EYE_RANDOM:
return EyeStatus.NON_EYE_REGION;
default:
return EyeStatus.UNKNOWN; // 默认返回未知
}
}
public static DetectionResponse convertToFaceAttributeResponse(SeetaRect[] seetaResult, List<SeetaPointF[]> seetaPointFSList, List<FaceAttribute> faceAttributeList){
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return null;
}
List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
for(int i = 0; i < seetaResult.length; i++){
SeetaRect rect = seetaResult[i];
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height);
FaceInfo faceInfo = new FaceInfo();
if(seetaPointFSList != null && seetaPointFSList.size() > 0){
SeetaPointF[] seetaPointFS = seetaPointFSList.get(i);
List<Point> keyPoints = Arrays.stream(seetaPointFS)
.map(p -> new Point(p.x, p.y))
.collect(Collectors.toList());
faceInfo.setKeyPoints(keyPoints);
}
if(faceAttributeList != null && faceAttributeList.size() > 0){
faceInfo.setFaceAttribute(faceAttributeList.get(i));
}
detectionInfoList.add(new DetectionInfo(rectangle, 0, faceInfo));
}
return new DetectionResponse(detectionInfoList);
}
/**
* 转换为FaceDetectedResult
* @param seetaResult
* @return
*/
public static DetectionResponse convertToDetectionResponse(SeetaRect[] seetaResult, List<SeetaPointF[]> seetaPointFSList, List<LivenessStatus> livenessStatusList){
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return null;
}
DetectionResponse detectionResponse = new DetectionResponse();
List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
for(int i = 0; i < seetaResult.length; i++){
SeetaRect rect = seetaResult[i];
SeetaPointF[] seetaPointFS = seetaPointFSList.get(i);
//过滤置信度
/*if(config.getConfidenceThreshold() > 0){
continue;
}*/
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height);
List<Point> keyPoints = Arrays.stream(seetaPointFS)
.map(p -> new Point(p.x, p.y))
.collect(Collectors.toList());
FaceInfo faceInfo = new FaceInfo(keyPoints);
faceInfo.setLivenessStatus(new LivenessResult(livenessStatusList.get(i)));
DetectionInfo detectionInfo = new DetectionInfo(rectangle, 0, faceInfo);
detectionInfoList.add(detectionInfo);
}
detectionResponse.setDetectionInfoList(detectionInfoList);
return detectionResponse;
}
/**
* 绘制人脸属性
@@ -569,7 +285,7 @@ public class FaceUtils {
* @throws IOException
*/
public static void drawBoxesWithFaceAttribute(BufferedImage sourceImage, DetectionResponse detectionResponse, String savePath) throws IOException {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new FaceException("图像无效");
}
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
@@ -589,7 +305,7 @@ public class FaceUtils {
//绘制人脸关键点
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
}
// 判断人脸框是否足够大
if (rectangle.getHeight() > 60 && detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getFaceAttribute() != null) {
@@ -638,7 +354,7 @@ public class FaceUtils {
lines.add("姿态: P=" + pitch + " Y=" + yaw + " R=" + roll);
}
if (!lines.isEmpty()) {
drawMultilineTextWithBackground(graphics, lines, rectangle.getX(), rectangle.getY()); // 适当偏移
Graphics2DUtils.drawMultilineTextWithBackground(graphics, lines, rectangle.getX(), rectangle.getY()); // 适当偏移
}
}
@@ -647,27 +363,7 @@ public class FaceUtils {
ImageIO.write(sourceImage, "png", new File(savePath));
}
private static void drawMultilineTextWithBackground(Graphics2D g, List<String> 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);
}
}
/**
* 将 Milvus 查询返回的得分转换为 0~1 范围的相似度
@@ -691,6 +387,81 @@ public class FaceUtils {
}
}
/**
* 裁剪人脸
* @param image
* @param rectangle
* @return
*/
public static Image cropFace(Image image, DetectionRectangle rectangle){
return image.getSubImage(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
}
/**
* 绘制人脸
* @param image
* @param rectangle
* @return
*/
// public static Image drawFaceName(Image image, DetectionResponse detectionResponse){
//
// }
/**
* 将 Mtcnn 批量结果转换为 DJL 的 DetectedObjects
* @param mtcnnBatchResult
* @param imageWidth
* @param imageHeight
* @return
*/
public static DetectedObjects toDetectedObjects(MtcnnBatchResult mtcnnBatchResult, int imageWidth, int imageHeight) {
List<String> classNames = new ArrayList<>();
List<Double> probs = new ArrayList<>();
List<BoundingBox> boxes = new ArrayList<>();
NDArray boxesND = mtcnnBatchResult.boxes.get(0);
NDArray probsND = mtcnnBatchResult.probs.get(0);
NDArray pointsND = mtcnnBatchResult.points.get(0);
if(pointsND != null){
pointsND = pointsND.toType(DataType.FLOAT64, false);
}
if(boxesND == null || probsND == null || pointsND == null){
return new DetectedObjects(classNames, probs, boxes);
}
long numBoxes = boxesND.getShape().get(0);
for (int i = 0; i < numBoxes; i++) {
NDArray box = boxesND.get(i); // [x1, y1, x2, y2]
NDArray prob = probsND.get(i);
NDArray pointND = pointsND.get(i); // shape [5,2]
float x1 = box.getFloat(0);
float y1 = box.getFloat(1);
float x2 = box.getFloat(2);
float y2 = box.getFloat(3);
// 转换为 DJL 的 Rectangle需要归一化到 [0,1]
double x = x1 / imageWidth;
double y = y1 / imageHeight;
double w = (x2 - x1) / imageWidth;
double h = (y2 - y1) / imageHeight;
List<ai.djl.modality.cv.output.Point> keyPoints = new ArrayList<>();
double[] flatPoints = pointND.toDoubleArray(); // 一维长度 10
for (int p = 0; p < 5; p++) {
keyPoints.add(new ai.djl.modality.cv.output.Point(flatPoints[p * 2], flatPoints[p * 2 + 1]));
}
Landmark landmark =
new Landmark(x, y, w, h, keyPoints);
// BoundingBox rect = new ai.djl.modality.cv.output.Rectangle(x, y, w, h);
classNames.add("Face"); // 默认类别是人脸
probs.add((double) prob.getFloat());
boxes.add(landmark);
}
return new DetectedObjects(classNames, probs, boxes);
}
}

View File

@@ -1,6 +1,24 @@
package cn.smartjavaai.face.utils;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
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.LivenessResult;
import cn.smartjavaai.common.enums.face.EyeStatus;
import cn.smartjavaai.common.enums.face.GenderType;
import cn.smartjavaai.common.enums.face.LivenessStatus;
import cn.smartjavaai.face.enums.QualityGrade;
import com.seeta.sdk.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Seetaface6工具类
@@ -10,4 +28,188 @@ import cn.smartjavaai.face.enums.QualityGrade;
public class Seetaface6Utils {
/**
* 子图中人脸关键点坐标 - Coordinates of key points in the image
*
* @param pointFS
* @return
*/
public static double[][] facePoints(SeetaPointF[] pointFS) {
// 图中关键点坐标 - Coordinates of key points in the image
// 1. left_eye_x , left_eye_y
// 2. right_eye_x , right_eye_y
// 3. nose_x , nose_y
// 4. left_mouth_x , left_mouth_y
// 5. right_mouth_x , right_mouth_y
double[][] pointsArray = new double[5][2]; // 保存人脸关键点 - Save facial key points
int i = 0;
for (SeetaPointF point : pointFS) {
pointsArray[i][0] = point.getX();
pointsArray[i][1] = point.getY();
i++;
}
return pointsArray;
}
/**
* bgr转图片
* @return 图片
*/
public static BufferedImage toBufferedImage(SeetaImageData seetaImageData) {
int type = BufferedImage.TYPE_3BYTE_BGR;
BufferedImage image = new BufferedImage(seetaImageData.width, seetaImageData.height, type);
image.getRaster().setDataElements(0, 0, seetaImageData.width, seetaImageData.height, seetaImageData.data);
return image;
}
/**
* 将DetectionRectangle转换为SeetaRect
* @param detectionRectangle
* @return
*/
public static SeetaRect convertToSeetaRect(DetectionRectangle detectionRectangle){
SeetaRect seetaRect = new SeetaRect();
seetaRect.x = detectionRectangle.getX();
seetaRect.y = detectionRectangle.getY();
seetaRect.width = detectionRectangle.getWidth();
seetaRect.height = detectionRectangle.getHeight();
return seetaRect;
}
/**
* 将PointList转换为SeetaPointF[]
* @param pointList
* @return
*/
public static SeetaPointF[] convertToSeetaPointF(List<Point> pointList){
return pointList.stream()
.map(p -> {
SeetaPointF sp = new SeetaPointF();
sp.x = p.getX();
sp.y = p.getY();
return sp;
})
.toArray(SeetaPointF[]::new);
}
/**
* 将SeetaAntiSpoofing.Status转换为LivenessStatus
* @param status
* @return
*/
public static LivenessStatus convertToLivenessStatus(FaceAntiSpoofing.Status status){
if(status == null){
return LivenessStatus.UNKNOWN;
}
switch (status) {
case REAL:
return LivenessStatus.LIVE;
case SPOOF:
return LivenessStatus.NON_LIVE;
case FUZZY:
return LivenessStatus.UNKNOWN;
case DETECTING:
return LivenessStatus.DETECTING;
default:
return LivenessStatus.UNKNOWN; // 默认返回未知
}
}
/**
* 转为GenderType
* @param gender
* @return
*/
public static GenderType convertToGenderType(GenderPredictor.GENDER gender){
if(gender == null){
return GenderType.UNKNOWN;
}
switch (gender) {
case MALE:
return GenderType.MALE;
case FEMALE:
return GenderType.FEMALE;
default:
return GenderType.UNKNOWN; // 默认返回未知
}
}
/**
* 转为EyeStatus
* @param eyeState
* @return
*/
public static EyeStatus convertToEyeStatus(EyeStateDetector.EYE_STATE eyeState){
if(eyeState == null){
return EyeStatus.UNKNOWN;
}
switch (eyeState) {
case EYE_OPEN:
return EyeStatus.OPEN;
case EYE_CLOSE:
return EyeStatus.CLOSED;
case EYE_RANDOM:
return EyeStatus.NON_EYE_REGION;
default:
return EyeStatus.UNKNOWN; // 默认返回未知
}
}
public static DetectionResponse convertToFaceAttributeResponse(SeetaRect[] seetaResult, List<SeetaPointF[]> seetaPointFSList, List<FaceAttribute> faceAttributeList){
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return null;
}
List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
for(int i = 0; i < seetaResult.length; i++){
SeetaRect rect = seetaResult[i];
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height);
FaceInfo faceInfo = new FaceInfo();
if(seetaPointFSList != null && seetaPointFSList.size() > 0){
SeetaPointF[] seetaPointFS = seetaPointFSList.get(i);
List<Point> keyPoints = Arrays.stream(seetaPointFS)
.map(p -> new Point(p.x, p.y))
.collect(Collectors.toList());
faceInfo.setKeyPoints(keyPoints);
}
if(faceAttributeList != null && faceAttributeList.size() > 0){
faceInfo.setFaceAttribute(faceAttributeList.get(i));
}
detectionInfoList.add(new DetectionInfo(rectangle, 0, faceInfo));
}
return new DetectionResponse(detectionInfoList);
}
/**
* 转换为FaceDetectedResult
* @param seetaResult
* @return
*/
public static DetectionResponse convertToDetectionResponse(SeetaRect[] seetaResult, List<SeetaPointF[]> seetaPointFSList, List<LivenessStatus> livenessStatusList){
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return null;
}
DetectionResponse detectionResponse = new DetectionResponse();
List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
for(int i = 0; i < seetaResult.length; i++){
SeetaRect rect = seetaResult[i];
SeetaPointF[] seetaPointFS = seetaPointFSList.get(i);
//过滤置信度
/*if(config.getConfidenceThreshold() > 0){
continue;
}*/
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height);
List<Point> keyPoints = Arrays.stream(seetaPointFS)
.map(p -> new Point(p.x, p.y))
.collect(Collectors.toList());
FaceInfo faceInfo = new FaceInfo(keyPoints);
faceInfo.setLivenessStatus(new LivenessResult(livenessStatusList.get(i)));
DetectionInfo detectionInfo = new DetectionInfo(rectangle, 0, faceInfo);
detectionInfoList.add(detectionInfo);
}
detectionResponse.setDetectionInfoList(detectionInfoList);
return detectionResponse;
}
}

View File

@@ -237,7 +237,7 @@ public class SQLiteClient implements VectorDBClient {
private void loadAllFeaturesToMemory() {
try {
int pageSize = 1000;
int page = 0;
int page = 1;
while (true) {
List<FaceVector> batch = faceDao.findFace(page, pageSize);
if (CollectionUtils.isEmpty(batch)) {

View File

@@ -1,9 +1,12 @@
import ai.djl.Application;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.repository.Artifact;
import ai.djl.repository.MRL;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.face.config.FaceDetConfig;
@@ -20,6 +23,8 @@ import cn.smartjavaai.face.utils.SimilarityUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
@@ -85,9 +90,18 @@ public class Test {
// }
FaceDetModel faceDetModel = getFaceDetModel();
R<Void> result = faceDetModel.detectAndDraw("/Users/wenjie/Downloads/facetest/00974.png", "/Users/wenjie/Downloads/xx333.png");
log.info("result:{}", result.isSuccess() + " msg:" + result.getMessage());
// FaceDetModel faceDetModel = getFaceDetModel();
// R<Void> result = faceDetModel.detectAndDraw("/Users/wenjie/Downloads/facetest/00974.png", "/Users/wenjie/Downloads/xx333.png");
// log.info("result:{}", result.isSuccess() + " msg:" + result.getMessage());
Image ime = ImageFactory.getInstance().fromFile(Paths.get("/Users/wenjie/Downloads/facetest/00974.png"));
// SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("/Users/wenjie/Downloads/facetest/00974.png"));
// image.save(Files.newOutputStream(Paths.get("/Users/wenjie/Downloads/xx333.png")), "png");
//
//
Image ime2 = ImageFactory.getInstance().fromFile(Paths.get("/Users/wenjie/Downloads/facetest/00974.png"));
image.getSubImage(0, 0, 100, 100);
}

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</parent>
<artifactId>ocr</artifactId>
@@ -42,7 +42,7 @@
</dependency>
</dependencies>
<version>1.0.24</version>
<version>1.0.25</version>
<name>ocr</name>
<description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -1,5 +1,6 @@
package cn.smartjavaai.ocr.entity;
import ai.djl.modality.cv.Image;
import lombok.Data;
import java.util.ArrayList;
@@ -20,7 +21,7 @@ public class OcrInfo {
private String fullText;
private String base64Img;
private transient Image drawnImage;
public OcrInfo(List<List<OcrItem>> lineList, String fullText) {

View File

@@ -158,6 +158,7 @@ public class OcrModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -179,6 +180,7 @@ public class OcrModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -199,6 +201,7 @@ public class OcrModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -220,4 +223,61 @@ public class OcrModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
commonDetModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
commonDetModelMap.clear();
commonRecModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
commonRecModelMap.clear();
directionModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
directionModelMap.clear();
}
/**
* 移除缓存的检测模型
* @param modelEnum
*/
public static void removeDetModelFromCache(CommonDetModelEnum modelEnum) {
commonDetModelMap.remove(modelEnum);
}
/**
* 移除缓存的识别模型
* @param modelEnum
*/
public static void removeRecModelFromCache(CommonRecModelEnum modelEnum) {
commonRecModelMap.remove(modelEnum);
}
/**
* 移除缓存的方向分类模型
* @param modelEnum
*/
public static void removeDirectionModelFromCache(DirectionModelEnum modelEnum) {
directionModelMap.remove(modelEnum);
}
}

View File

@@ -4,9 +4,7 @@ import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.ocr.config.PlateDetModelConfig;
import cn.smartjavaai.ocr.config.PlateRecModelConfig;
import cn.smartjavaai.ocr.config.TableStructureConfig;
import cn.smartjavaai.ocr.enums.PlateDetModelEnum;
import cn.smartjavaai.ocr.enums.PlateRecModelEnum;
import cn.smartjavaai.ocr.enums.TableStructureModelEnum;
import cn.smartjavaai.ocr.enums.*;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.model.plate.CRNNPlateRecModel;
import cn.smartjavaai.ocr.model.plate.PlateDetModel;
@@ -133,6 +131,7 @@ public class PlateModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -153,6 +152,7 @@ public class PlateModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -165,4 +165,44 @@ public class PlateModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
detModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
detModelMap.clear();
recModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
recModelMap.clear();
}
/**
* 移除缓存的检测模型
* @param modelEnum
*/
public static void removeDetModelFromCache(PlateDetModelEnum modelEnum) {
detModelMap.remove(modelEnum);
}
/**
* 移除缓存的识别模型
* @param modelEnum
*/
public static void removeRecModelFromCache(PlateRecModelEnum modelEnum) {
recModelMap.remove(modelEnum);
}
}

View File

@@ -103,6 +103,7 @@ public class TableRecModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -116,4 +117,26 @@ public class TableRecModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
tableStructureModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
tableStructureModelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(TableStructureModelEnum modelEnum) {
tableStructureModelMap.remove(modelEnum);
}
}

View File

@@ -27,6 +27,8 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default List<OcrBox> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -37,6 +39,7 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default List<OcrBox> detect(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -47,6 +50,7 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default List<OcrBox> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -74,15 +78,26 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default BufferedImage detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制结果
* @param sourceImage
* @return
*/
default Image detectAndDraw(Image sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 文本检测(批量)
* @param imageList BufferedImage
* @return
*/
@Deprecated
default List<List<OcrBox>> batchDetect(List<BufferedImage> imageList) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -100,5 +115,9 @@ public interface OcrCommonDetModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -11,13 +11,15 @@ import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.criteria.OcrCommonDetCriterialFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
import lombok.extern.slf4j.Slf4j;
@@ -81,12 +83,12 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
} catch (IOException e) {
throw new OcrException("无效的图片", e);
}
List<OcrBox> ocrBoxList = detect(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrBoxList;
}
@@ -103,16 +105,15 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
throw new OcrException("图像文件不存在");
}
try {
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
Image img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
List<OcrBox> boxList = detect(img);
if(Objects.isNull(boxList) || boxList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRect((Mat)img.getWrappedImage(), boxList);
Path output = Paths.get(outputPath);
log.debug("Saving to {}", output.toAbsolutePath().toString());
img.save(Files.newOutputStream(output), "png");
((Mat) img.getWrappedImage()).release();
OcrUtils.drawOcrDetResult(img, boxList, 12);
//4通道保存jpg会有问题
ImageUtils.save(img, Paths.get(outputPath), "png");
ImageUtils.releaseOpenCVMat(img);
} catch (IOException e) {
throw new OcrException(e);
}
@@ -121,12 +122,12 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
@Override
public List<OcrBox> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
List<OcrBox> ocrBoxList = detect(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrBoxList;
}
@@ -145,29 +146,30 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
@Override
public BufferedImage detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
List<OcrBox> ocrBoxList = detect(img);
if(Objects.isNull(ocrBoxList) || ocrBoxList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRect((Mat)img.getWrappedImage(), ocrBoxList);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 调用 save 方法将 Image 写入字节流
img.save(outputStream, "png");
// 将字节流转换为 BufferedImage
byte[] imageBytes = outputStream.toByteArray();
return ImageIO.read(new ByteArrayInputStream(imageBytes));
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
} finally {
if (img != null){
((Mat) img.getWrappedImage()).release();
}
OcrUtils.drawOcrDetResult(img, ocrBoxList, 12);
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
ImageUtils.releaseOpenCVMat(img);
return bufferedImage;
}
@Override
public Image detectAndDraw(Image sourceImage) {
List<OcrBox> ocrBoxList = detect(sourceImage);
if(Objects.isNull(ocrBoxList) || ocrBoxList.isEmpty()){
throw new OcrException("未检测到文字");
}
Image img = ImageUtils.copy(sourceImage);
OcrUtils.drawOcrDetResult(img, ocrBoxList, 12);
return img;
}
@Override
@@ -175,13 +177,13 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
List<Image> djlImageList = new ArrayList<>(imageList.size());
try {
for (BufferedImage bufferedImage : imageList) {
djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage)));
djlImageList.add(SmartImageFactory.getInstance().fromBufferedImage(bufferedImage));
}
return batchDetectDJLImage(djlImageList);
} catch (Exception e) {
throw new OcrException(e);
} finally {
djlImageList.forEach(image -> ((Mat)image.getWrappedImage()).release());
djlImageList.forEach(image -> ImageUtils.releaseOpenCVMat(image));
}
}
@@ -215,6 +217,10 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
}
}
@Override
public GenericObjectPool<Predictor<Image, NDList>> getPool() {
return detPredictorPool;
@@ -222,6 +228,9 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
@Override
public void close() throws Exception {
if (fromFactory) {
OcrModelFactory.removeDetModelFromCache(config.getModelEnum());
}
try {
if (detPredictorPool != null) {
detPredictorPool.close();
@@ -238,5 +247,15 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -12,7 +12,8 @@ import ai.djl.ndarray.types.Shape;
import ai.djl.translate.Batchifier;
import ai.djl.translate.Translator;
import ai.djl.translate.TranslatorContext;
import cn.smartjavaai.ocr.opencv.OcrNDArrayUtils;
import cn.smartjavaai.common.utils.DJLCommonUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
@@ -98,10 +99,10 @@ public class PPOCRDetTranslator implements Translator<Image, NDList> {
if (this.use_dilation) {
Mat mask = new Mat();
//convert from NDArray to Mat
Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation);
Mat srcMat = DJLCommonUtils.uint8NDArrayToMat(segmentation);
// size 越小,腐蚀的单位越小,图片越接近原图
// Mat dilation_kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2));
Mat dilation_kernel = OcrNDArrayUtils.uint8ArrayToMat(new byte[][]{{1, 1}, {1, 1}});
Mat dilation_kernel = OpenCVUtils.uint8ArrayToMat(new byte[][]{{1, 1}, {1, 1}});
/**
* 膨胀说明: 图像的一部分区域与指定的核进行卷积, 求核的最`大`值并赋值给指定区域。 膨胀可以理解为图像中`高亮区域`的'领域扩大'。
* 意思是高亮部分会侵蚀不是高亮的部分,使高亮部分越来越多。
@@ -115,7 +116,7 @@ public class PPOCRDetTranslator implements Translator<Image, NDList> {
srcMat.release();
dilation_kernel.release();
} else {
Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation);
Mat srcMat = DJLCommonUtils.uint8NDArrayToMat(segmentation);
//destination Matrix
Scalar scalar = new Scalar(255);
Core.multiply(srcMat, scalar, newMask);
@@ -462,20 +463,20 @@ public class PPOCRDetTranslator implements Translator<Image, NDList> {
box.set(new NDIndex(":, 1"), box.get(":, 1").sub(ymin));
//mask - convert from NDArray to Mat
Mat maskMat = OcrNDArrayUtils.uint8NDArrayToMat(mask);
Mat maskMat = DJLCommonUtils.uint8NDArrayToMat(mask);
//mask - convert from NDArray to Mat - 4 rows, 2 cols
Mat boxMat = OcrNDArrayUtils.floatNDArrayToMat(box, CvType.CV_32S);
Mat boxMat = DJLCommonUtils.floatNDArrayToMat(box, CvType.CV_32S);
// boxMat.reshape(1, new int[]{1, 4, 2});
List<MatOfPoint> pts = new ArrayList<>();
MatOfPoint matOfPoint = OcrNDArrayUtils.matToMatOfPoint(boxMat); // new MatOfPoint(boxMat);
MatOfPoint matOfPoint = OpenCVUtils.matToMatOfPoint(boxMat); // new MatOfPoint(boxMat);
pts.add(matOfPoint);
Imgproc.fillPoly(maskMat, pts, new Scalar(1));
NDArray subBitMap = bitmap.get(ymin + ":" + (ymax + 1) + "," + xmin + ":" + (xmax + 1));
Mat bitMapMat = OcrNDArrayUtils.floatNDArrayToMat(subBitMap);
Mat bitMapMat = DJLCommonUtils.floatNDArrayToMat(subBitMap);
Scalar score = Core.mean(bitMapMat, maskMat);
float scoreValue = (float) score.val[0];

View File

@@ -40,6 +40,7 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default List<OcrItem> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -50,6 +51,7 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default List<OcrItem> detect(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -60,6 +62,7 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default List<OcrItem> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -103,12 +106,26 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default BufferedImage detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制结果
* @param sourceImage
* @return
*/
default Image detectAndDraw(Image sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, DirectionInfo>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -1,44 +1,35 @@
package cn.smartjavaai.ocr.model.common.direction;
import ai.djl.Device;
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.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.entity.*;
import cn.smartjavaai.ocr.enums.AngleEnum;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.model.common.direction.criteria.DirectionCriteriaFactory;
import cn.smartjavaai.ocr.model.common.direction.translator.PpWordRotateTranslator;
import cn.smartjavaai.ocr.utils.OcrUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
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 javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.Collections;
@@ -63,6 +54,8 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
private OcrCommonDetModel textDetModel;
public static final int FONT_SIZE = 45;
@Override
public void loadModel(DirectionModelConfig config){
@@ -100,14 +93,12 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return detect(img);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
}finally {
if(img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -122,7 +113,7 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
if(Objects.isNull(boxeList) || boxeList.isEmpty()){
throw new OcrException("未检测到文本");
}
Mat srcMat = (Mat) image.getWrappedImage();
Mat srcMat = ImageUtils.toMat(image);
return detect(boxeList, srcMat);
}
@@ -171,7 +162,7 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
// }
@Override
public List<OcrItem> detect(List<OcrBox> boxList,Mat srcMat){
public List<OcrItem> detect(List<OcrBox> boxList, Mat srcMat){
if(Objects.isNull(boxList) || boxList.isEmpty()){
throw new OcrException("boxList为空");
}
@@ -189,32 +180,30 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
List<OcrItem> itemList = detect(img);
if(Objects.isNull(itemList) || itemList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRectWithText((Mat) img.getWrappedImage(), itemList);
Path output = Paths.get(outputPath);
log.debug("Saving to {}", output.toAbsolutePath().toString());
img.save(Files.newOutputStream(output), "png");
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawOcrResult(bufferedImage, itemList, FONT_SIZE);
log.debug("Saving to {}", outputPath);
BufferedImageUtils.saveImage(bufferedImage, outputPath);
} catch (IOException e) {
throw new OcrException(e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@Override
public List<OcrItem> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
List<OcrItem> ocrItemList = detect(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrItemList;
}
@@ -223,39 +212,41 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
if(Objects.isNull(imageData)){
throw new OcrException("图像无效");
}
Image img = null;
try {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
return detect(image);
img = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new OcrException("错误的图像", e);
throw new RuntimeException(e);
}
List<OcrItem> ocrItemList = detect(img);
ImageUtils.releaseOpenCVMat(img);
return ocrItemList;
}
@Override
public BufferedImage detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
List<OcrItem> ocrItemList = detect(img);
if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRectWithText((Mat) img.getWrappedImage(), ocrItemList);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 调用 save 方法将 Image 写入字节流
img.save(outputStream, "png");
// 将字节流转换为 BufferedImage
byte[] imageBytes = outputStream.toByteArray();
return ImageIO.read(new ByteArrayInputStream(imageBytes));
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
} finally {
if (img != null){
((Mat) img.getWrappedImage()).release();
}
BufferedImage drawImage = BufferedImageUtils.copyBufferedImage(sourceImage);
OcrUtils.drawOcrResult(drawImage, ocrItemList, FONT_SIZE);
return drawImage;
}
@Override
public Image detectAndDraw(Image sourceImage) {
List<OcrItem> ocrItemList = detect(sourceImage);
if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){
throw new OcrException("未检测到文字");
}
Image drawImage = ImageUtils.copy(sourceImage);
OcrUtils.drawOcrResult(drawImage, ocrItemList, FONT_SIZE);
return drawImage;
}
@Override
@@ -291,7 +282,7 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
//高宽比 > 1.5 纵向
if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) {
//旋转图片90度
subImg = OcrUtils.rotateImg(manager, subImg);
subImg = ImageUtils.rotateImg(manager, subImg);
isRotatedList.add(true);
imageList.add(subImg);
}else{
@@ -303,6 +294,8 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
}
List<List<OcrItem>> result = new ArrayList<>();
List<DirectionInfo> directionInfos = batchDetect(imageList);
//释放
imageList.forEach(image -> ImageUtils.releaseOpenCVMat(image));
if(CollectionUtils.isEmpty(directionInfos)){
throw new OcrException("方向检测失败");
}
@@ -378,6 +371,9 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
@Override
public void close() throws Exception {
if (fromFactory) {
OcrModelFactory.removeDirectionModelFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -393,4 +389,14 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -47,6 +47,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default OcrInfo recognize(String imagePath, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,6 +67,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default OcrInfo recognize(BufferedImage image, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -76,6 +78,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default OcrInfo recognize(byte[] imageData, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -90,11 +93,22 @@ public interface OcrCommonRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 识别并绘制结果
* @param image
* @return
*/
@Deprecated
default OcrInfo recognizeAndDraw(Image image, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 识别并绘制结果
* @param sourceImage
* @return
*/
@Deprecated
default BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -103,6 +117,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default String recognizeAndDrawToBase64(byte[] imageData, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -112,10 +127,12 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default OcrInfo recognizeAndDraw(byte[] imageData, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
@Deprecated
default List<OcrInfo> batchRecognize(List<BufferedImage> imageList, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -128,4 +145,8 @@ public interface OcrCommonRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -4,7 +4,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.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
@@ -12,14 +11,16 @@ import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.hutool.core.img.ImgUtil;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.OcrRecModelConfig;
import cn.smartjavaai.ocr.config.OcrRecOptions;
import cn.smartjavaai.ocr.entity.*;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel;
import cn.smartjavaai.ocr.model.common.recognize.criteria.OcrCommonRecCriterialFactory;
@@ -33,7 +34,6 @@ import org.opencv.core.Mat;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
@@ -95,14 +95,12 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return recognize(img, options);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
if (img != null) {
((Mat) img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -138,7 +136,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
//高宽比 > 1.5
if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) {
//旋转图片90度
subImg = OcrUtils.rotateImg(manager, subImg);
subImg = ImageUtils.rotateImg(manager, subImg);
//ImageUtils.saveImage(subImg, i + "rotate.png", "build/output");
}
imageList.add(subImg);
@@ -236,30 +234,31 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
if (!FileUtils.isFileExists(imagePath)) {
throw new OcrException("图像文件不存在");
}
Image img = null;
try {
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
OcrInfo ocrInfo = recognize(img, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
Mat wrappedImage = (Mat) img.getWrappedImage();
BufferedImage bufferedImage = OpenCVUtils.mat2Image(wrappedImage);
OcrUtils.drawRectWithText(bufferedImage, ocrInfo, fontSize);
ImageUtils.saveImage(bufferedImage, outputPath);
wrappedImage.release();
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawOcrResult(bufferedImage, ocrInfo, fontSize);
BufferedImageUtils.saveImage(bufferedImage, outputPath);
} catch (IOException e) {
throw new OcrException(e);
}finally {
ImageUtils.releaseOpenCVMat(img);
}
}
@Override
public OcrInfo recognize(BufferedImage image, OcrRecOptions options) {
if (!ImageUtils.isImageValid(image)) {
if (!BufferedImageUtils.isImageValid(image)) {
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
OcrInfo ocrInfo = recognize(img, options);
((Mat) img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrInfo;
}
@@ -278,15 +277,15 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
@Override
public BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options) {
if (!ImageUtils.isImageValid(sourceImage)) {
if (!BufferedImageUtils.isImageValid(sourceImage)) {
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
OcrInfo ocrInfo = recognize(img, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
return sourceImage;
}
@@ -301,7 +300,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
try {
BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageData));
OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
return ImgUtil.toBase64(sourceImage, "png");
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
@@ -313,18 +312,21 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
if (Objects.isNull(imageData)) {
throw new OcrException("图像无效");
}
OcrInfo ocrInfo = recognize(imageData, options);
Image img = null;
try {
img = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new RuntimeException(e);
}
OcrInfo ocrInfo = recognize(img, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
try {
BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageData));
OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize);
ocrInfo.setBase64Img(ImgUtil.toBase64(sourceImage, "png"));
return ocrInfo;
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
}
//opencv中文乱码使用BufferedImage
BufferedImage sourceImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
ocrInfo.setDrawnImage(SmartImageFactory.getInstance().fromBufferedImage(sourceImage));
return ocrInfo;
}
@Override
@@ -332,13 +334,13 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
List<Image> djlImageList = new ArrayList<>(imageList.size());
try {
for (BufferedImage bufferedImage : imageList) {
djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage)));
djlImageList.add(SmartImageFactory.getInstance().fromBufferedImage(bufferedImage));
}
return batchRecognizeDJLImage(djlImageList, options);
} catch (Exception e) {
throw new OcrException(e);
} finally {
djlImageList.forEach(image -> ((Mat) image.getWrappedImage()).release());
djlImageList.forEach(image -> ImageUtils.releaseOpenCVMat(image));
}
}
@@ -370,7 +372,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
throw new OcrException("请配置方向模型");
}
List<Mat> matList = imageList.stream()
.map(image -> (Mat) image.getWrappedImage())
.map(image -> ImageUtils.toMat(image))
.collect(Collectors.toList());
List<List<OcrItem>> ocrItemList = directionModel.batchDetect(boxeList, matList);
if (CollectionUtils.isEmpty(ocrItemList) || ocrItemList.size() != imageList.size()) {
@@ -378,7 +380,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
allImageAlignList = new ArrayList<Image>();
for (int i = 0; i < ocrItemList.size(); i++) {
Mat srcMat = (Mat) imageList.get(i).getWrappedImage();
Mat srcMat = ImageUtils.toMat(imageList.get(i));
List<Image> imageAlignList = batchAlignWithDirection(ocrItemList.get(i), srcMat, manager);
// for(int j = 0; j < imageAlignList.size(); j++){
// ImageUtils.saveImage(imageAlignList.get(j),"dir-"+i+"-"+j+".png","/Users/xxx/Downloads/testing33");
@@ -387,10 +389,10 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
} else {
for (int i = 0; i < boxeList.size(); i++) {
Mat srcMat = (Mat) imageList.get(i).getWrappedImage();
Mat srcMat = ImageUtils.toMat(imageList.get(i));
List<Image> imageAlignList = batchAlign(boxeList.get(i), srcMat, manager);
// for(int j = 0; j < imageAlignList.size(); j++){
// ImageUtils.saveImage(imageAlignList.get(j),i+"-"+j+".png","/Users/xxx/Downloads/testing33");
// ImageUtils.saveImage(imageAlignList.get(j),i+"-"+j+".png","/Users/wenjie/Downloads/testing33");
// }
allImageAlignList.addAll(imageAlignList);
}
@@ -435,7 +437,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
try {
predictor = recPredictorPool.borrowObject();
List<String> textList = predictor.batchPredict(imageAlignList);
imageAlignList.forEach(subImg -> ((Mat) subImg.getWrappedImage()).release());
imageAlignList.forEach(subImg -> ImageUtils.releaseOpenCVMat(subImg));
return textList;
} catch (Exception e) {
throw new OcrException("OCR检测错误", e);
@@ -455,6 +457,19 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
}
@Override
public OcrInfo recognizeAndDraw(Image image, int fontSize, OcrRecOptions options) {
OcrInfo ocrInfo = recognize(image, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
BufferedImage sourceImage = ImageUtils.toBufferedImage(image);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
ocrInfo.setDrawnImage(SmartImageFactory.getInstance().fromBufferedImage(sourceImage));
return ocrInfo;
}
@Override
public void setTextDetModel(OcrCommonDetModel detModel) {
this.textDetModel = detModel;
@@ -482,6 +497,9 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
@Override
public void close() throws Exception {
if (fromFactory) {
OcrModelFactory.removeRecModelFromCache(config.getRecModelEnum());
}
try {
if (recPredictorPool != null) {
recPredictorPool.close();
@@ -497,4 +515,14 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -4,7 +4,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.repository.zoo.Criteria;
@@ -13,19 +12,18 @@ import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.generator.UUIDGenerator;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionRectangle;
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.ocr.config.PlateDetModelConfig;
import cn.smartjavaai.ocr.config.PlateRecModelConfig;
import cn.smartjavaai.ocr.entity.PlateInfo;
import cn.smartjavaai.ocr.entity.PlateResult;
import cn.smartjavaai.ocr.enums.PlateType;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.model.plate.criteria.PlateDetCriterialFactory;
import cn.smartjavaai.ocr.model.plate.criteria.PlateRecCriterialFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
@@ -97,13 +95,13 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<List<PlateInfo>> plateResult = recognize(img);
return plateResult;
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -118,12 +116,12 @@ public class CRNNPlateRecModel implements PlateRecModel{
@Override
public R<List<PlateInfo>> recognize(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
R<List<PlateInfo>> plateResult = recognize(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return plateResult;
}
@@ -140,7 +138,7 @@ public class CRNNPlateRecModel implements PlateRecModel{
if(Objects.isNull(config.getPlateDetModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定车牌检测模型");
}
DetectedObjects detectedObjects = config.getPlateDetModel().detect(image);
DetectedObjects detectedObjects = config.getPlateDetModel().detectCore(image);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
@@ -151,23 +149,26 @@ public class CRNNPlateRecModel implements PlateRecModel{
for (PlateInfo plateInfo : plateInfoList){
DetectionRectangle detectionRectangle = plateInfo.getDetectionRectangle();
// Image subImage = image.getSubImage(detectionRectangle.getX(), detectionRectangle.getY(), detectionRectangle.getWidth(), detectionRectangle.getHeight());
Mat imageMat = ImageUtils.toMat(image);
//透视变换
Image subImage = OcrUtils.transformAndCrop((Mat)image.getWrappedImage(), plateInfo.getBox());
Mat subMat = OcrUtils.transformAndCropToMat(imageMat, plateInfo.getBox());
//双层车牌
if(plateInfo.getPlateType() == PlateType.DOUBLE){
Mat mergeImage = getSplitMerge((Mat)subImage.getWrappedImage());
subImage = ImageFactory.getInstance().fromImage(mergeImage);
subMat = getSplitMerge(subMat);
}
Image subImage = SmartImageFactory.getInstance().fromMat(subMat);
PlateResult plateResult = predictor.predict(subImage);
if(Objects.nonNull(plateResult)){
plateInfo.setPlateNumber(plateResult.getPlateNo());
plateInfo.setPlateColor(plateResult.getPlateColor());
}
ImageUtils.releaseOpenCVMat(subImage);
}
return R.ok(plateInfoList);
} catch (Exception e) {
throw new OcrException("车牌识别错误", e);
}finally {
if (predictor != null) {
try {
recPredictorPool.returnObject(predictor); //归还
@@ -247,14 +248,12 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromInputStream(inputStream);
img = SmartImageFactory.getInstance().fromInputStream(inputStream);
return recognize(img);
} catch (IOException e) {
throw new OcrException("无效图片输入流", e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -265,7 +264,7 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<List<PlateInfo>> plateResult = recognize(img);
if(!plateResult.isSuccess()){
return R.fail(plateResult.getCode(), plateResult.getMessage());
@@ -273,22 +272,20 @@ public class CRNNPlateRecModel implements PlateRecModel{
if(CollectionUtils.isEmpty(plateResult.getData())){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
BufferedImage bufferedImage = OpenCVUtils.mat2Image((Mat)img.getWrappedImage());
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawPlateInfo(bufferedImage, plateResult.getData());
ImageIO.write(bufferedImage, "png", new File(outputPath));
return R.ok();
} catch (IOException e) {
throw new OcrException(e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@Override
public R<BufferedImage> recognizeAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
@@ -306,6 +303,25 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
}
@Override
public R<Image> recognizeAndDraw(Image image) {
try {
R<List<PlateInfo>> plateResult = recognize(image);
if(!plateResult.isSuccess()){
return R.fail(plateResult.getCode(), plateResult.getMessage());
}
if(CollectionUtils.isEmpty(plateResult.getData())){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
//opencv中文乱码使用BufferedImage
BufferedImage sourceImage = ImageUtils.toBufferedImage(image);
OcrUtils.drawPlateInfo(sourceImage, plateResult.getData());
Image drawImage = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
return R.ok(drawImage);
} catch (Exception e) {
throw new OcrException("导出图片失败", e);
}
}
@Override
public GenericObjectPool<Predictor<Image, PlateResult>> getPool() {
@@ -314,6 +330,9 @@ public class CRNNPlateRecModel implements PlateRecModel{
@Override
public void close() throws Exception {
if (fromFactory) {
PlateModelFactory.removeRecModelFromCache(config.getModelEnum());
}
try {
if (recPredictorPool != null) {
recPredictorPool.close();
@@ -329,4 +348,14 @@ public class CRNNPlateRecModel implements PlateRecModel{
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -32,6 +32,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -42,6 +43,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param inputStream
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(InputStream inputStream) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -52,6 +54,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<List<PlateInfo>> detectBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -62,6 +65,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -72,6 +76,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -81,7 +86,17 @@ public interface PlateDetModel extends AutoCloseable{
* @param image DJL Image
* @return
*/
default DetectedObjects detect(Image image){
default DetectedObjects detectCore(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 车牌检测
* @param image DJL Image
* @return
*/
default R<List<PlateInfo>> detect(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -100,16 +115,28 @@ public interface PlateDetModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<BufferedImage> detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制结果
* @param image
* @return
*/
default Image detectAndDraw(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, DetectedObjects>> getPool(){
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -31,6 +31,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -41,6 +42,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param inputStream
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(InputStream inputStream) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -51,6 +53,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognizeBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -61,6 +64,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -71,6 +75,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -107,12 +112,21 @@ public interface PlateRecModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<BufferedImage> recognizeAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<Image> recognizeAndDraw(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, PlateResult>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -4,7 +4,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.DetectedObjects;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
@@ -12,17 +11,17 @@ import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.smartjavaai.common.cv.SmartImageFactory;
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.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.config.PlateDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.entity.PlateInfo;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.model.common.detect.criteria.OcrCommonDetCriterialFactory;
import cn.smartjavaai.ocr.model.plate.criteria.PlateDetCriterialFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
@@ -90,17 +89,15 @@ public class Yolov5PlateDetModel implements PlateDetModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<List<PlateInfo>> plateInfoList = detect(img);
return plateInfoList;
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
ImageUtils.releaseOpenCVMat(img);
}
DetectedObjects detectedObjects = detect(img);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, img);
((Mat)img.getWrappedImage()).release();
return R.ok(plateInfoList);
}
@Override
@@ -114,17 +111,13 @@ public class Yolov5PlateDetModel implements PlateDetModel{
@Override
public R<List<PlateInfo>> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
DetectedObjects detectedObjects = detect(img);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, img);
((Mat)img.getWrappedImage()).release();
return R.ok(plateInfoList);
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
R<List<PlateInfo>> plateInfoList = detect(img);
ImageUtils.releaseOpenCVMat(img);
return plateInfoList;
}
@Override
@@ -136,7 +129,7 @@ public class Yolov5PlateDetModel implements PlateDetModel{
}
@Override
public DetectedObjects detect(Image image) {
public DetectedObjects detectCore(Image image) {
Predictor<Image, DetectedObjects> predictor = null;
try {
predictor = detPredictorPool.borrowObject();
@@ -164,14 +157,15 @@ public class Yolov5PlateDetModel implements PlateDetModel{
if(Objects.isNull(inputStream)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
DetectedObjects detection = detect(img);
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detection, img);
((Mat)img.getWrappedImage()).release();
return R.ok(plateInfoList);
img = SmartImageFactory.getInstance().fromInputStream(inputStream);
R<List<PlateInfo>> plateInfoList = detect(img);
return plateInfoList;
} catch (IOException e) {
throw new OcrException("无效图片输入流", e);
} finally {
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -181,8 +175,8 @@ public class Yolov5PlateDetModel implements PlateDetModel{
return R.fail(R.Status.FILE_NOT_FOUND);
}
try {
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detectedObjects = detect(img);
Image 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);
}
@@ -198,11 +192,11 @@ public class Yolov5PlateDetModel implements PlateDetModel{
@Override
public R<BufferedImage> 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);
}
@@ -219,6 +213,27 @@ public class Yolov5PlateDetModel implements PlateDetModel{
}
}
@Override
public R<List<PlateInfo>> detect(Image image) {
DetectedObjects detectedObjects = detectCore(image);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, image);
return R.ok(plateInfoList);
}
@Override
public Image detectAndDraw(Image image) {
DetectedObjects detectedObjects = detectCore(image);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
throw new OcrException("未检测到车牌");
}
Image img = ImageUtils.copy(image);
img.drawBoundingBoxes(detectedObjects);
return img;
}
@Override
public GenericObjectPool<Predictor<Image, DetectedObjects>> getPool() {
return detPredictorPool;
@@ -226,6 +241,9 @@ public class Yolov5PlateDetModel implements PlateDetModel{
@Override
public void close() throws Exception {
if (fromFactory) {
PlateModelFactory.removeDetModelFromCache(config.getModelEnum());
}
try {
if (detPredictorPool != null) {
detPredictorPool.close();
@@ -241,4 +259,14 @@ public class Yolov5PlateDetModel implements PlateDetModel{
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -11,8 +11,10 @@ import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
@@ -22,6 +24,8 @@ import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.entity.OcrItem;
import cn.smartjavaai.ocr.entity.TableStructureResult;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.factory.TableRecModelFactory;
import cn.smartjavaai.ocr.model.table.criteria.StructureCriteriaFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
import lombok.extern.slf4j.Slf4j;
@@ -49,8 +53,11 @@ public class CommonTableStructureModel implements TableStructureModel{
private GenericObjectPool<Predictor<Image, TableStructureResult>> predictorPool;
private TableStructureConfig config;
@Override
public void loadModel(TableStructureConfig config) {
this.config = config;
if(StringUtils.isBlank(config.getModelPath())){
throw new OcrException("modelPath is null");
}
@@ -74,19 +81,17 @@ public class CommonTableStructureModel implements TableStructureModel{
@Override
public R<TableStructureResult> 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));
img = SmartImageFactory.getInstance().fromBufferedImage(image);
return detect(img);
} catch (Exception e) {
throw new OcrException(e);
} finally {
if(Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -97,14 +102,12 @@ public class CommonTableStructureModel implements TableStructureModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return detect(img);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
if (Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -153,6 +156,9 @@ public class CommonTableStructureModel implements TableStructureModel{
@Override
public void close() throws Exception {
if (fromFactory) {
TableRecModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -168,4 +174,14 @@ public class CommonTableStructureModel implements TableStructureModel{
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -1,18 +1,12 @@
package cn.smartjavaai.ocr.model.table;
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.modality.cv.output.Rectangle;
import ai.djl.translate.TranslateException;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.OcrRecModelConfig;
import cn.smartjavaai.ocr.config.OcrRecOptions;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.entity.OcrInfo;
@@ -28,7 +22,6 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.opencv.core.Mat;
import javax.imageio.ImageIO;
import java.awt.*;
@@ -107,19 +100,17 @@ public class TableRecognizer {
* @return
*/
public R<TableStructureResult> recognize(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));
img = SmartImageFactory.getInstance().fromBufferedImage(image);
return recognize(img);
} catch (Exception e) {
throw new OcrException(e);
} finally {
if(Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -134,14 +125,12 @@ public class TableRecognizer {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return recognize(img);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
if (Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -176,9 +165,13 @@ public class TableRecognizer {
for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){
OcrItem item = tableStructureResult.getOcrItemList().get(i);
DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle();
ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED);
BufferedImageUtils.drawRectAndText(image, detectionRectangle, i + "", Color.RED);
}
try {
BufferedImageUtils.saveImage(image, savePath);
} catch (IOException e) {
throw new OcrException(e);
}
ImageUtils.saveImage(image, savePath);
}
@@ -195,7 +188,25 @@ public class TableRecognizer {
for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){
OcrItem item = tableStructureResult.getOcrItemList().get(i);
DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle();
ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED);
BufferedImageUtils.drawRectAndText(image, detectionRectangle, i + "", Color.RED);
}
return image;
}
/**
* 绘制表格
* @param tableStructureResult
* @param image
* @return
*/
public Image drawTable(TableStructureResult tableStructureResult, Image image){
if(Objects.isNull(tableStructureResult) || CollectionUtils.isEmpty(tableStructureResult.getTableTagList())){
throw new OcrException("表格结构为空");
}
for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){
OcrItem item = tableStructureResult.getOcrItemList().get(i);
DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle();
ImageUtils.drawRectAndText(image, detectionRectangle, i + "");
}
return image;
}

View File

@@ -31,6 +31,7 @@ public interface TableStructureModel extends AutoCloseable{
* @param image
* @return
*/
@Deprecated
default R<TableStructureResult> detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -40,6 +41,7 @@ public interface TableStructureModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<TableStructureResult> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -49,6 +51,7 @@ public interface TableStructureModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default R<TableStructureResult> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,4 +69,10 @@ public interface TableStructureModel extends AutoCloseable{
default GenericObjectPool<Predictor<Image, TableStructureResult>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -1,228 +0,0 @@
package cn.smartjavaai.ocr.opencv;
import ai.djl.ndarray.NDArray;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import java.util.ArrayList;
import java.util.List;
/**
* NDArray Utils
*
*/
public class OcrNDArrayUtils {
/**
* Mat To MatOfPoint
* @param mat
* @return
*/
public static MatOfPoint matToMatOfPoint(Mat mat) {
int rows = mat.rows();
MatOfPoint matOfPoint = new MatOfPoint();
List<Point> 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;
}
/**
* 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;
}
/**
* 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 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;
}
/**
* 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;
}
/**
* List To Mat
* @param points
* @return
*/
public static Mat toMat(List<ai.djl.modality.cv.output.Point> 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;
}
}

Some files were not shown because too many files have changed in this diff Show More