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