mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-15 22:57:26 +00:00
320 lines
13 KiB
Java
320 lines
13 KiB
Java
package cn.smartjavaai.face.algo;
|
|
|
|
import cn.smartjavaai.common.entity.Point;
|
|
import cn.smartjavaai.common.entity.Rectangle;
|
|
import cn.smartjavaai.common.utils.ImageUtils;
|
|
import cn.smartjavaai.face.AbstractFaceAlgorithm;
|
|
import cn.smartjavaai.face.FaceDetectedResult;
|
|
import cn.smartjavaai.face.ModelConfig;
|
|
import cn.smartjavaai.face.dao.FaceDao;
|
|
import cn.smartjavaai.face.entity.FaceData;
|
|
import cn.smartjavaai.face.entity.FaceResult;
|
|
import com.seetaface.NativeLoader;
|
|
import com.seetaface.SeetaFace6JNI;
|
|
import com.seetaface.model.RecognizeResult;
|
|
import com.seetaface.model.SeetaImageData;
|
|
import com.seetaface.model.SeetaRect;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import java.awt.image.BufferedImage;
|
|
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;
|
|
|
|
/**
|
|
* SeetaFace6 人脸算法
|
|
* @author dwj
|
|
*/
|
|
@Slf4j
|
|
public class SeetaFace6Algo extends AbstractFaceAlgorithm {
|
|
|
|
|
|
private ModelConfig config;
|
|
|
|
|
|
@Override
|
|
public void loadModel(ModelConfig config) throws Exception {
|
|
this.config = config;
|
|
if (NativeLoader.seetaFace6SDK == null) {
|
|
synchronized (SeetaFace6JNI.class) {
|
|
if(StringUtils.isBlank(config.getModelPath())){
|
|
throw new Exception("modelPath is null");
|
|
}
|
|
//加载依赖库
|
|
NativeLoader.loadNativeLibraries(config.getModelPath());
|
|
log.info("Loading seetaFace6 library successfully.");
|
|
NativeLoader.seetaFace6SDK = new SeetaFace6JNI();
|
|
//加载模型
|
|
boolean isSuccess = NativeLoader.seetaFace6SDK.initModel(config.getModelPath());
|
|
if(!isSuccess){
|
|
throw new Exception("seetaFace6模型初始化失败," + config.getModelPath());
|
|
}
|
|
log.info("Load seetaFace6 model success!");
|
|
new Thread(new Runnable() {
|
|
public void run() {
|
|
try {
|
|
log.info("start load faceDb...");
|
|
loadFaceDb();
|
|
log.info("Load faceDb success!");
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}).start();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public FaceDetectedResult detect(String imagePath) throws Exception {
|
|
// 将图片路径转换为 BufferedImage
|
|
BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
|
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
|
imageData.data = ImageUtils.getMatrixBGR(image);
|
|
SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
|
|
return convertToFaceDetectedResult(seetaResult);
|
|
}
|
|
|
|
@Override
|
|
public FaceDetectedResult detect(InputStream imageInputStream) throws Exception {
|
|
BufferedImage image = ImageIO.read(imageInputStream);
|
|
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
|
imageData.data = ImageUtils.getMatrixBGR(image);
|
|
SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
|
|
return convertToFaceDetectedResult(seetaResult);
|
|
}
|
|
|
|
@Override
|
|
public float[] featureExtraction(String imagePath) throws Exception {
|
|
BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
|
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
|
imageData.data = ImageUtils.getMatrixBGR(image);
|
|
return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
|
|
}
|
|
|
|
@Override
|
|
public float[] featureExtraction(InputStream inputStream) throws Exception {
|
|
BufferedImage image = ImageIO.read(inputStream);
|
|
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
|
imageData.data = ImageUtils.getMatrixBGR(image);
|
|
return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
|
|
}
|
|
|
|
@Override
|
|
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
|
|
return NativeLoader.seetaFace6SDK.calculateSimilarity(feature1, feature2);
|
|
}
|
|
|
|
@Override
|
|
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
|
|
return featureComparison(new FileInputStream(Paths.get(imagePath1).toAbsolutePath().toString()),
|
|
new FileInputStream(Paths.get(imagePath2).toAbsolutePath().toString()));
|
|
}
|
|
|
|
@Override
|
|
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
|
BufferedImage image1 = ImageIO.read(inputStream1);
|
|
BufferedImage image2 = ImageIO.read(inputStream2);
|
|
SeetaImageData imageData1 = new SeetaImageData(image1.getWidth(), image1.getHeight(), 3);
|
|
imageData1.data = ImageUtils.getMatrixBGR(image1);
|
|
|
|
SeetaImageData imageData2 = new SeetaImageData(image2.getWidth(), image2.getHeight(), 3);
|
|
imageData2.data = ImageUtils.getMatrixBGR(image2);
|
|
//裁剪
|
|
byte[][] cropImg1 = NativeLoader.seetaFace6SDK.crop(imageData1);
|
|
byte[][] cropImg2 = NativeLoader.seetaFace6SDK.crop(imageData2);
|
|
if(cropImg1 == null || cropImg1.length == 0){
|
|
throw new Exception("未发现人脸");
|
|
}
|
|
if(cropImg2 == null || cropImg2.length == 0){
|
|
throw new Exception("未发现人脸");
|
|
}
|
|
|
|
BufferedImage cropImage1 = ImageUtils.bgrToBufferedImage(cropImg1[0], 256, 256);
|
|
BufferedImage cropImage2 = ImageUtils.bgrToBufferedImage(cropImg2[0], 256, 256);
|
|
|
|
SeetaImageData cropImageData1 = new SeetaImageData(cropImage1.getWidth(), cropImage1.getHeight(), 3);
|
|
cropImageData1.data = ImageUtils.getMatrixBGR(cropImage1);
|
|
SeetaImageData cropImageData2 = new SeetaImageData(cropImage2.getWidth(), cropImage2.getHeight(), 3);
|
|
cropImageData2.data = ImageUtils.getMatrixBGR(cropImage2);
|
|
return NativeLoader.seetaFace6SDK.compare(cropImageData1, cropImageData2);
|
|
}
|
|
|
|
|
|
/**
|
|
* 转换为FaceDetectedResult
|
|
* @param seetaResult
|
|
* @return
|
|
*/
|
|
private FaceDetectedResult convertToFaceDetectedResult(SeetaRect[] seetaResult){
|
|
FaceDetectedResult faceDetectedResult = new FaceDetectedResult();
|
|
List<Rectangle> RectangleList = new ArrayList<Rectangle>();
|
|
List<Double> probabilities = new ArrayList<Double>();
|
|
if(seetaResult != null && seetaResult.length > 0){
|
|
for(SeetaRect rect : seetaResult){
|
|
Rectangle rectangle = new Rectangle();
|
|
List<Point> pointList = new ArrayList<>();
|
|
pointList.add(new Point(rect.x,rect.y));
|
|
pointList.add(new Point(rect.x + rect.width,rect.y));
|
|
pointList.add(new Point(rect.x,rect.y + rect.height));
|
|
pointList.add(new Point(rect.x + rect.width,rect.y + rect.height));
|
|
rectangle.setPointList(pointList);
|
|
rectangle.setHeight(rect.height);
|
|
rectangle.setWidth(rect.width);
|
|
RectangleList.add(rectangle);
|
|
probabilities.add(new Double(rect.score));
|
|
}
|
|
}
|
|
faceDetectedResult.setProbabilities(probabilities);
|
|
faceDetectedResult.setRectangles(RectangleList);
|
|
return faceDetectedResult;
|
|
}
|
|
|
|
@Override
|
|
public boolean register(String key, String imagePath) throws Exception {
|
|
return register(key, new FileInputStream(Paths.get(imagePath).toAbsolutePath().toString()));
|
|
}
|
|
|
|
|
|
@Override
|
|
public boolean register(String key, InputStream inputStream) throws Exception {
|
|
if(!checkFaceDb()){
|
|
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
|
}
|
|
//裁剪人脸
|
|
BufferedImage image = ImageIO.read(inputStream);
|
|
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
|
imageData.data = ImageUtils.getMatrixBGR(image);
|
|
byte[][] bytes = NativeLoader.seetaFace6SDK.crop(imageData);
|
|
if (bytes == null || bytes.length == 0) {
|
|
log.info("register face fail: key={}, error=no valid face", key);
|
|
return false;
|
|
}
|
|
long index = NativeLoader.seetaFace6SDK.registerCroppedFace(bytes[0]);
|
|
if (index < 0) {
|
|
log.info("register face fail: key={}, index={}", key, index);
|
|
return false;
|
|
}
|
|
//持久化到sqlite数据库
|
|
FaceData face = new FaceData();
|
|
face.setKey(key);
|
|
face.setIndex(index);
|
|
face.setImgData(bytes[0]);
|
|
new FaceDao(config.getFaceDbPath()).save(face);
|
|
return true;
|
|
}
|
|
|
|
public boolean register(String key, FaceData faceData) throws Exception {
|
|
long index = NativeLoader.seetaFace6SDK.registerCroppedFace(faceData.getImgData());
|
|
if (index < 0) {
|
|
log.info("register face fail: key={}, index={}", key, index);
|
|
return false;
|
|
}
|
|
int rows = new FaceDao(config.getFaceDbPath()).updateIndex(index, faceData);
|
|
return rows > 0;
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
public FaceResult search(String imagePath) throws Exception {
|
|
return search(new FileInputStream(Paths.get(imagePath).toAbsolutePath().toString()));
|
|
}
|
|
|
|
@Override
|
|
public FaceResult search(InputStream inputStream) throws Exception{
|
|
if(!checkFaceDb()){
|
|
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
|
}
|
|
BufferedImage image = ImageIO.read(inputStream);
|
|
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
|
imageData.data = ImageUtils.getMatrixBGR(image);
|
|
RecognizeResult recognizeResult = NativeLoader.seetaFace6SDK.query(imageData);
|
|
return searchFaceDb(recognizeResult);
|
|
|
|
}
|
|
|
|
@Override
|
|
public long removeRegister(String... keys) throws Exception {
|
|
if(!checkFaceDb()){
|
|
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
|
}
|
|
List<Long> list = new FaceDao(config.getFaceDbPath()).findIndexList(keys);
|
|
if (list == null) {
|
|
return 0;
|
|
}
|
|
long[] array = list.stream().mapToLong(Long::longValue).toArray();
|
|
long rows = NativeLoader.seetaFace6SDK.delete(array);
|
|
new FaceDao(config.getFaceDbPath()).deleteFace(keys);
|
|
return rows;
|
|
}
|
|
|
|
@Override
|
|
public long clearFace() throws Exception{
|
|
if(!checkFaceDb()){
|
|
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
|
}
|
|
long rows = NativeLoader.seetaFace6SDK.delete(new long[]{-1});
|
|
new FaceDao(config.getFaceDbPath()).deleteAll();
|
|
return rows;
|
|
}
|
|
|
|
/**
|
|
* 检查是否存在人脸库
|
|
* @return
|
|
*/
|
|
private boolean checkFaceDb(){
|
|
if(Objects.nonNull(config) && StringUtils.isNotBlank(config.getFaceDbPath())){
|
|
File file = new File(config.getFaceDbPath());
|
|
return file.exists() && file.isFile();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private FaceResult searchFaceDb(RecognizeResult recognizeResult) throws SQLException, ClassNotFoundException {
|
|
if(recognizeResult != null && recognizeResult.index >= 0){
|
|
String key = new FaceDao(config.getFaceDbPath()).findKeyByIndex(recognizeResult.index);
|
|
return new FaceResult(key, recognizeResult.similar);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* 加载人脸库
|
|
* @throws SQLException
|
|
* @throws ClassNotFoundException
|
|
*/
|
|
private void loadFaceDb() throws SQLException, ClassNotFoundException {
|
|
if(!checkFaceDb()){
|
|
log.info("未配置人脸库");
|
|
return;
|
|
}
|
|
//分页查询人脸库
|
|
int pageNo = 0, pageSize = 100;
|
|
while (true) {
|
|
List<FaceData> list = new FaceDao(config.getFaceDbPath()).findFace(pageNo, pageSize);
|
|
if (list == null) {
|
|
break;
|
|
}
|
|
list.forEach(face -> {
|
|
try {
|
|
register(face.getKey(), face);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
});
|
|
if (list.size() < pageSize) {
|
|
break;
|
|
}
|
|
pageNo++;
|
|
}
|
|
}
|
|
}
|