mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-09 19:18:52 +00:00
初始提交
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.idea
|
||||
.idea/
|
||||
target
|
||||
log
|
||||
*.iml
|
||||
/.settings/
|
||||
/logging.file_IS_UNDEFINED/
|
||||
|
||||
BIN
output.jpg
Normal file
BIN
output.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
110
pom.xml
Normal file
110
pom.xml
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>smartjavaai-face</module>
|
||||
<module>smartjavaai-common</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<!-- <maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>-->
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<djl.version>0.32.0-SNAPSHOT</djl.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ai.djl</groupId>
|
||||
<artifactId>bom</artifactId>
|
||||
<version>${djl.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||
<version>2.24.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl</groupId>
|
||||
<artifactId>basicdataset</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl</groupId>
|
||||
<artifactId>model-zoo</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl.timeseries</groupId>
|
||||
<artifactId>timeseries</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl.huggingface</groupId>
|
||||
<artifactId>tokenizers</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl.audio</groupId>
|
||||
<artifactId>audio</artifactId>
|
||||
</dependency>
|
||||
<!-- MXNet -->
|
||||
<dependency>
|
||||
<groupId>ai.djl.mxnet</groupId>
|
||||
<artifactId>mxnet-model-zoo</artifactId>
|
||||
</dependency>
|
||||
<!-- Pytorch -->
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-model-zoo</artifactId>
|
||||
</dependency>
|
||||
<!-- TensorFlow -->
|
||||
<dependency>
|
||||
<groupId>ai.djl.tensorflow</groupId>
|
||||
<artifactId>tensorflow-model-zoo</artifactId>
|
||||
</dependency>
|
||||
<!-- ONNXRuntime -->
|
||||
<dependency>
|
||||
<groupId>ai.djl.onnxruntime</groupId>
|
||||
<artifactId>onnxruntime-engine</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<version>7.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>1.9.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
20
smartjavaai-common/pom.xml
Normal file
20
smartjavaai-common/pom.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.smartjavaai.common.entity;
|
||||
|
||||
import ai.djl.util.JsonUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 点
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class Point implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int x;
|
||||
private int y;
|
||||
|
||||
public Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(int y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonUtils.GSON_COMPACT.toJson(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.smartjavaai.common.entity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 矩形区域
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class Rectangle {
|
||||
|
||||
/**
|
||||
* 矩形区域点集合
|
||||
*/
|
||||
List<Point> pointList;
|
||||
|
||||
/**
|
||||
* 矩形区域宽度
|
||||
*/
|
||||
int width;
|
||||
|
||||
/**
|
||||
* 矩形区域高度
|
||||
*/
|
||||
int height;
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public List<Point> getPointList() {
|
||||
return pointList;
|
||||
}
|
||||
|
||||
public void setPointList(List<Point> pointList) {
|
||||
this.pointList = pointList;
|
||||
}
|
||||
}
|
||||
33
smartjavaai-face/pom.xml
Normal file
33
smartjavaai-face/pom.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
* @author dwj
|
||||
* @date 2025/2/20
|
||||
*/
|
||||
public abstract class AbstractFaceAlgorithm implements FaceAlgorithm{
|
||||
@Override
|
||||
public void loadModel(ModelConfig config) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceDetectedResult detect(String imagePath) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceDetectedResult detect(InputStream imageInputStream) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import ai.djl.MalformedModelException;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.translate.TranslateException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public interface FaceAlgorithm {
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
* @throws Exception
|
||||
*/
|
||||
void loadModel(ModelConfig config) throws Exception; // 加载模型
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
FaceDetectedResult detect(String imagePath) throws Exception;
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param imageInputStream 图片输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
FaceDetectedResult detect(InputStream imageInputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
float[] featureExtraction(String imagePath) throws Exception;
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param inputStream 输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
float[] featureExtraction(InputStream inputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 计算相似度
|
||||
* @param feature1 图1特征
|
||||
* @param feature2 图2特征
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
float calculSimilar(float[] feature1, float[] feature2) throws Exception;
|
||||
|
||||
/**
|
||||
* 特征比较
|
||||
* @param imagePath1 图1路径
|
||||
* @param imagePath2 图2路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
float featureComparison(String imagePath1, String imagePath2) throws Exception;
|
||||
|
||||
/**
|
||||
* 特征比较
|
||||
* @param inputStream1 图1输入流
|
||||
* @param inputStream2 图2输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.face.algo.RetinaFace;
|
||||
import cn.smartjavaai.face.algo.UltraLightFastGenericFace;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 人脸算法工厂
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class FaceAlgorithmFactory {
|
||||
|
||||
/**
|
||||
* 算法注册表
|
||||
*/
|
||||
private static final Map<String, Class<? extends FaceAlgorithm>> registry =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 注册算法
|
||||
* @param name
|
||||
* @param clazz
|
||||
*/
|
||||
public static void registerAlgorithm(String name, Class<? extends FaceAlgorithm> clazz) {
|
||||
registry.put(name.toLowerCase(), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用ModelConfig创建算法
|
||||
* @param config
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static FaceAlgorithm createFaceAlgorithm(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.loadModel(config);
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认算法
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static FaceAlgorithm createFaceAlgorithm() throws Exception {
|
||||
// 初始化配置
|
||||
ModelConfig config = new ModelConfig();
|
||||
config.setAlgorithmName("retinaface");
|
||||
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
|
||||
* @throws Exception
|
||||
*/
|
||||
public static FaceAlgorithm createLightFaceAlgorithm() throws Exception {
|
||||
// 初始化配置
|
||||
ModelConfig config = new ModelConfig();
|
||||
config.setAlgorithmName("ultralightfastgenericface");
|
||||
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;
|
||||
}
|
||||
|
||||
// 初始化默认算法
|
||||
static {
|
||||
registerAlgorithm("retinaface", RetinaFace.class);
|
||||
registerAlgorithm("ultralightfastgenericface", UltraLightFastGenericFace.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/2/20
|
||||
*/
|
||||
public class FaceConfig {
|
||||
|
||||
/**
|
||||
* 置信度阈值
|
||||
*/
|
||||
public static final float DEFAULT_CONFIDENCE_THRESHOLD = 0.85F;
|
||||
|
||||
/**
|
||||
* 每张特征图保留的最大候选框数量
|
||||
*/
|
||||
public static final int MAX_FACE_LIMIT = 5000;
|
||||
|
||||
/**
|
||||
* nms阈值:控制重叠框的合并程度
|
||||
*/
|
||||
public static final float NMS_THRESHOLD = 0.45F;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
|
||||
import cn.smartjavaai.common.entity.Rectangle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 人脸检测结果
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class FaceDetectedResult {
|
||||
|
||||
|
||||
/**
|
||||
* 置信度
|
||||
*/
|
||||
private List<Double> probabilities;
|
||||
|
||||
/**
|
||||
* 人脸框
|
||||
*/
|
||||
private List<Rectangle> rectangles;
|
||||
|
||||
public List<Double> getProbabilities() {
|
||||
return probabilities;
|
||||
}
|
||||
|
||||
public void setProbabilities(List<Double> probabilities) {
|
||||
this.probabilities = probabilities;
|
||||
}
|
||||
|
||||
public List<Rectangle> getRectangles() {
|
||||
return rectangles;
|
||||
}
|
||||
|
||||
public void setRectangles(List<Rectangle> rectangles) {
|
||||
this.rectangles = rectangles;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
|
||||
* with the License. A copy of the License is located at
|
||||
*
|
||||
* http://aws.amazon.com/apache2.0/
|
||||
*
|
||||
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
|
||||
* OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*/
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.output.*;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDArrays;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.ndarray.types.DataType;
|
||||
import ai.djl.ndarray.types.Shape;
|
||||
import ai.djl.translate.Translator;
|
||||
import ai.djl.translate.TranslatorContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Face Detection Translator
|
||||
*/
|
||||
public class FaceDetectionTranslator implements Translator<Image, DetectedObjects> {
|
||||
|
||||
private double confThresh;
|
||||
private double nmsThresh;
|
||||
private int topK;
|
||||
private double[] variance;
|
||||
private int[][] scales;
|
||||
private int[] steps;
|
||||
|
||||
public FaceDetectionTranslator(
|
||||
double confThresh,
|
||||
double nmsThresh,
|
||||
double[] variance,
|
||||
int topK,
|
||||
int[][] scales,
|
||||
int[] steps) {
|
||||
this.confThresh = confThresh;
|
||||
this.nmsThresh = nmsThresh;
|
||||
this.variance = variance;
|
||||
this.topK = topK;
|
||||
this.scales = scales;
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public NDList processInput(TranslatorContext ctx, Image input) {
|
||||
|
||||
ctx.setAttachment("width", input.getWidth());
|
||||
ctx.setAttachment("height", input.getHeight());
|
||||
|
||||
NDArray array = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
|
||||
array = array.transpose(2, 0, 1).flip(0); // HWC -> CHW RGB -> BGR
|
||||
// The network by default takes float32
|
||||
if (!array.getDataType().equals(DataType.FLOAT32)) {
|
||||
array = array.toType(DataType.FLOAT32, false);
|
||||
}
|
||||
NDArray mean =
|
||||
ctx.getNDManager().create(new float[] {104f, 117f, 123f}, new Shape(3, 1, 1));
|
||||
array = array.sub(mean);
|
||||
return new NDList(array);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public DetectedObjects processOutput(TranslatorContext ctx, NDList list) {
|
||||
|
||||
int width = (int) ctx.getAttachment("width");
|
||||
int height = (int) ctx.getAttachment("height");
|
||||
|
||||
NDManager manager = ctx.getNDManager();
|
||||
double scaleXY = variance[0];
|
||||
double scaleWH = variance[1];
|
||||
|
||||
NDArray prob = list.get(1).get(":, 1:");
|
||||
prob =
|
||||
NDArrays.stack(
|
||||
new NDList(
|
||||
prob.argMax(1).toType(DataType.FLOAT32, false),
|
||||
prob.max(new int[] {1})));
|
||||
|
||||
NDArray boxRecover = boxRecover(manager, width, height, scales, steps);
|
||||
NDArray boundingBoxes = list.get(0);
|
||||
NDArray bbWH = boundingBoxes.get(":, 2:").mul(scaleWH).exp().mul(boxRecover.get(":, 2:"));
|
||||
NDArray bbXY =
|
||||
boundingBoxes
|
||||
.get(":, :2")
|
||||
.mul(scaleXY)
|
||||
.mul(boxRecover.get(":, 2:"))
|
||||
.add(boxRecover.get(":, :2"))
|
||||
.sub(bbWH.mul(0.5f));
|
||||
|
||||
boundingBoxes = NDArrays.concat(new NDList(bbXY, bbWH), 1);
|
||||
|
||||
NDArray landms = list.get(2);
|
||||
landms = decodeLandm(landms, boxRecover, scaleXY);
|
||||
|
||||
// filter the result below the threshold
|
||||
NDArray cutOff = prob.get(1).gt(confThresh);
|
||||
boundingBoxes = boundingBoxes.transpose().booleanMask(cutOff, 1).transpose();
|
||||
landms = landms.transpose().booleanMask(cutOff, 1).transpose();
|
||||
prob = prob.booleanMask(cutOff, 1);
|
||||
|
||||
// start categorical filtering
|
||||
long[] order = prob.get(1).argSort().get(":" + topK).toLongArray();
|
||||
prob = prob.transpose();
|
||||
List<String> retNames = new ArrayList<>();
|
||||
List<Double> retProbs = new ArrayList<>();
|
||||
List<BoundingBox> retBB = new ArrayList<>();
|
||||
|
||||
Map<Integer, List<BoundingBox>> recorder = new ConcurrentHashMap<>();
|
||||
|
||||
for (int i = order.length - 1; i >= 0; i--) {
|
||||
long currMaxLoc = order[i];
|
||||
float[] classProb = prob.get(currMaxLoc).toFloatArray();
|
||||
int classId = (int) classProb[0];
|
||||
double probability = classProb[1];
|
||||
|
||||
double[] boxArr = boundingBoxes.get(currMaxLoc).toDoubleArray();
|
||||
double[] landmsArr = landms.get(currMaxLoc).toDoubleArray();
|
||||
Rectangle rect = new Rectangle(boxArr[0], boxArr[1], boxArr[2], boxArr[3]);
|
||||
List<BoundingBox> boxes = recorder.getOrDefault(classId, new ArrayList<>());
|
||||
boolean belowIoU = true;
|
||||
for (BoundingBox box : boxes) {
|
||||
if (box.getIoU(rect) > nmsThresh) {
|
||||
belowIoU = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (belowIoU) {
|
||||
List<Point> keyPoints = new ArrayList<>();
|
||||
for (int j = 0; j < 5; j++) { // 5 face landmarks
|
||||
double x = landmsArr[j * 2];
|
||||
double y = landmsArr[j * 2 + 1];
|
||||
keyPoints.add(new Point(x * width, y * height));
|
||||
}
|
||||
Landmark landmark =
|
||||
new Landmark(boxArr[0], boxArr[1], boxArr[2], boxArr[3], keyPoints);
|
||||
|
||||
boxes.add(landmark);
|
||||
recorder.put(classId, boxes);
|
||||
String className = "Face"; // classes.get(classId)
|
||||
retNames.add(className);
|
||||
retProbs.add(probability);
|
||||
retBB.add(landmark);
|
||||
}
|
||||
}
|
||||
|
||||
return new DetectedObjects(retNames, retProbs, retBB);
|
||||
}
|
||||
|
||||
private NDArray boxRecover(
|
||||
NDManager manager, int width, int height, int[][] scales, int[] steps) {
|
||||
int[][] aspectRatio = new int[steps.length][2];
|
||||
for (int i = 0; i < steps.length; i++) {
|
||||
int wRatio = (int) Math.ceil((float) width / steps[i]);
|
||||
int hRatio = (int) Math.ceil((float) height / steps[i]);
|
||||
aspectRatio[i] = new int[] {hRatio, wRatio};
|
||||
}
|
||||
|
||||
List<double[]> defaultBoxes = new ArrayList<>();
|
||||
|
||||
for (int idx = 0; idx < steps.length; idx++) {
|
||||
int[] scale = scales[idx];
|
||||
for (int h = 0; h < aspectRatio[idx][0]; h++) {
|
||||
for (int w = 0; w < aspectRatio[idx][1]; w++) {
|
||||
for (int i : scale) {
|
||||
double skx = i * 1.0 / width;
|
||||
double sky = i * 1.0 / height;
|
||||
double cx = (w + 0.5) * steps[idx] / width;
|
||||
double cy = (h + 0.5) * steps[idx] / height;
|
||||
defaultBoxes.add(new double[] {cx, cy, skx, sky});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double[][] boxes = new double[defaultBoxes.size()][defaultBoxes.get(0).length];
|
||||
for (int i = 0; i < defaultBoxes.size(); i++) {
|
||||
boxes[i] = defaultBoxes.get(i);
|
||||
}
|
||||
return manager.create(boxes).clip(0.0, 1.0);
|
||||
}
|
||||
|
||||
// decode face landmarks, 5 points per face
|
||||
private NDArray decodeLandm(NDArray pre, NDArray priors, double scaleXY) {
|
||||
NDArray point1 =
|
||||
pre.get(":, :2").mul(scaleXY).mul(priors.get(":, 2:")).add(priors.get(":, :2"));
|
||||
NDArray point2 =
|
||||
pre.get(":, 2:4").mul(scaleXY).mul(priors.get(":, 2:")).add(priors.get(":, :2"));
|
||||
NDArray point3 =
|
||||
pre.get(":, 4:6").mul(scaleXY).mul(priors.get(":, 2:")).add(priors.get(":, :2"));
|
||||
NDArray point4 =
|
||||
pre.get(":, 6:8").mul(scaleXY).mul(priors.get(":, 2:")).add(priors.get(":, :2"));
|
||||
NDArray point5 =
|
||||
pre.get(":, 8:10").mul(scaleXY).mul(priors.get(":, 2:")).add(priors.get(":, :2"));
|
||||
return NDArrays.concat(new NDList(point1, point2, point3, point4, point5), 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
/**
|
||||
* 模型配置
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
|
||||
public class ModelConfig {
|
||||
|
||||
/**
|
||||
* 人脸算法名称
|
||||
*/
|
||||
private String algorithmName;
|
||||
|
||||
/**
|
||||
* 置信度阈值
|
||||
*/
|
||||
private double confidenceThreshold;
|
||||
|
||||
/**
|
||||
* 非极大抑制阈值 作用:消除重叠检测框,保留最优结果
|
||||
*/
|
||||
private double nmsThresh;
|
||||
|
||||
/**
|
||||
* 最大检测人脸数量
|
||||
*/
|
||||
private int maxFaceCount;
|
||||
|
||||
public String getAlgorithmName() {
|
||||
return algorithmName;
|
||||
}
|
||||
|
||||
public void setAlgorithmName(String algorithmName) {
|
||||
this.algorithmName = algorithmName;
|
||||
}
|
||||
|
||||
public double getConfidenceThreshold() {
|
||||
return confidenceThreshold;
|
||||
}
|
||||
|
||||
public void setConfidenceThreshold(double confidenceThreshold) {
|
||||
this.confidenceThreshold = confidenceThreshold;
|
||||
}
|
||||
|
||||
public double getNmsThresh() {
|
||||
return nmsThresh;
|
||||
}
|
||||
|
||||
public void setNmsThresh(double nmsThresh) {
|
||||
this.nmsThresh = nmsThresh;
|
||||
}
|
||||
|
||||
public int getMaxFaceCount() {
|
||||
return maxFaceCount;
|
||||
}
|
||||
|
||||
public void setMaxFaceCount(int maxFaceCount) {
|
||||
this.maxFaceCount = maxFaceCount;
|
||||
}
|
||||
}
|
||||
62
smartjavaai-face/src/main/java/cn/smartjavaai/face/Test.java
Normal file
62
smartjavaai-face/src/main/java/cn/smartjavaai/face/Test.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.common.entity.Rectangle;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class Test {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// 初始化配置
|
||||
ModelConfig config = new ModelConfig();
|
||||
config.setAlgorithmName("retinaface");
|
||||
//config.setAlgorithmName("ultralightfastgenericface");
|
||||
//config.setModelPath("/Users/wenjie/Documents/idea_workplace/SmartJavaAI/models/retinaface.pt");
|
||||
config.setConfidenceThreshold(0.85f);
|
||||
config.setMaxFaceCount(100);
|
||||
config.setNmsThresh(0.45f);
|
||||
|
||||
FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
|
||||
File imageFile = new File("/Users/wenjie/Downloads/djl-master/examples/src/test/resources/largest_selfie.jpg");
|
||||
FaceDetectedResult result = currentAlgorithm.detect("/Users/wenjie/Downloads/djl-master/examples/src/test/resources/largest_selfie.jpg");
|
||||
//FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(imageFile));
|
||||
|
||||
|
||||
// 1. 加载原始图片
|
||||
File input = new File("/Users/wenjie/Downloads/djl-master/examples/src/test/resources/largest_selfie.jpg");
|
||||
BufferedImage image = ImageIO.read(input);
|
||||
|
||||
// 2. 创建绘图上下文
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
|
||||
// 3. 配置绘制参数
|
||||
graphics.setColor(Color.RED); // 边框颜色
|
||||
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
|
||||
|
||||
for(Rectangle rectangle : result.getRectangles()){
|
||||
// 4. 绘制矩形(左上角坐标x=50,y=100,宽200,高150)
|
||||
graphics.drawRect(rectangle.getPointList().get(0).getX(),
|
||||
rectangle.getPointList().get(0).getY(), rectangle.getWidth(), rectangle.getHeight());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 5. 释放资源并保存
|
||||
graphics.dispose();
|
||||
ImageIO.write(image, "jpg", new File("output.jpg"));
|
||||
|
||||
|
||||
System.out.println("111111");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package cn.smartjavaai.face.algo;
|
||||
|
||||
import ai.djl.MalformedModelException;
|
||||
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.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
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;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* RetinaFace实现
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class RetinaFace extends AbstractFaceAlgorithm {
|
||||
|
||||
private Criteria<Image, DetectedObjects> criteria;
|
||||
|
||||
/**
|
||||
* 特征图层的基础缩放比例
|
||||
*/
|
||||
public static final int[][] scales = {{16, 32}, {64, 128}, {256, 512}};
|
||||
/**
|
||||
* 特征图相对于原图的采样步长
|
||||
*/
|
||||
public static final int[] steps = {8, 16, 32};
|
||||
/**
|
||||
* 缩放系数
|
||||
*/
|
||||
public static final double[] variance = {0.1f, 0.2f};
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
*/
|
||||
@Override
|
||||
public void loadModel(ModelConfig config) {
|
||||
FaceDetectionTranslator translator =
|
||||
new FaceDetectionTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), variance, config.getMaxFaceCount(), scales, steps);
|
||||
criteria =
|
||||
Criteria.builder()
|
||||
.setTypes(Image.class, DetectedObjects.class)
|
||||
.optModelUrls("https://resources.djl.ai/test-models/pytorch/retinaface.zip")
|
||||
//.optModelPath(modelPath)
|
||||
// Load model from local file, e.g:
|
||||
.optModelName("retinaface") // specify model file prefix
|
||||
.optTranslator(translator)
|
||||
.optProgress(new ProgressBar())
|
||||
.optEngine("PyTorch") // Use PyTorch engine
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测人脸
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public FaceDetectedResult detect(String imagePath) throws Exception{
|
||||
Path facePath = Paths.get(imagePath);
|
||||
Image img = ImageFactory.getInstance().fromFile(facePath);
|
||||
try (ZooModel<Image, DetectedObjects> model = criteria.loadModel();
|
||||
Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
|
||||
DetectedObjects detection = predictor.predict(img);
|
||||
return convertToFaceDetectedResult(detection,img);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测人脸
|
||||
* @param imageInputStream 图片流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public FaceDetectedResult detect(InputStream imageInputStream) throws Exception {
|
||||
Image img = ImageFactory.getInstance().fromInputStream(imageInputStream);
|
||||
try (ZooModel<Image, DetectedObjects> model = criteria.loadModel();
|
||||
Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
|
||||
DetectedObjects detection = predictor.predict(img);
|
||||
return convertToFaceDetectedResult(detection,img);
|
||||
/*saveBoundingBoxImage(img, detection);
|
||||
return detection;*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为FaceDetectedResult
|
||||
* @param detection
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
private FaceDetectedResult convertToFaceDetectedResult(DetectedObjects detection, Image img){
|
||||
FaceDetectedResult faceDetectedResult = new FaceDetectedResult();
|
||||
List<Double> probabilities = new ArrayList<>(detection.getProbabilities());
|
||||
List<DetectedObjects.DetectedObject> detectedObjectList = detection.items();
|
||||
List<Rectangle> RectangleList = detectedObjectList.parallelStream()
|
||||
.map(obj -> {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
List<Point> pointList = new ArrayList<>();
|
||||
ai.djl.modality.cv.output.Rectangle rectangleDjl = obj.getBoundingBox().getBounds();
|
||||
int x = (int)(rectangleDjl.getX() * (double)img.getWidth());
|
||||
int y = (int)(rectangleDjl.getY() * (double)img.getHeight());
|
||||
int width = (int)(rectangleDjl.getWidth() * (double)img.getWidth());
|
||||
int height = (int)(rectangleDjl.getHeight() * (double)img.getHeight());
|
||||
pointList.add(new Point(x,y));
|
||||
pointList.add(new Point(x + width,y));
|
||||
pointList.add(new Point(x,y + height));
|
||||
pointList.add(new Point(x + width,y + height));
|
||||
rectangle.setPointList(pointList);
|
||||
rectangle.setHeight(height);
|
||||
rectangle.setWidth(width);
|
||||
return rectangle;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
faceDetectedResult.setProbabilities(probabilities);
|
||||
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 {
|
||||
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
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.*;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/2/19
|
||||
*/
|
||||
public class UltraLightFastGenericFace extends AbstractFaceAlgorithm {
|
||||
|
||||
|
||||
private Criteria<Image, DetectedObjects> criteria;
|
||||
|
||||
/**
|
||||
* 特征图层的基础缩放比例
|
||||
*/
|
||||
private static final int[][] scales = {{10, 16, 24}, {32, 48}, {64, 96}, {128, 192, 256}};
|
||||
/**
|
||||
* 特征图相对于原图的采样步长
|
||||
*/
|
||||
private static final int[] steps = {8, 16, 32, 64};
|
||||
/**
|
||||
* 缩放系数
|
||||
*/
|
||||
private static final double[] variance = {0.1f, 0.2f};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
*/
|
||||
@Override
|
||||
public void loadModel(ModelConfig config) {
|
||||
FaceDetectionTranslator translator =
|
||||
new FaceDetectionTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), variance, config.getMaxFaceCount(), scales, steps);
|
||||
criteria =
|
||||
Criteria.builder()
|
||||
.setTypes(Image.class, DetectedObjects.class)
|
||||
.optModelUrls("https://resources.djl.ai/test-models/pytorch/ultranet.zip")
|
||||
.optTranslator(translator)
|
||||
.optProgress(new ProgressBar())
|
||||
.optEngine("PyTorch") // Use PyTorch engine
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测人脸
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public FaceDetectedResult detect(String imagePath) throws Exception{
|
||||
Path facePath = Paths.get(imagePath);
|
||||
Image img = ImageFactory.getInstance().fromFile(facePath);
|
||||
try (ZooModel<Image, DetectedObjects> model = criteria.loadModel();
|
||||
Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
|
||||
DetectedObjects detection = predictor.predict(img);
|
||||
return convertToFaceDetectedResult(detection,img);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测人脸
|
||||
* @param imageInputStream 图片输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public FaceDetectedResult detect(InputStream imageInputStream) throws Exception {
|
||||
Image img = ImageFactory.getInstance().fromInputStream(imageInputStream);
|
||||
try (ZooModel<Image, DetectedObjects> model = criteria.loadModel();
|
||||
Predictor<Image, DetectedObjects> predictor = model.newPredictor()) {
|
||||
DetectedObjects detection = predictor.predict(img);
|
||||
return convertToFaceDetectedResult(detection,img);
|
||||
/*saveBoundingBoxImage(img, detection);
|
||||
return detection;*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换检测结果
|
||||
* @param detection
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
private FaceDetectedResult convertToFaceDetectedResult(DetectedObjects detection, Image img){
|
||||
FaceDetectedResult faceDetectedResult = new FaceDetectedResult();
|
||||
List<Double> probabilities = new ArrayList<>(detection.getProbabilities());
|
||||
List<DetectedObjects.DetectedObject> detectedObjectList = detection.items();
|
||||
List<Rectangle> RectangleList = detectedObjectList.parallelStream()
|
||||
.map(obj -> {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
List<Point> pointList = new ArrayList<>();
|
||||
ai.djl.modality.cv.output.Rectangle rectangleDjl = obj.getBoundingBox().getBounds();
|
||||
int x = (int)(rectangleDjl.getX() * (double)img.getWidth());
|
||||
int y = (int)(rectangleDjl.getY() * (double)img.getHeight());
|
||||
int width = (int)(rectangleDjl.getWidth() * (double)img.getWidth());
|
||||
int height = (int)(rectangleDjl.getHeight() * (double)img.getHeight());
|
||||
pointList.add(new Point(x,y));
|
||||
pointList.add(new Point(x + width,y));
|
||||
pointList.add(new Point(x,y + height));
|
||||
pointList.add(new Point(x + width,y + height));
|
||||
rectangle.setPointList(pointList);
|
||||
rectangle.setHeight(height);
|
||||
rectangle.setWidth(width);
|
||||
return rectangle;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
faceDetectedResult.setProbabilities(probabilities);
|
||||
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 {
|
||||
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user