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

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

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

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

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

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

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

View File

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

View File

@@ -6,21 +6,61 @@ package cn.smartjavaai.face.enums;
*/
public enum FaceRecModelEnum {
FACENET_MODEL("FaceNetModel"),
SEETA_FACE6_MODEL("SeetaFace6Model"),
SEETA_FACE6_LIGHT_MODEL("SeetaFace6Model"),
INSIGHT_FACE_IRSE50_MODEL("InsightFaceIRSE50Model"),
INSIGHT_FACE_MOBILE_FACENET_MODEL("InsightFaceMobilefacenetModel"),
ELASTIC_FACE_MODEL("ElasticFaceModel");
FACENET_MODEL("PyTorch", 112, 112, 0.7f),
SEETA_FACE6_MODEL("c++", 0, 0, 0.62f),
SEETA_FACE6_LIGHT_MODEL("c++", 0, 0, 0.62f),
INSIGHT_FACE_IRSE50_MODEL("PyTorch", 112, 112, 0.62f),
INSIGHT_FACE_MOBILE_FACENET_MODEL("PyTorch", 112, 112, 0.64f),
ELASTIC_FACE_MODEL("PyTorch", 112, 112, 0.61f),
SPHERE_FACE_20A_ONNX("OnnxRuntime", 96, 112, 0.7f),
SPHERE_FACE_20A_PT("PyTorch", 96, 112, 0.7f),
DREAM_IJBA_RES18_NAIVE("OnnxRuntime", 224, 224, 0.74f),
EVOLVE_FACE_IR50("PyTorch", 112, 112, 0.62f),
EVOLVE_FACE_IR50_ASIA("PyTorch", 112, 112, 0.62f),
EVOLVE_FACE_IR152("PyTorch", 112, 112, 0.62f),
VGG_FACE("PyTorch", 224, 224, 0.75f);
private final String modelClassName;
/**
* 模型输入尺寸:宽
*/
private final int inputWidth;
FaceRecModelEnum(String modelClassName) {
this.modelClassName = modelClassName;
/**
* 模型输入尺寸:高
*/
private final int inputHeight;
/**
* 模型引擎
*/
private final String engine;
/**
* 相似度阈值
*/
private final float threshold;
FaceRecModelEnum(String engine, int inputWidth, int inputHeight, float threshold) {
this.inputWidth = inputWidth;
this.inputHeight = inputHeight;
this.engine = engine;
this.threshold = threshold;
}
public String getModelClassName() {
return modelClassName;
public int getInputWidth() {
return inputWidth;
}
public int getInputHeight() {
return inputHeight;
}
public String getEngine() {
return engine;
}
public float getThreshold() {
return threshold;
}
/**

View File

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

View File

@@ -2,6 +2,8 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.FaceAttributeConfig;
import cn.smartjavaai.face.enums.ExpressionModelEnum;
import cn.smartjavaai.face.enums.FaceAttributeModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.attribute.Seetaface6FaceAttributeModel;
import lombok.extern.slf4j.Slf4j;
@@ -21,12 +23,12 @@ public class FaceAttributeModelFactory {
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
private static volatile FaceAttributeModelFactory instance;
private static final ConcurrentHashMap<String, FaceAttributeModel> modelMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<FaceAttributeModelEnum, FaceAttributeModel> modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
private static final Map<String, Class<? extends FaceAttributeModel>> registry =
private static final Map<FaceAttributeModelEnum, Class<? extends FaceAttributeModel>> registry =
new ConcurrentHashMap<>();
@@ -48,8 +50,8 @@ public class FaceAttributeModelFactory {
* @param name
* @param clazz
*/
private static void registerModel(String name, Class<? extends FaceAttributeModel> clazz) {
registry.put(name.toLowerCase(), clazz);
private static void registerModel(FaceAttributeModelEnum faceAttributeModelEnum, Class<? extends FaceAttributeModel> clazz) {
registry.put(faceAttributeModelEnum, clazz);
}
@@ -62,7 +64,7 @@ public class FaceAttributeModelFactory {
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
throw new FaceException("未配置活体检测模型");
}
return modelMap.computeIfAbsent(config.getModelEnum().name(), k -> {
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceModel(config);
});
}
@@ -73,9 +75,9 @@ public class FaceAttributeModelFactory {
* @return
*/
private FaceAttributeModel createFaceModel(FaceAttributeConfig config) {
Class<?> clazz = registry.get(config.getModelEnum().getModelClassName().toLowerCase());
Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){
throw new FaceException("Unsupported algorithm");
throw new FaceException("Unsupported model");
}
FaceAttributeModel model = null;
try {
@@ -84,14 +86,37 @@ public class FaceAttributeModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
// 初始化默认算法
static {
registerModel("seetaface6model", Seetaface6FaceAttributeModel.class);
registerModel(FaceAttributeModelEnum.SEETA_FACE6_MODEL, Seetaface6FaceAttributeModel.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
modelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
modelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(FaceAttributeModelEnum modelEnum) {
modelMap.remove(modelEnum);
}
}

View File

@@ -3,6 +3,7 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
import cn.smartjavaai.face.enums.FaceAttributeModelEnum;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.facedect.CommonFaceDetModel;
@@ -93,16 +94,17 @@ public class FaceDetModelFactory {
private FaceDetModel createFaceDetModel(FaceDetConfig config) {
Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){
throw new FaceException("Unsupported algorithm");
throw new FaceException("Unsupported model");
}
FaceDetModel algorithm = null;
FaceDetModel model = null;
try {
algorithm = (FaceDetModel) clazz.newInstance();
model = (FaceDetModel) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new FaceException(e);
}
algorithm.loadModel(config);
return algorithm;
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -133,4 +135,26 @@ public class FaceDetModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
modelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
modelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(FaceDetModelEnum modelEnum) {
modelMap.remove(modelEnum);
}
}

View File

@@ -2,6 +2,8 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.QualityConfig;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.enums.QualityModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.quality.FaceQualityModel;
import cn.smartjavaai.face.model.quality.Seetaface6QualityModel;
@@ -21,12 +23,12 @@ public class FaceQualityModelFactory {
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
private static volatile FaceQualityModelFactory instance;
private static final ConcurrentHashMap<String, FaceQualityModel> modelMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<QualityModelEnum, FaceQualityModel> modelMap = new ConcurrentHashMap<>();
/**
* 模型注册表
*/
private static final Map<String, Class<? extends FaceQualityModel>> registry =
private static final Map<QualityModelEnum, Class<? extends FaceQualityModel>> registry =
new ConcurrentHashMap<>();
@@ -45,11 +47,11 @@ public class FaceQualityModelFactory {
/**
* 注册模型
* @param name
* @param qualityModelEnum
* @param clazz
*/
private static void registerModel(String name, Class<? extends FaceQualityModel> clazz) {
registry.put(name.toLowerCase(), clazz);
private static void registerModel(QualityModelEnum qualityModelEnum, Class<? extends FaceQualityModel> clazz) {
registry.put(qualityModelEnum, clazz);
}
@@ -62,7 +64,7 @@ public class FaceQualityModelFactory {
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
throw new FaceException("未配置质量评估模型");
}
return modelMap.computeIfAbsent(config.getModelEnum().name(), k -> {
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceModel(config);
});
}
@@ -73,7 +75,7 @@ public class FaceQualityModelFactory {
* @return
*/
private FaceQualityModel createFaceModel(QualityConfig config) {
Class<?> clazz = registry.get(config.getModelEnum().getModelClassName().toLowerCase());
Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){
throw new FaceException("Unsupported algorithm");
}
@@ -84,14 +86,37 @@ public class FaceQualityModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
// 初始化默认算法
static {
registerModel("seetaface6model", Seetaface6QualityModel.class);
registerModel(QualityModelEnum.SEETA_FACE6_MODEL, Seetaface6QualityModel.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
modelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
modelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(QualityModelEnum modelEnum) {
modelMap.remove(modelEnum);
}
}

View File

@@ -4,6 +4,7 @@ import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.FaceRecConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.enums.QualityModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.facerec.*;
import lombok.extern.slf4j.Slf4j;
@@ -79,14 +80,15 @@ public class FaceRecModelFactory {
if(clazz == null){
throw new FaceException("Unsupported model");
}
FaceRecModel algorithm = null;
FaceRecModel model = null;
try {
algorithm = (FaceRecModel) clazz.newInstance();
model = (FaceRecModel) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new FaceException(e);
}
algorithm.loadModel(config);
return algorithm;
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -98,7 +100,36 @@ public class FaceRecModelFactory {
registerAlgorithm(FaceRecModelEnum.ELASTIC_FACE_MODEL, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.SEETA_FACE6_MODEL, SeetaFace6FaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.SEETA_FACE6_LIGHT_MODEL, SeetaFace6FaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.DREAM_IJBA_RES18_NAIVE, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.VGG_FACE, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.SPHERE_FACE_20A_ONNX, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.SPHERE_FACE_20A_PT, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.EVOLVE_FACE_IR50, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.EVOLVE_FACE_IR50_ASIA, CommonFaceRecModel.class);
registerAlgorithm(FaceRecModelEnum.EVOLVE_FACE_IR152, CommonFaceRecModel.class);
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
modelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
modelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(FaceRecModelEnum modelEnum) {
modelMap.remove(modelEnum);
}
}

View File

@@ -2,6 +2,7 @@ package cn.smartjavaai.face.factory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.face.config.LivenessConfig;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.enums.LivenessModelEnum;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.model.liveness.CommonLivenessModel;
@@ -87,6 +88,7 @@ public class LivenessModelFactory {
throw new FaceException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -99,4 +101,26 @@ public class LivenessModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
modelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
modelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(LivenessModelEnum modelEnum) {
modelMap.remove(modelEnum);
}
}

View File

@@ -1,5 +1,6 @@
package cn.smartjavaai.face.model.attribute;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.face.FaceAttribute;
@@ -22,12 +23,12 @@ public interface FaceAttributeModel extends AutoCloseable{
void loadModel(FaceAttributeConfig config); // 加载模型
/**
* 人脸属性识别(多人脸)
* @param imagePath 图片路径
* @return
*/
@Deprecated
default DetectionResponse detect(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -37,6 +38,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default DetectionResponse detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -46,6 +48,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imageData 图片字节流
* @return
*/
@Deprecated
default DetectionResponse detect(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -56,6 +59,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default List<FaceAttribute> detect(String imagePath, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,6 +70,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default FaceAttribute detect(String imagePath, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -76,6 +81,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default List<FaceAttribute> detect(byte[] imageData,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -86,6 +92,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default FaceAttribute detect(byte[] imageData, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -97,6 +104,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionResponse 人脸检测结果
* @return
*/
@Deprecated
default List<FaceAttribute> detect(BufferedImage image,DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -107,6 +115,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
@Deprecated
default FaceAttribute detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -117,6 +126,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param image
* @return
*/
@Deprecated
default FaceAttribute detectTopFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -127,6 +137,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imagePath
* @return
*/
@Deprecated
default FaceAttribute detectTopFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -136,6 +147,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default FaceAttribute detectTopFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -145,6 +157,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param image
* @return
*/
@Deprecated
default FaceAttribute detectCropedFace(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -154,6 +167,7 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imagePath
* @return
*/
@Deprecated
default FaceAttribute detectCropedFace(String imagePath){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -163,17 +177,69 @@ public interface FaceAttributeModel extends AutoCloseable{
* @param imageData
* @return
*/
@Deprecated
default FaceAttribute detectCropedFace(byte[] imageData){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸属性识别(多人脸)
* @param image
* @return
*/
default DetectionResponse detect(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸属性识别(多人脸)
* @param image
* @param faceDetectionResponse 人脸检测结果
* @return
*/
default List<FaceAttribute> detect(Image image, DetectionResponse faceDetectionResponse){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸属性识别(单人脸)
* @param image
* @param faceDetectionRectangle 人脸检测结果-人脸框
* @return
*/
default FaceAttribute detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸属性识别(分数最高人脸)
* @param image
* @return
*/
default FaceAttribute detectTopFace(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 人脸属性识别(裁剪后的人脸)
* @param image
* @return
*/
default FaceAttribute detectCropedFace(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -1,12 +1,15 @@
package cn.smartjavaai.face.model.attribute;
import ai.djl.engine.Engine;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.face.FaceAttribute;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.entity.face.HeadPose;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.EyeStatus;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.PoolUtils;
@@ -14,8 +17,10 @@ import cn.smartjavaai.face.config.FaceAttributeConfig;
import cn.smartjavaai.common.enums.face.GenderType;
import cn.smartjavaai.face.context.PredictorContext;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceAttributeModelFactory;
import cn.smartjavaai.face.seetaface.NativeLoader;
import cn.smartjavaai.face.utils.FaceUtils;
import cn.smartjavaai.face.utils.Seetaface6Utils;
import com.seeta.pool.*;
import com.seeta.sdk.*;
import lombok.extern.slf4j.Slf4j;
@@ -150,7 +155,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public DetectionResponse detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
//创建推力器上下文
@@ -168,7 +173,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
//检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){
@@ -182,7 +187,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
faceAttributeList.add(faceAttribute);
}
return FaceUtils.convertToFaceAttributeResponse(seetaResult, seetaPointFSList, faceAttributeList);
return Seetaface6Utils.convertToFaceAttributeResponse(seetaResult, seetaPointFSList, faceAttributeList);
} catch (Exception e) {
throw new FaceException("人脸属性检测错误", e);
} finally {
@@ -212,15 +217,15 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
if (config.isEnableGender()){
GenderPredictor.GENDER[] gender = new GenderPredictor.GENDER[1];
boolean isSuccess = predictorContext.genderPredictor.PredictGenderWithCrop(imageData, landmarks, gender);
genderType = isSuccess ? FaceUtils.convertToGenderType(gender[0]) : GenderType.UNKNOWN;
genderType = isSuccess ? Seetaface6Utils.convertToGenderType(gender[0]) : GenderType.UNKNOWN;
}
//眼睛状态检测
EyeStatus leftEyeStatus = null;
EyeStatus rightEyeStatus = null;
if (config.isEnableEyeStatus()){
EyeStateDetector.EYE_STATE[] eyeState = predictorContext.eyeStateDetector.detect(imageData, landmarks);
leftEyeStatus = FaceUtils.convertToEyeStatus(eyeState[0]);
rightEyeStatus = FaceUtils.convertToEyeStatus(eyeState[1]);
leftEyeStatus = Seetaface6Utils.convertToEyeStatus(eyeState[0]);
rightEyeStatus = Seetaface6Utils.convertToEyeStatus(eyeState[1]);
}
//年龄检测
Integer age = 0;
@@ -278,7 +283,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public List<FaceAttribute> detect(BufferedImage image, DetectionResponse faceDetectionResponse) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
@@ -297,8 +302,8 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
imageData.data = BufferedImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
SeetaPointF[] landmarks = null;
FaceInfo faceInfo = detectionInfo.getFaceInfo();
//如果没有人脸标识,则提取人脸标识
@@ -307,7 +312,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
landmarks = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, landmarks);
}else{
landmarks = FaceUtils.convertToSeetaPointF(faceInfo.getKeyPoints());
landmarks = Seetaface6Utils.convertToSeetaPointF(faceInfo.getKeyPoints());
}
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
@@ -365,7 +370,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public FaceAttribute detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
if(Objects.isNull(faceDetectionRectangle)){
@@ -380,13 +385,13 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = FaceUtils.convertToSeetaRect(faceDetectionRectangle);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(faceDetectionRectangle);
SeetaPointF[] landmarks = null;
if(keyPoints == null || keyPoints.isEmpty()){
throw new FaceException("人脸关键点keyPoints为空");
}
landmarks = FaceUtils.convertToSeetaPointF(keyPoints);
landmarks = Seetaface6Utils.convertToSeetaPointF(keyPoints);
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
return faceAttribute;
@@ -431,10 +436,196 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
@Override
public FaceAttribute detectTopFace(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new FaceException("图像无效");
}
FaceLandmarker faceLandmarker = null;
FaceDetector detectPredictor = null;
//创建推力器上下文
PredictorContext predictorContext = new PredictorContext();
try {
detectPredictor = faceDetectorPool.borrowObject();
faceLandmarker = faceLandmarkerPool.borrowObject();
predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
//检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){
throw new FaceException("无人脸数据");
}
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaResult[0], landmarks);
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaResult[0], landmarks, predictorContext);
return faceAttribute;
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
} finally {
if (detectPredictor != null) {
try {
faceDetectorPool.returnObject(detectPredictor);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceLandmarker != null) {
try {
faceLandmarkerPool.returnObject(faceLandmarker);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
}
}
@Override
public DetectionResponse detect(Image image) {
//创建推力器上下文
PredictorContext predictorContext = new PredictorContext();
FaceLandmarker faceLandmarker = null;
FaceDetector detectPredictor = null;
List<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
List<FaceAttribute> faceAttributeList = new ArrayList<FaceAttribute>();
try {
detectPredictor = faceDetectorPool.borrowObject();
faceLandmarker = faceLandmarkerPool.borrowObject();
predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
//检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){
throw new FaceException("无人脸数据");
}
for(SeetaRect seetaRect : seetaResult){
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, landmarks);
seetaPointFSList.add(landmarks);
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
faceAttributeList.add(faceAttribute);
}
return Seetaface6Utils.convertToFaceAttributeResponse(seetaResult, seetaPointFSList, faceAttributeList);
} catch (Exception e) {
throw new FaceException("人脸属性检测错误", e);
} finally {
// 统一归还所有 Predictor 到池
PoolUtils.returnToPool(faceDetectorPool, detectPredictor);
PoolUtils.returnToPool(faceLandmarkerPool, faceLandmarker);
PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
}
}
@Override
public List<FaceAttribute> detect(Image image, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
throw new FaceException("无人脸数据");
}
//创建推力器上下文
PredictorContext predictorContext = new PredictorContext();
FaceLandmarker faceLandmarker = null;
List<FaceAttribute> faceAttributeList = new ArrayList<FaceAttribute>();
try {
faceLandmarker = faceLandmarkerPool.borrowObject();
predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
SeetaPointF[] landmarks = null;
FaceInfo faceInfo = detectionInfo.getFaceInfo();
//如果没有人脸标识,则提取人脸标识
if(faceInfo == null || faceInfo.getKeyPoints() == null || faceInfo.getKeyPoints().isEmpty()){
//提取人脸的5点人脸标识
landmarks = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, landmarks);
}else{
landmarks = Seetaface6Utils.convertToSeetaPointF(faceInfo.getKeyPoints());
}
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
faceAttributeList.add(faceAttribute);
}
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
} finally {
if (faceLandmarker != null) {
try {
faceLandmarkerPool.returnObject(faceLandmarker);
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
}
return faceAttributeList;
}
@Override
public FaceAttribute detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
if(Objects.isNull(faceDetectionRectangle)){
throw new FaceException("无人脸数据");
}
//创建推力器上下文
PredictorContext predictorContext = new PredictorContext();
try {
predictorContext.genderPredictor = config.isEnableGender() ? genderPredictorPool.borrowObject() : null;
predictorContext.agePredictor = config.isEnableAge() ? agePredictorPool.borrowObject() : null;
predictorContext.maskDetector = config.isEnableMask() ? maskDetectorPool.borrowObject() : null;
predictorContext.eyeStateDetector = config.isEnableEyeStatus() ? eyeStateDetectorPool.borrowObject() : null;
predictorContext.poseEstimator = config.isEnableHeadPose() ? poseEstimatorPool.borrowObject() : null;
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(faceDetectionRectangle);
SeetaPointF[] landmarks = null;
if(keyPoints == null || keyPoints.isEmpty()){
throw new FaceException("人脸关键点keyPoints为空");
}
landmarks = Seetaface6Utils.convertToSeetaPointF(keyPoints);
//人脸属性检测
FaceAttribute faceAttribute = detect(imageData, seetaRect, landmarks, predictorContext);
return faceAttribute;
} catch (Exception e) {
throw new FaceException("活体检测错误", e);
} finally {
PoolUtils.returnToPool(genderPredictorPool, predictorContext.genderPredictor);
PoolUtils.returnToPool(agePredictorPool, predictorContext.agePredictor);
PoolUtils.returnToPool(maskDetectorPool, predictorContext.maskDetector);
PoolUtils.returnToPool(eyeStateDetectorPool, predictorContext.eyeStateDetector);
PoolUtils.returnToPool(poseEstimatorPool, predictorContext.poseEstimator);
}
}
@Override
public FaceAttribute detectTopFace(Image image) {
FaceLandmarker faceLandmarker = null;
FaceDetector detectPredictor = null;
//创建推力器上下文
@@ -485,7 +676,6 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
}
}
public FaceDetectorPool getFaceDetectorPool() {
return faceDetectorPool;
}
@@ -514,8 +704,20 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
return poseEstimatorPool;
}
private boolean fromFactory = false;
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
@Override
public void close() throws Exception {
if (fromFactory) {
FaceAttributeModelFactory.removeFromCache(config.getModelEnum());
}
if(Objects.nonNull(faceDetectorPool)){
faceDetectorPool.close();
}

View File

@@ -1,47 +1,33 @@
package cn.smartjavaai.face.model.expression;
import ai.djl.Device;
import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.Classifications;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.ExpressionResult;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.entity.face.LivenessResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.FacialExpression;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.FaceExpressionConfig;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.ExpressionModelFactory;
import cn.smartjavaai.face.model.expression.criterial.EmotionCriteriaFactory;
import cn.smartjavaai.face.model.expression.translator.DenseNetEmotionTranslator;
import cn.smartjavaai.face.preprocess.DJLImagePreprocessor;
import cn.smartjavaai.face.preprocess.DJLImageFacePreprocessor;
import cn.smartjavaai.face.utils.FaceUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.opencv.core.Mat;
import org.opencv.face.Face;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -68,6 +54,9 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(config.getModelPath())){
throw new FaceException("modelPath为空");
}
if(Objects.isNull(config.getDetectModel())){
throw new FaceException("未指定人脸检测模型");
}
this.config = config;
@@ -92,7 +81,7 @@ public class CommonEmotionModel implements ExpressionModel{
Predictor<Image, Classifications> predictor = null;
try (NDManager manager = model.getNDManager().newSubManager()){
predictor = predictorPool.borrowObject();
DJLImagePreprocessor imagePreprocessor = new DJLImagePreprocessor(image, manager);
DJLImageFacePreprocessor imagePreprocessor = new DJLImageFacePreprocessor(image, manager);
Image faceImg = image;
if(config.isAlign()){
//仿射变换
@@ -131,40 +120,24 @@ public class CommonEmotionModel implements ExpressionModel{
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
// 将图片路径转换为 BufferedImage
BufferedImage image = null;
Image image = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
image = SmartImageFactory.getInstance().fromFile(imagePath);
R<DetectionResponse> detectionResponseR = detect(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return detect(image);
}
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image);
if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
for(DetectionInfo detectionInfo : faceDetectionResponse.getData().getDetectionInfoList()){
FaceInfo faceInfo = detectionInfo.getFaceInfo();
if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
}
Classifications classifications = detectCore(djlImage, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
Classifications.Classification bestClass = classifications.best();
FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
result.setClassifications(classifications);
faceInfo.setExpressionResult(result);
}
((Mat)djlImage.getWrappedImage()).release();
return faceDetectionResponse;
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<DetectionResponse> detectionResponseR = detect(imageDjl);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -172,11 +145,15 @@ public class CommonEmotionModel implements ExpressionModel{
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)));
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<DetectionResponse> detectionResponseR = detect(imageDjl);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -184,8 +161,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detect(imageData);
Image image = null;
try {
image = SmartImageFactory.getInstance().fromBase64(base64Image);
R<DetectionResponse> detectionResponseR = detect(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
@@ -193,14 +178,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
// 将图片路径转换为 BufferedImage
BufferedImage image = null;
Image image = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
image = SmartImageFactory.getInstance().fromFile(imagePath);
R<List<ExpressionResult>> detectionResponseR = detect(image, faceDetectionResponse);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return detect(image, faceDetectionResponse);
}
@Override
@@ -208,37 +195,23 @@ public class CommonEmotionModel implements ExpressionModel{
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionResponse);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<List<ExpressionResult>> detectionResponseR = detect(imageDjl, faceDetectionResponse);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<List<ExpressionResult>> detect(BufferedImage image, DetectionResponse faceDetectionResponse) {
if(!ImageUtils.isImageValid(image)){
R.fail(R.Status.INVALID_IMAGE);
}
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
R.fail(R.Status.NO_FACE_DETECTED);
}
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
List<ExpressionResult> expressionResults = new ArrayList<>();
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
FaceInfo faceInfo = detectionInfo.getFaceInfo();
if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
}
Classifications classifications = detectCore(djlImage, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
Classifications.Classification bestClass = classifications.best();
FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
result.setClassifications(classifications);
expressionResults.add(result);
}
((Mat)djlImage.getWrappedImage()).release();
return R.ok(expressionResults);
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<List<ExpressionResult>> detectionResponseR = detect(imageDjl, faceDetectionResponse);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
@@ -246,8 +219,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detect(imageData, faceDetectionResponse);
Image image = null;
try {
image = SmartImageFactory.getInstance().fromBase64(base64Image);
R<List<ExpressionResult>> detectionResponseR = detect(image, faceDetectionResponse);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
@@ -255,14 +236,16 @@ public class CommonEmotionModel implements ExpressionModel{
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
// 将图片路径转换为 BufferedImage
BufferedImage image = null;
Image image = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
image = SmartImageFactory.getInstance().fromFile(imagePath);
R<ExpressionResult> detectionResponseR = detect(image, faceDetectionRectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
return detect(image, faceDetectionRectangle, keyPoints);
}
@Override
@@ -270,26 +253,26 @@ public class CommonEmotionModel implements ExpressionModel{
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
return detect(ImageIO.read(new ByteArrayInputStream(imageData)), faceDetectionRectangle, keyPoints);
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new FaceException("错误的图像", e);
throw new RuntimeException(e);
}
R<ExpressionResult> detectionResponseR = detect(imageDjl, faceDetectionRectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<ExpressionResult> detect(BufferedImage image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Classifications classifications = detectCore(djlImage, faceDetectionRectangle, keyPoints);
Classifications.Classification bestClass = classifications.best();
FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
result.setClassifications(classifications);
((Mat)djlImage.getWrappedImage()).release();
return R.ok(result);
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<ExpressionResult> detectionResponseR = detect(imageDjl, faceDetectionRectangle, keyPoints);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@@ -298,15 +281,134 @@ public class CommonEmotionModel implements ExpressionModel{
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detect(imageData, faceDetectionRectangle, keyPoints);
Image image = null;
try {
image = SmartImageFactory.getInstance().fromBase64(base64Image);
R<ExpressionResult> detectionResponseR = detect(image, faceDetectionRectangle, keyPoints);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public R<ExpressionResult> detectTopFace(BufferedImage image) {
Image imageDjl = SmartImageFactory.getInstance().fromBufferedImage(image);
R<ExpressionResult> detectionResponseR = detectTopFace(imageDjl);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<ExpressionResult> detectTopFace(String imagePath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image image = null;
try {
image = SmartImageFactory.getInstance().fromFile(imagePath);
R<ExpressionResult> detectionResponseR = detectTopFace(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public R<ExpressionResult> detectTopFace(byte[] imageData) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image imageDjl = null;
try {
imageDjl = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new RuntimeException(e);
}
R<ExpressionResult> detectionResponseR = detectTopFace(imageDjl);
ImageUtils.releaseOpenCVMat(imageDjl);
return detectionResponseR;
}
@Override
public R<ExpressionResult> detectTopFaceBase64(String base64Image) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image image = null;
try {
image = SmartImageFactory.getInstance().fromBase64(base64Image);
R<ExpressionResult> detectionResponseR = detectTopFace(image);
return detectionResponseR;
} catch (IOException e) {
throw new FaceException("无效图片", e);
}finally {
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public R<DetectionResponse> detect(Image image) {
if(Objects.isNull(config.getDetectModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定检测模型");
}
R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image);
if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
for(DetectionInfo detectionInfo : faceDetectionResponse.getData().getDetectionInfoList()){
FaceInfo faceInfo = detectionInfo.getFaceInfo();
if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
}
Classifications classifications = detectCore(image, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
Classifications.Classification bestClass = classifications.best();
FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
result.setClassifications(classifications);
faceInfo.setExpressionResult(result);
}
return faceDetectionResponse;
}
@Override
public R<List<ExpressionResult>> detect(Image image, DetectionResponse faceDetectionResponse) {
if(Objects.isNull(faceDetectionResponse) || Objects.isNull(faceDetectionResponse.getDetectionInfoList()) || faceDetectionResponse.getDetectionInfoList().isEmpty()){
R.fail(R.Status.NO_FACE_DETECTED);
}
List<ExpressionResult> expressionResults = new ArrayList<>();
for(DetectionInfo detectionInfo : faceDetectionResponse.getDetectionInfoList()){
FaceInfo faceInfo = detectionInfo.getFaceInfo();
if(Objects.isNull(faceInfo) || Objects.isNull(faceInfo.getKeyPoints())){
return R.fail(R.Status.Unknown.getCode(), "未检测到人脸关键点");
}
Classifications classifications = detectCore(image, detectionInfo.getDetectionRectangle(), faceInfo.getKeyPoints());
Classifications.Classification bestClass = classifications.best();
FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
result.setClassifications(classifications);
expressionResults.add(result);
}
return R.ok(expressionResults);
}
@Override
public R<ExpressionResult> detect(Image image, DetectionRectangle faceDetectionRectangle, List<Point> keyPoints) {
Classifications classifications = detectCore(image, faceDetectionRectangle, keyPoints);
Classifications.Classification bestClass = classifications.best();
FacialExpression expression = FacialExpression.fromLabel(bestClass.getClassName());
ExpressionResult result = new ExpressionResult(expression, (float)bestClass.getProbability());
result.setClassifications(classifications);
return R.ok(result);
}
@Override
public R<ExpressionResult> detectTopFace(Image image) {
R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image);
if(Objects.isNull(faceDetectionResponse.getData()) || Objects.isNull(faceDetectionResponse.getData().getDetectionInfoList()) || faceDetectionResponse.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
@@ -319,49 +421,26 @@ public class CommonEmotionModel implements ExpressionModel{
return detect(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getKeyPoints());
}
@Override
public R<ExpressionResult> detectTopFace(String imagePath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
// 将图片路径转换为 BufferedImage
BufferedImage image = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}
return detectTopFace(image);
}
@Override
public R<ExpressionResult> detectTopFace(byte[] imageData) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
return detectTopFace(ImageIO.read(new ByteArrayInputStream(imageData)));
} catch (IOException e) {
throw new FaceException("错误的图像", e);
}
}
@Override
public R<ExpressionResult> detectTopFaceBase64(String base64Image) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detectTopFace(imageData);
}
@Override
public GenericObjectPool<Predictor<Image, Classifications>> getPool() {
return predictorPool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
@Override
public void close() {
if (fromFactory) {
ExpressionModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();

View File

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

View File

@@ -9,15 +9,15 @@ import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.ExpressionModelFactory;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.criterial.FaceDetCriteriaFactory;
import cn.smartjavaai.face.utils.FaceUtils;
import lombok.extern.slf4j.Slf4j;
@@ -48,6 +48,8 @@ public class CommonFaceDetModel implements FaceDetModel{
private ZooModel<Image, DetectedObjects> model;
private FaceDetConfig config;
/**
* 加载模型
@@ -57,6 +59,7 @@ public class CommonFaceDetModel implements FaceDetModel{
public void loadModel(FaceDetConfig config){
Criteria<Image, DetectedObjects> criteria = FaceDetCriteriaFactory.createCriteria(config);
try {
this.config = config;
model = criteria.loadModel();
this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model));
int predictorPoolSize = config.getPredictorPoolSize();
@@ -72,14 +75,13 @@ public class CommonFaceDetModel implements FaceDetModel{
}
}
@Override
public R<DetectionResponse> detect(Image image) {
DetectedObjects detection = detectCore(image);
return R.ok(FaceUtils.convertToDetectionResponse(detection, image));
}
/**
* 检测人脸
* @param imagePath 图片路径
* @return
* @throws Exception
*/
@Override
public R<DetectionResponse> detect(String imagePath){
if(!FileUtils.isFileExists(imagePath)){
@@ -87,13 +89,17 @@ public class CommonFaceDetModel implements FaceDetModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detection = detect(img);
return R.ok(FaceUtils.convertToDetectionResponse(detection,img));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detection = detectCore(img);
DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detection, img);
if(detectionResponse == null){
return R.fail(R.Status.NO_FACE_DETECTED);
}
return R.ok(detectionResponse);
} catch (IOException e) {
throw new FaceException("无效的图片", e);
} finally {
if (img != null) {
if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -113,13 +119,17 @@ public class CommonFaceDetModel implements FaceDetModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromInputStream(imageInputStream);
DetectedObjects detection = detect(img);
return R.ok(FaceUtils.convertToDetectionResponse(detection,img));
img = SmartImageFactory.getInstance().fromInputStream(imageInputStream);
DetectedObjects detection = detectCore(img);
DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detection,img);
if(detectionResponse == null){
return R.fail(R.Status.NO_FACE_DETECTED);
}
return R.ok(detectionResponse);
} catch (IOException e) {
throw new FaceException("无效图片输入流", e);
} finally {
if (img != null) {
if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -128,18 +138,22 @@ public class CommonFaceDetModel implements FaceDetModel{
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
DetectedObjects detection = detect(img);
return R.ok(FaceUtils.convertToDetectionResponse(detection,img));
img = SmartImageFactory.getInstance().fromBufferedImage(image);
DetectedObjects detection = detectCore(img);
DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detection,img);
if(detectionResponse == null){
return R.fail(R.Status.NO_FACE_DETECTED);
}
return R.ok(detectionResponse);
} catch (Exception e) {
throw new FaceException(e);
} finally {
if (img != null) {
if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -164,14 +178,27 @@ public class CommonFaceDetModel implements FaceDetModel{
}
@Override
public R<Void> detectAndDraw(String imagePath, String outputPath) {
public R<DetectionResponse> detectAndDraw(Image image) {
DetectedObjects detectedObjects = detectCore(image);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
Image drawnImage = ImageUtils.copy(image);
drawnImage.drawBoundingBoxes(detectedObjects);
DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detectedObjects, drawnImage);
detectionResponse.setDrawnImage(drawnImage);
return R.ok(detectionResponse);
}
@Override
public R<DetectionResponse> detectAndDraw(String imagePath, String outputPath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detectedObjects = detect(img);
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detectedObjects = detectCore(img);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -179,11 +206,15 @@ public class CommonFaceDetModel implements FaceDetModel{
Path output = Paths.get(outputPath);
log.debug("Saving to {}", output.toAbsolutePath().toString());
img.save(Files.newOutputStream(output), "png");
return R.ok();
DetectionResponse detectionResponse = FaceUtils.convertToDetectionResponse(detectedObjects,img);
if(detectionResponse == null){
return R.fail(R.Status.NO_FACE_DETECTED);
}
return R.ok(detectionResponse);
} catch (IOException e) {
throw new FaceException(e);
} finally {
if (img != null){
if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
@@ -191,11 +222,11 @@ public class CommonFaceDetModel implements FaceDetModel{
@Override
public R<BufferedImage> detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
DetectedObjects detectedObjects = detect(img);
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
DetectedObjects detectedObjects = detectCore(img);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -210,18 +241,21 @@ public class CommonFaceDetModel implements FaceDetModel{
} catch (IOException e) {
throw new FaceException("导出图片失败", e);
} finally {
if (img != null){
if (img != null && img.getWrappedImage() instanceof Mat) {
((Mat)img.getWrappedImage()).release();
}
}
}
/**
* 人脸检测
* @param image
* @return
*/
public DetectedObjects detect(Image image){
@Override
public DetectedObjects detectCore(Image image){
Predictor<Image, DetectedObjects> predictor = null;
try {
predictor = predictorPool.borrowObject();
@@ -250,8 +284,22 @@ public class CommonFaceDetModel implements FaceDetModel{
return predictorPool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
@Override
public void close() {
if (fromFactory) {
FaceDetModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();

View File

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

View File

@@ -5,7 +5,6 @@ import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.ndarray.NDArray;
@@ -16,6 +15,7 @@ import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import ai.djl.translate.NoopTranslator;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.face.FaceInfo;
@@ -24,6 +24,7 @@ import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.criterial.FaceDetCriteriaFactory;
import cn.smartjavaai.face.model.facedect.mtcnn.*;
import cn.smartjavaai.face.utils.FaceUtils;
@@ -53,7 +54,7 @@ import java.util.Objects;
* @author dwj
*/
@Slf4j
public class MtcnnFaceDetModel implements FaceDetModel{
public class MtcnnFaceDetModel extends CommonFaceDetModel{
public ZooModel<NDList, NDList> pNetModel;
@@ -65,6 +66,8 @@ public class MtcnnFaceDetModel implements FaceDetModel{
private GenericObjectPool<Predictor<NDList, NDList>> rnetPredictorPool;
private GenericObjectPool<Predictor<NDList, NDList>> onetPredictorPool;
private FaceDetConfig config;
/**
* 加载模型
@@ -72,6 +75,7 @@ public class MtcnnFaceDetModel implements FaceDetModel{
*/
@Override
public void loadModel(FaceDetConfig config){
this.config = config;
if(StringUtils.isBlank(config.getModelPath())){
throw new FaceException("modelPath is null");
}
@@ -130,146 +134,13 @@ public class MtcnnFaceDetModel implements FaceDetModel{
}
/**
* 检测人脸
* @param imagePath 图片路径
* @return
* @throws Exception
*/
@Override
public R<DetectionResponse> detect(String imagePath){
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
return detect(img);
} catch (IOException e) {
throw new FaceException("无效的图片", e);
} finally {
if (img != null) {
((Mat)img.getWrappedImage()).release();
}
}
}
/**
* 检测人脸
* @param imageInputStream 图片流
* @return
* @throws Exception
*/
@Override
public R<DetectionResponse> detect(InputStream imageInputStream){
if(Objects.isNull(imageInputStream)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromInputStream(imageInputStream);
return detect(img);
} catch (IOException e) {
throw new FaceException("无效图片输入流", e);
} finally {
if (img != null) {
((Mat)img.getWrappedImage()).release();
}
}
}
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
return detect(img);
} catch (Exception e) {
throw new FaceException(e);
} finally {
if (img != null) {
((Mat)img.getWrappedImage()).release();
}
}
}
@Override
public R<DetectionResponse> detect(byte[] imageData) {
if(Objects.isNull(imageData)){
return R.fail(R.Status.INVALID_IMAGE);
}
return detect(new ByteArrayInputStream(imageData));
}
@Override
public R<DetectionResponse> detectBase64(String base64Image) {
if(StringUtils.isBlank(base64Image)){
return R.fail(R.Status.INVALID_IMAGE);
}
byte[] imageData = Base64ImageUtils.base64ToImage(base64Image);
return detect(imageData);
}
@Override
public R<Void> detectAndDraw(String imagePath, String outputPath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<DetectionResponse> detectionResponseR = detect(img);
if(!detectionResponseR.isSuccess()){
return R.fail(detectionResponseR.getCode(), detectionResponseR.getMessage());
}
if(Objects.isNull(detectionResponseR.getData()) ||
CollectionUtils.isEmpty(detectionResponseR.getData().getDetectionInfoList())){
return R.fail(R.Status.NO_FACE_DETECTED);
}
BufferedImage sourceImage = OpenCVUtils.mat2Image((Mat)img.getWrappedImage());
FaceUtils.drawBoundingBoxes(sourceImage, detectionResponseR.getData(), outputPath);
return R.ok();
} catch (IOException e) {
throw new FaceException(e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
}
}
@Override
public R<BufferedImage> detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
R<DetectionResponse> detectionResponseR = detect(sourceImage);
if(!detectionResponseR.isSuccess()){
return R.fail(detectionResponseR.getCode(), detectionResponseR.getMessage());
}
if(Objects.isNull(detectionResponseR.getData()) ||
CollectionUtils.isEmpty(detectionResponseR.getData().getDetectionInfoList())){
return R.fail(R.Status.NO_FACE_DETECTED);
}
return R.ok(FaceUtils.drawBoundingBoxes(sourceImage, detectionResponseR.getData()));
} catch (IOException e) {
throw new FaceException("导出图片失败", e);
}
}
/**
* 人脸检测
* @param image
* @return
*/
public R<DetectionResponse> detect(Image image){
@Override
public DetectedObjects detectCore(Image image){
Predictor<NDList, NDList> pNetPredictor = null;
Predictor<NDList, NDList> rNetPredictor = null;
Predictor<NDList, NDList> oNetPredictor = null;
@@ -281,35 +152,33 @@ public class MtcnnFaceDetModel implements FaceDetModel{
int w = image.getWidth();
//第一阶段
NDList outputPnet = PNetModel.firstStage(manager, pNetPredictor, image);
if(CollectionUtils.isEmpty(outputPnet)){
return R.fail(R.Status.NO_FACE_DETECTED);
return DJLCommonUtils.buildEmptyDetectedObjects();
}
NDArray boxes = outputPnet.get(0);
NDArray image_inds = outputPnet.get(1);
NDArray imgs = outputPnet.get(2);
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_inds) || DJLCommonUtils.isNDArrayEmpty(imgs)){
return R.fail(R.Status.NO_FACE_DETECTED);
return DJLCommonUtils.buildEmptyDetectedObjects();
}
NDList pad = MtcnnUtils.pad(boxes, w, h);
//第二阶段
NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs,boxes,pad, image_inds);
NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs, boxes, pad, image_inds);
if(CollectionUtils.isEmpty(outputRnet)){
return R.fail(R.Status.NO_FACE_DETECTED);
return DJLCommonUtils.buildEmptyDetectedObjects();
}
NDArray image_indsFiltered = outputRnet.get(0);
NDArray scoresFiltered = outputRnet.get(1);
boxes = outputRnet.get(2);
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_indsFiltered) || DJLCommonUtils.isNDArrayEmpty(scoresFiltered)){
return R.fail(R.Status.NO_FACE_DETECTED);
return DJLCommonUtils.buildEmptyDetectedObjects();
}
//第三阶段
MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs,boxes, w, h, scoresFiltered, image_indsFiltered);
DetectionResponse detectionResponse = convertToDetectionResponse(oNetResult);
if(Objects.isNull(detectionResponse)){
return R.fail(R.Status.NO_FACE_DETECTED);
}
return R.ok(detectionResponse);
MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs, boxes, w, h, scoresFiltered, image_indsFiltered);
return FaceUtils.toDetectedObjects(oNetResult, w, h);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
if (pNetPredictor != null) {
@@ -354,50 +223,50 @@ public class MtcnnFaceDetModel implements FaceDetModel{
/**
* 转换为FaceDetectedResult
* @param mtcnnBatchResult
* @return
*/
public static DetectionResponse convertToDetectionResponse(MtcnnBatchResult mtcnnBatchResult){
if(Objects.isNull(mtcnnBatchResult) || CollectionUtils.isEmpty(mtcnnBatchResult.boxes)
|| CollectionUtils.isEmpty(mtcnnBatchResult.points)
|| CollectionUtils.isEmpty(mtcnnBatchResult.probs)){
return null;
}
DetectionResponse detectionResponse = new DetectionResponse();
List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
NDArray boxes = mtcnnBatchResult.boxes.get(0);
NDArray probs = mtcnnBatchResult.probs.get(0);
NDArray points = mtcnnBatchResult.points.get(0);
if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){
return null;
}
long numBoxes = boxes.getShape().get(0);
for (int i = 0; i < numBoxes; i++) {
float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
float score = probs.getFloat(i);
NDArray pointND = points.get(i); // shape [5,2]
float[] flatPoints = pointND.toFloatArray(); // 一维长度 10
List<Point> keyPoints = new ArrayList<Point>();
for (int p = 0; p < 5; p++) {
keyPoints.add(new Point(flatPoints[p * 2], flatPoints[p * 2 + 1]));
}
int x = Math.round(boxCoords[0]);
int y = Math.round(boxCoords[1]);
int w = Math.round(boxCoords[2] - boxCoords[0]);
int h = Math.round(boxCoords[3] - boxCoords[1]);
DetectionRectangle rectangle = new DetectionRectangle(x, y, w, h);
FaceInfo faceInfo = new FaceInfo(keyPoints);
DetectionInfo detectionInfo = new DetectionInfo(rectangle, score, faceInfo);
detectionInfoList.add(detectionInfo);
}
detectionResponse.setDetectionInfoList(detectionInfoList);
return detectionResponse;
}
// /**
// * 转换为FaceDetectedResult
// * @param mtcnnBatchResult
// * @return
// */
// public static DetectionResponse convertToDetectionResponse(MtcnnBatchResult mtcnnBatchResult){
// if(Objects.isNull(mtcnnBatchResult) || CollectionUtils.isEmpty(mtcnnBatchResult.boxes)
// || CollectionUtils.isEmpty(mtcnnBatchResult.points)
// || CollectionUtils.isEmpty(mtcnnBatchResult.probs)){
// return null;
// }
// DetectionResponse detectionResponse = new DetectionResponse();
// List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
//
// NDArray boxes = mtcnnBatchResult.boxes.get(0);
// NDArray probs = mtcnnBatchResult.probs.get(0);
// NDArray points = mtcnnBatchResult.points.get(0);
//
// if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){
// return null;
// }
// long numBoxes = boxes.getShape().get(0);
// for (int i = 0; i < numBoxes; i++) {
// float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
// float score = probs.getFloat(i);
// NDArray pointND = points.get(i); // shape [5,2]
// float[] flatPoints = pointND.toFloatArray(); // 一维长度 10
// List<Point> keyPoints = new ArrayList<Point>();
// for (int p = 0; p < 5; p++) {
// keyPoints.add(new Point(flatPoints[p * 2], flatPoints[p * 2 + 1]));
// }
// int x = Math.round(boxCoords[0]);
// int y = Math.round(boxCoords[1]);
// int w = Math.round(boxCoords[2] - boxCoords[0]);
// int h = Math.round(boxCoords[3] - boxCoords[1]);
//
// DetectionRectangle rectangle = new DetectionRectangle(x, y, w, h);
// FaceInfo faceInfo = new FaceInfo(keyPoints);
// DetectionInfo detectionInfo = new DetectionInfo(rectangle, score, faceInfo);
// detectionInfoList.add(detectionInfo);
// }
// detectionResponse.setDetectionInfoList(detectionInfoList);
// return detectionResponse;
// }
@@ -413,8 +282,22 @@ public class MtcnnFaceDetModel implements FaceDetModel{
return onetPredictorPool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
@Override
public void close() {
if (fromFactory) {
FaceDetModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (pnetPredictorPool != null) {
pnetPredictorPool.close();

View File

@@ -1,14 +1,18 @@
package cn.smartjavaai.face.model.facedect;
import ai.djl.engine.Engine;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.seetaface.NativeLoader;
import cn.smartjavaai.face.utils.FaceUtils;
import com.seeta.pool.*;
@@ -81,6 +85,59 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
}
@Override
public R<DetectionResponse> detect(Image image) {
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
FaceDetector predictor = null;
FaceLandmarker faceLandmarker = null;
try {
predictor = faceDetectorPool.borrowObject();
predictor.set(FaceDetector.Property.PROPERTY_THRESHOLD, config.getConfidenceThreshold() > 0 ? config.getConfidenceThreshold() : THRESHOLD);
faceLandmarker = faceLandmarkerPool.borrowObject();
SeetaRect[] seetaResult = predictor.Detect(imageData);
List<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
for(SeetaRect seetaRect : seetaResult){
//提取人脸的5点人脸标识
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
faceLandmarker.mark(imageData, seetaRect, pointFS);
seetaPointFSList.add(pointFS);
}
return R.ok(FaceUtils.convertToDetectionResponse(seetaResult, seetaPointFSList));
} catch (Exception e) {
throw new FaceException("目标检测错误", e);
}finally {
if (predictor != null) {
try {
faceDetectorPool.returnObject(predictor); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
if (faceLandmarker != null) {
try {
faceLandmarkerPool.returnObject(faceLandmarker); //归还
} catch (Exception e) {
log.warn("归还Predictor失败", e);
}
}
}
}
@Override
public R<DetectionResponse> detectAndDraw(Image image) {
R<DetectionResponse> result = detect(image);
if(result.getCode() != R.Status.SUCCESS.getCode()){
return R.fail(result.getCode(), result.getMessage());
}
if(Objects.isNull(result.getData()) || Objects.isNull(result.getData().getDetectionInfoList()) || result.getData().getDetectionInfoList().isEmpty()){
return R.fail(R.Status.NO_FACE_DETECTED);
}
Image drawnImage = ImageUtils.drawBoundingBoxes(image, result.getData());
result.getData().setDrawnImage(drawnImage);
return result;
}
@Override
public R<DetectionResponse> detect(String imagePath) {
if(!FileUtils.isFileExists(imagePath)){
@@ -112,11 +169,11 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
@Override
public R<DetectionResponse> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
imageData.data = BufferedImageUtils.getMatrixBGR(image);
FaceDetector predictor = null;
FaceLandmarker faceLandmarker = null;
try {
@@ -174,19 +231,14 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
}
@Override
public R<Void> detectAndDraw(String imagePath, String outputPath) {
public R<DetectionResponse> detectAndDraw(String imagePath, String outputPath) {
if(!FileUtils.isFileExists(imagePath)){
return R.fail(R.Status.FILE_NOT_FOUND);
}
Image image = null;
Image drawImage = null;
try {
//创建保存路径
Path imageOutputPath = Paths.get(outputPath);
BufferedImage image = null;
try {
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
} catch (IOException e) {
throw new FaceException("无效图片路径", e);
}
image = SmartImageFactory.getInstance().fromFile(imagePath);
R<DetectionResponse> result = detect(image);
if(result.getCode() != R.Status.SUCCESS.getCode()){
return R.fail(result.getCode(), result.getMessage());
@@ -195,16 +247,20 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return R.fail(R.Status.NO_FACE_DETECTED);
}
//绘制人脸框
FaceUtils.drawBoundingBoxes(image, result.getData(), imageOutputPath.toAbsolutePath().toString());
return R.ok();
drawImage = ImageUtils.drawBoundingBoxes(image, result.getData());
ImageUtils.save(drawImage, Paths.get(outputPath), "png");
return result;
} catch (IOException e) {
throw new FaceException(e);
throw new FaceException("保存图片失败", e);
} finally {
ImageUtils.releaseOpenCVMat(image);
ImageUtils.releaseOpenCVMat(drawImage);
}
}
@Override
public R<BufferedImage> detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
R<DetectionResponse> result = detect(sourceImage);
@@ -215,14 +271,14 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return R.fail(R.Status.NO_FACE_DETECTED);
}
//绘制人脸框
try {
return R.ok(FaceUtils.drawBoundingBoxes(sourceImage, result.getData()));
} catch (IOException e) {
throw new RuntimeException(e);
}
BufferedImage drawnImage = BufferedImageUtils.copyBufferedImage(sourceImage);
BufferedImageUtils.drawBoundingBoxes(drawnImage, result.getData());
return R.ok(drawnImage);
}
public FaceDetectorPool getFaceDetectorPool() {
return faceDetectorPool;
}
@@ -231,8 +287,23 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return faceLandmarkerPool;
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
@Override
public void close() throws Exception {
if (fromFactory) {
FaceDetModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (faceDetectorPool != null) {
faceDetectorPool.close();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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