mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-11 04:08:55 +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