mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-12 20:58:51 +00:00
1、FaceNet 特征提取新增人脸对齐
2、人脸检测新5点人脸关键点定位 3、特征提取接口支持多人脸和最佳人脸提取 4、修复人脸框边界精度问题 5、更新 Maven 发布的 groupId
This commit is contained in:
@@ -4,13 +4,13 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.10</version>
|
||||
<version>1.0.11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.10</version>
|
||||
<version>1.0.11</version>
|
||||
<name>smartjavaai-face</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
@@ -47,6 +47,7 @@
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.36.0.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.smartjavaai.face.entity.FaceResult;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
@@ -46,16 +47,6 @@ public abstract class AbstractFaceModel implements FaceModel {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float calculSimilar(float[] feature1, float[] feature2) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
@@ -111,15 +102,6 @@ public abstract class AbstractFaceModel implements FaceModel {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(BufferedImage sourceImage) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(byte[] imageData) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float featureComparison(BufferedImage sourceImage1, BufferedImage sourceImag2) {
|
||||
@@ -140,4 +122,64 @@ public abstract class AbstractFaceModel implements FaceModel {
|
||||
public FaceResult search(byte[] imageData) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(String imagePath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(byte[] imageData) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(BufferedImage image) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(BufferedImage image, FaceExtractConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(String imagePath, FaceExtractConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(byte[] imageData, FaceExtractConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(BufferedImage image) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(String imagePath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(byte[] imageData) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(BufferedImage image, FaceExtractConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(String imagePath, FaceExtractConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(byte[] imageData, FaceExtractConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人脸特征提取配置
|
||||
* @author dwj
|
||||
* @date 2025/4/24
|
||||
*/
|
||||
@Data
|
||||
public class FaceExtractConfig {
|
||||
|
||||
/**
|
||||
* 是否裁剪人脸
|
||||
*/
|
||||
private boolean cropFace = true;
|
||||
|
||||
/**
|
||||
* 是否对齐人脸
|
||||
*/
|
||||
private boolean align = true;
|
||||
|
||||
/**
|
||||
* 人脸检测模型配置
|
||||
*/
|
||||
private FaceModelConfig detectModelConfig;
|
||||
|
||||
public FaceExtractConfig() {
|
||||
}
|
||||
|
||||
public FaceExtractConfig(boolean cropFace, boolean align, FaceModelConfig detectModelConfig) {
|
||||
this.cropFace = cropFace;
|
||||
this.align = align;
|
||||
this.detectModelConfig = detectModelConfig;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import cn.smartjavaai.face.entity.FaceResult;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
@@ -61,34 +62,6 @@ public interface FaceModel {
|
||||
*/
|
||||
BufferedImage detectAndDraw(BufferedImage sourceImage);
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
float[] featureExtraction(String imagePath);
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param inputStream 输入流
|
||||
* @return
|
||||
*/
|
||||
float[] featureExtraction(InputStream inputStream);
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param sourceImage BufferedImage图片数据
|
||||
* @return
|
||||
*/
|
||||
float[] featureExtraction(BufferedImage sourceImage);
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param imageData 图片字节流
|
||||
* @return
|
||||
*/
|
||||
float[] featureExtraction(byte[] imageData);
|
||||
|
||||
/**
|
||||
* 计算相似度
|
||||
* @param feature1 图1特征
|
||||
@@ -203,4 +176,94 @@ public interface FaceModel {
|
||||
*/
|
||||
long clearFace();
|
||||
|
||||
/**
|
||||
* 特征提取(使用默认配置)
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
List<float[]> extractFeatures(String imagePath);
|
||||
|
||||
/**
|
||||
* 特征提取(使用默认配置)
|
||||
* @param imageData 图片字节流
|
||||
* @return
|
||||
*/
|
||||
List<float[]> extractFeatures(byte[] imageData);
|
||||
|
||||
/**
|
||||
* 特征提取(使用默认配置)
|
||||
* @param image BufferedImage
|
||||
* @return
|
||||
*/
|
||||
List<float[]> extractFeatures(BufferedImage image);
|
||||
|
||||
/**
|
||||
* 特征提取(使用自定义配置)
|
||||
* 强制裁剪操作
|
||||
* @param image BufferedImage
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
List<float[]> extractFeatures(BufferedImage image, FaceExtractConfig config);
|
||||
|
||||
/**
|
||||
* 特征提取(使用自定义配置)
|
||||
* @param imagePath 图片路径
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
List<float[]> extractFeatures(String imagePath, FaceExtractConfig config);
|
||||
|
||||
/**
|
||||
* 特征提取(使用自定义配置)
|
||||
* @param imageData 图片字节流
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
List<float[]> extractFeatures(byte[] imageData, FaceExtractConfig config);
|
||||
|
||||
/**
|
||||
* 提取分数最高人脸特征(使用默认配置)
|
||||
* @param image BufferedImage
|
||||
* @return
|
||||
*/
|
||||
float[] extractTopFaceFeature(BufferedImage image);
|
||||
|
||||
/**
|
||||
* 提取分数最高人脸特征(使用默认配置)
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
float[] extractTopFaceFeature(String imagePath);
|
||||
|
||||
/**
|
||||
* 提取分数最高人脸特征(使用默认配置)
|
||||
* @param imageData 图片字节流
|
||||
* @return
|
||||
*/
|
||||
float[] extractTopFaceFeature(byte[] imageData);
|
||||
|
||||
/**
|
||||
* 提取分数最高人脸特征(使用自定义配置)
|
||||
* @param image BufferedImage
|
||||
* @return
|
||||
*/
|
||||
float[] extractTopFaceFeature(BufferedImage image, FaceExtractConfig config);
|
||||
|
||||
/**
|
||||
* 提取分数最高人脸特征(使用自定义配置)
|
||||
* @param imagePath 图片路径
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
float[] extractTopFaceFeature(String imagePath, FaceExtractConfig config);
|
||||
|
||||
/**
|
||||
* 提取分数最高人脸特征(使用自定义配置)
|
||||
* @param imageData 图片字节流
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
float[] extractTopFaceFeature(byte[] imageData, FaceExtractConfig config);
|
||||
|
||||
}
|
||||
|
||||
@@ -50,6 +50,15 @@ public class FaceModelConfig {
|
||||
*/
|
||||
private int gpuId = 0;
|
||||
|
||||
public FaceModelConfig() {
|
||||
}
|
||||
|
||||
public FaceModelConfig(FaceModelEnum modelEnum) {
|
||||
this.modelEnum = modelEnum;
|
||||
}
|
||||
|
||||
public FaceModelConfig(FaceModelEnum modelEnum, String modelPath) {
|
||||
this.modelEnum = modelEnum;
|
||||
this.modelPath = modelPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package cn.smartjavaai.face.factory;
|
||||
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import cn.smartjavaai.face.model.SeetaFace6Model;
|
||||
import com.seetaface.SeetaFace6JNI;
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
|
||||
/**
|
||||
* Predictor 工厂类
|
||||
* @author dwj
|
||||
* @date 2025/4/8
|
||||
*/
|
||||
public class SeetaFace6Factory extends BasePooledObjectFactory<SeetaFace6JNI> {
|
||||
|
||||
@Override
|
||||
public SeetaFace6JNI create() {
|
||||
return new SeetaFace6JNI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<SeetaFace6JNI> wrap(SeetaFace6JNI obj) {
|
||||
return new DefaultPooledObject<>(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyObject(PooledObject<SeetaFace6JNI> p) {
|
||||
//p.getObject().dispose(); // 如果需要释放 native 资源
|
||||
SeetaFace6JNI object = p.getObject();
|
||||
object = null;
|
||||
}
|
||||
}
|
||||
@@ -5,31 +5,41 @@ import ai.djl.MalformedModelException;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.ImageFactory;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.opencv.OpenCVImageFactory;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
import cn.smartjavaai.common.entity.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||
import cn.smartjavaai.common.utils.FileUtils;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.AbstractFaceModel;
|
||||
import cn.smartjavaai.face.FaceModelConfig;
|
||||
import cn.smartjavaai.face.*;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.translator.FaceFeatureTranslator;
|
||||
import cn.smartjavaai.face.utils.FaceAlignUtils;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import cn.smartjavaai.face.utils.OpenCVUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -117,65 +127,6 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
Image img = null;
|
||||
try {
|
||||
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片", e);
|
||||
}
|
||||
return featureExtraction(img);
|
||||
}
|
||||
|
||||
/**
|
||||
* 特征提取
|
||||
* @param inputStream 输入流
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) {
|
||||
if(Objects.isNull(inputStream)){
|
||||
throw new FaceException("图像输入流无效");
|
||||
}
|
||||
Image img = null;
|
||||
try {
|
||||
img = ImageFactory.getInstance().fromInputStream(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片输入流", e);
|
||||
}
|
||||
return featureExtraction(img);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(BufferedImage sourceImage) {
|
||||
if(!ImageUtils.isImageValid(sourceImage)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
Image img = ImageFactory.getInstance().fromImage(sourceImage);
|
||||
return featureExtraction(img);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(byte[] imageData) {
|
||||
if(Objects.isNull(imageData)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
try {
|
||||
return featureExtraction(ImageIO.read(new ByteArrayInputStream(imageData)));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片字节流", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算相似度
|
||||
* @param feature1 图1特征
|
||||
@@ -207,34 +158,19 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo
|
||||
if(!FileUtils.isFileExists(imagePath1) || !FileUtils.isFileExists(imagePath2)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
float[] feature1 = featureExtraction(imagePath1);
|
||||
float[] feature2 = featureExtraction(imagePath2);
|
||||
float[] feature1 = extractTopFaceFeature(imagePath1);
|
||||
float[] feature2 = extractTopFaceFeature(imagePath2);
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 特征比较
|
||||
* @param inputStream1 图1输入流
|
||||
* @param inputStream2 图2输入流
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) {
|
||||
if(Objects.isNull(inputStream1) || Objects.isNull(inputStream2)){
|
||||
throw new FaceException("图像输入流无效");
|
||||
}
|
||||
float[] feature1 = featureExtraction(inputStream1);
|
||||
float[] feature2 = featureExtraction(inputStream2);
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float featureComparison(BufferedImage sourceImage1, BufferedImage sourceImag2) {
|
||||
if(!ImageUtils.isImageValid(sourceImage1) || !ImageUtils.isImageValid(sourceImag2)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
float[] feature1 = featureExtraction(sourceImage1);
|
||||
float[] feature2 = featureExtraction(sourceImag2);
|
||||
float[] feature1 = extractTopFaceFeature(sourceImage1);
|
||||
float[] feature2 = extractTopFaceFeature(sourceImag2);
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
|
||||
@@ -243,11 +179,203 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo
|
||||
if(Objects.isNull(imageData1) || Objects.isNull(imageData2)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
float[] feature1 = featureExtraction(imageData1);
|
||||
float[] feature2 = featureExtraction(imageData2);
|
||||
float[] feature1 = extractTopFaceFeature(imageData1);
|
||||
float[] feature2 = extractTopFaceFeature(imageData2);
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认特征提取配置
|
||||
* @return
|
||||
*/
|
||||
private FaceExtractConfig getDefaultConfig() {
|
||||
FaceExtractConfig config = new FaceExtractConfig();
|
||||
FaceModelConfig detectModelConfig = new FaceModelConfig();
|
||||
detectModelConfig.setModelEnum(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE);
|
||||
config.setDetectModelConfig(detectModelConfig);
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(String imagePath) {
|
||||
return extractFeatures(imagePath, getDefaultConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(byte[] imageData) {
|
||||
return extractFeatures(imageData, getDefaultConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(BufferedImage image) {
|
||||
return extractFeatures(image, getDefaultConfig());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(BufferedImage image, FaceExtractConfig config) {
|
||||
if(Objects.isNull(config)){
|
||||
throw new FaceException("config为null");
|
||||
}
|
||||
List<float[]> featureList = new ArrayList<float[]>();
|
||||
if(Objects.isNull(config.getDetectModelConfig())){
|
||||
throw new FaceException("config.detectModelConfig为null");
|
||||
}
|
||||
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config.getDetectModelConfig());
|
||||
DetectionResponse detectedResult = faceModel.detect(image);
|
||||
if(Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getRectangleList()) || detectedResult.getRectangleList().isEmpty()){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
|
||||
NDManager manager = NDManager.newBaseManager();
|
||||
for (DetectionRectangle rectangle : detectedResult.getRectangleList()){
|
||||
float[] features = null;
|
||||
//裁剪人脸
|
||||
Image subImage = djlImage.getSubImage(rectangle.getX(), rectangle.getY() , rectangle.getWidth() , rectangle.getHeight());
|
||||
//人脸对齐
|
||||
if(config.isAlign()){
|
||||
//获取子图中人脸关键点坐标
|
||||
double[][] pointsArray = FaceUtils.facePoints(rectangle.getKeyPoints());
|
||||
NDArray srcPoints = manager.create(pointsArray);
|
||||
NDArray dstPoints = FaceUtils.faceTemplate512x512(manager);
|
||||
// 5点仿射变换
|
||||
Mat affine_matrix = OpenCVUtils.toOpenCVMat(manager, srcPoints, dstPoints);
|
||||
/*Mat sourceMat = OpenCVUtils.image2Mat(image);
|
||||
Mat mat = FaceAlignUtils.warpAffine(sourceMat, affine_matrix);
|
||||
//OpenCVUtils.mat2Image(mat);
|
||||
Image alignedImg = ImageFactory.getInstance().fromImage(mat);
|
||||
features = featureExtraction(alignedImg);*/
|
||||
Mat mat = FaceAlignUtils.warpAffine((Mat) djlImage.getWrappedImage(), affine_matrix);
|
||||
Image alignedImg = OpenCVImageFactory.getInstance().fromImage(mat);
|
||||
features = featureExtraction(alignedImg);
|
||||
}else{
|
||||
//不对齐人脸
|
||||
features = featureExtraction(subImage);
|
||||
}
|
||||
if(Objects.nonNull(features)){
|
||||
featureList.add(features);
|
||||
}
|
||||
}
|
||||
return featureList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(String imagePath, FaceExtractConfig config) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
// 将图片路径转换为 BufferedImage
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片路径", e);
|
||||
}
|
||||
return extractFeatures(image, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(byte[] imageData, FaceExtractConfig config) {
|
||||
if(Objects.isNull(imageData)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
try {
|
||||
return extractFeatures(ImageIO.read(new ByteArrayInputStream(imageData)), config);
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("错误的图像", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(BufferedImage image) {
|
||||
return extractTopFaceFeature(image, getDefaultConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(String imagePath) {
|
||||
return extractTopFaceFeature(imagePath, getDefaultConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(byte[] imageData) {
|
||||
return extractTopFaceFeature(imageData, getDefaultConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(BufferedImage image, FaceExtractConfig config) {
|
||||
if(Objects.isNull(config)){
|
||||
throw new FaceException("config为null");
|
||||
}
|
||||
if(Objects.isNull(config.getDetectModelConfig())){
|
||||
throw new FaceException("config.detectModelConfig为null");
|
||||
}
|
||||
Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
|
||||
float[] features = null;
|
||||
if(config.isCropFace()){
|
||||
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config.getDetectModelConfig());
|
||||
DetectionResponse detectedResult = faceModel.detect(image);
|
||||
if(Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getRectangleList()) || detectedResult.getRectangleList().isEmpty()){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//只取第一个人脸
|
||||
DetectionRectangle rectangle = detectedResult.getRectangleList().get(0);
|
||||
//裁剪人脸
|
||||
Image subImage = djlImage.getSubImage(rectangle.getX(), rectangle.getY() , rectangle.getWidth() , rectangle.getHeight());
|
||||
//人脸对齐
|
||||
if(config.isAlign()){
|
||||
NDManager manager = NDManager.newBaseManager();
|
||||
//获取子图中人脸关键点坐标
|
||||
double[][] pointsArray = FaceUtils.facePoints(rectangle.getKeyPoints());
|
||||
NDArray srcPoints = manager.create(pointsArray);
|
||||
NDArray dstPoints = FaceUtils.faceTemplate512x512(manager);
|
||||
// 5点仿射变换
|
||||
Mat affine_matrix = OpenCVUtils.toOpenCVMat(manager, srcPoints, dstPoints);
|
||||
/*Mat sourceMat = OpenCVUtils.image2Mat(image);
|
||||
Mat mat = FaceAlignUtils.warpAffine(sourceMat, affine_matrix);
|
||||
OpenCVUtils.mat2Image(mat);
|
||||
Image alignedImg = ImageFactory.getInstance().fromImage(OpenCVUtils.mat2Image(mat));
|
||||
features = featureExtraction(alignedImg);*/
|
||||
Mat mat = FaceAlignUtils.warpAffine((Mat) djlImage.getWrappedImage(), affine_matrix);
|
||||
Image alignedImg = OpenCVImageFactory.getInstance().fromImage(mat);
|
||||
features = featureExtraction(alignedImg);
|
||||
}else{
|
||||
//不对齐人脸
|
||||
features = featureExtraction(subImage);
|
||||
}
|
||||
}else{
|
||||
//不裁剪人脸直接提取特征
|
||||
features = featureExtraction(djlImage);
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(String imagePath, FaceExtractConfig config) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
// 将图片路径转换为 BufferedImage
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片路径", e);
|
||||
}
|
||||
return extractTopFaceFeature(image, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(byte[] imageData, FaceExtractConfig config) {
|
||||
if(Objects.isNull(imageData)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
try {
|
||||
return extractTopFaceFeature(ImageIO.read(new ByteArrayInputStream(imageData)), config);
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("错误的图像", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (predictorPool != null) {
|
||||
@@ -255,4 +383,6 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import cn.smartjavaai.face.*;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.translator.FaceDetectionTranslator;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import cn.smartjavaai.face.utils.OpenCVUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
@@ -144,7 +145,7 @@ public class RetinaFaceModel extends AbstractFaceModel implements AutoCloseable{
|
||||
if(!ImageUtils.isImageValid(image)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
Image img = ImageFactory.getInstance().fromImage(image);
|
||||
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
|
||||
DetectedObjects detection = detect(img);
|
||||
return FaceUtils.convertToDetectionResponse(detection,img);
|
||||
}
|
||||
@@ -186,7 +187,7 @@ public class RetinaFaceModel extends AbstractFaceModel implements AutoCloseable{
|
||||
if(!ImageUtils.isImageValid(sourceImage)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
Image img = ImageFactory.getInstance().fromImage(sourceImage);
|
||||
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
|
||||
DetectedObjects detectedObjects = detect(img);
|
||||
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
|
||||
throw new FaceException("未识别到人脸");
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
package cn.smartjavaai.face.model;
|
||||
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||
import cn.smartjavaai.common.utils.FileUtils;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.AbstractFaceModel;
|
||||
import cn.smartjavaai.face.FaceExtractConfig;
|
||||
import cn.smartjavaai.face.FaceModelConfig;
|
||||
import cn.smartjavaai.face.dao.FaceDao;
|
||||
import cn.smartjavaai.face.entity.FaceData;
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.utils.FaceAlignUtils;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import com.seeta.pool.*;
|
||||
import com.seeta.sdk.*;
|
||||
import com.seetaface.NativeLoader;
|
||||
import com.seetaface.SeetaFace6JNI;
|
||||
import cn.smartjavaai.face.seetaface.NativeLoader;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -31,6 +25,7 @@ import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -154,10 +149,19 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
FaceDetector predictor = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
try {
|
||||
predictor = faceDetectorPool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
SeetaRect[] seetaResult = predictor.Detect(imageData);
|
||||
return FaceUtils.convertToDetectionResponse(seetaResult, config);
|
||||
List<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
|
||||
for(SeetaRect seetaRect : seetaResult){
|
||||
//提取人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaRect, pointFS);
|
||||
seetaPointFSList.add(pointFS);
|
||||
}
|
||||
return FaceUtils.convertToDetectionResponse(seetaResult, config, seetaPointFSList);
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
@@ -168,6 +172,13 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,104 +306,6 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(BufferedImage image) {
|
||||
if(!ImageUtils.isImageValid(image)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
FaceDetector faceDetector = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
try {
|
||||
faceDetector = faceDetectorPool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//提取第一个人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaResult[0], pointFS);
|
||||
|
||||
//提取特征
|
||||
float[] features = new float[faceRecognizer.GetExtractFeatureSize()];
|
||||
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
|
||||
if(!isSuccess){
|
||||
throw new FaceException("人脸特征提取失败");
|
||||
}
|
||||
return features;
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
if (faceDetector != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(faceDetector); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片路径", e);
|
||||
}
|
||||
return featureExtraction(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) {
|
||||
if(Objects.isNull(inputStream)){
|
||||
throw new FaceException("图像输入流无效");
|
||||
}
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片输入流", e);
|
||||
}
|
||||
return featureExtraction(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(byte[] imageData) {
|
||||
if(Objects.isNull(imageData)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
try {
|
||||
return featureExtraction(ImageIO.read(new ByteArrayInputStream(imageData)));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("错误的图像", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float calculSimilar(float[] feature1, float[] feature2) {
|
||||
@@ -897,4 +810,185 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
pageNo++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(String imagePath) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
// 将图片路径转换为 BufferedImage
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片路径", e);
|
||||
}
|
||||
return extractFeatures(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(byte[] imageData) {
|
||||
if(Objects.isNull(imageData)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
try {
|
||||
return extractFeatures(ImageIO.read(new ByteArrayInputStream(imageData)));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("错误的图像", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<float[]> extractFeatures(BufferedImage image) {
|
||||
if(!ImageUtils.isImageValid(image)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
List<float[]> featureList = new ArrayList<float[]>();
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
FaceDetector faceDetector = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
try {
|
||||
faceDetector = faceDetectorPool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
for(SeetaRect seetaRect : seetaResult){
|
||||
//提取人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaRect, pointFS);
|
||||
//提取特征
|
||||
float[] features = new float[faceRecognizer.GetExtractFeatureSize()];
|
||||
//CropFaceV2 + ExtractCroppedFace 已包含裁剪+人脸对齐
|
||||
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
|
||||
if(!isSuccess){
|
||||
throw new FaceException("人脸特征提取失败");
|
||||
}
|
||||
featureList.add(features);
|
||||
}
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
if (faceDetector != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(faceDetector); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return featureList;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(BufferedImage image) {
|
||||
if(!ImageUtils.isImageValid(image)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
float[] features = null;
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
FaceDetector faceDetector = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
try {
|
||||
faceDetector = faceDetectorPool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//提取人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaResult[0], pointFS);
|
||||
//提取特征
|
||||
features = new float[faceRecognizer.GetExtractFeatureSize()];
|
||||
//CropFaceV2 + ExtractCroppedFace 已包含裁剪+人脸对齐
|
||||
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
|
||||
if(!isSuccess){
|
||||
throw new FaceException("人脸特征提取失败");
|
||||
}
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
if (faceDetector != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(faceDetector); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(String imagePath) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new FaceException("图像文件不存在");
|
||||
}
|
||||
// 将图片路径转换为 BufferedImage
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("无效图片路径", e);
|
||||
}
|
||||
return extractTopFaceFeature(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] extractTopFaceFeature(byte[] imageData) {
|
||||
if(Objects.isNull(imageData)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
try {
|
||||
return extractTopFaceFeature(ImageIO.read(new ByteArrayInputStream(imageData)));
|
||||
} catch (IOException e) {
|
||||
throw new FaceException("错误的图像", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import cn.smartjavaai.face.*;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.translator.FaceDetectionTranslator;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import cn.smartjavaai.face.utils.OpenCVUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
@@ -133,7 +134,7 @@ public class UltraLightFastGenericFaceModel extends AbstractFaceModel implements
|
||||
|
||||
@Override
|
||||
public DetectionResponse detect(BufferedImage image) {
|
||||
Image img = ImageFactory.getInstance().fromImage(image);
|
||||
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
|
||||
DetectedObjects detection = detect(img);
|
||||
return FaceUtils.convertToDetectionResponse(detection,img);
|
||||
}
|
||||
@@ -175,7 +176,7 @@ public class UltraLightFastGenericFaceModel extends AbstractFaceModel implements
|
||||
if(!ImageUtils.isImageValid(sourceImage)){
|
||||
throw new FaceException("图像无效");
|
||||
}
|
||||
Image img = ImageFactory.getInstance().fromImage(sourceImage);
|
||||
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
|
||||
DetectedObjects detectedObjects = detect(img);
|
||||
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
|
||||
throw new FaceException("未识别到人脸");
|
||||
@@ -222,6 +223,9 @@ public class UltraLightFastGenericFaceModel extends AbstractFaceModel implements
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (predictorPool != null) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.seetaface;
|
||||
package cn.smartjavaai.face.seetaface;
|
||||
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
@@ -9,18 +9,11 @@ import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.face.FaceModelConfig;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import com.seeta.sdk.SeetaDevice;
|
||||
import com.seeta.sdk.util.DllItem;
|
||||
import com.seeta.sdk.util.LoadNativeCore;
|
||||
import jdk.dynalink.linker.support.Lookup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -37,18 +30,9 @@ public class NativeLoader {
|
||||
|
||||
|
||||
private static Path seetaface6NativePath;
|
||||
private static final String[] WIN_LIBS = {"tennis.dll","tennis_haswell.dll","tennis_pentium.dll","tennis_sandy_bridge.dll","SeetaAuthorize.dll","SeetaFaceAntiSpoofingX600.dll","SeetaFaceDetector600.dll","SeetaFaceLandmarker600.dll","SeetaFaceRecognizer610.dll","SeetaFace6JNI.dll"};
|
||||
//private static final String[] WIN_LIBS = {"tennis","tennis_haswell","tennis_pentium","tennis_sandy_bridge","SeetaAuthorize","SeetaFaceAntiSpoofingX600","SeetaFaceDetector600","SeetaFaceLandmarker600","SeetaFaceRecognizer610","SeetaFace6JNI"};
|
||||
private static final String[] LINUX_CENTOS_LIBS = {"libSeetaAuthorize.so","libtennis.so","libtennis_haswell.so","libtennis_pentium.so","libtennis_sandy_bridge.so","libSeetaFaceDetector600.so","libSeetaAgePredictor600.so","libSeetaEyeStateDetector200.so","libSeetaFaceAntiSpoofingX600.so","libSeetaFaceLandmarker600.so","libSeetaFaceRecognizer610.so","libSeetaGenderPredictor600.so","libSeetaMaskDetector200.so","libSeetaPoseEstimation600.so","libSeetaFaceTracking600.so","libSeetaQualityAssessor300.so"};
|
||||
private static final String[] LINUX_UBUNTU_LIBS = {"libSeetaAuthorize.so","libtennis.so","libtennis_haswell.so","libtennis_pentium.so","libtennis_sandy_bridge.so","libSeetaFaceDetector600.so","libSeetaAgePredictor600.so","libSeetaEyeStateDetector200.so","libSeetaFaceAntiSpoofingX600.so","libSeetaFaceLandmarker600.so","libSeetaFaceRecognizer610.so","libSeetaGenderPredictor600.so","libSeetaMaskDetector200.so","libSeetaPoseEstimation600.so","libSeetaFaceTracking600.so","libSeetaQualityAssessor300.so"};
|
||||
|
||||
private static final String SEETAFACE_LIB_DIR = "seetaface6";
|
||||
|
||||
public static SeetaFace6JNI seetaFace6SDK;
|
||||
|
||||
public static final String AMD64 = "amd64";
|
||||
|
||||
public static final String x86_64 = "amd64";
|
||||
|
||||
/**
|
||||
* 定义dll 路径和加载顺序的文件
|
||||
@@ -204,7 +188,7 @@ public class NativeLoader {
|
||||
*/
|
||||
private static File extractLibrary(String libPath) throws IOException {
|
||||
String resourcePath = libPath;
|
||||
try (InputStream in = com.seetaface.NativeLoader.class.getResourceAsStream(resourcePath)) {
|
||||
try (InputStream in = NativeLoader.class.getResourceAsStream(resourcePath)) {
|
||||
if (in == null) throw new FileNotFoundException(resourcePath);
|
||||
Path path = Paths.get(resourcePath);
|
||||
String fileName = path.getFileName().toString();
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.smartjavaai.face.utils;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.opencv.OpenCVImageFactory;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import com.seeta.sdk.SeetaImageData;
|
||||
import com.seeta.sdk.SeetaPointF;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 人脸对齐
|
||||
* @author Calvin
|
||||
*/
|
||||
public class FaceAlignUtils {
|
||||
/**
|
||||
* 根据目标点,进行旋转仿射变换
|
||||
* Perform rotation and affine transformation based on the target 5 points
|
||||
*
|
||||
* @param src
|
||||
* @param rot_mat
|
||||
* @return
|
||||
*/
|
||||
public static Mat warpAffine(Mat src, Mat rot_mat) {
|
||||
Mat rot = new Mat();
|
||||
// 进行仿射变换,变换后大小为src的大小
|
||||
// Perform affine transformation, the size after transformation is the same as the size of src
|
||||
Scalar scalar = new Scalar(135, 133, 132);
|
||||
Size size = new Size(512, 512);
|
||||
Imgproc.warpAffine(src, rot, rot_mat, size, 0, 0, scalar);
|
||||
return rot;
|
||||
}
|
||||
public static Mat warpAffine(Mat src, Mat rot_mat, int width, int height) {
|
||||
Mat rot = new Mat();
|
||||
Size size = new Size(width, height);
|
||||
Imgproc.warpAffine(src, rot, rot_mat, size);
|
||||
return rot;
|
||||
}
|
||||
|
||||
|
||||
public static Mat warpAffine(Mat src, Mat rot_mat, int width, int height, int flags) {
|
||||
Mat rot = new Mat();
|
||||
Size size = new Size(width, height);
|
||||
Imgproc.warpAffine(src, rot, rot_mat, size, flags);
|
||||
return rot;
|
||||
}
|
||||
|
||||
public static SeetaImageData faceAlign(BufferedImage sourceImage, SeetaPointF[] pointFS) {
|
||||
NDManager manager = NDManager.newBaseManager();
|
||||
//获取子图中人脸关键点坐标
|
||||
double[][] pointsArray = FaceUtils.facePoints(pointFS);
|
||||
NDArray srcPoints = manager.create(pointsArray);
|
||||
NDArray dstPoints = FaceUtils.faceTemplate512x512(manager);
|
||||
// 5点仿射变换
|
||||
Mat affine_matrix = OpenCVUtils.toOpenCVMat(manager, srcPoints, dstPoints);
|
||||
Mat mat = FaceAlignUtils.warpAffine(OpenCVUtils.image2Mat(sourceImage), affine_matrix);
|
||||
BufferedImage alignImage = OpenCVUtils.mat2Image(mat);
|
||||
SeetaImageData imageData = new SeetaImageData(alignImage.getWidth(), alignImage.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(alignImage);
|
||||
return imageData;
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,16 @@ package cn.smartjavaai.face.utils;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.output.BoundingBox;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.Point;
|
||||
import cn.smartjavaai.common.entity.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.Point;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.FaceModelConfig;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import com.seeta.sdk.SeetaImageData;
|
||||
import com.seeta.sdk.SeetaPointF;
|
||||
import com.seeta.sdk.SeetaRect;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -16,10 +20,8 @@ import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -49,11 +51,21 @@ public class FaceUtils {
|
||||
while(iterator.hasNext()) {
|
||||
DetectedObjects.DetectedObject result = (DetectedObjects.DetectedObject)iterator.next();
|
||||
BoundingBox box = result.getBoundingBox();
|
||||
int x = (int)(box.getBounds().getX() * (double)img.getWidth());
|
||||
int y = (int)(box.getBounds().getY() * (double)img.getHeight());
|
||||
int width = (int)(box.getBounds().getWidth() * (double)img.getWidth());
|
||||
int height = (int)(box.getBounds().getHeight() * (double)img.getHeight());
|
||||
List<Point> keyPoints = new ArrayList<Point>();
|
||||
box.getBounds().getPath().forEach(point -> {
|
||||
keyPoints.add(new Point(point.getX(), point.getY()));
|
||||
});
|
||||
int x = (int)(box.getBounds().getX() * img.getWidth());
|
||||
int y = (int)(box.getBounds().getY() * img.getHeight());
|
||||
int width = (int)(box.getBounds().getWidth() * img.getWidth());
|
||||
int height = (int)(box.getBounds().getHeight() * img.getHeight());
|
||||
// 修正边界,防止越界
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x + width > img.getWidth()) width = img.getWidth() - x;
|
||||
if (y + height > img.getHeight()) height = img.getHeight() - y;
|
||||
DetectionRectangle rectangle = new DetectionRectangle(x, y, width, height, detection.getProbabilities().get(index).floatValue());
|
||||
rectangle.setKeyPoints(keyPoints);
|
||||
rectangleList.add(rectangle);
|
||||
index++;
|
||||
}
|
||||
@@ -66,18 +78,24 @@ public class FaceUtils {
|
||||
* @param seetaResult
|
||||
* @return
|
||||
*/
|
||||
public static DetectionResponse convertToDetectionResponse(SeetaRect[] seetaResult, FaceModelConfig config){
|
||||
public static DetectionResponse convertToDetectionResponse(SeetaRect[] seetaResult, FaceModelConfig config,List<SeetaPointF[]> seetaPointFSList){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
return null;
|
||||
}
|
||||
DetectionResponse detectionResponse = new DetectionResponse();
|
||||
List<DetectionRectangle> rectangleList = new ArrayList<DetectionRectangle>();
|
||||
for(SeetaRect rect : seetaResult){
|
||||
for(int i = 0; i < seetaResult.length; i++){
|
||||
SeetaRect rect = seetaResult[i];
|
||||
SeetaPointF[] seetaPointFS = seetaPointFSList.get(i);
|
||||
//过滤置信度
|
||||
/*if(config.getConfidenceThreshold() > 0){
|
||||
continue;
|
||||
}*/
|
||||
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height, 0);
|
||||
List<Point> keyPoints = Arrays.stream(seetaPointFS)
|
||||
.map(p -> new Point(p.x, p.y))
|
||||
.collect(Collectors.toList());
|
||||
rectangle.setKeyPoints(keyPoints);
|
||||
rectangleList.add(rectangle);
|
||||
}
|
||||
detectionResponse.setRectangleList(rectangleList);
|
||||
@@ -108,6 +126,10 @@ public class FaceUtils {
|
||||
graphics.setColor(Color.RED);// 边框颜色
|
||||
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
|
||||
drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
|
||||
//绘制人脸关键点
|
||||
if(rectangle.getKeyPoints() != null){
|
||||
drawLandmarks(graphics, rectangle.getKeyPoints());
|
||||
}
|
||||
}
|
||||
graphics.dispose();
|
||||
ImageIO.write(sourceImage, "jpg", new File(savePath));
|
||||
@@ -136,6 +158,10 @@ public class FaceUtils {
|
||||
graphics.setColor(Color.RED);// 边框颜色
|
||||
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
|
||||
drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
|
||||
//绘制人脸关键点
|
||||
if(rectangle.getKeyPoints() != null){
|
||||
drawLandmarks(graphics, rectangle.getKeyPoints());
|
||||
}
|
||||
}
|
||||
graphics.dispose();
|
||||
return sourceImage;
|
||||
@@ -163,5 +189,124 @@ public class FaceUtils {
|
||||
g.drawString(text, x + padding, y + ascent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修正检测框
|
||||
* @param rectangle
|
||||
* @param imageWidth
|
||||
* @param imageHeight
|
||||
* @return
|
||||
*/
|
||||
public static DetectionRectangle correctRect(DetectionRectangle rectangle, int imageWidth, int imageHeight) {
|
||||
int x = rectangle.getX();
|
||||
int y = rectangle.getY();
|
||||
int width = rectangle.getWidth();
|
||||
int height = rectangle.getHeight();
|
||||
// 修正x, y防止越界
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
// 宽高不能超出图片范围
|
||||
if (x + width > imageWidth) {
|
||||
width = imageWidth - x;
|
||||
}
|
||||
if (y + height > imageHeight) {
|
||||
height = imageHeight - y;
|
||||
}
|
||||
// 防止最终 width 或 height 为负或为 0
|
||||
if (width <= 0 || height <= 0) {
|
||||
return null; // 无效区域
|
||||
}
|
||||
return new DetectionRectangle(x, y, width, height, rectangle.score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 子图中人脸关键点坐标 - Coordinates of key points in the image
|
||||
*
|
||||
* @param points
|
||||
* @return
|
||||
*/
|
||||
public static double[][] facePoints(List<Point> points) {
|
||||
// 图中关键点坐标 - Coordinates of key points in the image
|
||||
// 1. left_eye_x , left_eye_y
|
||||
// 2. right_eye_x , right_eye_y
|
||||
// 3. nose_x , nose_y
|
||||
// 4. left_mouth_x , left_mouth_y
|
||||
// 5. right_mouth_x , right_mouth_y
|
||||
double[][] pointsArray = new double[5][2]; // 保存人脸关键点 - Save facial key points
|
||||
int i = 0;
|
||||
for (Point point : points) {
|
||||
pointsArray[i][0] = point.getX();
|
||||
pointsArray[i][1] = point.getY();
|
||||
i++;
|
||||
}
|
||||
return pointsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 子图中人脸关键点坐标 - Coordinates of key points in the image
|
||||
*
|
||||
* @param pointFS
|
||||
* @return
|
||||
*/
|
||||
public static double[][] facePoints(SeetaPointF[] pointFS) {
|
||||
// 图中关键点坐标 - Coordinates of key points in the image
|
||||
// 1. left_eye_x , left_eye_y
|
||||
// 2. right_eye_x , right_eye_y
|
||||
// 3. nose_x , nose_y
|
||||
// 4. left_mouth_x , left_mouth_y
|
||||
// 5. right_mouth_x , right_mouth_y
|
||||
double[][] pointsArray = new double[5][2]; // 保存人脸关键点 - Save facial key points
|
||||
int i = 0;
|
||||
for (SeetaPointF point : pointFS) {
|
||||
pointsArray[i][0] = point.getX();
|
||||
pointsArray[i][1] = point.getY();
|
||||
i++;
|
||||
}
|
||||
return pointsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 512x512的目标点 - Target point of 512x512
|
||||
* standard 5 landmarks for FFHQ faces with 512 x 512
|
||||
*
|
||||
* @param manager
|
||||
* @return
|
||||
*/
|
||||
public static NDArray faceTemplate512x512(NDManager manager) {
|
||||
double[][] coord5point = {
|
||||
{192.98138, 239.94708}, // 512x512的目标点 - Target point of 512x512
|
||||
{318.90277, 240.1936},
|
||||
{256.63416, 314.01935},
|
||||
{201.26117, 371.41043},
|
||||
{313.08905, 371.15118}
|
||||
};
|
||||
NDArray points = manager.create(coord5point);
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* bgr转图片
|
||||
* @return 图片
|
||||
*/
|
||||
public static BufferedImage toBufferedImage(SeetaImageData seetaImageData) {
|
||||
int type = BufferedImage.TYPE_3BYTE_BGR;
|
||||
BufferedImage image = new BufferedImage(seetaImageData.width, seetaImageData.height, type);
|
||||
image.getRaster().setDataElements(0, 0, seetaImageData.width, seetaImageData.height, seetaImageData.data);
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制人脸关键点
|
||||
* @param g
|
||||
* @param keyPoints
|
||||
*/
|
||||
private static void drawLandmarks(Graphics2D g, List<Point> keyPoints) {
|
||||
g.setColor(new Color(246, 96, 0));
|
||||
BasicStroke bStroke = new BasicStroke(4.0F, 0, 0);
|
||||
g.setStroke(bStroke);
|
||||
for (Point point : keyPoints){
|
||||
g.drawRect((int)point.getX(), (int)point.getY(), 2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.smartjavaai.face.utils;
|
||||
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
|
||||
/**
|
||||
* OpenCV 工具类
|
||||
*/
|
||||
public class OpenCVUtils {
|
||||
/**
|
||||
* canny算法,边缘检测
|
||||
*
|
||||
* @param src
|
||||
* @return
|
||||
*/
|
||||
public static Mat canny(Mat src) {
|
||||
Mat mat = src.clone();
|
||||
Imgproc.Canny(src, mat, 100, 200);
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 画线
|
||||
*
|
||||
* @param mat
|
||||
* @param point1
|
||||
* @param point2
|
||||
*/
|
||||
public static void line(Mat mat, Point point1, Point point2) {
|
||||
Imgproc.line(mat, point1, point2, new Scalar(255, 255, 255), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* NDArray to opencv_core.Mat
|
||||
*
|
||||
* @param manager
|
||||
* @param srcPoints
|
||||
* @param dstPoints
|
||||
* @return
|
||||
*/
|
||||
public static Mat toOpenCVMat(NDManager manager, NDArray srcPoints, NDArray dstPoints) {
|
||||
NDArray svdMat = SVDUtils.transformationFromPoints(manager, srcPoints, dstPoints);
|
||||
double[] doubleArray = svdMat.toDoubleArray();
|
||||
Mat newSvdMat = new Mat(2, 3, CvType.CV_64F);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
newSvdMat.put(i, j, doubleArray[i * 3 + j]);
|
||||
}
|
||||
}
|
||||
return newSvdMat;
|
||||
}
|
||||
|
||||
/**
|
||||
* double[][] points array to Mat
|
||||
* @param points
|
||||
* @return
|
||||
*/
|
||||
public static Mat toOpenCVMat(double[][] points) {
|
||||
Mat mat = new Mat(5, 2, CvType.CV_64F);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
mat.put(i, j, points[i * 5 + j]);
|
||||
}
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 变换矩阵的逆矩阵
|
||||
*
|
||||
* @param src
|
||||
* @return
|
||||
*/
|
||||
public static Mat invertAffineTransform(Mat src) {
|
||||
Mat dst = src.clone();
|
||||
Imgproc.invertAffineTransform(src, dst);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mat to BufferedImage
|
||||
*
|
||||
* @param mat
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage mat2Image(Mat mat) {
|
||||
int width = mat.width();
|
||||
int height = mat.height();
|
||||
byte[] data = new byte[width * height * (int) mat.elemSize()];
|
||||
Imgproc.cvtColor(mat, mat, 4);
|
||||
mat.get(0, 0, data);
|
||||
BufferedImage ret = new BufferedImage(width, height, 5);
|
||||
ret.getRaster().setDataElements(0, 0, width, height, data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* BufferedImage to Mat
|
||||
*
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public static Mat image2Mat(BufferedImage img) {
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
byte[] data = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
|
||||
Mat mat = new Mat(height, width, CvType.CV_8UC3);
|
||||
mat.put(0, 0, data);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package cn.smartjavaai.face.utils;
|
||||
|
||||
import Jama.Matrix;
|
||||
import Jama.SingularValueDecomposition;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
|
||||
/**
|
||||
* 仿射变换处理工具
|
||||
*/
|
||||
public class SVDUtils {
|
||||
/**
|
||||
* 计算仿射变换矩阵
|
||||
* Calculate affine transformation matrix
|
||||
*
|
||||
* @param manager
|
||||
* @param points1
|
||||
* @param points2
|
||||
* @return
|
||||
*/
|
||||
public static NDArray transformationFromPoints(
|
||||
NDManager manager, NDArray points1, NDArray points2) {
|
||||
// 按列计算均值
|
||||
// Calculate column-wise mean
|
||||
NDArray c1 = points1.mean(new int[]{0}); // axis=0 列操作 - axis=0 column operation
|
||||
NDArray c2 = points2.mean(new int[]{0}); // axis=0 列操作 - axis=0 column operation
|
||||
// 按列减去均值
|
||||
// Subtract column-wise mean
|
||||
points1 = points1.sub(c1);
|
||||
points2 = points2.sub(c2);
|
||||
|
||||
// 计算全局标准差
|
||||
// Calculate global standard deviation
|
||||
double s1 = std(points1);
|
||||
double s2 = std(points2);
|
||||
|
||||
// 矩阵除以全局标准差
|
||||
// Matrix divided by global standard deviation
|
||||
NDArray djl_s1 = manager.create(s1);
|
||||
NDArray djl_s2 = manager.create(s2);
|
||||
points1 = points1.div(djl_s1);
|
||||
points2 = points2.div(djl_s2);
|
||||
|
||||
double[] points1D = points1.toDoubleArray();
|
||||
double[] points2D = points2.toDoubleArray();
|
||||
|
||||
// DJL 格式转换成Jamma格式
|
||||
// Convert DJL format to Jama format
|
||||
double[][] m1 = new double[5][2];
|
||||
double[][] m2 = new double[5][2];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
m1[i][j] = points1D[i * 2 + j];
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
m2[i][j] = points2D[i * 2 + j];
|
||||
}
|
||||
}
|
||||
Matrix p1 = new Matrix(m1);
|
||||
Matrix p2 = new Matrix(m2);
|
||||
|
||||
// 进行奇异值分解
|
||||
// Perform singular value decomposition
|
||||
Matrix p3 = p1.transpose().times(p2);
|
||||
SingularValueDecomposition s = p3.svd();
|
||||
|
||||
Matrix U = s.getU();
|
||||
Matrix S = s.getS();
|
||||
Matrix V = s.getV();
|
||||
// TODO 为什么第2列的符号是反的?
|
||||
// Why is the sign of the second column opposite?
|
||||
m1 = U.getArray();
|
||||
m1[0][1] = -m1[0][1];
|
||||
m1[1][1] = -m1[1][1];
|
||||
m2 = V.getArray();
|
||||
m2[0][1] = -m2[0][1];
|
||||
m2[1][1] = -m2[1][1];
|
||||
|
||||
Matrix R = (U.times(V)).transpose();
|
||||
|
||||
double[][] rArray = R.getArray();
|
||||
NDArray newR = manager.create(rArray);
|
||||
// np.vstack([np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), np.matrix([0.,0., 1.])])
|
||||
// (s2 / s1) * R
|
||||
NDArray leftPart = djl_s2.div(djl_s1).mul(newR);
|
||||
// c2.T - (s2 / s1) * R * c1.T)
|
||||
NDArray rightPart = c2.reshape(2, 1).sub(leftPart.matMul(c1.reshape(2, 1)));
|
||||
// numpy.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T))
|
||||
NDArray upPart = leftPart.concat(rightPart, 1);
|
||||
// np.matrix([0.,0., 1.])
|
||||
double[] downArray = {0d, 0d, 1d};
|
||||
NDArray downPart = manager.create(downArray).reshape(1, 3);
|
||||
|
||||
NDArray all = upPart.concat(downPart, 0);
|
||||
// System.out.println("all: " + all);
|
||||
|
||||
return upPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算全局标准差
|
||||
* Calculate global standard deviation
|
||||
*
|
||||
* @param points
|
||||
* @return
|
||||
*/
|
||||
public static double std(NDArray points) {
|
||||
points = points.square();
|
||||
double[] doubleResult = points.toDoubleArray();
|
||||
double std = 0;
|
||||
for (int i = 0; i < doubleResult.length; i++) {
|
||||
std = std + doubleResult[i];
|
||||
}
|
||||
std = (float) Math.sqrt(std / doubleResult.length);
|
||||
return std;
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package com.seetaface;
|
||||
|
||||
|
||||
import com.seetaface.model.RecognizeResult;
|
||||
import com.seetaface.model.SeetaImageData;
|
||||
import com.seetaface.model.SeetaPointF;
|
||||
import com.seetaface.model.SeetaRect;
|
||||
|
||||
/**
|
||||
* seetaface6 sdk
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaFace6JNI {
|
||||
|
||||
/**
|
||||
* 初始化,指定人脸识别模型文件目录
|
||||
*
|
||||
* @param modelDir
|
||||
* @return
|
||||
*/
|
||||
|
||||
public native boolean initModel(String modelDir);
|
||||
|
||||
/**
|
||||
* 检测人脸
|
||||
*
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public native SeetaRect[] detect(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 根据人脸检测关键点
|
||||
* 关键定定位输入的是原始图片和人脸检测结果,给出指定人脸上的关键点的依次坐标。
|
||||
* 这里检测到的5点坐标循序依次为,左眼中心、右眼中心、鼻尖、左嘴角和右嘴角。
|
||||
* 注意这里的左右是基于图片内容的左右,并不是图片中人的左右,即左眼中心就是图片中左边的眼睛的中心。
|
||||
*
|
||||
* @param img
|
||||
* @param faces
|
||||
* @return
|
||||
*/
|
||||
public native SeetaPointF[] mark(SeetaImageData img, SeetaRect faces);
|
||||
|
||||
/**
|
||||
* 1 v 1 人脸比对
|
||||
*
|
||||
* @param img1
|
||||
* @param img2
|
||||
* @return 相似度范围在0~1,返回负数表示出错
|
||||
*/
|
||||
public native float compare(SeetaImageData img1, SeetaImageData img2);
|
||||
|
||||
/**
|
||||
* 提取人脸区域特性
|
||||
* @param face crop方法返回的人脸图像
|
||||
* @return
|
||||
*/
|
||||
public native float[] extractCroppedFace(byte[] face);
|
||||
|
||||
/**
|
||||
* 提取一个图像中最大人脸的特征
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public native float[] extractMaxFace(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 计算两个特性的相似度
|
||||
* @param features1
|
||||
* @param features2
|
||||
* @return
|
||||
*/
|
||||
public native float calculateSimilarity(float[] features1, float[] features2);
|
||||
|
||||
/**
|
||||
* 注册人脸
|
||||
*
|
||||
* @param img
|
||||
* @return The returned value is the index of face database. Reture -1 if failed
|
||||
*/
|
||||
public native long register(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 注册裁剪后的人脸,推荐使用该方法
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public native long registerCroppedFace(byte[] bytes);
|
||||
|
||||
/**
|
||||
* 从人脸库中搜索,返回相似度最高的索引
|
||||
*
|
||||
* @param img
|
||||
* @return index saves the index of face databese, which is same as the retured value by Register. similar saves the most similar.
|
||||
*/
|
||||
public native RecognizeResult query(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 用裁剪后的人脸进行搜索
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public native RecognizeResult queryByCroppedFace(byte[] bytes);
|
||||
|
||||
/**
|
||||
* 将人脸从数据库中删除
|
||||
* @param index -1: 删除所有
|
||||
* @return 返回删除记录数
|
||||
*/
|
||||
public native long delete(long[] index);
|
||||
|
||||
/**
|
||||
* 人脸提取
|
||||
*
|
||||
* @param img
|
||||
* @return The returned value is face data. Reture null if failed
|
||||
*/
|
||||
public native byte[][] crop(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 图片活体检测
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public native int predictImage(SeetaImageData img);
|
||||
|
||||
public native void dispose();
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
|
||||
/**
|
||||
* 人脸识别结果
|
||||
*/
|
||||
public class RecognizeResult {
|
||||
public int index;
|
||||
public float similar;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
/**
|
||||
* 人脸识别参数
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaImageData {
|
||||
public SeetaImageData() {
|
||||
|
||||
}
|
||||
|
||||
public SeetaImageData(int width, int height, int channels) {
|
||||
this.data = new byte[width * height * channels];
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
public SeetaImageData(int width, int height) {
|
||||
this(width, height, 3);
|
||||
}
|
||||
|
||||
public byte[] data;
|
||||
public int width;
|
||||
public int height;
|
||||
public int channels;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
/**
|
||||
* SeetaPointF
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaPointF {
|
||||
public double x;
|
||||
public double y;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
/**
|
||||
* SeetaPointF
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaRect {
|
||||
public int x;
|
||||
public int y;
|
||||
public int width;
|
||||
public int height;
|
||||
public float score;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SeetaRect{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
", width=" + width +
|
||||
", height=" + height +
|
||||
", score=" + score +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user