mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-10 03:28:49 +00:00
【人脸识别】 新增多种人脸识别模型
【底层优化】 支持自由选择 OpenCV 或 BufferedImage 作为图像引擎 【通用图像】 全部模型启用 Image 输入,支持各类图片格式与 Image 的互转 【模型管理】 优化模型生命周期,关闭后可重新创建 【人脸识别】 支持在人脸查询结果中绘制姓名标注 【人脸检测】 新增人脸裁剪功能 【修复】 修复若干已知问题,提升系统稳定性
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cn.smartjavaai.face.model.facerec;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
*/
|
||||
public class FaceRecTranslatorBuilder {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user