Repository: xikuqi/OpenCV
Branch: master
Commit: d21521c3b556
Files: 29
Total size: 52.3 KB
Directory structure:
gitextract_yuq3c2t4/
├── LICENSE
├── README.md
├── a.txt
├── build.pom
├── doc/
│ └── lib-linux-x64.tar.bz2
├── pom.xml
├── seetafaceJNI-1.1.jar
├── seetafaceJNI-2.0.jar
├── seetafaceJNI-3.0.jar
└── src/
├── main/
│ ├── java/
│ │ └── com/
│ │ ├── cnsugar/
│ │ │ ├── ai/
│ │ │ │ └── face/
│ │ │ │ ├── FaceHelper.java
│ │ │ │ ├── SeetafaceBuilder.java
│ │ │ │ ├── bean/
│ │ │ │ │ ├── FaceIndex.java
│ │ │ │ │ └── Result.java
│ │ │ │ ├── dao/
│ │ │ │ │ └── FaceDao.java
│ │ │ │ └── utils/
│ │ │ │ └── ImageUtils.java
│ │ │ └── common/
│ │ │ └── sqlite/
│ │ │ ├── JdbcPool.java
│ │ │ ├── JdbcPoolFactory.java
│ │ │ ├── RowMapper.java
│ │ │ └── SqliteUtils.java
│ │ └── seetaface2/
│ │ ├── SeetaFace2JNI.java
│ │ └── model/
│ │ ├── FaceLandmark.java
│ │ ├── RecognizeResult.java
│ │ ├── RegisterData.java
│ │ ├── SeetaImageData.java
│ │ ├── SeetaPointF.java
│ │ └── SeetaRect.java
│ └── resources/
│ ├── log4j2.xml
│ └── seetaface.properties
└── test/
└── java/
└── Test.java
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
BSD 2-Clause License
Copyright (c) 2019, cnsugar
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# seetafaceJNI
#### 项目介绍
基于中科院seetaface2进行封装的JAVA人脸识别算法库,支持人脸识别、1:1比对、1:N比对。
seetaface2:https://github.com/seetaface/SeetaFaceEngine2
#### 环境配置
1、下载model( https://pan.baidu.com/s/1HJj8PEnv3SOu6ZxVpAHPXg ) 文件到本地,并解压出来;
2、下载doc目录中对应的lib包到本地并解压:Windows(64位)环境下载lib-win-x64.zip、Linux(64位)下载lib-linux-x64.tar.bz2,Linux环境还需要安装依赖库,详见:https://my.oschina.net/u/1580184/blog/3042404 ;
3、将doc中的faces-data.db下载到本地;(PS:如果不需要使用1:N人脸搜索,不需要此文件,需要将seetafce.properties中的sqlite.db.file配置注释掉);
4、将src/main/resources/中的seetaface.properties文件放到项目的resources根目录中;
```properties
#linux系统中依赖的lib名称
libs=holiday,SeetaFaceDetector200,SeetaPointDetector200,SeetaFaceRecognizer200,SeetaFaceCropper200,SeetaFace2JNI
#Windows系统中依赖的lib名称
#libs=libgcc_s_sjlj-1,libeay32,libquadmath-0,ssleay32,libgfortran-3,libopenblas,holiday,SeetaFaceDetector200,SeetaPointDetector200,SeetaFaceRecognizer200,SeetaFaceCropper200,SeetaFace2JNI
#lib存放目录
libs.path=/usr/local/seetaface2/lib
#model存放目录
bindata.dir=/usr/local/seetaface2/bindata
##sqlite配置(如果不用1:N人脸搜索功能,请删除下面5项sqlite开头的配置)
sqlite.db.file=/data/faces-data.db
sqlite.conn.maxTotal=50
sqlite.conn.maxIdle=5
sqlite.conn.minIdle=0
sqlite.conn.maxWaitMillis=60000
```
5、将seetafaceJNI-2.0.jar和依赖包导入到项目中,pom如下:
```xml
4.2.8.RELEASE
2.8.2
1.7.25
com.cnsugar.ai
seetafaceJNI
2.0
org.springframework
spring-core
${spring.version}
org.slf4j
slf4j-api
${slf4j.version}
org.xerial
sqlite-jdbc
3.25.2
org.apache.commons
commons-pool2
2.4.2
```
6、调用FaceHelper中的方法。
#### 使用方法
所有方法都封装到了FaceHelper工具类中
```java
/**
* 人脸比对
*
* @param img1
* @param img2
* @return 相似度
*/
float compare(File img1, File img2);
float compare(byte[] img1, byte[] img2);
float compare(BufferedImage image1, BufferedImage image2);
/**
* 注册人脸(会裁剪图片)
*
* @param key 人脸照片唯一标识
* @param img 人脸照片
* @return
*/
boolean register(String key, byte[] img);
/**
* 注册人脸(不裁剪图片)
*
* @param key 人脸照片唯一标识
* @param image 人脸照片
* @return
*/
boolean register(String key, BufferedImage image)
/**
* 搜索人脸
*
* @param img 人脸照片
* @return
*/
Result search(byte[] img);
Result search(BufferedImage image);
/**
* 人脸提取(裁剪)
*
* @param img
* @return return cropped face
*/
BufferedImage crop(byte[] img);
BufferedImage crop(BufferedImage image);
/**
* 人脸识别
*
* @param img
* @return
*/
SeetaRect[] detect(byte[] img);
SeetaRect[] detect(BufferedImage image);
/**
* 人脸识别(包含5个特征点位置)
*
* @param image
* @return
*/
FaceLandmark detectLandmark(BufferedImage image);
/**
* 删除已注册的人脸
* @param keys
*/
void removeRegister(String... keys);
/**
* 清除人脸库数据
*/
void clear();
```
- 示例代码:1:1人脸比对
```java
@org.junit.Test
public void testCompare() throws Exception {
String img1 = "F:\\ai\\demo-pic39.jpg";
String img2 = "F:\\ai\\left_pic_one.jpg";
System.out.println("result:"+FaceHelper.compare(new File(img1), new File(img2)));
}
```
- 示例代码:1:N人脸搜索
先调用FaceHelper.register()方法将人脸图片注册到seetaface2的人脸库(内存)中,同时会将图片存在sqlite数据库中进行持久化,下次应用程序启动时会自动从sqlite中把图片读取出来重新注册到seetafce2的内存库中
```java
@org.junit.Test
public void testRegister() throws IOException {
//将F:\ai\star目录下的jpg、png图片都注册到人脸库中,以文件名为key
Collection files = FileUtils.listFiles(new File("F:\\ai\\star"), new String[]{"jpg", "png"}, false);
for (File file : files) {
String key = file.getName();
try {
FaceHelper.register(key, FileUtils.readFileToByteArray(file));
} catch (Exception e) {
e.printStackTrace();
}
}
}
@org.junit.Test
public void testSearch() throws IOException {
SeetafaceBuilder.build();//系统启动时先调用初始化方法
//等待初始化完成
while (SeetafaceBuilder.getFaceDbStatus() == SeetafaceBuilder.FacedbStatus.LOADING || SeetafaceBuilder.getFaceDbStatus() == SeetafaceBuilder.FacedbStatus.READY) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long l = System.currentTimeMillis();
Result result = FaceHelper.search(FileUtils.readFileToByteArray(new File("F:\\ai\\gtl.jpg")));
System.out.println("搜索结果:" + result + ", 耗时:" + (System.currentTimeMillis() - l));
}
```
================================================
FILE: a.txt
================================================
111
================================================
FILE: build.pom
================================================
4.0.0
com.cnsugar.ai
seetafaceJNI
3.0
4.2.8.RELEASE
2.8.2
1.7.25
org.springframework
spring-core
${spring.version}
org.slf4j
slf4j-api
${slf4j.version}
org.xerial
sqlite-jdbc
3.25.2
org.apache.commons
commons-pool2
2.4.2
================================================
FILE: pom.xml
================================================
4.0.0
com.cnsugar.ai
seetafaceJNI
3.0
4.2.8.RELEASE
2.8.2
1.7.25
org.springframework
spring-core
${spring.version}
org.slf4j
slf4j-api
${slf4j.version}
org.apache.logging.log4j
log4j-api
${log4j.version}
org.apache.logging.log4j
log4j-core
${log4j.version}
org.apache.logging.log4j
log4j-slf4j-impl
${log4j.version}
org.xerial
sqlite-jdbc
3.25.2
org.apache.commons
commons-pool2
2.4.2
junit
junit
4.13-beta-1
test
commons-io
commons-io
2.6
${artifactId}-${version}
org.apache.maven.plugins
maven-compiler-plugin
org.apache.maven.plugins
maven-jar-plugin
**/*.class
================================================
FILE: src/main/java/com/cnsugar/ai/face/FaceHelper.java
================================================
package com.cnsugar.ai.face;
import com.cnsugar.ai.face.bean.FaceIndex;
import com.cnsugar.ai.face.bean.Result;
import com.cnsugar.ai.face.dao.FaceDao;
import com.cnsugar.ai.face.utils.ImageUtils;
import com.seetaface2.SeetaFace2JNI;
import com.seetaface2.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
/**
* @Author Sugar
* @Version 2019/4/22 15:56
*/
public class FaceHelper {
private static Logger logger = LoggerFactory.getLogger(FaceHelper.class);
private static int CROP_SIZE = 256 * 256 * 3;
private static SeetaFace2JNI seeta = SeetafaceBuilder.build();
/**
* 人脸比对
*
* @param img1
* @param img2
* @return 相似度
* @throws IOException
*/
public static float compare(File img1, File img2) throws IOException {
BufferedImage image1 = ImageIO.read(img1);
BufferedImage image2 = ImageIO.read(img2);
return compare(image1, image2);
}
/**
* 人脸比对
*
* @param img1
* @param img2
* @return 相似度
*/
public static float compare(byte[] img1, byte[] img2) throws IOException {
BufferedImage image1 = ImageIO.read(new ByteArrayInputStream(img1));
BufferedImage image2 = ImageIO.read(new ByteArrayInputStream(img2));
return compare(image1, image2);
}
/**
* 人脸比对
*
* @param image1
* @param image2
* @return 相似度
*/
public static float compare(BufferedImage image1, BufferedImage image2) {
if (image1 == null || image2 == null) {
return 0;
}
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);
return seeta.compare(imageData1, imageData2);
}
/**
* 注册人脸(会对人脸进行裁剪)
*
* @param key 人脸照片唯一标识
* @param img 人脸照片
* @return
* @throws IOException
*/
public static boolean register(String key, byte[] img) throws IOException {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(img));
//先对人脸进行裁剪
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
byte[] bytes = seeta.crop(imageData);
if (bytes == null || bytes.length != CROP_SIZE) {
logger.info("register face fail: key={}, error=no valid face", key);
return false;
}
imageData = new SeetaImageData(256, 256, 3);
imageData.data = bytes;
int index = seeta.register(imageData);
if (index < 0) {
logger.info("register face fail: key={}, index={}", key, index);
return false;
}
FaceIndex face = new FaceIndex();
face.setKey(key);
face.setImgData(imageData.data);
face.setWidth(imageData.width);
face.setHeight(imageData.height);
face.setChannel(imageData.channels);
face.setIndex(index);
FaceDao.saveOrUpdate(face);
logger.info("Register face success: key={}, index={}", key, index);
return true;
}
/**
* 注册人脸(不裁剪图片)
*
* @param key 人脸照片唯一标识
* @param image 人脸照片
* @return
* @throws IOException
*/
public static boolean register(String key, BufferedImage image) throws IOException {
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
int index = seeta.register(imageData);
if (index < 0) {
logger.info("register face fail: key={}, index={}", key, index);
return false;
}
FaceIndex face = new FaceIndex();
face.setKey(key);
face.setImgData(imageData.data);
face.setWidth(imageData.width);
face.setHeight(imageData.height);
face.setChannel(imageData.channels);
FaceDao.saveOrUpdate(face);
face.setIndex(index);
logger.info("Register face success: key={}, index={}", key, index);
return true;
}
/**
* 搜索人脸
*
* @param img 人脸照片
* @return
* @throws IOException
*/
public static Result search(byte[] img) throws IOException {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(img));
return search(image);
}
/**
* 搜索人脸
*
* @param image 人脸照片
* @return
* @throws IOException
*/
public static Result search(BufferedImage image) {
if (image == null) {
return null;
}
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
RecognizeResult rr = seeta.recognize(imageData);
if (rr == null || rr.index == -1) {
return null;
}
Result result = new Result(rr);
result.setKey(FaceDao.findKeyByIndex(rr.index));
return result;
}
/**
* 人脸提取(裁剪)
*
* @param img
* @return return cropped face
*/
public static BufferedImage crop(byte[] img) throws IOException {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(img));
return crop(image);
}
/**
* 人脸提取(裁剪)
*
* @param image
* @return return cropped face
*/
public static BufferedImage crop(BufferedImage image) {
if (image == null) {
return null;
}
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
byte[] bytes = seeta.crop(imageData);
if (bytes == null || bytes.length != CROP_SIZE) {
return null;
}
return ImageUtils.bgrToBufferedImage(bytes, 256,256);
}
/**
* 人脸识别
*
* @param img
* @return
*/
public static SeetaRect[] detect(byte[] img) throws IOException {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(img));
return detect(image);
}
/**
* 人脸识别
*
* @param image
* @return
*/
public static SeetaRect[] detect(BufferedImage image) {
if (image == null) {
return null;
}
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
return seeta.detect(imageData);
}
/**
* 人脸特征识别
*
* @param image
* @return
*/
public static FaceLandmark detectLandmark(BufferedImage image) {
if (image == null) {
return null;
}
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
imageData.data = ImageUtils.getMatrixBGR(image);
SeetaRect[] rects = seeta.detect(imageData);
if (rects == null) {
return null;
}
FaceLandmark faces = new FaceLandmark();
faces.rects = rects;
faces.points = seeta.detect(imageData, rects);
return faces;
}
/**
* 删除已注册的人脸
* @param keys
*/
public static void removeRegister(String... keys) {
FaceDao.deleteFaceImg(keys);//删除数据库的人脸
SeetafaceBuilder.buildIndex();//重新建立索引
}
/**
* 清除人脸库数据
*/
public static void clear() {
FaceDao.clearImg();
FaceDao.clearIndex();
seeta.clear();
}
}
================================================
FILE: src/main/java/com/cnsugar/ai/face/SeetafaceBuilder.java
================================================
package com.cnsugar.ai.face;
import com.cnsugar.ai.face.bean.FaceIndex;
import com.cnsugar.ai.face.dao.FaceDao;
import com.cnsugar.common.sqlite.JdbcPool;
import com.seetaface2.SeetaFace2JNI;
import com.seetaface2.model.SeetaImageData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.DefaultResourceLoader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;
/**
* @Author Sugar
* @Version 2019/4/22 14:28
*/
public class SeetafaceBuilder {
private static Logger logger = LoggerFactory.getLogger(SeetafaceBuilder.class);
private volatile static SeetaFace2JNI seeta = null;
public enum FacedbStatus {
READY, LOADING, OK, INACTIV;
}
private volatile static FacedbStatus face_db_status = FacedbStatus.READY;
public static SeetaFace2JNI build() {
if (seeta == null) {
synchronized (SeetafaceBuilder.class) {
if (seeta != null) {
return seeta;
}
init();
}
}
return seeta;
}
/**
* 建立人脸库索引
*/
public static void buildIndex() {
synchronized (SeetafaceBuilder.class) {
while (face_db_status == FacedbStatus.LOADING || face_db_status == FacedbStatus.READY) {
//等待之前的任务初始化完成
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
face_db_status = FacedbStatus.READY;
new Thread(() -> {
seeta.clear();
loadFaceDb();
}).start();
}
}
/**
* 返回人脸数据库状态
* @return
*/
public static FacedbStatus getFaceDbStatus() {
return face_db_status;
}
private static void init() {
Properties prop = getConfig();
String separator = System.getProperty("path.separator");
String sysLib = System.getProperty("java.library.path");
if (sysLib.endsWith(separator)) {
System.setProperty("java.library.path", sysLib + prop.getProperty("libs.path", ""));
} else {
System.setProperty("java.library.path", sysLib + separator + prop.getProperty("libs.path", ""));
}
try {//使java.library.path生效
Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
sysPathsField.setAccessible(true);
sysPathsField.set(null, null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
String[] libs = prop.getProperty("libs", "").split(",");
for (String lib : libs) {
logger.debug("load library: {}", lib);
System.loadLibrary(lib);
}
String bindata = prop.getProperty("bindata.dir");
logger.debug("bindata dir: {}", bindata);
seeta = new SeetaFace2JNI();
seeta.initModel(bindata);
String db_file = prop.getProperty("sqlite.db.file");
if (db_file != null) {
System.setProperty("seetaface.db", db_file);
System.setProperty(JdbcPool.MAX_TOTAL, prop.getProperty(JdbcPool.MAX_TOTAL));
System.setProperty(JdbcPool.MAX_IDLE, prop.getProperty(JdbcPool.MAX_IDLE));
System.setProperty(JdbcPool.MIN_IDLE, prop.getProperty(JdbcPool.MIN_IDLE));
System.setProperty(JdbcPool.MAX_WAIT_MILLIS, prop.getProperty(JdbcPool.MAX_WAIT_MILLIS));
new Thread(() -> loadFaceDb()).start();
} else {
face_db_status = FacedbStatus.INACTIV;
logger.warn("没有配置sqlite.db.file,人脸注册(register)及人脸搜索(1 v N)功能将无法使用!!!");
}
logger.info("Seetaface init completed!!!");
}
/**
* 加载人脸库
*/
private static void loadFaceDb() {
if (face_db_status != FacedbStatus.READY) {
return;
}
if (System.getProperty("seetaface.db") == null) {
face_db_status = FacedbStatus.INACTIV;
logger.error("没有配置sqlite.db.file!!!");
return;
}
face_db_status = FacedbStatus.LOADING;
logger.info("load face data...");
FaceDao.clearIndex();
int pageNo = 0, pageSize = 100;
while (true) {
List list = FaceDao.findFaceImgs(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++;
}
face_db_status = FacedbStatus.OK;
}
/**
* 将历史注册过的所有人脸重新加载到内存库中
*
* @param key 人脸照片唯一标识
* @param face 人脸照片
* @return
* @throws IOException
*/
private static void register(String key, FaceIndex face) {
SeetaImageData imageData = new SeetaImageData(face.getWidth(), face.getHeight(), face.getChannel());
imageData.data = face.getImgData();
int index = seeta.register(imageData);
if (index < 0) {
logger.info("Register face fail: key={}, index={}", key, index);
return;
}
FaceIndex faceIndex = new FaceIndex();
faceIndex.setKey(key);
faceIndex.setIndex(index);
FaceDao.saveOrUpdateIndex(faceIndex);
logger.info("Register face success: key={}, index={}", key, index);
}
private static Properties getConfig() {
Properties properties = new Properties();
String location = "classpath:/seetaface.properties";
try (InputStream is = new DefaultResourceLoader().getResource(location).getInputStream()) {
properties.load(is);
logger.debug("seetaface config: {}", properties.toString());
} catch (IOException ex) {
logger.error("Could not load property file:" + location, ex);
}
return properties;
}
}
================================================
FILE: src/main/java/com/cnsugar/ai/face/bean/FaceIndex.java
================================================
package com.cnsugar.ai.face.bean;
import java.io.Serializable;
/**
* @Author Sugar
* @Version 2019/4/22 17:14
*/
public class FaceIndex implements Serializable {
private String key;
private int index;
private byte[] imgData;
private int width;
private int height;
private int channel;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public byte[] getImgData() {
return imgData;
}
public void setImgData(byte[] imgData) {
this.imgData = imgData;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getChannel() {
return channel;
}
public void setChannel(int channel) {
this.channel = channel;
}
}
================================================
FILE: src/main/java/com/cnsugar/ai/face/bean/Result.java
================================================
package com.cnsugar.ai.face.bean;
import com.seetaface2.model.RecognizeResult;
import java.io.Serializable;
/**
* @Author Sugar
* @Version 2019/4/22 17:50
*/
public class Result implements Serializable {
private String key;
private float similar;
public Result() {
}
public Result(RecognizeResult result) {
this.similar = result.similar;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public float getSimilar() {
return similar;
}
public void setSimilar(float similar) {
this.similar = similar;
}
@Override
public String toString() {
return "{" +
"" + key +
": " + similar +
'}';
}
}
================================================
FILE: src/main/java/com/cnsugar/ai/face/dao/FaceDao.java
================================================
package com.cnsugar.ai.face.dao;
import com.cnsugar.ai.face.bean.FaceIndex;
import com.cnsugar.common.sqlite.RowMapper;
import com.cnsugar.common.sqlite.SqliteUtils;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* @Author Sugar
* @Version 2019/4/22 17:14
*/
public class FaceDao {
public static String findKeyByIndex(int index) {
try {
return SqliteUtils.queryForString("select \"key\" from face_index where \"index\"=" + index);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public static boolean saveOrUpdate(FaceIndex index) {
try {
SqliteUtils.executeUpdate("INSERT OR REPLACE INTO face_img (\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?)", new Object[]{index
.getKey(), index.getImgData(), index.getWidth(), index.getHeight(), index.getChannel()});
SqliteUtils.executeUpdate("INSERT OR REPLACE INTO face_index (\"index\",\"key\") VALUES (?,?)", new Object[]{index
.getIndex(), index.getKey()});
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static boolean saveOrUpdateIndex(FaceIndex index) {
try {
SqliteUtils.executeUpdate("INSERT OR REPLACE INTO face_index (\"index\",\"key\") VALUES (?,?)", new Object[]{index
.getIndex(), index.getKey()});
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static boolean clearIndex() {
try {
SqliteUtils.executeUpdate("DELETE FROM face_index");
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static List findFaceImgs(int pageNo, int pageSize) {
String sql = "select \"key\",\"img_data\",\"width\",\"height\",\"channel\" from face_img " +
" limit " + pageNo * pageSize + "," + pageSize;
try {
return SqliteUtils.queryForList(sql, new RowMapper() {
@Override
public FaceIndex mapRow(ResultSet rs) throws SQLException {
FaceIndex face = new FaceIndex();
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;
}
});
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static boolean deleteFaceImg(String... keys) {
StringBuilder in = new StringBuilder();
for (int i = 0; i < keys.length; i++) {
if (i > 0) {
in.append(",");
}
in.append("?");
}
try {
SqliteUtils.executeUpdate("DELETE FROM face_img where key in ("+in+")", keys);
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static boolean clearImg() {
try {
SqliteUtils.executeUpdate("DELETE FROM face_img");
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
}
================================================
FILE: src/main/java/com/cnsugar/ai/face/utils/ImageUtils.java
================================================
package com.cnsugar.ai.face.utils;
//import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
//import java.awt.image.ColorConvertOp;
import java.awt.image.ComponentSampleModel;
import java.util.Arrays;
/**
* @Author Sugar
* @Version 2019/4/4 16:05
*/
public class ImageUtils {
/**
* @param image
* @param bandOffset 用于推断通道顺序
* @return
*/
private static boolean equalBandOffsetWith3Byte(BufferedImage image, int[] bandOffset) {
if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) {
if (image.getData().getSampleModel() instanceof ComponentSampleModel) {
ComponentSampleModel sampleModel = (ComponentSampleModel) image.getData().getSampleModel();
if (Arrays.equals(sampleModel.getBandOffsets(), bandOffset)) {
return true;
}
}
}
return false;
}
/**
* 推断图像是否为BGR格式
*
* @return
*/
public static boolean isBGR3Byte(BufferedImage image) {
return equalBandOffsetWith3Byte(image, new int[]{0, 1, 2});
}
/**
* 对图像解码返回BGR格式矩阵数据
*
* @param image
* @return
*/
public static byte[] getMatrixBGR(BufferedImage image) {
byte[] matrixBGR;
if (isBGR3Byte(image)) {
matrixBGR = (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
} else {
// ARGB格式图像数据
int intrgb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
matrixBGR = new byte[image.getWidth() * image.getHeight() * 3];
// ARGB转BGR格式
for (int i = 0, j = 0; i < intrgb.length; ++i, j += 3) {
matrixBGR[j] = (byte) (intrgb[i] & 0xff);
matrixBGR[j + 1] = (byte) ((intrgb[i] >> 8) & 0xff);
matrixBGR[j + 2] = (byte) ((intrgb[i] >> 16) & 0xff);
}
}
return matrixBGR;
}
/**
* 判断图像是否为RGB格式
*
* @return
*/
// public static boolean isRGB3Byte(BufferedImage image) {
// return equalBandOffsetWith3Byte(image, new int[]{2, 1, 0});
// }
/**
* * 对图像解码返回RGB格式矩阵数据
* * @param image
* * @return
*
*/
// public static byte[] getMatrixRGB(BufferedImage image) {
// byte[] matrixRGB;
// if (isRGB3Byte(image)) {
// matrixRGB = (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
// } else {
// // 转RGB格式
// BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
// new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgbImage);
// matrixRGB = (byte[]) rgbImage.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
// }
// return matrixRGB;
// }
public static BufferedImage bgrToBufferedImage(byte[] data, int width, int height) {
int type = BufferedImage.TYPE_3BYTE_BGR;
// bgr to rgb
byte b;
for (int i = 0; i < data.length; i = i + 3) {
b = data[i];
data[i] = data[i + 2];
data[i + 2] = b;
}
BufferedImage image = new BufferedImage(width, height, type);
image.getRaster().setDataElements(0, 0, width, height, data);
return image;
}
/**
* 裁剪图片方法
*
* @param bufferedImage 图像源
* @param startX 裁剪开始x坐标
* @param startY 裁剪开始y坐标
* @param endX 裁剪结束x坐标
* @param endY 裁剪结束y坐标
* @return
*/
// public static BufferedImage cropImage(BufferedImage bufferedImage, int startX, int startY, int endX, int endY) {
// int width = bufferedImage.getWidth();
// int height = bufferedImage.getHeight();
// if (startX == -1) {
// startX = 0;
// }
// if (startY == -1) {
// startY = 0;
// }
// if (endX == -1) {
// endX = width - 1;
// }
// if (endY == -1) {
// endY = height - 1;
// }
// BufferedImage result = new BufferedImage(endX - startX, endY - startY, 4);
// for (int x = startX; x < endX; ++x) {
// for (int y = startY; y < endY; ++y) {
// int rgb = bufferedImage.getRGB(x, y);
// result.setRGB(x - startX, y - startY, rgb);
// }
// }
// return result;
// }
}
================================================
FILE: src/main/java/com/cnsugar/common/sqlite/JdbcPool.java
================================================
package com.cnsugar.common.sqlite;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @Author Sugar
* @Version 2019/4/23 17:11
*/
public class JdbcPool {
private volatile static JdbcPool pool;
public static final String MAX_TOTAL = "sqlite.conn.maxTotal";
public static final String MAX_IDLE = "sqlite.conn.maxIdle";
public static final String MIN_IDLE = "sqlite.conn.minIdle";
public static final String MAX_WAIT_MILLIS = "sqlite.conn.maxWaitMillis";
public static JdbcPool getInstance() {
if (pool == null) {
synchronized (JdbcPool.class) {
if (pool == null) {
pool = new JdbcPool();
}
}
}
return pool;
}
private static GenericObjectPool connPool;
private JdbcPool() {
connPool = new GenericObjectPool(new JdbcPoolFactory(), getDefaultConfig());
}
private GenericObjectPoolConfig getDefaultConfig() {
GenericObjectPoolConfig conf = new GenericObjectPoolConfig();
conf.setMaxTotal(Integer.parseInt(System.getProperty(MAX_TOTAL, "100")));
conf.setMaxIdle(Integer.parseInt(System.getProperty(MAX_IDLE, "0")));
conf.setMinIdle(Integer.parseInt(System.getProperty(MIN_IDLE, "0")));
conf.setMaxWaitMillis(Integer.parseInt(System.getProperty(MAX_WAIT_MILLIS, "60000")));
return conf;
}
public Connection getConnection() {
try {
return connPool.borrowObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void close(PreparedStatement ps, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
}
}
}
public static void returnConnection(Connection conn) {
connPool.returnObject(conn);
}
public static void returnConnectionAndClose(Connection conn, PreparedStatement ps, ResultSet rs) {
close(ps, rs);
returnConnection(conn);
}
}
================================================
FILE: src/main/java/com/cnsugar/common/sqlite/JdbcPoolFactory.java
================================================
package com.cnsugar.common.sqlite;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.DriverManager;
/**
* @Author Sugar
* @Version 2019/4/23 17:16
*/
public class JdbcPoolFactory extends BasePooledObjectFactory {
private static final Logger logger = LoggerFactory.getLogger(JdbcPoolFactory.class);
private static final String DRIVER = "org.sqlite.JDBC";
public static final String DB_NAME = "seetaface.db";
private static String URL;
static {
String db = System.getProperty(DB_NAME);
if (db == null || db.isEmpty()) {
logger.error("System.getProperty(\"{}\") is null", DB_NAME);
} else {
URL = "jdbc:sqlite:" + db;
logger.info(URL);
}
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public Connection create() throws Exception {
if (URL == null) {
return null;
}
return DriverManager.getConnection(URL);
}
@Override
public PooledObject wrap(Connection conn) {
return new DefaultPooledObject(conn);
}
}
================================================
FILE: src/main/java/com/cnsugar/common/sqlite/RowMapper.java
================================================
package com.cnsugar.common.sqlite;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @Author Sugar
* @Version 2019/4/23 17:46
*/
public interface RowMapper {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
*
* @param rs the ResultSet to map (pre-initialized for the current row)
* @return the result object for the current row
* @throws SQLException if a SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
T mapRow(ResultSet rs) throws SQLException;
}
================================================
FILE: src/main/java/com/cnsugar/common/sqlite/SqliteUtils.java
================================================
package com.cnsugar.common.sqlite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author Sugar
* @Version 2019/4/23 16:38
*/
public class SqliteUtils {
private static final Logger logger = LoggerFactory.getLogger(SqliteUtils.class);
/**
* 执行sql查询
*
* @param sql select 语句
* @return 查询结果
* @throws SQLException
*/
public static String queryForString(String sql) throws SQLException {
Connection conn = JdbcPool.getInstance().getConnection();
try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);) {
if (rs.next()) {
return rs.getString(1);
}
return null;
} finally {
JdbcPool.returnConnection(conn);
}
}
/**
* 执行select查询,返回对象
*
* @param sql select 语句
* @param mapper 结果集的行数据处理类对象
* @return
* @throws SQLException
*/
public static T queryForObject(String sql, RowMapper mapper) throws SQLException {
Connection conn = JdbcPool.getInstance().getConnection();
try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);) {
if (rs.next()) {
return mapper.mapRow(rs);
}
} finally {
JdbcPool.returnConnection(conn);
}
return null;
}
/**
* 执行select查询,返回结果列表
*
* @param sql select 语句
* @param mapper 结果集的行数据处理类对象
* @return
* @throws SQLException
*/
public static List queryForList(String sql, RowMapper mapper) throws SQLException {
List list = new ArrayList();
logger.debug("queryForList: sql={}", sql);
Connection conn = JdbcPool.getInstance().getConnection();
try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);) {
while (rs.next()) {
list.add(mapper.mapRow(rs));
}
} finally {
JdbcPool.returnConnection(conn);
}
return list;
}
/**
* 执行数据库更新sql语句
*
* @param sql
* @return 更新行数
* @throws SQLException
*/
public static int executeUpdate(String sql) throws SQLException {
return executeUpdate(sql, null);
}
/**
* 执行数据库更新sql语句
*
* @param sql
* @return 更新行数
* @throws SQLException
*/
public static int executeUpdate(String sql, Object[] args) throws SQLException {
logger.debug("executeUpdate: sql={}, parameter={}", sql, arrayToString(args));
Connection conn = JdbcPool.getInstance().getConnection();
if (args == null || args.length == 0) {
try (Statement stmt = conn.createStatement()) {
return stmt.executeUpdate(sql);
} finally {
JdbcPool.returnConnection(conn);
}
} 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 {
JdbcPool.returnConnection(conn);
}
}
}
/**
* Object[] 转成 String
*
* @param objs 对象数组
* @return
*/
protected static String arrayToString(Object[] objs) {
if (objs == null) {
return "[]";
}
StringBuffer buf = new StringBuffer();
buf.append("[");
for (int j = 0; j < objs.length; j++) {
if (j > 0) {
buf.append(", ");
}
buf.append(String.valueOf(objs[j]));
}
buf.append("]");
return buf.toString();
}
}
================================================
FILE: src/main/java/com/seetaface2/SeetaFace2JNI.java
================================================
package com.seetaface2;
import com.seetaface2.model.*;
/**
* SeetaFace2JNI接口
*/
public class SeetaFace2JNI {
/**
* 初始化,指定人脸识别模型文件目录,该目录下应当包括这3个文件:
* SeetaFaceDetector2.0.ats,
* SeetaFaceRecognizer2.0.ats,
* SeetaPointDetector2.0.pts5.ats
*
* @param modelDir
* @return
*/
public native synchronized boolean initModel(String modelDir);
/**
* 检测人脸
*
* @param img
* @return
*/
public native synchronized SeetaRect[] detect(SeetaImageData img);
/**
* 特征对齐
*
* @param img
* @param faces
* @return
*/
public native synchronized SeetaPointF[] detect(SeetaImageData img, SeetaRect[] faces);
/**
* 1 v 1 人脸比对
*
* @param img1
* @param img2
* @return 相似度范围在0~1,返回负数表示出错
*/
public native synchronized float compare(SeetaImageData img1, SeetaImageData img2);
/**
* 注册人脸
*
* @param img
* @return The returned value is the index of face database. Reture -1 if failed
*/
public native synchronized int register(SeetaImageData img);
/**
* Recognize face and get the most similar face index
*
* @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 synchronized RecognizeResult recognize(SeetaImageData img);
/**
* Clear face database
*/
public native synchronized void clear();
/**
* 人脸提取
*
* @param img
* @return The returned value is face data. Reture null if failed
*/
public native synchronized byte[] crop(SeetaImageData img);
}
================================================
FILE: src/main/java/com/seetaface2/model/FaceLandmark.java
================================================
package com.seetaface2.model;
public class FaceLandmark {
public SeetaRect[] rects;
public SeetaPointF[] points;
}
================================================
FILE: src/main/java/com/seetaface2/model/RecognizeResult.java
================================================
package com.seetaface2.model;
/**
* @Author Sugar
* @Version 2019/4/4 18:07
*/
public class RecognizeResult {
public int index;
public float similar;
}
================================================
FILE: src/main/java/com/seetaface2/model/RegisterData.java
================================================
package com.seetaface2.model;
/**
* @Author Sugar
* @Version 2019/4/23 11:03
*/
public class RegisterData {
public byte[] data;
public int index;
}
================================================
FILE: src/main/java/com/seetaface2/model/SeetaImageData.java
================================================
package com.seetaface2.model;
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;
}
================================================
FILE: src/main/java/com/seetaface2/model/SeetaPointF.java
================================================
package com.seetaface2.model;
public class SeetaPointF {
public double x;
public double y;
}
================================================
FILE: src/main/java/com/seetaface2/model/SeetaRect.java
================================================
package com.seetaface2.model;
public class SeetaRect {
public int x;
public int y;
public int width;
public int height;
}
================================================
FILE: src/main/resources/log4j2.xml
================================================
logs
================================================
FILE: src/main/resources/seetaface.properties
================================================
#依赖的lib名称,注意依赖关系顺序,用逗号隔开
#linux os
#libs=holiday,SeetaFaceDetector200,SeetaPointDetector200,SeetaFaceRecognizer200,SeetaFaceCropper200,SeetaFace2JNI
#windows os
libs=libgcc_s_sjlj-1,libeay32,libquadmath-0,ssleay32,libgfortran-3,libopenblas,holiday,SeetaFaceDetector200,SeetaPointDetector200,SeetaFaceRecognizer200,SeetaFaceCropper200,SeetaFace2JNI
#依赖的lib存放目录, 等同于-Djava.library.path=
#linux os
#libs.path=/usr/local/seetaface2/lib
#windows os
libs.path=F:\\ai-face\\SeetaFace2JNI\\x64\\Release;F:\\ai-face\\SeetaFaceEngine2\\win\\lib\\x64
#seetaface model目录
#linux os
#bindata.dir=/usr/local/seetaface2/bindata
#windows os
bindata.dir=F:\\ai-face\\SeetaFaceEngine2\\bindata
##sqlite config
sqlite.db.file=./faces-data.db
sqlite.conn.maxTotal=50
sqlite.conn.maxIdle=5
sqlite.conn.minIdle=0
sqlite.conn.maxWaitMillis=60000
================================================
FILE: src/test/java/Test.java
================================================
import com.cnsugar.ai.face.FaceHelper;
import com.cnsugar.ai.face.SeetafaceBuilder;
import com.cnsugar.ai.face.bean.Result;
import com.seetaface2.model.SeetaRect;
import org.apache.commons.io.FileUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
public class Test {
private void init() {
SeetafaceBuilder.build();//系统启动时先调用初始化方法
//等待初始化完成
while (SeetafaceBuilder.getFaceDbStatus() == SeetafaceBuilder.FacedbStatus.LOADING || SeetafaceBuilder.getFaceDbStatus() == SeetafaceBuilder.FacedbStatus.READY) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@org.junit.Test
public void testCompare() throws Exception {
init();
String img1 = "F:\\ai\\demo-pic39.jpg";
String img2 = "F:\\ai\\left_pic_one.jpg";
System.out.println("result:" + FaceHelper.compare(new File(img1), new File(img2)));
}
@org.junit.Test
public void testRegister() throws IOException {
//将F:\ai\star目录下的jpg、png图片都注册到人脸库中,以文件名为key
Collection files = FileUtils.listFiles(new File("F:\\ai\\star"), new String[]{"jpg", "png"}, false);
for (File file : files) {
String key = file.getName();
try {
FaceHelper.register(key, FileUtils.readFileToByteArray(file));
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(1);
}
@org.junit.Test
public void testSearch() throws IOException {
init();
long l = System.currentTimeMillis();
Result result = FaceHelper.search(FileUtils.readFileToByteArray(new File("F:\\ai\\gtl.jpg")));
System.out.println("搜索结果:" + result + ", 耗时:" + (System.currentTimeMillis() - l));
}
@org.junit.Test
public void testDetect() throws IOException {
SeetaRect[] rects = FaceHelper.detect(FileUtils.readFileToByteArray(new File("F:\\ai\\刘诗诗-bbbbbbbbbbbbbbbbbb.jpg")));
if (rects != null) {
for (SeetaRect rect : rects) {
System.out.println("x="+rect.x+", y="+rect.y+", width="+rect.width+", height="+rect.height);
}
}
}
@org.junit.Test
public void testCorp() throws IOException {
BufferedImage image = FaceHelper.crop(FileUtils.readFileToByteArray(new File("F:\\ai\\刘诗诗-bbbbbbbbbbbbbbbbbb.jpg")));
if (image != null) {
ImageIO.write(image, "jpg", new File("F:\\ai\\corp-face1.jpg"));
}
}
@org.junit.Test
public void testDelete() {
FaceHelper.removeRegister("Angelababy.jpg", "乔欣.jpg");
}
}