初始提交

This commit is contained in:
dengwenjie
2025-02-21 11:26:58 +08:00
commit 28f55e85cc
17 changed files with 1442 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.idea
.idea/
target
log
*.iml
/.settings/
/logging.file_IS_UNDEFINED/

BIN
output.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

110
pom.xml Normal file
View 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>

View 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>

View File

@@ -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);
}
}

View File

@@ -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
View 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>

View File

@@ -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("默认不支持该功能");
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View 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");
}
}

View File

@@ -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 {
}*/
}

View File

@@ -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 {
}*/
}