mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-13 21:28:52 +00:00
临时提交
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
package cn.smartjavaai.common.utils;
|
package cn.smartjavaai.common.utils;
|
||||||
|
|
||||||
|
import ai.djl.ndarray.NDArray;
|
||||||
|
import cn.smartjavaai.common.entity.R;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author dwj
|
* @author dwj
|
||||||
@@ -29,4 +33,14 @@ public class DJLCommonUtils {
|
|||||||
return Files.exists(servingFile);
|
return Files.exists(servingFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断 NDArray 是否为空
|
||||||
|
* @param ndArray
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isNDArrayEmpty(NDArray ndArray){
|
||||||
|
return Objects.isNull(ndArray) || ndArray.size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package cn.smartjavaai.common.utils;
|
package cn.smartjavaai.common.utils;
|
||||||
|
|
||||||
|
import ai.djl.modality.cv.output.Landmark;
|
||||||
|
import ai.djl.modality.cv.output.Point;
|
||||||
import ai.djl.modality.cv.output.Rectangle;
|
import ai.djl.modality.cv.output.Rectangle;
|
||||||
import ai.djl.modality.cv.util.NDImageUtils;
|
import ai.djl.modality.cv.util.NDImageUtils;
|
||||||
import ai.djl.ndarray.NDArray;
|
import ai.djl.ndarray.NDArray;
|
||||||
@@ -8,7 +10,9 @@ import ai.djl.ndarray.index.NDIndex;
|
|||||||
import ai.djl.ndarray.types.DataType;
|
import ai.djl.ndarray.types.DataType;
|
||||||
import ai.djl.ndarray.types.Shape;
|
import ai.djl.ndarray.types.Shape;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按比例缩放,剩余空间用指定颜色填充
|
* 按比例缩放,剩余空间用指定颜色填充
|
||||||
@@ -61,7 +65,7 @@ public class LetterBoxUtils {
|
|||||||
// NDArray paddingImg = manager
|
// NDArray paddingImg = manager
|
||||||
// .full(new Shape(targetW, targetH, 3), padColor, DataType.UINT8);
|
// .full(new Shape(targetW, targetH, 3), padColor, DataType.UINT8);
|
||||||
|
|
||||||
NDArray paddingImg = manager.zeros(new Shape(targetW, targetH, 3), DataType.FLOAT32);
|
NDArray paddingImg = manager.zeros(new Shape(targetH, targetW, 3), DataType.FLOAT32);
|
||||||
paddingImg = paddingImg.add(114);
|
paddingImg = paddingImg.add(114);
|
||||||
|
|
||||||
int padW = targetW - newW;
|
int padW = targetW - newW;
|
||||||
@@ -145,4 +149,65 @@ public class LetterBoxUtils {
|
|||||||
return new Rectangle(x1, y1, boxW, boxH);
|
return new Rectangle(x1, y1, boxW, boxH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复缩放后的 box(左上角坐标)
|
||||||
|
* @param landmark
|
||||||
|
* @param scale
|
||||||
|
* @param origImageWidth
|
||||||
|
* @param origImageHeight
|
||||||
|
*/
|
||||||
|
public static Landmark restoreBox(Landmark landmark, float scale, int origImageWidth, int origImageHeight, int inputWidth, int inputHeight, boolean isNormalized){
|
||||||
|
double x = 0;
|
||||||
|
double y = 0;
|
||||||
|
double width = 0;
|
||||||
|
double height = 0;
|
||||||
|
if(isNormalized){
|
||||||
|
x = landmark.getX() * inputWidth;
|
||||||
|
y = landmark.getY() * inputHeight;
|
||||||
|
width = landmark.getWidth() * inputWidth;
|
||||||
|
height = landmark.getHeight() * inputHeight;
|
||||||
|
}else{
|
||||||
|
x = landmark.getX();
|
||||||
|
y = landmark.getY();
|
||||||
|
width = landmark.getWidth();
|
||||||
|
height = landmark.getHeight();
|
||||||
|
}
|
||||||
|
double paddingWidth = (inputWidth - origImageWidth * scale) / 2;
|
||||||
|
double paddingHeight = (inputHeight - origImageHeight * scale) / 2;
|
||||||
|
|
||||||
|
// 去掉 padding
|
||||||
|
double x_noPad = x - paddingWidth;
|
||||||
|
double y_noPad = y - paddingHeight;
|
||||||
|
|
||||||
|
//模型输出就是原图坐标
|
||||||
|
double x1 = x_noPad / scale / origImageWidth;
|
||||||
|
double y1 = y_noPad / scale / origImageHeight;
|
||||||
|
double boxW = width / scale / origImageWidth ;
|
||||||
|
double boxH = height / scale / origImageHeight;
|
||||||
|
|
||||||
|
List<Point> points = new ArrayList<>();
|
||||||
|
// 要求关键点未归一化
|
||||||
|
landmark.getPath().forEach(point -> {
|
||||||
|
double pointX = (point.getX() - paddingWidth) / scale;
|
||||||
|
double pointY = (point.getY() - paddingHeight) / scale;
|
||||||
|
points.add(new Point(pointX, pointY));
|
||||||
|
});
|
||||||
|
return new Landmark(x1, y1, boxW, boxH, points);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缩放后的图片大小
|
||||||
|
* @param origW 原始图片宽度
|
||||||
|
* @param origH 原始图片高度
|
||||||
|
* @param targetWidth 目标图片宽度
|
||||||
|
* @param targetHeight 目标图片高度
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int[] getResizeSize(int origW, int origH, int targetWidth, int targetHeight){
|
||||||
|
float r = Math.min(targetWidth / (float) origW, targetHeight / (float) origH);
|
||||||
|
int newW = Math.round(origW * r);
|
||||||
|
int newH = Math.round(origH * r);
|
||||||
|
return new int[]{newW, newH};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,16 +81,23 @@ public class NMSUtils {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static NDArray batchedNms(NDArray boxes, NDArray scores, NDArray idxs, float iouThreshold, NDManager manager) {
|
public static NDArray batchedNms(NDArray boxes, NDArray scores, NDArray idxs, float iouThreshold, NDManager manager) {
|
||||||
|
|
||||||
|
// System.out.println("---------------boxes:" + Arrays.toString(boxes.toFloatArray()));
|
||||||
|
|
||||||
List<NDArray> keepList = new ArrayList<>();
|
List<NDArray> keepList = new ArrayList<>();
|
||||||
|
|
||||||
// 获取唯一 batch id
|
// 获取唯一 batch id
|
||||||
NDArray uniqueIdxs = idxs.unique().get(0);
|
NDArray uniqueIdxs = idxs.unique().get(0);
|
||||||
|
|
||||||
for (long batchId : uniqueIdxs.toLongArray()) {
|
for (long batchId : uniqueIdxs.toLongArray()) {
|
||||||
// 找出当前 batch 的框
|
// 找出当前 batch 的框
|
||||||
NDArray mask = idxs.eq(batchId);
|
NDArray mask = idxs.eq(batchId);
|
||||||
NDArray batchBoxes = boxes.get(mask);
|
NDArray batchBoxes = boxes.get(mask);
|
||||||
NDArray batchScores = scores.get(mask);
|
NDArray batchScores = scores.get(mask);
|
||||||
|
|
||||||
// 执行单 batch NMS
|
// 执行单 batch NMS
|
||||||
int[] keepIndices = nms(batchBoxes, batchScores, iouThreshold);
|
int[] keepIndices = mtcnnNms(batchBoxes, batchScores, iouThreshold);
|
||||||
|
|
||||||
if (keepIndices.length > 0) {
|
if (keepIndices.length > 0) {
|
||||||
// 将局部索引映射回全局索引
|
// 将局部索引映射回全局索引
|
||||||
NDArray globalIndices = manager.arange(boxes.getShape().get(0))
|
NDArray globalIndices = manager.arange(boxes.getShape().get(0))
|
||||||
@@ -101,10 +108,70 @@ public class NMSUtils {
|
|||||||
keepList.add(globalIndices);
|
keepList.add(globalIndices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepList.isEmpty()) {
|
if (keepList.isEmpty()) {
|
||||||
return manager.create(new long[0]);
|
return manager.create(new long[0]);
|
||||||
}
|
}
|
||||||
return NDArrays.concat(new NDList(keepList));
|
return NDArrays.concat(new NDList(keepList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int[] mtcnnNms(NDArray boxes, NDArray scores, float iouThreshold) {
|
||||||
|
if (boxes.isEmpty()) {
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
NDArray x1 = boxes.get(":, 0");
|
||||||
|
NDArray y1 = boxes.get(":, 1");
|
||||||
|
NDArray x2 = boxes.get(":, 2");
|
||||||
|
NDArray y2 = boxes.get(":, 3");
|
||||||
|
|
||||||
|
// 面积
|
||||||
|
NDArray areas = x2.sub(x1).add(1).mul(y2.sub(y1).add(1));
|
||||||
|
|
||||||
|
// scores 降序索引
|
||||||
|
NDArray order = scores.argSort();
|
||||||
|
//System.out.println("order:" + order.getShape());
|
||||||
|
//System.out.println("order:" + Arrays.toString(order.toLongArray()));
|
||||||
|
|
||||||
|
List<Integer> keep = new ArrayList<>();
|
||||||
|
|
||||||
|
while (order.size() > 0) {
|
||||||
|
int i = (int) order.getLong(-1);
|
||||||
|
keep.add(i);
|
||||||
|
|
||||||
|
if (order.size() == 1) break; // 没框了就退出
|
||||||
|
|
||||||
|
// 剩余框
|
||||||
|
NDArray idx = order.get("0:-1");
|
||||||
|
|
||||||
|
NDArray xx1 = x1.get(i).maximum(x1.get(idx));
|
||||||
|
NDArray yy1 = y1.get(i).maximum(y1.get(idx));
|
||||||
|
NDArray xx2 = x2.get(i).minimum(x2.get(idx));
|
||||||
|
NDArray yy2 = y2.get(i).minimum(y2.get(idx));
|
||||||
|
|
||||||
|
NDArray w = xx2.sub(xx1).add(1).maximum(0);
|
||||||
|
NDArray h = yy2.sub(yy1).add(1).maximum(0);
|
||||||
|
NDArray inter = w.mul(h);
|
||||||
|
|
||||||
|
NDArray union = areas.get(i).minimum(areas.get(idx));
|
||||||
|
NDArray iou = inter.div(union);
|
||||||
|
|
||||||
|
// System.out.println("Max IoU: " + iou.max().getFloat());
|
||||||
|
// System.out.println("Min IoU: " + iou.min().getFloat());
|
||||||
|
// System.out.println("Mean IoU: " + iou.mean().getFloat());
|
||||||
|
|
||||||
|
// System.out.println("Before: " + order.size());
|
||||||
|
// 保留 IoU <= 阈值的框
|
||||||
|
NDArray mask = iou.lte(iouThreshold);
|
||||||
|
// System.out.println("Mask size: " + mask.size() + " True count: " + mask.sum());
|
||||||
|
|
||||||
|
// 更新 order
|
||||||
|
order = idx.get(mask);
|
||||||
|
// System.out.println("After: " + order.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return keep.stream().mapToInt(Integer::intValue).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.smartjavaai</groupId>
|
<groupId>cn.smartjavaai</groupId>
|
||||||
<artifactId>smartjavaai-bom</artifactId>
|
<artifactId>bom</artifactId>
|
||||||
<version>${smartjavaai.version}</version>
|
<version>${smartjavaai.version}</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<!-- 注意这里是import -->
|
<!-- 注意这里是import -->
|
||||||
@@ -94,14 +94,14 @@
|
|||||||
<!--人脸识别模块-->
|
<!--人脸识别模块-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.smartjavaai</groupId>
|
<groupId>cn.smartjavaai</groupId>
|
||||||
<artifactId>smartjavaai-face</artifactId>
|
<artifactId>face</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-jni</artifactId>
|
<artifactId>pytorch-jni</artifactId>
|
||||||
<version>2.5.1-0.32.0</version>
|
<version>2.7.1-0.34.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>linux-aarch64</classifier>
|
<classifier>linux-aarch64</classifier>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -84,16 +84,16 @@ public class Test {
|
|||||||
log.info("user ./gradlew listModel --args='-a' to show artifact detail");
|
log.info("user ./gradlew listModel --args='-a' to show artifact detail");
|
||||||
log.info("============================================================");
|
log.info("============================================================");
|
||||||
}
|
}
|
||||||
Map<Application, List<Artifact>> models = ModelZoo.listModels();
|
// Map<Application, List<Artifact>> models = ModelZoo.listModels();
|
||||||
for (Map.Entry<Application, List<Artifact>> entry : models.entrySet()) {
|
// for (Map.Entry<Application, List<Artifact>> entry : models.entrySet()) {
|
||||||
String appName = entry.getKey().toString();
|
// String appName = entry.getKey().toString();
|
||||||
for (Artifact artifact : entry.getValue()) {
|
// for (Artifact artifact : entry.getValue()) {
|
||||||
if (withArtifacts) {
|
// if (withArtifacts) {
|
||||||
log.info("{} djl://{}", appName, artifact);
|
// log.info("{} djl://{}", appName, artifact);
|
||||||
} else {
|
// } else {
|
||||||
log.info("{} {}", appName, artifact);
|
// log.info("{} {}", appName, artifact);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,15 +63,21 @@ public class ExpressionRecDemo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸检测模型
|
* 获取人脸检测模型(均衡模型)
|
||||||
|
* 均衡模型:兼顾速度和精度
|
||||||
|
* 注意事项:SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public FaceDetModel getFaceDetModel(){
|
public FaceDetModel getFaceDetModel(){
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
|
config.setModelEnum(FaceDetModelEnum.MTCNN);
|
||||||
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
config.setDevice(device);
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model");
|
||||||
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +89,7 @@ public class ExpressionRecDemo {
|
|||||||
public ExpressionModel getExpressionModel(){
|
public ExpressionModel getExpressionModel(){
|
||||||
FaceExpressionConfig config = new FaceExpressionConfig();
|
FaceExpressionConfig config = new FaceExpressionConfig();
|
||||||
config.setModelEnum(ExpressionModelEnum.FrEmotion);
|
config.setModelEnum(ExpressionModelEnum.FrEmotion);
|
||||||
config.setModelPath("/Users/xxx/Documents/develop/model/emotion/fr_expression.onnx");
|
config.setModelPath("/Users/wenjie/Documents/develop/model/emotion/fr_expression.onnx");
|
||||||
config.setDevice(device);
|
config.setDevice(device);
|
||||||
config.setAlign(true);
|
config.setAlign(true);
|
||||||
config.setDetectModel(getFaceDetModel());
|
config.setDetectModel(getFaceDetModel());
|
||||||
|
|||||||
@@ -58,18 +58,58 @@ public class FaceDetDemo {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸检测模型
|
* 获取人脸检测模型(均衡模型)
|
||||||
* 注意事项:高精度模型,速度较慢
|
* 均衡模型:兼顾速度和精度
|
||||||
|
* 注意事项:SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public FaceDetModel getFaceDetModel(){
|
public FaceDetModel getFaceDetModel(){
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
//高精度模型,速度慢
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
|
config.setModelEnum(FaceDetModelEnum.MTCNN);
|
||||||
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model");
|
||||||
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取人脸检测模型(高精度模型)
|
||||||
|
* 注意事项:高精度模型,识别准确度高,速度慢
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public FaceDetModel getProFaceDetModel(){
|
||||||
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
|
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);
|
||||||
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
config.setModelPath("/Users/wenjie/Documents/develop/face_model/retinaface.pt");
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model/retinaface.pt");
|
||||||
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取人脸检测模型(极速模型)
|
||||||
|
* 注意事项:极速模型,识别准确度低,速度快
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public FaceDetModel getFastFaceDetModel(){
|
||||||
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
|
config.setModelEnum(FaceDetModelEnum.YOLOV5_FACE_320);
|
||||||
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
|
||||||
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,47 +124,21 @@ public class FaceDetDemo {
|
|||||||
config.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
|
config.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
|
||||||
//指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234)
|
//指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234)
|
||||||
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
|
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
|
||||||
|
config.setConfidenceThreshold(0.9);
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 人脸检测(默认配置)
|
* 人脸检测
|
||||||
* 使用默认模型参数检测,默认模型:retinaface,需联网,会自动下载模型
|
* 注意事项:
|
||||||
* 图片参数:图片路径
|
* 1、此用例使用均衡模型,可以切换高精度模型或极速模型
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testFaceDetect(){
|
public void testFaceDetect(){
|
||||||
try {
|
try {
|
||||||
FaceDetModel faceModel = getFaceDetModel();
|
FaceDetModel faceModel = getFaceDetModel();
|
||||||
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
|
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
|
||||||
// if(detectedResult.isSuccess()){
|
|
||||||
// log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
|
|
||||||
// }else{
|
|
||||||
// log.info("人脸检测失败:{}", detectedResult.getMessage());
|
|
||||||
// }
|
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
R<DetectionResponse> detectedResult2 = faceModel.detect("/Users/wenjie/Downloads/facetest/surprise.png");
|
|
||||||
log.info("耗时:{}", System.currentTimeMillis() - start);
|
|
||||||
if(detectedResult2.isSuccess()){
|
|
||||||
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult2.getData()));
|
|
||||||
}else{
|
|
||||||
log.info("人脸检测失败:{}", detectedResult2.getMessage());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 人脸检测(自定义模型参数)
|
|
||||||
* 图片参数:图片路径
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testFaceDetectCustomConfig(){
|
|
||||||
try {
|
|
||||||
FaceDetModel faceModel = getFaceDetModel();
|
|
||||||
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
|
|
||||||
if(detectedResult.isSuccess()){
|
if(detectedResult.isSuccess()){
|
||||||
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
|
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
|
||||||
}else{
|
}else{
|
||||||
@@ -181,10 +195,12 @@ public class FaceDetDemo {
|
|||||||
public void testDetectFaceGPU(){
|
public void testDetectFaceGPU(){
|
||||||
try {
|
try {
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
//高精度模型,速度慢
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
|
config.setModelEnum(FaceDetModelEnum.MTCNN);
|
||||||
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
config.setModelPath("/Users/xxx/Documents/develop/model/retinaface.pt");
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model");
|
||||||
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
config.setDevice(DeviceEnum.GPU);
|
config.setDevice(DeviceEnum.GPU);
|
||||||
FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel(config);
|
FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel(config);
|
||||||
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
|
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
|
||||||
@@ -200,7 +216,7 @@ public class FaceDetDemo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 人脸检测(Seetaface6)
|
* 人脸检测(Seetaface6)
|
||||||
* 图片参数:图片路径
|
* 注意事项:不支持macos
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testFaceDetectSeetaface6(){
|
public void testFaceDetectSeetaface6(){
|
||||||
@@ -220,12 +236,12 @@ public class FaceDetDemo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 摄像头人脸检测
|
* 摄像头人脸检测
|
||||||
* 注意事项:如果视频比较卡,可以使用轻量的人脸检测模型
|
* 注意事项:实时检测,需要使用极速模型
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDetectCamera(){
|
public void testDetectCamera(){
|
||||||
try {
|
try {
|
||||||
FaceDetModel faceModel = getSeetaface6DetModel();
|
FaceDetModel faceModel = getFastFaceDetModel();
|
||||||
OpenCV.loadShared();
|
OpenCV.loadShared();
|
||||||
VideoCapture capture = new VideoCapture(0);
|
VideoCapture capture = new VideoCapture(0);
|
||||||
if (!capture.isOpened()) {
|
if (!capture.isOpened()) {
|
||||||
|
|||||||
@@ -53,39 +53,65 @@ public class FaceRecDemo {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸检测模型(高精度,速度慢)
|
* 获取人脸检测模型(均衡模型)
|
||||||
* 追求准确度可以使用
|
* 均衡模型:兼顾速度和精度
|
||||||
* 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
* 注意事项:SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public FaceDetModel getHighAccuracyDetModel(){
|
public FaceDetModel getFaceDetModel(){
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
//高精度模型,速度慢
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
|
config.setModelEnum(FaceDetModelEnum.MTCNN);
|
||||||
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
// config.setModelPath("/Users/wenjie/Documents/develop/model/retinaface.pt");
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model");
|
||||||
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
config.setConfidenceThreshold(0.5f);
|
||||||
config.setDevice(device);
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸检测模型(高速模型,精度一般)
|
* 获取人脸检测模型(高精度模型)
|
||||||
* 追求速度可以使用
|
* 注意事项:
|
||||||
* 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
* 1、高精度模型,识别准确度高,速度慢
|
||||||
|
* 2、具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public FaceDetModel getHighSpeedDetModel(){
|
public FaceDetModel getProFaceDetModel(){
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
//高速模型,速度快,精度一般
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
config.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
|
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);
|
||||||
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
config.setModelPath("/Users/xxx/Documents/develop/model/sf3.0_models");
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model/retinaface.pt");
|
||||||
config.setDevice(device);
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取人脸检测模型(极速模型)
|
||||||
|
* 注意事项:
|
||||||
|
* 1、极速模型,识别准确度低,速度快
|
||||||
|
* 2、具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public FaceDetModel getFastFaceDetModel(){
|
||||||
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
|
config.setModelEnum(FaceDetModelEnum.YOLOV5_FACE_320);
|
||||||
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
|
||||||
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸识别模型(高精度,速度慢)
|
* 获取人脸识别模型(高精度,速度慢)
|
||||||
* 追求准确度可以使用
|
* 追求准确度可以使用
|
||||||
@@ -97,14 +123,14 @@ public class FaceRecDemo {
|
|||||||
//高精度模型,速度慢
|
//高精度模型,速度慢
|
||||||
config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);
|
config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);
|
||||||
//模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
//模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
|
config.setModelPath("/Users/wenjie/Documents/develop/model/elasticface.pt");
|
||||||
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
|
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
|
||||||
config.setCropFace(true);
|
config.setCropFace(true);
|
||||||
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
|
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
|
||||||
config.setAlign(true);
|
config.setAlign(true);
|
||||||
config.setDevice(device);
|
config.setDevice(device);
|
||||||
//指定人脸检测模型
|
//指定人脸检测模型
|
||||||
config.setDetectModel(getHighAccuracyDetModel());
|
config.setDetectModel(getProFaceDetModel());
|
||||||
return FaceRecModelFactory.getInstance().getModel(config);
|
return FaceRecModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +152,7 @@ public class FaceRecDemo {
|
|||||||
config.setAlign(false);
|
config.setAlign(false);
|
||||||
config.setDevice(device);
|
config.setDevice(device);
|
||||||
//指定人脸检测模型
|
//指定人脸检测模型
|
||||||
config.setDetectModel(getHighSpeedDetModel());
|
config.setDetectModel(getFastFaceDetModel());
|
||||||
return FaceRecModelFactory.getInstance().getModel(config);
|
return FaceRecModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,14 +163,14 @@ public class FaceRecDemo {
|
|||||||
public FaceRecModel getFaceRecModelWithDbConfig(){
|
public FaceRecModel getFaceRecModelWithDbConfig(){
|
||||||
FaceRecConfig config = new FaceRecConfig();
|
FaceRecConfig config = new FaceRecConfig();
|
||||||
//高精度模型,速度慢,追求速度请更换高速模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
//高精度模型,速度慢,追求速度请更换高速模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
||||||
config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);//人脸检测模型
|
config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);//人脸识别模型
|
||||||
config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
|
config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
|
||||||
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
|
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
|
||||||
config.setCropFace(true);
|
config.setCropFace(true);
|
||||||
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
|
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
|
||||||
config.setAlign(true);
|
config.setAlign(true);
|
||||||
//指定人脸检测模型,高精度模型,速度慢,追求速度请更换高速模型getHighSpeedDetModel
|
//指定人脸检测模型,可切换人脸检测模型(极速:getFastFaceDetModel,高精度:getProFaceDetModel),具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
||||||
config.setDetectModel(getHighAccuracyDetModel());
|
config.setDetectModel(getFaceDetModel());
|
||||||
config.setDevice(device);
|
config.setDevice(device);
|
||||||
|
|
||||||
//初始化向量数据库:Milvus数据库配置
|
//初始化向量数据库:Milvus数据库配置
|
||||||
@@ -175,8 +201,8 @@ public class FaceRecDemo {
|
|||||||
config.setCropFace(true);
|
config.setCropFace(true);
|
||||||
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
|
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
|
||||||
config.setAlign(true);
|
config.setAlign(true);
|
||||||
//指定人脸检测模型,高精度模型,速度慢,追求速度请更换高速模型getHighSpeedDetModel
|
//指定人脸检测模型,可切换人脸检测模型(极速:getFastFaceDetModel,高精度:getProFaceDetModel),具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
|
||||||
config.setDetectModel(getHighAccuracyDetModel());
|
config.setDetectModel(getFaceDetModel());
|
||||||
config.setDevice(device);
|
config.setDevice(device);
|
||||||
|
|
||||||
//初始化SQLite数据库
|
//初始化SQLite数据库
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class LivenessDetDemo {
|
|||||||
config.setModelEnum(LivenessModelEnum.IIC_FL_MODEL);
|
config.setModelEnum(LivenessModelEnum.IIC_FL_MODEL);
|
||||||
config.setDevice(device);
|
config.setDevice(device);
|
||||||
//需替换为实际模型存储路径
|
//需替换为实际模型存储路径
|
||||||
config.setModelPath("/Users/xxx/Documents/develop/model/anti/IIC_Fl.onnx");
|
config.setModelPath("/Users/wenjie/Documents/develop/model/anti/IIC_Fl.onnx");
|
||||||
//人脸活体阈值,可选,默认0.8,超过阈值则认为是真人,低于阈值是非活体
|
//人脸活体阈值,可选,默认0.8,超过阈值则认为是真人,低于阈值是非活体
|
||||||
config.setRealityThreshold(LivenessConstant.DEFAULT_REALITY_THRESHOLD);
|
config.setRealityThreshold(LivenessConstant.DEFAULT_REALITY_THRESHOLD);
|
||||||
/*视频检测帧数,可选,默认10,输出帧数超过这个number之后,就可以输出识别结果。
|
/*视频检测帧数,可选,默认10,输出帧数超过这个number之后,就可以输出识别结果。
|
||||||
@@ -122,15 +122,21 @@ public class LivenessDetDemo {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人脸检测模型
|
* 获取人脸检测模型(均衡模型)
|
||||||
|
* 均衡模型:兼顾速度和精度
|
||||||
|
* 注意事项:SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public FaceDetModel getFaceDetModel(){
|
public FaceDetModel getFaceDetModel(){
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
|
//人脸检测模型,SmartJavaAI提供了多种模型选择(更多模型,请查看文档),切换模型需要同时修改modelEnum及modelPath
|
||||||
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
|
config.setModelEnum(FaceDetModelEnum.MTCNN);
|
||||||
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
//下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
|
||||||
config.setDevice(device);
|
config.setModelPath("/Users/wenjie/Documents/develop/face_model");
|
||||||
|
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
|
||||||
|
config.setConfidenceThreshold(0.5f);
|
||||||
|
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-jni</artifactId>
|
<artifactId>pytorch-jni</artifactId>
|
||||||
<version>2.5.1-0.32.0</version>
|
<version>2.7.1-0.34.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-jni</artifactId>
|
<artifactId>pytorch-jni</artifactId>
|
||||||
<version>2.5.1-0.32.0</version>
|
<version>2.7.1-0.34.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-jni</artifactId>
|
<artifactId>pytorch-jni</artifactId>
|
||||||
<version>2.5.1-0.32.0</version>
|
<version>2.7.1-0.34.0</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu</artifactId>
|
<artifactId>pytorch-native-cpu</artifactId>
|
||||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
<groupId>ai.djl.pytorch</groupId>
|
<groupId>ai.djl.pytorch</groupId>
|
||||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||||
<version>2.5.1</version>
|
<version>2.7.1</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -9,24 +9,12 @@ import lombok.Data;
|
|||||||
public enum FaceDetModelEnum {
|
public enum FaceDetModelEnum {
|
||||||
|
|
||||||
RETINA_FACE("PyTorch",0,0, "https://resources.djl.ai/test-models/pytorch/retinaface.zip"),
|
RETINA_FACE("PyTorch",0,0, "https://resources.djl.ai/test-models/pytorch/retinaface.zip"),
|
||||||
RETINA_FACE_ONNX("OnnxRuntime",0,0, null),
|
RETINA_FACE_1080_720_ONNX("OnnxRuntime",1080,720, null),
|
||||||
RETINA_FACE_640_ONNX("OnnxRuntime",640,640, null),
|
RETINA_FACE_640_ONNX("OnnxRuntime",640,640, null),
|
||||||
RETINA_FACE_320_ONNX("OnnxRuntime",320,320, null),
|
|
||||||
RETINA_FACE_720_1280_ONNX("OnnxRuntime",720,1280, null),
|
|
||||||
RETINA_FACE_MOBILE_ONNX("OnnxRuntime",0,0, null),
|
|
||||||
RETINA_FACE_MOBILE_320_ONNX("OnnxRuntime",320,320, null),
|
|
||||||
RETINA_FACE_MOBILE_640_ONNX("OnnxRuntime",640,640, null),
|
|
||||||
RETINA_FACE_MOBILE_720_1280_ONNX("OnnxRuntime",720,1080, null),
|
|
||||||
ULTRA_LIGHT_FAST_GENERIC_FACE("PyTorch",0,0, "https://resources.djl.ai/test-models/pytorch/ultranet.zip"),
|
ULTRA_LIGHT_FAST_GENERIC_FACE("PyTorch",0,0, "https://resources.djl.ai/test-models/pytorch/ultranet.zip"),
|
||||||
SEETA_FACE6_MODEL(null,0,0, null),
|
SEETA_FACE6_MODEL(null,0,0, null),
|
||||||
YOLOV8_FACE("OnnxRuntime",640,640, null),
|
|
||||||
YOLOV5_FACE_640("OnnxRuntime", 640,640, null),
|
YOLOV5_FACE_640("OnnxRuntime", 640,640, null),
|
||||||
YOLOV5_FACE_320("OnnxRuntime",320,320, null),
|
YOLOV5_FACE_320("OnnxRuntime",320,320, null),
|
||||||
SCRFD_160("OnnxRuntime",160,160, null),
|
|
||||||
SCRFD_320("OnnxRuntime",320,320, null),
|
|
||||||
SCRFD_640("OnnxRuntime",640,640, null),
|
|
||||||
SCRFD_1280("OnnxRuntime",1280,1280, null),
|
|
||||||
|
|
||||||
MTCNN("OnnxRuntime",1280,1280, null);
|
MTCNN("OnnxRuntime",1280,1280, null);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import cn.smartjavaai.face.enums.FaceDetModelEnum;
|
|||||||
import cn.smartjavaai.face.exception.FaceException;
|
import cn.smartjavaai.face.exception.FaceException;
|
||||||
import cn.smartjavaai.face.model.facedect.CommonFaceDetModel;
|
import cn.smartjavaai.face.model.facedect.CommonFaceDetModel;
|
||||||
import cn.smartjavaai.face.model.facedect.FaceDetModel;
|
import cn.smartjavaai.face.model.facedect.FaceDetModel;
|
||||||
|
import cn.smartjavaai.face.model.facedect.MtcnnFaceDetModel;
|
||||||
import cn.smartjavaai.face.model.facedect.SeetaFace6FaceDetModel;
|
import cn.smartjavaai.face.model.facedect.SeetaFace6FaceDetModel;
|
||||||
import cn.smartjavaai.face.model.facerec.*;
|
import cn.smartjavaai.face.model.facerec.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -122,11 +123,13 @@ public class FaceDetModelFactory {
|
|||||||
// 初始化默认算法
|
// 初始化默认算法
|
||||||
static {
|
static {
|
||||||
registerAlgorithm(FaceDetModelEnum.RETINA_FACE, CommonFaceDetModel.class);
|
registerAlgorithm(FaceDetModelEnum.RETINA_FACE, CommonFaceDetModel.class);
|
||||||
|
registerAlgorithm(FaceDetModelEnum.RETINA_FACE_640_ONNX, CommonFaceDetModel.class);
|
||||||
|
registerAlgorithm(FaceDetModelEnum.RETINA_FACE_1080_720_ONNX, CommonFaceDetModel.class);
|
||||||
registerAlgorithm(FaceDetModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE, CommonFaceDetModel.class);
|
registerAlgorithm(FaceDetModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE, CommonFaceDetModel.class);
|
||||||
registerAlgorithm(FaceDetModelEnum.SEETA_FACE6_MODEL, SeetaFace6FaceDetModel.class);
|
registerAlgorithm(FaceDetModelEnum.SEETA_FACE6_MODEL, SeetaFace6FaceDetModel.class);
|
||||||
registerAlgorithm(FaceDetModelEnum.YOLOV8_FACE, CommonFaceDetModel.class);
|
|
||||||
registerAlgorithm(FaceDetModelEnum.YOLOV5_FACE_640, CommonFaceDetModel.class);
|
registerAlgorithm(FaceDetModelEnum.YOLOV5_FACE_640, CommonFaceDetModel.class);
|
||||||
registerAlgorithm(FaceDetModelEnum.YOLOV5_FACE_320, CommonFaceDetModel.class);
|
registerAlgorithm(FaceDetModelEnum.YOLOV5_FACE_320, CommonFaceDetModel.class);
|
||||||
|
registerAlgorithm(FaceDetModelEnum.MTCNN, MtcnnFaceDetModel.class);
|
||||||
log.debug("缓存目录:{}", Config.getCachePath());
|
log.debug("缓存目录:{}", Config.getCachePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ import cn.smartjavaai.common.entity.*;
|
|||||||
import cn.smartjavaai.common.entity.Point;
|
import cn.smartjavaai.common.entity.Point;
|
||||||
import cn.smartjavaai.common.entity.face.FaceInfo;
|
import cn.smartjavaai.common.entity.face.FaceInfo;
|
||||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||||
import cn.smartjavaai.common.utils.Base64ImageUtils;
|
import cn.smartjavaai.common.utils.*;
|
||||||
import cn.smartjavaai.common.utils.FileUtils;
|
|
||||||
import cn.smartjavaai.common.utils.ImageUtils;
|
|
||||||
import cn.smartjavaai.common.utils.OpenCVUtils;
|
|
||||||
import cn.smartjavaai.face.config.FaceDetConfig;
|
import cn.smartjavaai.face.config.FaceDetConfig;
|
||||||
import cn.smartjavaai.face.exception.FaceException;
|
import cn.smartjavaai.face.exception.FaceException;
|
||||||
import cn.smartjavaai.face.model.facedect.criterial.FaceDetCriteriaFactory;
|
import cn.smartjavaai.face.model.facedect.criterial.FaceDetCriteriaFactory;
|
||||||
@@ -85,8 +82,8 @@ public class MtcnnFaceDetModel implements FaceDetModel{
|
|||||||
Path rnetPath = modelPath.resolve("rnet_script.pt");
|
Path rnetPath = modelPath.resolve("rnet_script.pt");
|
||||||
Path onetPath = modelPath.resolve("onet_script.pt");
|
Path onetPath = modelPath.resolve("onet_script.pt");
|
||||||
pNetModel = getModel(pnetPath);
|
pNetModel = getModel(pnetPath);
|
||||||
rNetModel = getModel(pnetPath);
|
rNetModel = getModel(rnetPath);
|
||||||
oNetModel = getModel(pnetPath);
|
oNetModel = getModel(onetPath);
|
||||||
|
|
||||||
this.pnetPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(pNetModel));
|
this.pnetPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(pNetModel));
|
||||||
this.rnetPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(rNetModel));
|
this.rnetPredictorPool = new GenericObjectPool<>(new PredictorFactory<>(rNetModel));
|
||||||
@@ -266,22 +263,84 @@ public class MtcnnFaceDetModel implements FaceDetModel{
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public R<DetectionResponse> detect(Image image){
|
public R<DetectionResponse> detect(Image image){
|
||||||
try (NDManager manager = NDManager.newBaseManager(pNetModel.getNDManager().getDevice())){
|
Predictor<NDList, NDList> pNetPredictor = null;
|
||||||
List<Double> scales = MtcnnProcess.generateScales(image);
|
Predictor<NDList, NDList> rNetPredictor = null;
|
||||||
NDArray imgs = MtcnnProcess.processInput(manager, image);
|
Predictor<NDList, NDList> oNetPredictor = null;
|
||||||
|
try (NDManager manager = pNetModel.getNDManager().newSubManager();){
|
||||||
|
pNetPredictor = pnetPredictorPool.borrowObject();
|
||||||
|
rNetPredictor = rnetPredictorPool.borrowObject();
|
||||||
|
oNetPredictor = onetPredictorPool.borrowObject();
|
||||||
int h = image.getHeight();
|
int h = image.getHeight();
|
||||||
int w = image.getWidth();
|
int w = image.getWidth();
|
||||||
NDList outputPnet = PNetModel.firstStage(manager, pnetPredictorPool.borrowObject(), imgs, scales, w, h);
|
//第一阶段
|
||||||
|
NDList outputPnet = PNetModel.firstStage(manager, pNetPredictor, image);
|
||||||
|
if(CollectionUtils.isEmpty(outputPnet)){
|
||||||
|
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||||
|
}
|
||||||
NDArray boxes = outputPnet.get(0);
|
NDArray boxes = outputPnet.get(0);
|
||||||
NDArray image_inds = outputPnet.get(1);
|
NDArray image_inds = outputPnet.get(1);
|
||||||
|
NDArray imgs = outputPnet.get(2);
|
||||||
|
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_inds) || DJLCommonUtils.isNDArrayEmpty(imgs)){
|
||||||
|
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||||
|
}
|
||||||
NDList pad = MtcnnUtils.pad(boxes, w, h);
|
NDList pad = MtcnnUtils.pad(boxes, w, h);
|
||||||
NDList outputRnet = RNetModel.secondStage(manager, rnetPredictorPool.borrowObject(), imgs,boxes,pad, image_inds);
|
//第二阶段
|
||||||
|
NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs,boxes,pad, image_inds);
|
||||||
|
if(CollectionUtils.isEmpty(outputRnet)){
|
||||||
|
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||||
|
}
|
||||||
NDArray image_indsFiltered = outputRnet.get(0);
|
NDArray image_indsFiltered = outputRnet.get(0);
|
||||||
NDArray scoresFiltered = outputRnet.get(1);
|
NDArray scoresFiltered = outputRnet.get(1);
|
||||||
MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, onetPredictorPool.borrowObject(), imgs,boxes, w, h, scoresFiltered, image_indsFiltered);
|
boxes = outputRnet.get(2);
|
||||||
return R.ok(convertToDetectionResponse(oNetResult));
|
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_indsFiltered) || DJLCommonUtils.isNDArrayEmpty(scoresFiltered)){
|
||||||
|
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||||
|
}
|
||||||
|
//第三阶段
|
||||||
|
MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs,boxes, w, h, scoresFiltered, image_indsFiltered);
|
||||||
|
DetectionResponse detectionResponse = convertToDetectionResponse(oNetResult);
|
||||||
|
if(Objects.isNull(detectionResponse)){
|
||||||
|
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||||
|
}
|
||||||
|
return R.ok(detectionResponse);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (pNetPredictor != null) {
|
||||||
|
try {
|
||||||
|
pnetPredictorPool.returnObject(pNetPredictor); //归还
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("归还Predictor失败", e);
|
||||||
|
try {
|
||||||
|
pNetPredictor.close(); // 归还失败才销毁
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("关闭Predictor失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rNetPredictor != null) {
|
||||||
|
try {
|
||||||
|
rnetPredictorPool.returnObject(rNetPredictor); //归还
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("归还Predictor失败", e);
|
||||||
|
try {
|
||||||
|
rNetPredictor.close(); // 归还失败才销毁
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("关闭Predictor失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oNetPredictor != null) {
|
||||||
|
try {
|
||||||
|
onetPredictorPool.returnObject(oNetPredictor); //归还
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("归还Predictor失败", e);
|
||||||
|
try {
|
||||||
|
oNetPredictor.close(); // 归还失败才销毁
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("关闭Predictor失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +365,9 @@ public class MtcnnFaceDetModel implements FaceDetModel{
|
|||||||
NDArray probs = mtcnnBatchResult.probs.get(0);
|
NDArray probs = mtcnnBatchResult.probs.get(0);
|
||||||
NDArray points = mtcnnBatchResult.points.get(0);
|
NDArray points = mtcnnBatchResult.points.get(0);
|
||||||
|
|
||||||
|
if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
long numBoxes = boxes.getShape().get(0);
|
long numBoxes = boxes.getShape().get(0);
|
||||||
for (int i = 0; i < numBoxes; i++) {
|
for (int i = 0; i < numBoxes; i++) {
|
||||||
float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
|
float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import ai.djl.repository.zoo.Criteria;
|
|||||||
import ai.djl.training.util.ProgressBar;
|
import ai.djl.training.util.ProgressBar;
|
||||||
import ai.djl.translate.Translator;
|
import ai.djl.translate.Translator;
|
||||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||||
|
import cn.smartjavaai.common.utils.LetterBoxUtils;
|
||||||
import cn.smartjavaai.face.config.FaceDetConfig;
|
import cn.smartjavaai.face.config.FaceDetConfig;
|
||||||
import cn.smartjavaai.face.config.FaceExpressionConfig;
|
import cn.smartjavaai.face.config.FaceExpressionConfig;
|
||||||
import cn.smartjavaai.face.constant.FaceDetectConstant;
|
import cn.smartjavaai.face.constant.FaceDetectConstant;
|
||||||
@@ -19,9 +20,7 @@ import cn.smartjavaai.face.exception.FaceException;
|
|||||||
import cn.smartjavaai.face.model.expression.translator.DenseNetEmotionTranslator;
|
import cn.smartjavaai.face.model.expression.translator.DenseNetEmotionTranslator;
|
||||||
import cn.smartjavaai.face.model.expression.translator.FrEmotionTranslator;
|
import cn.smartjavaai.face.model.expression.translator.FrEmotionTranslator;
|
||||||
import cn.smartjavaai.face.translator.FaceDetectionTranslator;
|
import cn.smartjavaai.face.translator.FaceDetectionTranslator;
|
||||||
import cn.smartjavaai.face.translator.SCRFDFaceTranslator;
|
|
||||||
import cn.smartjavaai.face.translator.YoloV5FaceTranslator;
|
import cn.smartjavaai.face.translator.YoloV5FaceTranslator;
|
||||||
import cn.smartjavaai.face.translator.YoloV8FaceTranslator;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -73,33 +72,19 @@ public class FaceDetCriteriaFactory {
|
|||||||
*/
|
*/
|
||||||
public static Translator<Image, DetectedObjects> getTranslator(FaceDetConfig config) {
|
public static Translator<Image, DetectedObjects> getTranslator(FaceDetConfig config) {
|
||||||
Translator<Image, DetectedObjects> translator = null;
|
Translator<Image, DetectedObjects> translator = null;
|
||||||
if(config.getModelEnum() == FaceDetModelEnum.RETINA_FACE){
|
if(config.getModelEnum() == FaceDetModelEnum.RETINA_FACE || config.getModelEnum() == FaceDetModelEnum.RETINA_FACE_640_ONNX
|
||||||
|
|| config.getModelEnum() == FaceDetModelEnum.RETINA_FACE_1080_720_ONNX){
|
||||||
translator =
|
translator =
|
||||||
new FaceDetectionTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), RetinaFaceConstant.variance, FaceDetectConstant.MAX_FACE_LIMIT, RetinaFaceConstant.scales, RetinaFaceConstant.steps);
|
new FaceDetectionTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), RetinaFaceConstant.variance, FaceDetectConstant.MAX_FACE_LIMIT,
|
||||||
|
RetinaFaceConstant.scales, RetinaFaceConstant.steps, config.getModelEnum().getInputWidth(), config.getModelEnum().getInputHeight());
|
||||||
}else if (config.getModelEnum() == FaceDetModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE){
|
}else if (config.getModelEnum() == FaceDetModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE){
|
||||||
translator =
|
translator =
|
||||||
new FaceDetectionTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), UltraLightFastGenericFaceConstant.variance, FaceDetectConstant.MAX_FACE_LIMIT, UltraLightFastGenericFaceConstant.scales, UltraLightFastGenericFaceConstant.steps);
|
new FaceDetectionTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), UltraLightFastGenericFaceConstant.variance,
|
||||||
}else if (config.getModelEnum() == FaceDetModelEnum.YOLOV8_FACE){
|
FaceDetectConstant.MAX_FACE_LIMIT, UltraLightFastGenericFaceConstant.scales, UltraLightFastGenericFaceConstant.steps, config.getModelEnum().getInputWidth(), config.getModelEnum().getInputHeight());
|
||||||
Map<String, Object> arguments = new HashMap<>();
|
|
||||||
arguments.put("width", 640);
|
|
||||||
arguments.put("height", 640);
|
|
||||||
arguments.put("resizeShort", 640);
|
|
||||||
arguments.put("centerFit", true);
|
|
||||||
translator = YoloV8FaceTranslator.builder(arguments).build();
|
|
||||||
}else if (config.getModelEnum() == FaceDetModelEnum.YOLOV5_FACE_640
|
}else if (config.getModelEnum() == FaceDetModelEnum.YOLOV5_FACE_640
|
||||||
|| config.getModelEnum() == FaceDetModelEnum.YOLOV5_FACE_320){
|
|| config.getModelEnum() == FaceDetModelEnum.YOLOV5_FACE_320){
|
||||||
Map<String, Object> arguments = new HashMap<>();
|
translator = YoloV5FaceTranslator.builder()
|
||||||
arguments.put("width", config.getModelEnum().getInputWidth());
|
.setImageSize(config.getModelEnum().getInputWidth(), config.getModelEnum().getInputWidth()).build();
|
||||||
arguments.put("height", config.getModelEnum().getInputHeight());
|
|
||||||
arguments.put("resizeShort", true);
|
|
||||||
arguments.put("centerFit", true);
|
|
||||||
translator = YoloV5FaceTranslator.builder(arguments).build();
|
|
||||||
}else if (config.getModelEnum() == FaceDetModelEnum.SCRFD_160
|
|
||||||
|| config.getModelEnum() == FaceDetModelEnum.SCRFD_320
|
|
||||||
|| config.getModelEnum() == FaceDetModelEnum.SCRFD_640
|
|
||||||
|| config.getModelEnum() == FaceDetModelEnum.SCRFD_1280){
|
|
||||||
translator =
|
|
||||||
new SCRFDFaceTranslator(config.getConfidenceThreshold(), config.getNmsThresh(), 100, new int[]{8, 16, 32});
|
|
||||||
}
|
}
|
||||||
return translator;
|
return translator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class ONetModel {
|
|||||||
NDArray out0 = out.get(0).transpose(1, 0); // permute(1,0)
|
NDArray out0 = out.get(0).transpose(1, 0); // permute(1,0)
|
||||||
NDArray out1 = out.get(1).transpose(1, 0);
|
NDArray out1 = out.get(1).transpose(1, 0);
|
||||||
NDArray out2 = out.get(2).transpose(1, 0);
|
NDArray out2 = out.get(2).transpose(1, 0);
|
||||||
NDArray score = out1.get(1); // out1[1, :]
|
NDArray score = out2.get(1); // out1[1, :]
|
||||||
points = out1.duplicate();
|
points = out1.duplicate();
|
||||||
NDArray ipass = score.gt(0.7); // score > threshold[1]
|
NDArray ipass = score.gt(0.7); // score > threshold[1]
|
||||||
// ipass 为布尔/0-1张量,长度应等于 points 的第 1 维(这里是 7)
|
// ipass 为布尔/0-1张量,长度应等于 points 的第 1 维(这里是 7)
|
||||||
@@ -84,7 +84,7 @@ public class ONetModel {
|
|||||||
NDArray boxesSelected = boxes.get(manager.create(validIndices)); // 行筛选
|
NDArray boxesSelected = boxes.get(manager.create(validIndices)); // 行筛选
|
||||||
// 取前 4 列
|
// 取前 4 列
|
||||||
boxesSelected = boxesSelected.get(":, 0:4"); // 只保留前 4 列
|
boxesSelected = boxesSelected.get(":, 0:4"); // 只保留前 4 列
|
||||||
scoresFiltered = scoresFiltered.get(ipass).reshape(-1, 1); // score[ipass].unsqueeze(1)
|
scoresFiltered = score.get(ipass).reshape(-1, 1); // score[ipass].unsqueeze(1)
|
||||||
boxes = NDArrays.concat(new NDList(boxesSelected, scoresFiltered), 1); // 拼接成 (N,5)
|
boxes = NDArrays.concat(new NDList(boxesSelected, scoresFiltered), 1); // 拼接成 (N,5)
|
||||||
|
|
||||||
// 筛选 image_inds
|
// 筛选 image_inds
|
||||||
@@ -92,7 +92,6 @@ public class ONetModel {
|
|||||||
NDArray mv = out0.transpose() // (N, 4)
|
NDArray mv = out0.transpose() // (N, 4)
|
||||||
.get(ipass); // 1-D 花式索引在第 0 维,得到 (k, 4)
|
.get(ipass); // 1-D 花式索引在第 0 维,得到 (k, 4)
|
||||||
|
|
||||||
System.out.println("----------");
|
|
||||||
|
|
||||||
// w_i = boxes[:, 2] - boxes[:, 0] + 1
|
// w_i = boxes[:, 2] - boxes[:, 0] + 1
|
||||||
NDArray w_i = boxes.get(":,2").sub(boxes.get(":,0")).add(1);
|
NDArray w_i = boxes.get(":,2").sub(boxes.get(":,0")).add(1);
|
||||||
|
|||||||
@@ -23,53 +23,80 @@ import java.util.List;
|
|||||||
public class PNetModel {
|
public class PNetModel {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入图片
|
* 生成金字塔缩放比例列表
|
||||||
* @param imgs
|
* @param image
|
||||||
* @param w
|
|
||||||
* @param h
|
|
||||||
* @param scale
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static NDList processInput(NDArray imgs, int w, int h, double scale){
|
public static List<Double> generateScales(Image image){
|
||||||
int newH = (int) (h * scale + 1);
|
long h = image.getHeight();
|
||||||
int newW = (int) (w * scale + 1);
|
long w = image.getWidth();
|
||||||
// (N, H, W, C)
|
// 计算最小缩放比例
|
||||||
NDArray transposed = imgs.transpose(0, 2, 3, 1);
|
double minsize = 20;
|
||||||
transposed = NDImageUtils.resize(transposed, newW, newH, Image.Interpolation.AREA);
|
double m = 12.0 / minsize;
|
||||||
// (N, C, H, W)
|
double minl = Math.min(h, w) * m;
|
||||||
transposed = transposed.transpose(0, 3, 1, 2);
|
|
||||||
// 归一化
|
|
||||||
transposed = transposed.sub(127.5).mul(0.0078125f);
|
|
||||||
System.out.println("inputPnet: " + transposed.getShape());
|
|
||||||
return new NDList(transposed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NDList processOutput(NDList outputPnet, double scale, NDManager manager){
|
// 创建金字塔缩放比例列表
|
||||||
NDArray reg = outputPnet.get(0); // [B, 4, H, W]
|
double factor = 0.709; // 你原代码的 factor
|
||||||
NDArray probs = outputPnet.get(1); // [B, 2, H, W]
|
List<Double> scales = new ArrayList<>();
|
||||||
List<NDArray> boundingBox = generateBoundingBox(reg, probs.get(":, 1"), (float)scale, 0.6f);
|
double scale_i = m;
|
||||||
NDArray boxes_scale = boundingBox.get(0); // [N,9]
|
while (minl >= 12) {
|
||||||
NDArray imgIndND = boundingBox.get(1); // [N]
|
scales.add(scale_i);
|
||||||
NDArray pick = NMSUtils.batchedNms(boxes_scale.get(":,:4"), boxes_scale.get(":,4"), imgIndND, 0.5f, manager);
|
scale_i *= factor;
|
||||||
return new NDList(boxes_scale, imgIndND, pick);
|
minl *= factor;
|
||||||
|
}
|
||||||
|
return scales;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static NDList firstStage(NDManager manager, Predictor<NDList, NDList> pnetPredictor, NDArray imgs, List<Double> scales, int width, int height) throws TranslateException {
|
public static NDArray pNetPre(Image input,NDManager manager){
|
||||||
|
// Image -> NDArray (H, W, C)
|
||||||
|
NDArray array = input.toNDArray(manager, Image.Flag.COLOR);
|
||||||
|
// 增加 batch 维度 (1, H, W, C) ----
|
||||||
|
array = array.expandDims(0);
|
||||||
|
// 交换维度 (N, C, H, W)
|
||||||
|
array = array.transpose(0, 3, 1, 2);
|
||||||
|
// 转成模型的数据类型
|
||||||
|
if (!array.getDataType().equals(DataType.FLOAT32)) {
|
||||||
|
array = array.toType(DataType.FLOAT32, false);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static NDList firstStage(NDManager manager, Predictor<NDList, NDList> pnetPredictor, Image image) throws TranslateException {
|
||||||
|
List<Double> scales = MtcnnProcess.generateScales(image);
|
||||||
|
NDArray imgs = pNetPre(image,manager);
|
||||||
|
int h = image.getHeight();
|
||||||
|
int w = image.getWidth();
|
||||||
// 第一阶段
|
// 第一阶段
|
||||||
NDList boxes_list = new NDList();
|
NDList boxes_list = new NDList();
|
||||||
NDList image_inds_list = new NDList();
|
NDList image_inds_list = new NDList();
|
||||||
NDList scale_picks_list = new NDList();
|
NDList scale_picks_list = new NDList();
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
for (double scale : scales) {
|
for (double scale : scales) {
|
||||||
NDList inputPnet = processInput(imgs, width, height, scale);
|
int newH = (int) (h * scale + 1);
|
||||||
NDList outputPnet = pnetPredictor.predict(inputPnet);
|
int newW = (int) (w * scale + 1);
|
||||||
NDList output = processOutput(outputPnet, scale, manager);
|
// (N, H, W, C)
|
||||||
NDArray boxes_scale = output.get(0);
|
NDArray transposed = imgs.transpose(0, 2, 3, 1);
|
||||||
NDArray imgIndND = output.get(1);
|
transposed = NDImageUtils.resize(transposed, newW, newH, Image.Interpolation.AREA);
|
||||||
NDArray pick = output.get(2);
|
// (N, C, H, W)
|
||||||
|
transposed = transposed.transpose(0, 3, 1, 2);
|
||||||
|
// 归一化
|
||||||
|
transposed = transposed.sub(127.5).mul(0.0078125f);
|
||||||
|
|
||||||
|
|
||||||
|
NDList outputPnet = pnetPredictor.predict(new NDList(transposed));
|
||||||
|
NDArray reg = outputPnet.get(0); // [B, 4, H, W]
|
||||||
|
NDArray probs = outputPnet.get(1); // [B, 2, H, W]
|
||||||
|
|
||||||
|
List<NDArray> boundingBox = generateBoundingBox(reg, probs.get(":, 1"), (float)scale, 0.6f);
|
||||||
|
|
||||||
|
NDArray boxes_scale = boundingBox.get(0); // [N,9]
|
||||||
|
NDArray imgIndND = boundingBox.get(1); // [N]
|
||||||
|
|
||||||
|
NDArray pick = NMSUtils.batchedNms(boxes_scale.get(":,:4"), boxes_scale.get(":,4"), imgIndND, 0.5f,manager);
|
||||||
|
|
||||||
boxes_list.add(boxes_scale);
|
boxes_list.add(boxes_scale);
|
||||||
image_inds_list.add(imgIndND);
|
image_inds_list.add(imgIndND);
|
||||||
scale_picks_list.add(pick.add(offset));
|
scale_picks_list.add(pick.add(offset));
|
||||||
@@ -96,7 +123,6 @@ public class PNetModel {
|
|||||||
boxes = boxes.get(pick);
|
boxes = boxes.get(pick);
|
||||||
image_inds = image_inds.get(pick);
|
image_inds = image_inds.get(pick);
|
||||||
|
|
||||||
System.out.println(Arrays.toString(boxes.get(0).toFloatArray()));
|
|
||||||
|
|
||||||
NDArray regw = boxes.get(":, 2").sub(boxes.get(":, 0"));
|
NDArray regw = boxes.get(":, 2").sub(boxes.get(":, 0"));
|
||||||
NDArray regh = boxes.get(":, 3").sub(boxes.get(":, 1"));
|
NDArray regh = boxes.get(":, 3").sub(boxes.get(":, 1"));
|
||||||
@@ -108,7 +134,7 @@ public class PNetModel {
|
|||||||
|
|
||||||
boxes = NDArrays.stack(new NDList(qq1, qq2, qq3, qq4, boxes.get(":, 4")), 1);
|
boxes = NDArrays.stack(new NDList(qq1, qq2, qq3, qq4, boxes.get(":, 4")), 1);
|
||||||
boxes = MtcnnUtils.rerec(boxes);
|
boxes = MtcnnUtils.rerec(boxes);
|
||||||
return new NDList(boxes, image_inds);
|
return new NDList(boxes, image_inds, imgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,13 +157,8 @@ public class PNetModel {
|
|||||||
// mask = probs >= thresh -> [B,H,W]
|
// mask = probs >= thresh -> [B,H,W]
|
||||||
NDArray mask = probs.gte(threshold);
|
NDArray mask = probs.gte(threshold);
|
||||||
|
|
||||||
System.out.println("reg: " + reg.getShape());
|
|
||||||
System.out.println("probs shape: " + probs.getShape());
|
|
||||||
System.out.println("scale: " + scale);
|
|
||||||
System.out.println("mask: " + mask.getShape());
|
|
||||||
// mask_inds = mask.nonzero() -> [N,3] 每行: (batch, y, x)
|
// mask_inds = mask.nonzero() -> [N,3] 每行: (batch, y, x)
|
||||||
NDArray maskInds = mask.nonzero();
|
NDArray maskInds = mask.nonzero();
|
||||||
System.out.println("maskInds: " + maskInds.getShape());
|
|
||||||
|
|
||||||
// image_inds = mask_inds[:, 0]
|
// image_inds = mask_inds[:, 0]
|
||||||
NDArray imageInds = maskInds.get(":,0");
|
NDArray imageInds = maskInds.get(":,0");
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class RNetModel {
|
|||||||
log.debug("No face detected.");
|
log.debug("No face detected.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new NDList(image_indsFiltered, scoresFiltered);
|
return new NDList(image_indsFiltered, scoresFiltered,boxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
package cn.smartjavaai.face.translator;
|
package cn.smartjavaai.face.translator;
|
||||||
|
|
||||||
import ai.djl.modality.cv.Image;
|
import ai.djl.modality.cv.Image;
|
||||||
|
import ai.djl.modality.cv.ImageFactory;
|
||||||
import ai.djl.modality.cv.output.*;
|
import ai.djl.modality.cv.output.*;
|
||||||
import ai.djl.ndarray.NDArray;
|
import ai.djl.ndarray.NDArray;
|
||||||
import ai.djl.ndarray.NDArrays;
|
import ai.djl.ndarray.NDArrays;
|
||||||
@@ -22,7 +23,11 @@ import ai.djl.ndarray.types.DataType;
|
|||||||
import ai.djl.ndarray.types.Shape;
|
import ai.djl.ndarray.types.Shape;
|
||||||
import ai.djl.translate.Translator;
|
import ai.djl.translate.Translator;
|
||||||
import ai.djl.translate.TranslatorContext;
|
import ai.djl.translate.TranslatorContext;
|
||||||
|
import cn.smartjavaai.common.utils.LetterBoxUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -40,29 +45,50 @@ public class FaceDetectionTranslator implements Translator<Image, DetectedObject
|
|||||||
private int[][] scales;
|
private int[][] scales;
|
||||||
private int[] steps;
|
private int[] steps;
|
||||||
|
|
||||||
|
private int inputWidth = 0;
|
||||||
|
private int inputHeight = 0;
|
||||||
|
|
||||||
public FaceDetectionTranslator(
|
public FaceDetectionTranslator(
|
||||||
double confThresh,
|
double confThresh,
|
||||||
double nmsThresh,
|
double nmsThresh,
|
||||||
double[] variance,
|
double[] variance,
|
||||||
int topK,
|
int topK,
|
||||||
int[][] scales,
|
int[][] scales,
|
||||||
int[] steps) {
|
int[] steps,
|
||||||
|
int inputWidth,
|
||||||
|
int inputHeight) {
|
||||||
this.confThresh = confThresh;
|
this.confThresh = confThresh;
|
||||||
this.nmsThresh = nmsThresh;
|
this.nmsThresh = nmsThresh;
|
||||||
this.variance = variance;
|
this.variance = variance;
|
||||||
this.topK = topK;
|
this.topK = topK;
|
||||||
this.scales = scales;
|
this.scales = scales;
|
||||||
this.steps = steps;
|
this.steps = steps;
|
||||||
|
this.inputWidth = inputWidth;
|
||||||
|
this.inputHeight = inputHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public NDList processInput(TranslatorContext ctx, Image input) {
|
public NDList processInput(TranslatorContext ctx, Image input) {
|
||||||
|
ctx.setAttachment("sourceWidth", input.getWidth());
|
||||||
ctx.setAttachment("width", input.getWidth());
|
ctx.setAttachment("sourceHeight", input.getHeight());
|
||||||
ctx.setAttachment("height", input.getHeight());
|
ctx.setAttachment("width", inputWidth);
|
||||||
|
ctx.setAttachment("height", inputHeight);
|
||||||
|
|
||||||
NDArray array = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
|
NDArray array = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
|
||||||
|
|
||||||
|
if(inputWidth > 0 && inputHeight > 0){
|
||||||
|
//Letter box resize 640x640 with padding (保持比例,补边缘)
|
||||||
|
LetterBoxUtils.ResizeResult letterBoxResult = LetterBoxUtils.letterbox(ctx.getNDManager(), array, inputWidth, inputHeight, 114f, LetterBoxUtils.PaddingPosition.CENTER);
|
||||||
|
array = letterBoxResult.image;
|
||||||
|
ctx.setAttachment("needRecover", "1");//需要还原
|
||||||
|
ctx.setAttachment("scale", letterBoxResult.r);
|
||||||
|
}else{
|
||||||
|
ctx.setAttachment("needRecover", "0");//不需要还原
|
||||||
|
ctx.setAttachment("width", input.getWidth());
|
||||||
|
ctx.setAttachment("height", input.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
array = array.transpose(2, 0, 1).flip(0); // HWC -> CHW RGB -> BGR
|
array = array.transpose(2, 0, 1).flip(0); // HWC -> CHW RGB -> BGR
|
||||||
// The network by default takes float32
|
// The network by default takes float32
|
||||||
if (!array.getDataType().equals(DataType.FLOAT32)) {
|
if (!array.getDataType().equals(DataType.FLOAT32)) {
|
||||||
@@ -80,6 +106,13 @@ public class FaceDetectionTranslator implements Translator<Image, DetectedObject
|
|||||||
|
|
||||||
int width = (int) ctx.getAttachment("width");
|
int width = (int) ctx.getAttachment("width");
|
||||||
int height = (int) ctx.getAttachment("height");
|
int height = (int) ctx.getAttachment("height");
|
||||||
|
int sourceWidth = (int) ctx.getAttachment("sourceWidth");
|
||||||
|
int sourceHeight = (int) ctx.getAttachment("sourceHeight");
|
||||||
|
String needRecover = (String) ctx.getAttachment("needRecover");
|
||||||
|
float scale = 0;
|
||||||
|
if("1".equals(needRecover)){
|
||||||
|
scale = (float) ctx.getAttachment("scale");
|
||||||
|
}
|
||||||
|
|
||||||
NDManager manager = ctx.getNDManager();
|
NDManager manager = ctx.getNDManager();
|
||||||
double scaleXY = variance[0];
|
double scaleXY = variance[0];
|
||||||
@@ -91,7 +124,6 @@ public class FaceDetectionTranslator implements Translator<Image, DetectedObject
|
|||||||
new NDList(
|
new NDList(
|
||||||
prob.argMax(1).toType(DataType.FLOAT32, false),
|
prob.argMax(1).toType(DataType.FLOAT32, false),
|
||||||
prob.max(new int[] {1})));
|
prob.max(new int[] {1})));
|
||||||
|
|
||||||
NDArray boxRecover = boxRecover(manager, width, height, scales, steps);
|
NDArray boxRecover = boxRecover(manager, width, height, scales, steps);
|
||||||
NDArray boundingBoxes = list.get(0);
|
NDArray boundingBoxes = list.get(0);
|
||||||
NDArray bbWH = boundingBoxes.get(":, 2:").mul(scaleWH).exp().mul(boxRecover.get(":, 2:"));
|
NDArray bbWH = boundingBoxes.get(":, 2:").mul(scaleWH).exp().mul(boxRecover.get(":, 2:"));
|
||||||
@@ -141,6 +173,7 @@ public class FaceDetectionTranslator implements Translator<Image, DetectedObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (belowIoU) {
|
if (belowIoU) {
|
||||||
|
|
||||||
List<Point> keyPoints = new ArrayList<>();
|
List<Point> keyPoints = new ArrayList<>();
|
||||||
for (int j = 0; j < 5; j++) { // 5 face landmarks
|
for (int j = 0; j < 5; j++) { // 5 face landmarks
|
||||||
double x = landmsArr[j * 2];
|
double x = landmsArr[j * 2];
|
||||||
@@ -150,9 +183,14 @@ public class FaceDetectionTranslator implements Translator<Image, DetectedObject
|
|||||||
Landmark landmark =
|
Landmark landmark =
|
||||||
new Landmark(boxArr[0], boxArr[1], boxArr[2], boxArr[3], keyPoints);
|
new Landmark(boxArr[0], boxArr[1], boxArr[2], boxArr[3], keyPoints);
|
||||||
|
|
||||||
|
if(needRecover.equals("1")){
|
||||||
|
landmark = LetterBoxUtils.restoreBox(landmark, scale, sourceWidth, sourceHeight, width, height, true);
|
||||||
|
}
|
||||||
|
|
||||||
boxes.add(landmark);
|
boxes.add(landmark);
|
||||||
recorder.put(classId, boxes);
|
recorder.put(classId, boxes);
|
||||||
String className = "Face"; // classes.get(classId)
|
int percent = (int) Math.round(probability * 100);
|
||||||
|
String className = "face " + percent + "%"; // classes.get(classId)
|
||||||
retNames.add(className);
|
retNames.add(className);
|
||||||
retProbs.add(probability);
|
retProbs.add(probability);
|
||||||
retBB.add(landmark);
|
retBB.add(landmark);
|
||||||
|
|||||||
@@ -1,242 +0,0 @@
|
|||||||
package cn.smartjavaai.face.translator;
|
|
||||||
|
|
||||||
import ai.djl.modality.cv.Image;
|
|
||||||
import ai.djl.modality.cv.output.BoundingBox;
|
|
||||||
import ai.djl.modality.cv.output.DetectedObjects;
|
|
||||||
import ai.djl.modality.cv.output.Landmark;
|
|
||||||
import ai.djl.modality.cv.output.Point;
|
|
||||||
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.translate.Translator;
|
|
||||||
import ai.djl.translate.TranslatorContext;
|
|
||||||
import cn.smartjavaai.common.utils.LetterBoxUtils;
|
|
||||||
import cn.smartjavaai.common.utils.NMSUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SCRFD Translator
|
|
||||||
*/
|
|
||||||
public class SCRFDFaceTranslator implements Translator<Image, DetectedObjects> {
|
|
||||||
|
|
||||||
private double confThresh;
|
|
||||||
private double nmsThresh;
|
|
||||||
private int topK;
|
|
||||||
private int[] steps;
|
|
||||||
private int inputWidth = 640;
|
|
||||||
private int inputHeight = 640;
|
|
||||||
|
|
||||||
public SCRFDFaceTranslator(
|
|
||||||
double confThresh,
|
|
||||||
double nmsThresh,
|
|
||||||
int topK,
|
|
||||||
int[] steps) {
|
|
||||||
this.confThresh = confThresh;
|
|
||||||
this.nmsThresh = nmsThresh;
|
|
||||||
this.topK = topK;
|
|
||||||
this.steps = steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public NDList processInput(TranslatorContext ctx, Image input) {
|
|
||||||
|
|
||||||
ctx.setAttachment("width", input.getWidth());
|
|
||||||
ctx.setAttachment("height", input.getHeight());
|
|
||||||
|
|
||||||
NDManager manager = ctx.getNDManager();
|
|
||||||
NDArray array = input.toNDArray(manager, Image.Flag.COLOR);
|
|
||||||
|
|
||||||
//Letter box resize 640x640 with padding (保持比例,补边缘)
|
|
||||||
LetterBoxUtils.ResizeResult letterBoxResult = LetterBoxUtils.letterbox(manager, array, inputWidth, inputHeight, 0f, LetterBoxUtils.PaddingPosition.LEFT_TOP);
|
|
||||||
ctx.setAttachment("scale", letterBoxResult.r);
|
|
||||||
array = letterBoxResult.image;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
// 归一化
|
|
||||||
array = array.sub(127.5).mul(0.0078125);
|
|
||||||
return new NDList(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
@Override
|
|
||||||
public DetectedObjects processOutput(TranslatorContext ctx, NDList list) {
|
|
||||||
|
|
||||||
NDManager manager =
|
|
||||||
NDManager.newBaseManager(ctx.getNDManager().getDevice(), "PyTorch");
|
|
||||||
int sourceWidth = (int) ctx.getAttachment("width");
|
|
||||||
int sourceHeight = (int) ctx.getAttachment("height");
|
|
||||||
float detScale = (float) ctx.getAttachment("scale");
|
|
||||||
|
|
||||||
Map<String, NDArray> centerCache = new HashMap<>();
|
|
||||||
List<NDArray> scores_list = new ArrayList<>();
|
|
||||||
List<NDArray> bboxes_list = new ArrayList<>();
|
|
||||||
List<NDArray> kpss_list = new ArrayList<>();
|
|
||||||
// 步长长度
|
|
||||||
int fmc = steps.length;
|
|
||||||
int numAnchors = 2;
|
|
||||||
// 多尺度后处理
|
|
||||||
for (int idx = 0; idx < steps.length; idx++) {
|
|
||||||
int stride = steps[idx];
|
|
||||||
NDArray scores, bboxPreds, kpsPreds = null;
|
|
||||||
scores = list.get(idx);
|
|
||||||
bboxPreds = list.get(idx + fmc).mul(stride);
|
|
||||||
kpsPreds = list.get(idx + fmc * 2).mul(stride);
|
|
||||||
int height = inputHeight / stride;
|
|
||||||
int width = inputWidth / stride;
|
|
||||||
String key = height + "_" + width + "_" + stride;
|
|
||||||
// anchor centers cache
|
|
||||||
NDArray anchorCenters;
|
|
||||||
if (centerCache.containsKey(key)) {
|
|
||||||
anchorCenters = centerCache.get(key);
|
|
||||||
} else {
|
|
||||||
NDArray yv = manager.arange((float) height).reshape(height, 1).repeat(1, width);
|
|
||||||
NDArray xv = manager.arange((float) width).reshape(1, width).repeat(0, height);
|
|
||||||
// stack x, y 到最后一维
|
|
||||||
anchorCenters = xv.stack(yv, -1); // shape [height, width, 2]
|
|
||||||
// 乘 stride
|
|
||||||
anchorCenters = anchorCenters.mul(stride);
|
|
||||||
// 拉平成 [-1, 2],等价于 NumPy 的 reshape((-1, 2))
|
|
||||||
long total = anchorCenters.getShape().get(0) * anchorCenters.getShape().get(1);
|
|
||||||
// anchorCenters 现在是 [height*width, 2]
|
|
||||||
anchorCenters = anchorCenters.reshape(total, 2);
|
|
||||||
// 在第一维重复 numAnchors 次,直接拉平成最终形状
|
|
||||||
anchorCenters = anchorCenters.repeat(0, numAnchors); // shape [N*numAnchors, 2]
|
|
||||||
if (centerCache.size() < 100) {
|
|
||||||
centerCache.put(key, anchorCenters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NDArray pos_mask = scores.gte(confThresh); // scores >= thresh
|
|
||||||
NDArray pos_inds = pos_mask.nonzero(); // shape: [N, 2]
|
|
||||||
pos_inds = pos_inds.get(":, 0"); // 取第一列的索引
|
|
||||||
// System.out.println(Arrays.toString(pos_inds.toLongArray()));
|
|
||||||
// 计算 bbox
|
|
||||||
NDArray bboxes = distance2bbox(anchorCenters, bboxPreds); // [num_anchors, 4]
|
|
||||||
|
|
||||||
// 取出符合阈值的
|
|
||||||
NDArray pos_scores = scores.get(pos_inds);
|
|
||||||
NDArray pos_bboxes = bboxes.get(pos_inds);
|
|
||||||
|
|
||||||
scores_list.add(pos_scores);
|
|
||||||
bboxes_list.add(pos_bboxes);
|
|
||||||
|
|
||||||
NDArray kpss = distance2kps(anchorCenters, kpsPreds); // [num_anchors, num_kps*2]
|
|
||||||
kpss = kpss.reshape(kpss.getShape().get(0), -1, 2); // reshape (N, -1, 2)
|
|
||||||
NDArray pos_kpss = kpss.get(pos_inds);
|
|
||||||
kpss_list.add(pos_kpss);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 合并 scores
|
|
||||||
NDArray scores = NDArrays.concat(new NDList(scores_list), 0);
|
|
||||||
NDArray scoresRavel = scores.reshape(-1);
|
|
||||||
|
|
||||||
// 2. 得到排序索引
|
|
||||||
long[] orderLong = scoresRavel.argSort().flip(0).get(":" + topK).toLongArray();
|
|
||||||
|
|
||||||
// 3. 合并 bboxes
|
|
||||||
NDArray bboxes = NDArrays.concat(new NDList(bboxes_list), 0).div(detScale);
|
|
||||||
|
|
||||||
NDArray kpss = NDArrays.concat(new NDList(kpss_list), 0).div(detScale);
|
|
||||||
// 4. 拼接 [x1,y1,x2,y2,score]
|
|
||||||
NDArray preDet = bboxes.concat(scores.reshape(-1,1), 1);
|
|
||||||
// 5. 按 order 排序
|
|
||||||
preDet = preDet.get(manager.create(orderLong));
|
|
||||||
// 6. NMS
|
|
||||||
int[] keep = NMSUtils.nms(preDet.get(":,0:4"), preDet.get(":,4"), (float)nmsThresh);
|
|
||||||
NDArray det = preDet.get(manager.create(keep));
|
|
||||||
// System.out.println(Arrays.toString(det.toFloatArray()));
|
|
||||||
if (kpss != null) {
|
|
||||||
kpss = kpss.get(manager.create(orderLong));
|
|
||||||
kpss = kpss.get(manager.create(keep));
|
|
||||||
}
|
|
||||||
List<String> retNames = new ArrayList<>();
|
|
||||||
List<Double> retProbs = new ArrayList<>();
|
|
||||||
List<BoundingBox> retBB = new ArrayList<>();
|
|
||||||
long numDet = det.getShape().get(0); // N
|
|
||||||
long numCols = det.getShape().get(1); // 应该是 5: x1,y1,x2,y2,score
|
|
||||||
float[] flat = det.toFloatArray(); // 一维
|
|
||||||
for (int i = 0; i < numDet; i++) {
|
|
||||||
int base = (int) (i * numCols);
|
|
||||||
float x1 = flat[base] / sourceWidth;
|
|
||||||
float y1 = flat[base + 1] / sourceHeight;
|
|
||||||
float x2 = flat[base + 2] / sourceWidth;
|
|
||||||
float y2 = flat[base + 3] / sourceHeight;
|
|
||||||
float score = flat[base + 4];
|
|
||||||
|
|
||||||
retNames.add("face"); // 类别
|
|
||||||
retProbs.add((double) score);
|
|
||||||
|
|
||||||
float width = x2 - x1;
|
|
||||||
float height = y2 - y1;
|
|
||||||
Landmark rect = new Landmark(x1, y1, width, height, decodeKps(kpss.get(i)));
|
|
||||||
retBB.add(rect);
|
|
||||||
}
|
|
||||||
return new DetectedObjects(retNames, retProbs, retBB);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public NDArray distance2bbox(NDArray points, NDArray distance) {
|
|
||||||
// points: [N, 2], distance: [N, 4]
|
|
||||||
NDArray x1 = points.get(":, 0").sub(distance.get(":, 0")); // x - left
|
|
||||||
NDArray y1 = points.get(":, 1").sub(distance.get(":, 1")); // y - top
|
|
||||||
NDArray x2 = points.get(":, 0").add(distance.get(":, 2")); // x + right
|
|
||||||
NDArray y2 = points.get(":, 1").add(distance.get(":, 3")); // y + bottom
|
|
||||||
// stack([x1, y1, x2, y2], axis=-1)
|
|
||||||
NDList list = new NDList(x1.expandDims(1), y1.expandDims(1), x2.expandDims(1), y2.expandDims(1));
|
|
||||||
return NDArrays.concat(list, 1); // axis=1 表示最后一维
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public NDArray distance2kps(NDArray points, NDArray distance) {
|
|
||||||
// points: [N, 2], distance: [N, 2*num_kps]
|
|
||||||
int numKps = (int) distance.getShape().get(1) / 2;
|
|
||||||
List<NDArray> preds = new ArrayList<>();
|
|
||||||
for (int i = 0; i < numKps * 2; i += 2) {
|
|
||||||
NDArray px = points.get(":, " + (i % 2)).add(distance.get(":, " + i));
|
|
||||||
NDArray py = points.get(":, " + ((i % 2) + 1)).add(distance.get(":, " + (i + 1)));
|
|
||||||
preds.add(px);
|
|
||||||
preds.add(py);
|
|
||||||
}
|
|
||||||
// stack(preds, axis=-1)
|
|
||||||
NDList stackList = new NDList();
|
|
||||||
for (NDArray arr : preds) {
|
|
||||||
stackList.add(arr.expandDims(1));
|
|
||||||
}
|
|
||||||
return NDArrays.concat(stackList, 1); // shape [N, num_kps*2]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<Point> decodeKps(NDArray kpss) {
|
|
||||||
// 转成一维 float 数组
|
|
||||||
float[] flat = kpss.toFloatArray();
|
|
||||||
|
|
||||||
// reshape 成二维 [5][2]
|
|
||||||
int numPoints = (int) kpss.getShape().get(0); // 5
|
|
||||||
int dim = (int) kpss.getShape().get(1); // 2
|
|
||||||
float[][] kpsArray = new float[numPoints][dim];
|
|
||||||
for (int i = 0; i < numPoints; i++) {
|
|
||||||
for (int j = 0; j < dim; j++) {
|
|
||||||
kpsArray[i][j] = flat[i * dim + j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转成 Point 数组
|
|
||||||
List<Point> points = new ArrayList<>();
|
|
||||||
for (int i = 0; i < numPoints; i++) {
|
|
||||||
points.add(new Point(Math.round(kpsArray[i][0]), Math.round(kpsArray[i][1])));
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import ai.djl.ndarray.NDList;
|
|||||||
import ai.djl.ndarray.types.DataType;
|
import ai.djl.ndarray.types.DataType;
|
||||||
import ai.djl.ndarray.types.Shape;
|
import ai.djl.ndarray.types.Shape;
|
||||||
import ai.djl.translate.*;
|
import ai.djl.translate.*;
|
||||||
|
import cn.smartjavaai.common.utils.LetterBoxUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -79,8 +80,59 @@ public class YoloV5FaceTranslator implements Translator<Image, DetectedObjects>
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NDList processInput(TranslatorContext ctx, Image input) throws Exception {
|
||||||
|
NDArray array = input.toNDArray(ctx.getNDManager(), flag);
|
||||||
|
// NDList list = pipeline.transform(new NDList(array));
|
||||||
|
// Shape shape = list.get(0).getShape();
|
||||||
|
// int processedWidth;
|
||||||
|
// int processedHeight;
|
||||||
|
// long[] dim = shape.getShape();
|
||||||
|
// if (NDImageUtils.isCHW(shape)) {
|
||||||
|
// processedWidth = (int) dim[dim.length - 1];
|
||||||
|
// processedHeight = (int) dim[dim.length - 2];
|
||||||
|
// } else {
|
||||||
|
// processedWidth = (int) dim[dim.length - 2];
|
||||||
|
// processedHeight = (int) dim[dim.length - 3];
|
||||||
|
// }
|
||||||
|
|
||||||
|
LetterBoxUtils.ResizeResult letterBoxResult = LetterBoxUtils.letterbox(ctx.getNDManager(), array, width, height, 114f, LetterBoxUtils.PaddingPosition.CENTER);
|
||||||
|
array = letterBoxResult.image;
|
||||||
|
ctx.setAttachment("width", input.getWidth());
|
||||||
|
ctx.setAttachment("height", input.getHeight());
|
||||||
|
ctx.setAttachment("processedWidth", width);
|
||||||
|
ctx.setAttachment("processedHeight", height);
|
||||||
|
ctx.setAttachment("scale", letterBoxResult.r);
|
||||||
|
// 转为 float32 且归一化到 0~1
|
||||||
|
array = array.toType(DataType.FLOAT32, false).div(255f); // HWC
|
||||||
|
// HWC -> CHW
|
||||||
|
array = array.transpose(2, 0, 1); // CHW
|
||||||
|
// return new NDList(array.expandDims(0));
|
||||||
|
return new NDList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DetectedObjects processOutput(TranslatorContext ctx, NDList list) throws Exception {
|
||||||
|
int imageWidth = (Integer) ctx.getAttachment("width");
|
||||||
|
int imageHeight = (Integer) ctx.getAttachment("height");
|
||||||
|
float scale = (Float) ctx.getAttachment("scale");
|
||||||
|
switch (yoloOutputLayerType) {
|
||||||
|
case DETECT:
|
||||||
|
return processFromDetectOutput();
|
||||||
|
case AUTO:
|
||||||
|
if (list.get(0).getShape().dimension() > 2) {
|
||||||
|
return processFromDetectOutput();
|
||||||
|
} else {
|
||||||
|
return processFromBoxOutput(imageWidth, imageHeight, list, scale);
|
||||||
|
}
|
||||||
|
case BOX:
|
||||||
|
default:
|
||||||
|
return processFromBoxOutput(imageWidth, imageHeight, list, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
protected DetectedObjects processFromBoxOutput(int imageWidth, int imageHeight, NDList list) {
|
protected DetectedObjects processFromBoxOutput(int imageWidth, int imageHeight, NDList list, float scale) {
|
||||||
float[] flattened = list.get(0).toFloatArray();
|
float[] flattened = list.get(0).toFloatArray();
|
||||||
int sizeClasses = classes.size();
|
int sizeClasses = classes.size();
|
||||||
int stride = 15 + sizeClasses;
|
int stride = 15 + sizeClasses;
|
||||||
@@ -119,7 +171,7 @@ public class YoloV5FaceTranslator implements Translator<Image, DetectedObjects>
|
|||||||
classIds.add(maxIndex);
|
classIds.add(maxIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nms(imageWidth, imageHeight, boxes, classIds, scores);
|
return nms(imageWidth, imageHeight, boxes, classIds, scores, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DetectedObjects processFromDetectOutput() {
|
private DetectedObjects processFromDetectOutput() {
|
||||||
@@ -127,53 +179,16 @@ public class YoloV5FaceTranslator implements Translator<Image, DetectedObjects>
|
|||||||
"detect layer output is not supported yet, check correct YoloV5 export format");
|
"detect layer output is not supported yet, check correct YoloV5 export format");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DetectedObjects processOutput(TranslatorContext ctx, NDList list) throws Exception {
|
|
||||||
int imageWidth = (Integer) ctx.getAttachment("width");
|
|
||||||
int imageHeight = (Integer) ctx.getAttachment("height");
|
|
||||||
switch (yoloOutputLayerType) {
|
|
||||||
case DETECT:
|
|
||||||
return processFromDetectOutput();
|
|
||||||
case AUTO:
|
|
||||||
if (list.get(0).getShape().dimension() > 2) {
|
|
||||||
return processFromDetectOutput();
|
|
||||||
} else {
|
|
||||||
return processFromBoxOutput(imageWidth, imageHeight, list);
|
|
||||||
}
|
|
||||||
case BOX:
|
|
||||||
default:
|
|
||||||
return processFromBoxOutput(imageWidth, imageHeight, list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NDList processInput(TranslatorContext ctx, Image input) throws Exception {
|
|
||||||
NDArray array = input.toNDArray(ctx.getNDManager(), flag);
|
|
||||||
NDList list = pipeline.transform(new NDList(array));
|
|
||||||
Shape shape = list.get(0).getShape();
|
|
||||||
int processedWidth;
|
|
||||||
int processedHeight;
|
|
||||||
long[] dim = shape.getShape();
|
|
||||||
if (NDImageUtils.isCHW(shape)) {
|
|
||||||
processedWidth = (int) dim[dim.length - 1];
|
|
||||||
processedHeight = (int) dim[dim.length - 2];
|
|
||||||
} else {
|
|
||||||
processedWidth = (int) dim[dim.length - 2];
|
|
||||||
processedHeight = (int) dim[dim.length - 3];
|
|
||||||
}
|
|
||||||
ctx.setAttachment("width", input.getWidth());
|
|
||||||
ctx.setAttachment("height", input.getHeight());
|
|
||||||
ctx.setAttachment("processedWidth", processedWidth);
|
|
||||||
ctx.setAttachment("processedHeight", processedHeight);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DetectedObjects nms(
|
protected DetectedObjects nms(
|
||||||
int imageWidth,
|
int imageWidth,
|
||||||
int imageHeight,
|
int imageHeight,
|
||||||
List<Landmark> boxes,
|
List<Landmark> boxes,
|
||||||
List<Integer> classIds,
|
List<Integer> classIds,
|
||||||
List<Float> scores) {
|
List<Float> scores, float scale) {
|
||||||
List<String> retClasses = new ArrayList<>();
|
List<String> retClasses = new ArrayList<>();
|
||||||
List<Double> retProbs = new ArrayList<>();
|
List<Double> retProbs = new ArrayList<>();
|
||||||
List<BoundingBox> retBB = new ArrayList<>();
|
List<BoundingBox> retBB = new ArrayList<>();
|
||||||
@@ -196,34 +211,41 @@ public class YoloV5FaceTranslator implements Translator<Image, DetectedObjects>
|
|||||||
for (int index : nms) {
|
for (int index : nms) {
|
||||||
int pos = map.get(index);
|
int pos = map.get(index);
|
||||||
int id = classIds.get(pos);
|
int id = classIds.get(pos);
|
||||||
retClasses.add(classes.get(id));
|
|
||||||
|
int percent = (int) Math.round(scores.get(pos).doubleValue() * 100);
|
||||||
|
String className = "face " + percent + "%"; // classes.get(classId)
|
||||||
|
retClasses.add(className);
|
||||||
retProbs.add(scores.get(pos).doubleValue());
|
retProbs.add(scores.get(pos).doubleValue());
|
||||||
// Rectangle rect = boxes.get(pos);
|
// Rectangle rect = boxes.get(pos);
|
||||||
Landmark rect = boxes.get(pos);
|
Landmark rect = boxes.get(pos);
|
||||||
List<Point> keypoints = new ArrayList<>();
|
List<Point> keypoints = new ArrayList<>();
|
||||||
if (removePadding) {
|
|
||||||
int padW = (width - imageWidth) / 2;
|
//恢复原图坐标(除回比例,减掉 padding)
|
||||||
int padH = (height - imageHeight) / 2;
|
rect = LetterBoxUtils.restoreBox(rect, scale, imageWidth, imageHeight, width, height, false);
|
||||||
rect.getPath().forEach(point -> {
|
|
||||||
keypoints.add(new Point(point.getX() - padW, point.getY() - padH));
|
// if (removePadding) {
|
||||||
});
|
// int padW = (width - imageWidth) / 2;
|
||||||
rect =
|
// int padH = (height - imageHeight) / 2;
|
||||||
new Landmark(
|
// rect.getPath().forEach(point -> {
|
||||||
(rect.getX() - padW) / imageWidth,
|
// keypoints.add(new Point(point.getX() - padW, point.getY() - padH));
|
||||||
(rect.getY() - padH) / imageHeight,
|
// });
|
||||||
rect.getWidth() / imageWidth,
|
// rect =
|
||||||
rect.getHeight() / imageHeight,keypoints);
|
// new Landmark(
|
||||||
} else if (applyRatio) {
|
// (rect.getX() - padW) / imageWidth,
|
||||||
rect.getPath().forEach(point -> {
|
// (rect.getY() - padH) / imageHeight,
|
||||||
keypoints.add(new Point(point.getX() / width, point.getY() / height));
|
// rect.getWidth() / imageWidth,
|
||||||
});
|
// rect.getHeight() / imageHeight,keypoints);
|
||||||
rect =
|
// } else if (applyRatio) {
|
||||||
new Landmark(
|
// rect.getPath().forEach(point -> {
|
||||||
rect.getX() / width,
|
// keypoints.add(new Point(point.getX() / width, point.getY() / height));
|
||||||
rect.getY() / height,
|
// });
|
||||||
rect.getWidth() / width,
|
// rect =
|
||||||
rect.getHeight() / height,keypoints);
|
// new Landmark(
|
||||||
}
|
// rect.getX() / width,
|
||||||
|
// rect.getY() / height,
|
||||||
|
// rect.getWidth() / width,
|
||||||
|
// rect.getHeight() / height,keypoints);
|
||||||
|
// }
|
||||||
retBB.add(rect);
|
retBB.add(rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,476 +0,0 @@
|
|||||||
package cn.smartjavaai.face.translator;
|
|
||||||
|
|
||||||
import ai.djl.modality.cv.Image;
|
|
||||||
import ai.djl.modality.cv.output.*;
|
|
||||||
import ai.djl.modality.cv.transform.*;
|
|
||||||
import ai.djl.modality.cv.util.NDImageUtils;
|
|
||||||
import ai.djl.ndarray.NDArray;
|
|
||||||
import ai.djl.ndarray.NDList;
|
|
||||||
import ai.djl.ndarray.types.DataType;
|
|
||||||
import ai.djl.ndarray.types.Shape;
|
|
||||||
import ai.djl.translate.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* YoloV8Translator
|
|
||||||
* @author dwj
|
|
||||||
*/
|
|
||||||
public class YoloV8FaceTranslator implements Translator<Image, DetectedObjects> {
|
|
||||||
|
|
||||||
private int maxBoxes;
|
|
||||||
|
|
||||||
private YoloOutputType yoloOutputLayerType;
|
|
||||||
private float nmsThreshold;
|
|
||||||
|
|
||||||
protected float threshold;
|
|
||||||
// private BaseImageTranslator.SynsetLoader synsetLoader;
|
|
||||||
protected List<String> classes;
|
|
||||||
protected boolean applyRatio;
|
|
||||||
protected boolean removePadding;
|
|
||||||
|
|
||||||
protected Pipeline pipeline;
|
|
||||||
private Image.Flag flag;
|
|
||||||
private Batchifier batchifier;
|
|
||||||
protected int width;
|
|
||||||
protected int height;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an ImageTranslator with the provided builder.
|
|
||||||
*
|
|
||||||
* @param builder the data to build with
|
|
||||||
*/
|
|
||||||
protected YoloV8FaceTranslator(Builder builder) {
|
|
||||||
this.yoloOutputLayerType = builder.outputType;
|
|
||||||
this.nmsThreshold = builder.nmsThreshold;
|
|
||||||
maxBoxes = builder.maxBox;
|
|
||||||
this.threshold = builder.threshold;
|
|
||||||
// this.synsetLoader = builder.synsetLoader;
|
|
||||||
this.applyRatio = builder.applyRatio;
|
|
||||||
this.removePadding = builder.removePadding;
|
|
||||||
this.flag = builder.flag;
|
|
||||||
this.pipeline = builder.pipeline;
|
|
||||||
this.batchifier = builder.batchifier;
|
|
||||||
this.width = builder.width;
|
|
||||||
this.height = builder.height;
|
|
||||||
classes = Arrays.asList("face");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a builder to build a {@code YoloV8Translator} with specified arguments.
|
|
||||||
*
|
|
||||||
* @return a new builder
|
|
||||||
*/
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a builder to build a {@code YoloV8Translator} with specified arguments.
|
|
||||||
*
|
|
||||||
* @param arguments arguments to specify builder options
|
|
||||||
* @return a new builder
|
|
||||||
*/
|
|
||||||
public static Builder builder(Map<String, ?> arguments) {
|
|
||||||
Builder builder = new Builder();
|
|
||||||
builder.configPreProcess(arguments);
|
|
||||||
builder.configPostProcess(arguments);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
protected DetectedObjects processFromBoxOutput(int imageWidth, int imageHeight, NDList list) {
|
|
||||||
NDArray rawResult = list.get(0);
|
|
||||||
NDArray reshapedResult = rawResult.transpose();
|
|
||||||
Shape shape = reshapedResult.getShape();
|
|
||||||
float[] buf = reshapedResult.toFloatArray();
|
|
||||||
int numberRows = Math.toIntExact(shape.get(0));
|
|
||||||
int nClasses = Math.toIntExact(shape.get(1));
|
|
||||||
int padding = nClasses - classes.size();
|
|
||||||
System.out.println(Arrays.toString(reshapedResult.get(0).toFloatArray()));
|
|
||||||
// if (padding != 0 && padding != 4) {
|
|
||||||
// throw new IllegalStateException(
|
|
||||||
// "Expected classes: " + (nClasses - 4) + ", got " + classes.size());
|
|
||||||
// }
|
|
||||||
|
|
||||||
ArrayList<Landmark> boxes = new ArrayList<>();
|
|
||||||
ArrayList<Float> scores = new ArrayList<>();
|
|
||||||
ArrayList<Integer> classIds = new ArrayList<>();
|
|
||||||
|
|
||||||
// reverse order search in heap; searches through #maxBoxes for optimization when set
|
|
||||||
for (int i = numberRows - 1; i > numberRows - maxBoxes; --i) {
|
|
||||||
int index = i * nClasses;
|
|
||||||
float maxClassProb = buf[index + 4];
|
|
||||||
// int maxIndex = -1;
|
|
||||||
// for (int c = 4; c < nClasses; c++) {
|
|
||||||
// float classProb = buf[index + c];
|
|
||||||
// if (classProb > maxClassProb) {
|
|
||||||
// maxClassProb = classProb;
|
|
||||||
// maxIndex = c;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// maxIndex -= padding;
|
|
||||||
|
|
||||||
if (maxClassProb > threshold) {
|
|
||||||
float xPos = buf[index]; // center x
|
|
||||||
float yPos = buf[index + 1]; // center y
|
|
||||||
float w = buf[index + 2];
|
|
||||||
float h = buf[index + 3];
|
|
||||||
Rectangle rect =
|
|
||||||
new Rectangle(Math.max(0, xPos - w / 2), Math.max(0, yPos - h / 2), w, h);
|
|
||||||
// boxes.add(rect);
|
|
||||||
scores.add(maxClassProb);
|
|
||||||
classIds.add(0);
|
|
||||||
List<Point> keypoints = new ArrayList<>();
|
|
||||||
keypoints.add(new Point(buf[index + 5], buf[index + 6]));
|
|
||||||
keypoints.add(new Point(buf[index + 8], buf[index + 9]));
|
|
||||||
keypoints.add(new Point(buf[index + 11], buf[index + 12]));
|
|
||||||
keypoints.add(new Point(buf[index + 14], buf[index + 15]));
|
|
||||||
keypoints.add(new Point(buf[index + 17], buf[index + 18]));
|
|
||||||
Landmark kps = new Landmark(Math.max(0, xPos - w / 2), Math.max(0, yPos - h / 2), w, h, keypoints);
|
|
||||||
boxes.add(kps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nms(imageWidth, imageHeight, boxes, classIds, scores);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DetectedObjects processFromDetectOutput() {
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"detect layer output is not supported yet, check correct YoloV5 export format");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DetectedObjects processOutput(TranslatorContext ctx, NDList list) throws Exception {
|
|
||||||
int imageWidth = (Integer) ctx.getAttachment("width");
|
|
||||||
int imageHeight = (Integer) ctx.getAttachment("height");
|
|
||||||
switch (yoloOutputLayerType) {
|
|
||||||
case DETECT:
|
|
||||||
return processFromDetectOutput();
|
|
||||||
case AUTO:
|
|
||||||
if (list.get(0).getShape().dimension() > 2) {
|
|
||||||
return processFromDetectOutput();
|
|
||||||
} else {
|
|
||||||
return processFromBoxOutput(imageWidth, imageHeight, list);
|
|
||||||
}
|
|
||||||
case BOX:
|
|
||||||
default:
|
|
||||||
return processFromBoxOutput(imageWidth, imageHeight, list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NDList processInput(TranslatorContext ctx, Image input) throws Exception {
|
|
||||||
NDArray array = input.toNDArray(ctx.getNDManager(), flag);
|
|
||||||
NDList list = pipeline.transform(new NDList(array));
|
|
||||||
Shape shape = list.get(0).getShape();
|
|
||||||
int processedWidth;
|
|
||||||
int processedHeight;
|
|
||||||
long[] dim = shape.getShape();
|
|
||||||
if (NDImageUtils.isCHW(shape)) {
|
|
||||||
processedWidth = (int) dim[dim.length - 1];
|
|
||||||
processedHeight = (int) dim[dim.length - 2];
|
|
||||||
} else {
|
|
||||||
processedWidth = (int) dim[dim.length - 2];
|
|
||||||
processedHeight = (int) dim[dim.length - 3];
|
|
||||||
}
|
|
||||||
ctx.setAttachment("width", input.getWidth());
|
|
||||||
ctx.setAttachment("height", input.getHeight());
|
|
||||||
ctx.setAttachment("processedWidth", processedWidth);
|
|
||||||
ctx.setAttachment("processedHeight", processedHeight);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DetectedObjects nms(
|
|
||||||
int imageWidth,
|
|
||||||
int imageHeight,
|
|
||||||
List<Landmark> boxes,
|
|
||||||
List<Integer> classIds,
|
|
||||||
List<Float> scores) {
|
|
||||||
List<String> retClasses = new ArrayList<>();
|
|
||||||
List<Double> retProbs = new ArrayList<>();
|
|
||||||
List<BoundingBox> retBB = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int classId = 0; classId < classes.size(); classId++) {
|
|
||||||
List<Rectangle> r = new ArrayList<>();
|
|
||||||
List<Double> s = new ArrayList<>();
|
|
||||||
List<Integer> map = new ArrayList<>();
|
|
||||||
for (int j = 0; j < classIds.size(); ++j) {
|
|
||||||
if (classIds.get(j) == classId) {
|
|
||||||
r.add(boxes.get(j));
|
|
||||||
s.add(scores.get(j).doubleValue());
|
|
||||||
map.add(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (r.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
List<Integer> nms = Rectangle.nms(r, s, nmsThreshold);
|
|
||||||
for (int index : nms) {
|
|
||||||
int pos = map.get(index);
|
|
||||||
int id = classIds.get(pos);
|
|
||||||
retClasses.add(classes.get(id));
|
|
||||||
retProbs.add(scores.get(pos).doubleValue());
|
|
||||||
// Rectangle rect = boxes.get(pos);
|
|
||||||
Landmark rect = boxes.get(pos);
|
|
||||||
List<Point> keypoints = new ArrayList<>();
|
|
||||||
if (removePadding) {
|
|
||||||
int padW = (width - imageWidth) / 2;
|
|
||||||
int padH = (height - imageHeight) / 2;
|
|
||||||
rect.getPath().forEach(point -> {
|
|
||||||
keypoints.add(new Point(point.getX() - padW, point.getY() - padH));
|
|
||||||
});
|
|
||||||
rect =
|
|
||||||
new Landmark(
|
|
||||||
(rect.getX() - padW) / imageWidth,
|
|
||||||
(rect.getY() - padH) / imageHeight,
|
|
||||||
rect.getWidth() / imageWidth,
|
|
||||||
rect.getHeight() / imageHeight,keypoints);
|
|
||||||
} else if (applyRatio) {
|
|
||||||
rect.getPath().forEach(point -> {
|
|
||||||
keypoints.add(new Point(point.getX() / width, point.getY() / height));
|
|
||||||
});
|
|
||||||
rect =
|
|
||||||
new Landmark(
|
|
||||||
rect.getX() / width,
|
|
||||||
rect.getY() / height,
|
|
||||||
rect.getWidth() / width,
|
|
||||||
rect.getHeight() / height,keypoints);
|
|
||||||
}
|
|
||||||
retBB.add(rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new DetectedObjects(retClasses, retProbs, retBB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
private int maxBox = 8400;
|
|
||||||
|
|
||||||
YoloOutputType outputType;
|
|
||||||
float nmsThreshold;
|
|
||||||
|
|
||||||
protected float threshold = 0.2F;
|
|
||||||
protected boolean applyRatio;
|
|
||||||
protected boolean removePadding;
|
|
||||||
|
|
||||||
protected int width = 224;
|
|
||||||
protected int height = 224;
|
|
||||||
protected Image.Flag flag;
|
|
||||||
protected Pipeline pipeline;
|
|
||||||
protected Batchifier batchifier;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
this.outputType = YoloOutputType.AUTO;
|
|
||||||
this.nmsThreshold = 0.4F;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder optOutputType(YoloOutputType outputType) {
|
|
||||||
this.outputType = outputType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder optNmsThreshold(float nmsThreshold) {
|
|
||||||
this.nmsThreshold = nmsThreshold;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the translator.
|
|
||||||
*
|
|
||||||
* @return the new translator
|
|
||||||
*/
|
|
||||||
public YoloV8FaceTranslator build() {
|
|
||||||
if (pipeline == null) {
|
|
||||||
addTransform(
|
|
||||||
array -> array.transpose(2, 0, 1).toType(DataType.FLOAT32, false).div(255));
|
|
||||||
}
|
|
||||||
// validate();
|
|
||||||
return new YoloV8FaceTranslator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Builder self() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addTransform(Transform transform) {
|
|
||||||
if (this.pipeline == null) {
|
|
||||||
this.pipeline = new Pipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pipeline.add(transform);
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder optApplyRatio(boolean value) {
|
|
||||||
this.applyRatio = value;
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder optFlag(Image.Flag flag) {
|
|
||||||
this.flag = flag;
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setPipeline(Pipeline pipeline) {
|
|
||||||
this.pipeline = pipeline;
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setImageSize(int width, int height) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Builder optBatchifier(Batchifier batchifier) {
|
|
||||||
this.batchifier = batchifier;
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder optThreshold(float threshold) {
|
|
||||||
this.threshold = threshold;
|
|
||||||
return this.self();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
protected void configPostProcess(Map<String, ?> arguments) {
|
|
||||||
if (ArgumentsUtil.booleanValue(arguments, "optApplyRatio") || ArgumentsUtil.booleanValue(arguments, "applyRatio")) {
|
|
||||||
this.optApplyRatio(true);
|
|
||||||
}
|
|
||||||
this.threshold = ArgumentsUtil.floatValue(arguments, "threshold", 0.2F);
|
|
||||||
String centerFit = ArgumentsUtil.stringValue(arguments, "centerFit", "false");
|
|
||||||
this.removePadding = "true".equals(centerFit);
|
|
||||||
String type = ArgumentsUtil.stringValue(arguments, "outputType", "AUTO");
|
|
||||||
this.outputType = YoloOutputType.valueOf(type.toUpperCase(Locale.ENGLISH));
|
|
||||||
this.nmsThreshold = ArgumentsUtil.floatValue(arguments, "nmsThreshold", 0.4F);
|
|
||||||
maxBox = ArgumentsUtil.intValue(arguments, "maxBox", 8400);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void configPreProcess(Map<String, ?> arguments) {
|
|
||||||
if (this.pipeline == null) {
|
|
||||||
this.pipeline = new Pipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.width = ArgumentsUtil.intValue(arguments, "width", 224);
|
|
||||||
this.height = ArgumentsUtil.intValue(arguments, "height", 224);
|
|
||||||
if (arguments.containsKey("flag")) {
|
|
||||||
this.flag = Image.Flag.valueOf(arguments.get("flag").toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
String pad = ArgumentsUtil.stringValue(arguments, "pad", "false");
|
|
||||||
if ("true".equals(pad)) {
|
|
||||||
this.addTransform(new Pad(0.0));
|
|
||||||
} else if (!"false".equals(pad)) {
|
|
||||||
double padding = Double.parseDouble(pad);
|
|
||||||
this.addTransform(new Pad(padding));
|
|
||||||
}
|
|
||||||
|
|
||||||
String resize = ArgumentsUtil.stringValue(arguments, "resize", "false");
|
|
||||||
int w;
|
|
||||||
int shortEdge;
|
|
||||||
if ("true".equals(resize)) {
|
|
||||||
this.addTransform(new Resize(this.width, this.height));
|
|
||||||
} else if (!"false".equals(resize)) {
|
|
||||||
String[] tokens = resize.split("\\s*,\\s*");
|
|
||||||
w = (int)Double.parseDouble(tokens[0]);
|
|
||||||
if (tokens.length > 1) {
|
|
||||||
shortEdge = (int)Double.parseDouble(tokens[1]);
|
|
||||||
} else {
|
|
||||||
shortEdge = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
Image.Interpolation interpolation;
|
|
||||||
if (tokens.length > 2) {
|
|
||||||
interpolation = Image.Interpolation.valueOf(tokens[2]);
|
|
||||||
} else {
|
|
||||||
interpolation = Image.Interpolation.BILINEAR;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addTransform(new Resize(w, shortEdge, interpolation));
|
|
||||||
}
|
|
||||||
|
|
||||||
String resizeShort = ArgumentsUtil.stringValue(arguments, "resizeShort", "false");
|
|
||||||
if ("true".equals(resizeShort)) {
|
|
||||||
w = Math.max(this.width, this.height);
|
|
||||||
this.addTransform(new ResizeShort(w));
|
|
||||||
} else if (!"false".equals(resizeShort)) {
|
|
||||||
String[] tokens = resizeShort.split("\\s*,\\s*");
|
|
||||||
shortEdge = (int)Double.parseDouble(tokens[0]);
|
|
||||||
int longEdge;
|
|
||||||
if (tokens.length > 1) {
|
|
||||||
longEdge = (int)Double.parseDouble(tokens[1]);
|
|
||||||
} else {
|
|
||||||
longEdge = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Image.Interpolation interpolation;
|
|
||||||
if (tokens.length > 2) {
|
|
||||||
interpolation = Image.Interpolation.valueOf(tokens[2]);
|
|
||||||
} else {
|
|
||||||
interpolation = Image.Interpolation.BILINEAR;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addTransform(new ResizeShort(shortEdge, longEdge, interpolation));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ArgumentsUtil.booleanValue(arguments, "centerCrop", false)) {
|
|
||||||
this.addTransform(new CenterCrop(this.width, this.height));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ArgumentsUtil.booleanValue(arguments, "centerFit")) {
|
|
||||||
this.addTransform(new CenterFit(this.width, this.height));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ArgumentsUtil.booleanValue(arguments, "toTensor", true)) {
|
|
||||||
this.addTransform(new ToTensor());
|
|
||||||
}
|
|
||||||
|
|
||||||
String normalize = ArgumentsUtil.stringValue(arguments, "normalize", "false");
|
|
||||||
if ("true".equals(normalize)) {
|
|
||||||
float[] MEAN = new float[]{0.485F, 0.456F, 0.406F};
|
|
||||||
float[] STD = new float[]{0.229F, 0.224F, 0.225F};
|
|
||||||
this.addTransform(new Normalize(MEAN, STD));
|
|
||||||
} else if (!"false".equals(normalize)) {
|
|
||||||
String[] tokens = normalize.split("\\s*,\\s*");
|
|
||||||
if (tokens.length != 6) {
|
|
||||||
throw new IllegalArgumentException("Invalid normalize value: " + normalize);
|
|
||||||
}
|
|
||||||
|
|
||||||
float[] mean = new float[]{Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2])};
|
|
||||||
float[] std = new float[]{Float.parseFloat(tokens[3]), Float.parseFloat(tokens[4]), Float.parseFloat(tokens[5])};
|
|
||||||
this.addTransform(new Normalize(mean, std));
|
|
||||||
}
|
|
||||||
|
|
||||||
String range = (String)arguments.get("range");
|
|
||||||
if ("0,1".equals(range)) {
|
|
||||||
this.addTransform((a) -> {
|
|
||||||
return a.div(255.0F);
|
|
||||||
});
|
|
||||||
} else if ("-1,1".equals(range)) {
|
|
||||||
this.addTransform((a) -> {
|
|
||||||
return a.div(128.0F).sub(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.containsKey("batchifier")) {
|
|
||||||
this.batchifier = Batchifier.fromString((String)arguments.get("batchifier"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static enum YoloOutputType {
|
|
||||||
BOX,
|
|
||||||
DETECT,
|
|
||||||
AUTO;
|
|
||||||
|
|
||||||
private YoloOutputType() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -182,7 +182,12 @@ public class FaceUtils {
|
|||||||
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
|
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
|
||||||
graphics.setColor(Color.RED);// 边框颜色
|
graphics.setColor(Color.RED);// 边框颜色
|
||||||
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
|
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
|
||||||
drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
|
String className = "face";
|
||||||
|
if (detectionInfo.getScore() > 0){
|
||||||
|
int percent = (int) Math.round(detectionInfo.getScore() * 100);
|
||||||
|
className = "face " + percent + "%";
|
||||||
|
}
|
||||||
|
drawText(graphics, className , rectangle.getX(), rectangle.getY(), stroke, 4);
|
||||||
//绘制人脸关键点
|
//绘制人脸关键点
|
||||||
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
|
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
|
||||||
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
|
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
|
||||||
|
|||||||
@@ -36,8 +36,10 @@ public class Test {
|
|||||||
*/
|
*/
|
||||||
public static FaceDetModel getFaceDetModel(){
|
public static FaceDetModel getFaceDetModel(){
|
||||||
FaceDetConfig config = new FaceDetConfig();
|
FaceDetConfig config = new FaceDetConfig();
|
||||||
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
|
// config.setModelEnum(FaceDetModelEnum.YOLOV8_FACE);//人脸检测模型
|
||||||
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
|
config.setModelPath("/Users/wenjie/Documents/develop/model/yolo-face/yolov8s-face-lindevs.onnx");
|
||||||
|
// config.setModelPath("/Users/wenjie/Documents/develop/face_model");
|
||||||
|
config.setConfidenceThreshold(0.2);//只返回相似度大于该值的人脸
|
||||||
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
|
||||||
return FaceDetModelFactory.getInstance().getModel(config);
|
return FaceDetModelFactory.getInstance().getModel(config);
|
||||||
}
|
}
|
||||||
@@ -81,6 +83,11 @@ public class Test {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
FaceDetModel faceDetModel = getFaceDetModel();
|
||||||
|
R<Void> result = faceDetModel.detectAndDraw("/Users/wenjie/Downloads/facetest/00974.png", "/Users/wenjie/Downloads/xx333.png");
|
||||||
|
log.info("result:{}", result.isSuccess() + " msg:" + result.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user