支持离线下载模型

This commit is contained in:
dengwenjie
2025-02-26 11:23:39 +08:00
parent e1aebbf37d
commit 685727e67c
15 changed files with 447 additions and 309 deletions

View File

@@ -12,6 +12,11 @@ public abstract class AbstractFaceAlgorithm implements FaceAlgorithm{
throw new UnsupportedOperationException("默认不支持该功能");
}
@Override
public void loadFaceFeatureModel(ModelConfig config) throws Exception {
throw new UnsupportedOperationException("默认不支持该功能");
}
@Override
public FaceDetectedResult detect(String imagePath) throws Exception {
throw new UnsupportedOperationException("默认不支持该功能");

View File

@@ -20,6 +20,13 @@ public interface FaceAlgorithm {
*/
void loadModel(ModelConfig config) throws Exception; // 加载模型
/**
* 加载人脸特征提取模型
* @param config
* @throws Exception
*/
void loadFaceFeatureModel(ModelConfig config) throws Exception; // 加载模型
/**
* 人脸检测
* @param imagePath 图片路径

View File

@@ -1,5 +1,6 @@
package cn.smartjavaai.face;
import cn.smartjavaai.face.algo.FeatureExtractionAlgo;
import cn.smartjavaai.face.algo.RetinaFace;
import cn.smartjavaai.face.algo.UltraLightFastGenericFace;
@@ -55,13 +56,7 @@ public class FaceAlgorithmFactory {
config.setConfidenceThreshold(FaceConfig.DEFAULT_CONFIDENCE_THRESHOLD);
config.setMaxFaceCount(FaceConfig.MAX_FACE_LIMIT);
config.setNmsThresh(FaceConfig.NMS_THRESHOLD);
Class<?> clazz = registry.get(config.getAlgorithmName().toLowerCase());
if(clazz == null){
throw new IllegalArgumentException("Unsupported algorithm");
}
FaceAlgorithm algorithm = (FaceAlgorithm) clazz.newInstance();
algorithm.loadModel(config);
return algorithm;
return createFaceAlgorithm(config);
}
/**
@@ -85,10 +80,40 @@ public class FaceAlgorithmFactory {
return algorithm;
}
/**
* 使用ModelConfig创建人脸特征提取算法
* @param config
* @return
* @throws Exception
*/
public static FaceAlgorithm createFaceFeatureAlgorithm(ModelConfig config) throws Exception {
Class<?> clazz = registry.get(config.getAlgorithmName().toLowerCase());
if(clazz == null){
throw new IllegalArgumentException("Unsupported algorithm");
}
FaceAlgorithm algorithm = (FaceAlgorithm) clazz.newInstance();
algorithm.loadFaceFeatureModel(config);
return algorithm;
}
/**
* 创建人脸特征提取算法
* @return
* @throws Exception
*/
public static FaceAlgorithm createFaceFeatureAlgorithm() throws Exception {
// 初始化配置
ModelConfig config = new ModelConfig();
config.setAlgorithmName("featureExtraction");
return createFaceFeatureAlgorithm(config);
}
// 初始化默认算法
static {
registerAlgorithm("retinaface", RetinaFace.class);
registerAlgorithm("ultralightfastgenericface", UltraLightFastGenericFace.class);
//人脸特征提取
registerAlgorithm("featureExtraction", FeatureExtractionAlgo.class);
}
}

View File

@@ -27,6 +27,12 @@ public class ModelConfig {
*/
private int maxFaceCount;
/**
* 模型路径
*/
private String modelPath;
public String getAlgorithmName() {
return algorithmName;
}
@@ -58,4 +64,12 @@ public class ModelConfig {
public void setMaxFaceCount(int maxFaceCount) {
this.maxFaceCount = maxFaceCount;
}
public String getModelPath() {
return modelPath;
}
public void setModelPath(String modelPath) {
this.modelPath = modelPath;
}
}

View File

@@ -0,0 +1,159 @@
package cn.smartjavaai.face.algo;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.translator.ImageFeatureExtractorFactory;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.Rectangle;
import cn.smartjavaai.face.AbstractFaceAlgorithm;
import cn.smartjavaai.face.FaceDetectedResult;
import cn.smartjavaai.face.FaceDetectionTranslator;
import cn.smartjavaai.face.ModelConfig;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* RetinaFace实现
* @author dwj
*/
public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
private Criteria<Image, float[]> faceFeatureCriteria;
public static final List<Float> mean =
Arrays.asList(
127.5f / 255.0f,
127.5f / 255.0f,
127.5f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f);
/**
* 加载人脸特征提取模型
* @param config
* @throws Exception
*/
@Override
public void loadFaceFeatureModel(ModelConfig config) throws Exception {
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
faceFeatureCriteria = Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelUrls(StringUtils.isNotBlank(config.getModelPath()) ? null :
"https://resources.djl.ai/test-models/pytorch/face_feature.zip")
.optModelPath(StringUtils.isNotBlank(config.getModelPath()) ? Paths.get(config.getModelPath()) : null)
.optModelName("face_feature") // specify model file prefix
.optArgument("normalize", normalize)
.optTranslatorFactory(new ImageFeatureExtractorFactory())
.optProgress(new ProgressBar())
.optEngine("PyTorch") // Use PyTorch engine
.build();
}
/**
* 特征提取
* @param imagePath 图片路径
* @return
* @throws Exception
*/
@Override
public float[] featureExtraction(String imagePath) throws Exception {
Path imageFile = Paths.get(imagePath);
Image img = ImageFactory.getInstance().fromFile(imageFile);
img.getWrappedImage();
try (ZooModel<Image, float[]> model = faceFeatureCriteria.loadModel()) {
Predictor<Image, float[]> predictor = model.newPredictor();
return predictor.predict(img);
}
}
/**
* 特征提取
* @param inputStream 输入流
* @return
* @throws Exception
*/
@Override
public float[] featureExtraction(InputStream inputStream) throws Exception {
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
img.getWrappedImage();
try (ZooModel<Image, float[]> model = faceFeatureCriteria.loadModel()) {
Predictor<Image, float[]> predictor = model.newPredictor();
return predictor.predict(img);
}
}
/**
* 计算相似度
* @param feature1 图1特征
* @param feature2 图2特征
* @return
* @throws Exception
*/
@Override
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
float ret = 0.0f;
float mod1 = 0.0f;
float mod2 = 0.0f;
int length = feature1.length;
for (int i = 0; i < length; ++i) {
ret += feature1[i] * feature2[i];
mod1 += feature1[i] * feature1[i];
mod2 += feature2[i] * feature2[i];
}
return (float) ((ret / Math.sqrt(mod1) / Math.sqrt(mod2) + 1) / 2.0f);
}
/**
* 特征比较
* @param imagePath1 图1路径
* @param imagePath2 图2路径
* @return
* @throws Exception
*/
@Override
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
float[] feature1 = featureExtraction(imagePath1);
float[] feature2 = featureExtraction(imagePath2);
return calculSimilar(feature1, feature2);
}
/**
* 特征比较
* @param inputStream1 图1输入流
* @param inputStream2 图2输入流
* @return
* @throws Exception
*/
@Override
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
float[] feature1 = featureExtraction(inputStream1);
float[] feature2 = featureExtraction(inputStream2);
return calculSimilar(feature1, feature2);
}
/*@Override
public float[] recognize(FaceRegion region) {
return new float[0];
}*/
/*@Override
public void loadModel(ModelConfig config) throws Exception {
}*/
}

