mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-13 13:18:58 +00:00
集成算法seetaface6
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
@@ -51,4 +54,34 @@ public abstract class AbstractFaceAlgorithm implements FaceAlgorithm{
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(String key, String imagePath) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(String key, InputStream inputStream) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceResult search(String imagePath) throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceResult search(InputStream inputStream) throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeRegister(String... keys) throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clearFace() throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package cn.smartjavaai.face;
|
||||
import ai.djl.MalformedModelException;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.translate.TranslateException;
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -86,4 +88,46 @@ public interface FaceAlgorithm {
|
||||
*/
|
||||
float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception;
|
||||
|
||||
/**
|
||||
* 注册人脸
|
||||
* @param key
|
||||
* @param imagePath
|
||||
* @return
|
||||
*/
|
||||
boolean register(String key, String imagePath) throws Exception;
|
||||
|
||||
/**
|
||||
* 注册人脸
|
||||
* @param key
|
||||
* @param inputStream
|
||||
* @return
|
||||
*/
|
||||
boolean register(String key, InputStream inputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 查询人脸
|
||||
* @param imagePath
|
||||
* @return
|
||||
*/
|
||||
FaceResult search(String imagePath) throws Exception;
|
||||
|
||||
/**
|
||||
* 查询人脸
|
||||
* @param inputStream
|
||||
* @return
|
||||
*/
|
||||
FaceResult search(InputStream inputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除已标记人脸
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
long removeRegister(String... keys) throws Exception;
|
||||
|
||||
/**
|
||||
* 清空人脸库数据
|
||||
*/
|
||||
long clearFace() throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.face.algo.FeatureExtractionAlgo;
|
||||
import cn.smartjavaai.face.algo.RetinaFace;
|
||||
import cn.smartjavaai.face.algo.SeetaFace6Algo;
|
||||
import cn.smartjavaai.face.algo.UltraLightFastGenericFace;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -37,6 +38,7 @@ public class FaceAlgorithmFactory {
|
||||
public static FaceAlgorithm createFaceAlgorithm(ModelConfig config) throws Exception {
|
||||
Class<?> clazz = registry.get(config.getAlgorithmName().toLowerCase());
|
||||
if(clazz == null){
|
||||
System.out.println("No such algorithm: " + config.getAlgorithmName().toLowerCase());
|
||||
throw new IllegalArgumentException("Unsupported algorithm");
|
||||
}
|
||||
FaceAlgorithm algorithm = (FaceAlgorithm) clazz.newInstance();
|
||||
@@ -114,6 +116,7 @@ public class FaceAlgorithmFactory {
|
||||
registerAlgorithm("ultralightfastgenericface", UltraLightFastGenericFace.class);
|
||||
//人脸特征提取
|
||||
registerAlgorithm("featureExtraction", FeatureExtractionAlgo.class);
|
||||
registerAlgorithm("seetaface6", SeetaFace6Algo.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ public class ModelConfig {
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
/**
|
||||
* 人脸库路径
|
||||
*/
|
||||
private String faceDbPath;
|
||||
|
||||
|
||||
public String getAlgorithmName() {
|
||||
return algorithmName;
|
||||
@@ -72,4 +77,12 @@ public class ModelConfig {
|
||||
public void setModelPath(String modelPath) {
|
||||
this.modelPath = modelPath;
|
||||
}
|
||||
|
||||
public String getFaceDbPath() {
|
||||
return faceDbPath;
|
||||
}
|
||||
|
||||
public void setFaceDbPath(String faceDbPath) {
|
||||
this.faceDbPath = faceDbPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* RetinaFace实现
|
||||
* @author dwj
|
||||
*/
|
||||
public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
@@ -51,7 +50,7 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
* 加载人脸特征提取模型
|
||||
* @param config
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public void loadFaceFeatureModel(ModelConfig config) throws Exception {
|
||||
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
|
||||
@@ -71,12 +70,12 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征提取
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) throws Exception {
|
||||
Path imageFile = Paths.get(imagePath);
|
||||
@@ -85,12 +84,12 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return predictor.predict(img);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征提取
|
||||
* @param inputStream 输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) throws Exception {
|
||||
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
|
||||
@@ -98,13 +97,13 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return predictor.predict(img);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 计算相似度
|
||||
* @param feature1 图1特征
|
||||
* @param feature2 图2特征
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
|
||||
float ret = 0.0f;
|
||||
@@ -119,13 +118,13 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return (float) ((ret / Math.sqrt(mod1) / Math.sqrt(mod2) + 1) / 2.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征比较
|
||||
* @param imagePath1 图1路径
|
||||
* @param imagePath2 图2路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
|
||||
float[] feature1 = featureExtraction(imagePath1);
|
||||
@@ -133,19 +132,19 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征比较
|
||||
* @param inputStream1 图1输入流
|
||||
* @param inputStream2 图2输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
||||
float[] feature1 = featureExtraction(inputStream1);
|
||||
float[] feature2 = featureExtraction(inputStream2);
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
}*/
|
||||
|
||||
/*@Override
|
||||
public float[] recognize(FaceRegion region) {
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package cn.smartjavaai.face.dao;
|
||||
|
||||
import cn.smartjavaai.face.entity.FaceData;
|
||||
import cn.smartjavaai.face.sqllite.RowMapper;
|
||||
import cn.smartjavaai.face.sqllite.SqliteHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.BeanUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 人脸库持久层
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class FaceDao {
|
||||
|
||||
private static final String TABLE_NAME_IMG = "face";
|
||||
|
||||
|
||||
private String dbFilePath;
|
||||
|
||||
public FaceDao(String dbFilePath) {
|
||||
this.dbFilePath = dbFilePath;
|
||||
}
|
||||
|
||||
|
||||
public void save(FaceData faceData) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
sqliteHelper.executeUpdate("INSERT OR REPLACE INTO " + TABLE_NAME_IMG + " (\"index\",\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?,?)", new Object[]{faceData.getIndex(),faceData
|
||||
.getKey(), faceData.getImgData(), faceData.getWidth(), faceData.getHeight(), faceData.getChannel()});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用index查询key
|
||||
*
|
||||
* @param index
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public String findKeyByIndex(int index) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery("select \"key\" from " + TABLE_NAME_IMG + " where \"index\"=" + index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除全部
|
||||
*
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public long deleteAll() throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
long rows = sqliteHelper.executeUpdate("delete from " + TABLE_NAME_IMG);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询index
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public List<Long> findIndexList(String... keys) throws SQLException, ClassNotFoundException {
|
||||
// 使用 Stream API
|
||||
String inKeys = Arrays.stream(keys)
|
||||
.map(s -> "'" + s + "'")
|
||||
.reduce((s1, s2) -> s1 + "," + s2)
|
||||
.orElse("");
|
||||
String sql = "select \"index\" from " + TABLE_NAME_IMG + " where \"key\" in (" + inKeys + ")";
|
||||
log.info("sql:{}", sql.toString());
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery(sql, new RowMapper<Long>() {
|
||||
@Override
|
||||
public Long mapRow(ResultSet rs, int index) throws SQLException {
|
||||
return rs.getLong(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除人脸
|
||||
* @param keys
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public boolean deleteFace(String... keys) throws SQLException, ClassNotFoundException {
|
||||
String inKeys = Arrays.stream(keys)
|
||||
.map(s -> "'" + s + "'")
|
||||
.reduce((s1, s2) -> s1 + "," + s2)
|
||||
.orElse("");
|
||||
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
String sql = "delete from " + TABLE_NAME_IMG + " where \"key\" in (" + inKeys + ")";
|
||||
log.info("sql:{}", sql.toString());
|
||||
sqliteHelper.executeUpdate(sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询人脸
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public List<FaceData> findFace(int pageNo, int pageSize) throws SQLException, ClassNotFoundException {
|
||||
String sql = "select \"key\",\"img_data\",\"width\",\"height\",\"channel\" from " + TABLE_NAME_IMG +
|
||||
" limit " + pageNo * pageSize + "," + pageSize;
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery(sql, new RowMapper<FaceData>() {
|
||||
@Override
|
||||
public FaceData mapRow(ResultSet rs, int index) throws SQLException {
|
||||
FaceData face = new FaceData();
|
||||
face.setKey(rs.getString("key"));
|
||||
face.setImgData(rs.getBytes("img_data"));
|
||||
face.setWidth(rs.getInt("width"));
|
||||
face.setHeight(rs.getInt("height"));
|
||||
face.setChannel(rs.getInt("channel"));
|
||||
return face;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新index
|
||||
* @param index
|
||||
* @param faceData
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public int updateIndex(long index,FaceData faceData) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeUpdate("INSERT OR REPLACE INTO " + TABLE_NAME_IMG + " (\"index\",\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?,?)", new Object[]{index,faceData
|
||||
.getKey(), faceData.getImgData(), faceData.getWidth(), faceData.getHeight(), faceData.getChannel()});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.smartjavaai.face.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人脸数据
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public class FaceData {
|
||||
|
||||
private String key;
|
||||
private long index;
|
||||
private byte[] imgData;
|
||||
private int width = 256;
|
||||
private int height = 256;
|
||||
private int channel = 3;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.smartjavaai.face.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人脸查询结果
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public class FaceResult {
|
||||
|
||||
private String key;
|
||||
private float similar;
|
||||
|
||||
public FaceResult() {
|
||||
}
|
||||
|
||||
public FaceResult(String key, float similar) {
|
||||
this.key = key;
|
||||
this.similar = similar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
* ResultSetExtractor
|
||||
* @author dwj
|
||||
* @param <T>
|
||||
*/
|
||||
public interface ResultSetExtractor<T> {
|
||||
|
||||
public abstract T extractData(ResultSet rs);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* RowMapper
|
||||
* @author dwj
|
||||
* @param <T>
|
||||
*/
|
||||
public interface RowMapper<T> {
|
||||
public abstract T mapRow(ResultSet rs, int index) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sqlite帮助类
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqliteHelper {
|
||||
|
||||
private Connection connection;
|
||||
private Statement statement;
|
||||
private ResultSet resultSet;
|
||||
private String dbFilePath;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param dbFilePath sqlite db 文件路径
|
||||
* @throws ClassNotFoundException
|
||||
* @throws SQLException
|
||||
*/
|
||||
public SqliteHelper(String dbFilePath) throws ClassNotFoundException, SQLException {
|
||||
this.dbFilePath = dbFilePath;
|
||||
connection = getConnection(dbFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库连接
|
||||
* @param dbFilePath db文件路径
|
||||
* @return 数据库连接
|
||||
* @throws ClassNotFoundException
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Connection getConnection(String dbFilePath) throws ClassNotFoundException, SQLException {
|
||||
Connection conn = null;
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
conn = DriverManager.getConnection("jdbc:sqlite:" + dbFilePath);
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql查询
|
||||
* @param sql sql select 语句
|
||||
* @param rse 结果集处理类对象
|
||||
* @return 查询结果
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public <T> T executeQuery(String sql, ResultSetExtractor<T> rse) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
T rs = rse.extractData(resultSet);
|
||||
return rs;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行select查询,返回结果列表
|
||||
*
|
||||
* @param sql sql select 语句
|
||||
* @param rm 结果集的行数据处理类对象
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public <T> List<T> executeQuery(String sql, RowMapper<T> rm) throws SQLException, ClassNotFoundException {
|
||||
List<T> rsList = new ArrayList<T>();
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
while (resultSet.next()) {
|
||||
rsList.add(rm.mapRow(resultSet, resultSet.getRow()));
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
return rsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单查询某个字段
|
||||
* @param sql
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public String executeQuery(String sql) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
if(resultSet.next()){
|
||||
return resultSet.getString(1);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库更新sql语句
|
||||
* @param sql
|
||||
* @return 更新行数
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public int executeUpdate(String sql) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
int c = getStatement().executeUpdate(sql);
|
||||
return c;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行多个sql更新语句
|
||||
* @param sqls
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public void executeUpdate(String...sqls) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
for (String sql : sqls) {
|
||||
getStatement().executeUpdate(sql);
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库更新 sql List
|
||||
* @param sqls sql列表
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public void executeUpdate(List<String> sqls) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
for (String sql : sqls) {
|
||||
getStatement().executeUpdate(sql);
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() throws ClassNotFoundException, SQLException {
|
||||
if (null == connection) connection = getConnection(dbFilePath);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private Statement getStatement() throws SQLException, ClassNotFoundException {
|
||||
if (null == statement) statement = getConnection().createStatement();
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库资源关闭和释放
|
||||
*/
|
||||
public void destroyed() {
|
||||
try {
|
||||
if (null != statement) {
|
||||
statement.close();
|
||||
statement = null;
|
||||
}
|
||||
|
||||
if (null != connection) {
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
|
||||
if (null != resultSet) {
|
||||
resultSet.close();
|
||||
resultSet = null;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Sqlite数据库关闭时异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行select查询,返回结果列表
|
||||
*
|
||||
* @param sql sql select 语句
|
||||
* @param clazz 实体泛型
|
||||
* @return 实体集合
|
||||
* @throws SQLException 异常信息
|
||||
* @throws ClassNotFoundException 异常信息
|
||||
*/
|
||||
public <T> List<T> executeQueryList(String sql, Class<T> clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
List<T> rsList = new ArrayList<T>();
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
while (resultSet.next()) {
|
||||
T t = clazz.newInstance();
|
||||
for (Field field : t.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
field.set(t,resultSet.getObject(field.getName()));
|
||||
}
|
||||
rsList.add(t);
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
return rsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql查询,适用单条结果集
|
||||
* @param sql sql select 语句
|
||||
* @param clazz 结果集处理类对象
|
||||
* @return 查询结果
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public <T> T executeQuery(String sql, Class<T> clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
T t = clazz.newInstance();
|
||||
for (Field field : t.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
field.set(t,resultSet.getObject(field.getName()));
|
||||
}
|
||||
return t;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库更新sql语句
|
||||
* @param tableName 表名
|
||||
* @param param key-value键值对,key:表中字段名,value:值
|
||||
* @return 更新行数
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public int executeInsertOrUpdate(String tableName, Map<String,Object> param) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
StringBuffer sql = new StringBuffer();
|
||||
sql.append("INSERT OR REPLACE INTO ");
|
||||
sql.append(tableName);
|
||||
sql.append(" ( ");
|
||||
for (String key : param.keySet()) {
|
||||
sql.append("'" + key + "'");
|
||||
sql.append(",");
|
||||
}
|
||||
sql.delete(sql.length()-1,sql.length());
|
||||
sql.append(") VALUES ( ");
|
||||
for (String key : param.keySet()) {
|
||||
sql.append("'");
|
||||
sql.append(param.get(key));
|
||||
sql.append("',");
|
||||
}
|
||||
sql.delete(sql.length()-1,sql.length());
|
||||
sql.append(");");
|
||||
log.info("sql:{}", sql.toString());
|
||||
int c = getStatement().executeUpdate(sql.toString());
|
||||
return c;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
public int executeUpdate(String sql, Object[] args) throws SQLException, ClassNotFoundException {
|
||||
Connection conn = getConnection();
|
||||
if (args == null || args.length == 0) {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
return stmt.executeUpdate(sql);
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
} else {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
stmt.setObject(i + 1, args[i]);
|
||||
}
|
||||
return stmt.executeUpdate();
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user