View File

@@ -14,8 +14,8 @@ import ai.djl.translate.TranslateException;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.Rectangle;
import cn.smartjavaai.face.*;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStream;
@@ -36,6 +36,8 @@ public class RetinaFace extends AbstractFaceAlgorithm {
private Criteria<Image, DetectedObjects> criteria;
private Criteria<Image, float[]> faceFeatureCriteria;
/**
* 特征图层的基础缩放比例
*/
@@ -49,6 +51,7 @@ public class RetinaFace extends AbstractFaceAlgorithm {
*/
public static final double[] variance = {0.1f, 0.2f};
/**
* 加载模型
* @param config
@@ -60,16 +63,18 @@ public class RetinaFace extends AbstractFaceAlgorithm {
criteria =
Criteria.builder()
.setTypes(Image.class, DetectedObjects.class)
.optModelUrls("https://resources.djl.ai/test-models/pytorch/retinaface.zip")
//.optModelPath(modelPath)
.optModelUrls(StringUtils.isNotBlank(config.getModelPath()) ? null : "https://resources.djl.ai/test-models/pytorch/retinaface.zip")
// Load model from local file, e.g:
.optModelName("retinaface") // specify model file prefix
.optModelPath(StringUtils.isNotBlank(config.getModelPath()) ? Paths.get(config.getModelPath()) : null)
.optModelName(StringUtils.isNotBlank(config.getAlgorithmName()) ? config.getAlgorithmName() : "retinaface") // specify model file prefix
.optTranslator(translator)
.optProgress(new ProgressBar())
.optEngine("PyTorch") // Use PyTorch engine
.build();
}
/**
* 检测人脸
* @param imagePath 图片路径
@@ -138,140 +143,4 @@ public class RetinaFace extends AbstractFaceAlgorithm {
faceDetectedResult.setRectangles(RectangleList);
return faceDetectedResult;
}
/**
* 特征提取
* @param imagePath 图片路径
* @return
* @throws Exception
*/
@Override
public float[] featureExtraction(String imagePath) throws Exception {
Path imageFile = Paths.get(imagePath);
Image img = ImageFactory.getInstance().fromFile(imageFile);
img.getWrappedImage();
List<Float> mean =
Arrays.asList(
127.5f / 255.0f,
127.5f / 255.0f,
127.5f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
Criteria<Image, float[]> criteria =
Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelUrls(
"https://resources.djl.ai/test-models/pytorch/face_feature.zip")
.optModelName("face_feature") // specify model file prefix
.optArgument("normalize", normalize)
.optTranslatorFactory(new ImageFeatureExtractorFactory())
.optProgress(new ProgressBar())
.optEngine("PyTorch") // Use PyTorch engine
.build();
try (ZooModel<Image, float[]> model = criteria.loadModel()) {
Predictor<Image, float[]> predictor = model.newPredictor();
return predictor.predict(img);
}
}
/**
* 特征提取
* @param inputStream 输入流
* @return
* @throws Exception
*/
@Override
public float[] featureExtraction(InputStream inputStream) throws Exception {
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
img.getWrappedImage();
List<Float> mean =
Arrays.asList(
127.5f / 255.0f,
127.5f / 255.0f,
127.5f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
Criteria<Image, float[]> criteria =
Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelUrls(
"https://resources.djl.ai/test-models/pytorch/face_feature.zip")
.optModelName("face_feature") // specify model file prefix
.optArgument("normalize", normalize)
.optTranslatorFactory(new ImageFeatureExtractorFactory())
.optProgress(new ProgressBar())
.optEngine("PyTorch") // Use PyTorch engine
.build();
try (ZooModel<Image, float[]> model = criteria.loadModel()) {
Predictor<Image, float[]> predictor = model.newPredictor();
return predictor.predict(img);
}
}
/**
* 计算相似度
* @param feature1 图1特征
* @param feature2 图2特征
* @return
* @throws Exception
*/
@Override
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
float ret = 0.0f;
float mod1 = 0.0f;
float mod2 = 0.0f;
int length = feature1.length;
for (int i = 0; i < length; ++i) {
ret += feature1[i] * feature2[i];
mod1 += feature1[i] * feature1[i];
mod2 += feature2[i] * feature2[i];
}
return (float) ((ret / Math.sqrt(mod1) / Math.sqrt(mod2) + 1) / 2.0f);
}
/**
* 特征比较
* @param imagePath1 图1路径
* @param imagePath2 图2路径
* @return
* @throws Exception
*/
@Override
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
float[] feature1 = featureExtraction(imagePath1);
float[] feature2 = featureExtraction(imagePath2);
return calculSimilar(feature1, feature2);
}
/**
* 特征比较
* @param inputStream1 图1输入流
* @param inputStream2 图2输入流
* @return
* @throws Exception
*/
@Override
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
float[] feature1 = featureExtraction(inputStream1);
float[] feature2 = featureExtraction(inputStream2);
return calculSimilar(feature1, feature2);
}
/*@Override
public float[] recognize(FaceRegion region) {
return new float[0];
}*/
/*@Override
public void loadModel(ModelConfig config) throws Exception {
}*/
}

View File

@@ -131,139 +131,4 @@ public class UltraLightFastGenericFace extends AbstractFaceAlgorithm {
return faceDetectedResult;
}
/**
* 特征提取
* @param imagePath 图片路径
* @return
* @throws Exception
*/
@Override
public float[] featureExtraction(String imagePath) throws Exception {
Path imageFile = Paths.get(imagePath);
Image img = ImageFactory.getInstance().fromFile(imageFile);
img.getWrappedImage();
List<Float> mean =
Arrays.asList(
127.5f / 255.0f,
127.5f / 255.0f,
127.5f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
Criteria<Image, float[]> criteria =
Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelUrls(
"https://resources.djl.ai/test-models/pytorch/face_feature.zip")
.optModelName("face_feature") // specify model file prefix
.optArgument("normalize", normalize)
.optTranslatorFactory(new ImageFeatureExtractorFactory())
.optProgress(new ProgressBar())
.optEngine("PyTorch") // Use PyTorch engine
.build();
try (ZooModel<Image, float[]> model = criteria.loadModel()) {
Predictor<Image, float[]> predictor = model.newPredictor();
return predictor.predict(img);
}
}
/**
* 特征提取
* @param inputStream 输入流
* @return
* @throws Exception
*/
@Override
public float[] featureExtraction(InputStream inputStream) throws Exception {
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
img.getWrappedImage();
List<Float> mean =
Arrays.asList(
127.5f / 255.0f,
127.5f / 255.0f,
127.5f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f,
128.0f / 255.0f);
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
Criteria<Image, float[]> criteria =
Criteria.builder()
.setTypes(Image.class, float[].class)
.optModelUrls(
"https://resources.djl.ai/test-models/pytorch/face_feature.zip")
.optModelName("face_feature") // specify model file prefix
.optArgument("normalize", normalize)
.optTranslatorFactory(new ImageFeatureExtractorFactory())
.optProgress(new ProgressBar())
.optEngine("PyTorch") // Use PyTorch engine
.build();
try (ZooModel<Image, float[]> model = criteria.loadModel()) {
Predictor<Image, float[]> predictor = model.newPredictor();
return predictor.predict(img);
}
}
/**
* 计算相似度
* @param feature1 图1特征
* @param feature2 图2特征
* @return
* @throws Exception
*/
@Override
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
float ret = 0.0f;
float mod1 = 0.0f;
float mod2 = 0.0f;
int length = feature1.length;
for (int i = 0; i < length; ++i) {
ret += feature1[i] * feature2[i];
mod1 += feature1[i] * feature1[i];
mod2 += feature2[i] * feature2[i];
}
return (float) ((ret / Math.sqrt(mod1) / Math.sqrt(mod2) + 1) / 2.0f);
}
/**
* 特征比较
* @param imagePath1 图1路径
* @param imagePath2 图2路径
* @return
* @throws Exception
*/
@Override
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
float[] feature1 = featureExtraction(imagePath1);
float[] feature2 = featureExtraction(imagePath2);
return calculSimilar(feature1, feature2);
}
/**
* 特征比较
* @param inputStream1 图1输入流
* @param inputStream2 图2输入流
* @return
* @throws Exception
*/
@Override
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
float[] feature1 = featureExtraction(inputStream1);
float[] feature2 = featureExtraction(inputStream2);
return calculSimilar(feature1, feature2);
}
/*@Override
public float[] recognize(FaceRegion region) {
return new float[0];
}*/
/*@Override
public void loadModel(ModelConfig config) throws Exception {
}*/
}