從零玩轉人臉識別
前言
線上demo
(前往享受人臉識別)
文章作者個人部落格
(前往作者部落格)
本期教程人臉識別第三方平臺為虹軟科技,本文章講解的是人臉識別RGB活體追蹤技術,免費的功能很多可以自行搭配,希望在你看完本章課程有所收穫。
ArcFace 離線SDK,包含人臉檢測、性別檢測、年齡檢測、人臉識別、影象質量檢測、RGB活體檢測、IR活體檢測等能力,初次使用時需聯網啟用,啟用後即可在本地無網路環境下工作,可根據具體的業務需求結合人臉識別SDK靈活地進行應用層開發。
功能介紹
1. 人臉檢測
對傳入的影象資料進行人臉檢測,返回人臉的邊框以及朝向資訊,可用於後續的人臉識別、特徵提取、活體檢測等操作;
- 支援IMAGE模式和VIDEO模式人臉檢測。
- 支援單人臉、多人臉檢測,最多支援檢測人臉數為50。
2.人臉追蹤
對來自於視訊流中的影象資料,進行人臉檢測,並對檢測到的人臉進行持續跟蹤。(我們是實時的所以就只能使用第三方操作,先不使用這個)
3.人臉特徵提取
提取人臉特徵資訊,用於人臉的特徵比對。
4.人臉屬性檢測
人臉屬性,支援檢測年齡、性別以及3D角度。
人臉3D角度:俯仰角(pitch), 橫滾角(roll), 偏航角(yaw)。
5.活體檢測
離線活體檢測,靜默式識別,在人臉識別過程中判斷操作使用者是否為真人,有效防禦照片、視訊、紙張等不同型別的作弊攻擊,提高業務安全性,讓人臉識別更安全、更快捷,體驗更佳。支援單目RGB活體檢測、雙目(IR/RGB)活體檢測,可滿足各類人臉識別終端產品活體檢測應用。
開造
訪問地址: https://ai.arcsoft.com.cn/technology/faceTracking.html
進入開發者中心進行註冊以及認證個人資訊
1. 點選我的應用 > 新建應用
2.填寫資訊立即建立 點選 新增SDK
3.選中免費版人臉識別
4. 填寫授權碼資訊
選擇平臺先選擇windows的根據你的電腦配置來 是64位還是32位的, 語言選擇Java
5. 介紹sdk檔案
構建專案工程
匯入專案依賴
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> <druid-spring-boot-starter.version>1.2.6</druid-spring-boot-starter.version> <mybatis-spring-boot.version>2.1.4</mybatis-spring-boot.version> <pagehelper.boot.version>1.3.0</pagehelper.boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 人臉識別 --> <dependency> <groupId>com.arcsoft.face</groupId> <artifactId>arcsoft-sdk-face</artifactId> <scope>system</scope> <systemPath>${project.basedir}/lib/arcsoft-sdk-face-3.0.0.0.jar</systemPath> <version>3.0.0.0</version> </dependency> <!-- pool 物件池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency> <!-- Mysql驅動包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis-plus 增強CRUD --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency> <!-- pagehelper 分頁外掛 內建mybatis 依賴--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>${pagehelper.boot.version}</version> </dependency> <!-- 阿里資料來源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid-spring-boot-starter.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!-- Spring框架基本的核心工具 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <!-- JSON工具類 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.0</version> </dependency> <!-- SpringWeb模組 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <!-- servlet包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <!-- aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>top.yangbuyi.YangbuyiFaceDemoApplication</mainClass> <!-- 檔案要配置includeSystemScope屬性,否則可能會導致arcsoft-sdk-face-3.0.0.0.jar獲取不到 --> <includeSystemScope>true</includeSystemScope> <fork>true</fork> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
使用程式碼生成器生成CURD
版本請對應圖片當中的jebat全家桶群裡獲取3秒破解使用
生成完畢後在根目錄建立lib目錄將下載下來的人臉識別依賴匯入->右擊新增到庫
application.yml 修改配置檔案
# 開發環境配置
server:
# 伺服器的HTTP埠,預設為8080
port: 8080
servlet:
# 應用的訪問路徑
context-path: /
tomcat:
# tomcat的URI編碼
uri-encoding: UTF-8
# tomcat最大執行緒數,預設為200
max-threads: 800
# Tomcat啟動初始化的執行緒數,預設值25
min-spare-threads: 30
# Spring配置
spring:
# 同時執行其它配置檔案
profiles:
active: druid
mvc: # 把前端的接收到的時間格式 格式化為 yyyy-MM-dd HH:mm:ss
date-format: yyyy-MM-dd HH:mm:ss
jackson: # 把後臺的時間格式 格式化為 yyyy-MM-dd HH:mm:ss
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# 服務模組
devtools:
restart:
# 熱部署開關
enabled: true
# MyBatis配置
mybatis-plus:
# 搜尋指定包別名
typeAliasesPackage: top.yangbuyi.domain
# 配置mapper的掃描,找到所有的mapper.xml對映檔案
mapperLocations: classpath*:mapper/*Mapper.xml
# 載入全域性的配置檔案
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分頁外掛
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
# 人臉識別配置
# WIND64
config:
sdk-lib-path: M:\yangbuyiya-RBAC\libs\WIN64
app-id: 4QKtmacvsKqaCsoXyyujcs21JTAr79pTczPdZpuaEjhH
sdk-key: EgBjrmidnqstaL46msfHukeKanYXCujzeHokf2qcC3br
thread-pool-size: 5
application-druid.yml 資料來源配置
# 資料來源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
url: jdbc:mysql://127.0.0.1:3308/face?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 初始連線數
initialSize: 5
# 最小連線池數量
minIdle: 10
# 最大連線池數量
maxActive: 20
# 配置獲取連線等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一個連線在池中最大生存的時間,單位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置檢測連線是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 設定白名單,不填則允許所有訪問
allow:
url-pattern: /yangbuyi/druid/*
filter:
stat:
enabled: true
# 慢SQL記錄
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
resources 下建立mybatis資料夾建立mybatis-config.xml檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/> <!-- 全域性對映器啟用快取 -->
<setting name="useGeneratedKeys" value="true"/> <!-- 允許 JDBC 支援自動生成主鍵 -->
<setting name="defaultExecutorType" value="REUSE"/> <!-- 配置預設的執行器 -->
<setting name="logImpl" value="SLF4J"/> <!-- 指定 MyBatis 所用日誌的具體實現 -->
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> 駝峰式命名 -->
</settings>
</configuration>
專案基礎檔案配置
建立config檔案
建立ApplicationConfig全域性配置類
/**
* 程式註解配置
*
* @author yangbuyi
*/
@Configuration
// 表示通過aop框架暴露該代理物件,AopContext能夠訪問
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要掃描的Mapper類的包的路徑
@MapperScan("top.yangbuyi.mapper")
public class ApplicationConfig {
/**
* 時區配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization () {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
/**
* 處理Long型別精度丟失
*
* @return
*/
@Bean("jackson2ObjectMapperBuilderCustomizer")
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer () {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance)
.serializerByType(Long.TYPE, ToStringSerializer.instance);
}
}
建立全域性MybatisPlusConfig配置
/**
* @program: yangbuyi-rbac
* @ClassName: MybatisPlusConfig
* @create: 2022-04-25 15:48
* @author: yangbuyi.top
* @since: JDK1.8
* @MybatisPlusConfig: $
**/
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor () {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分頁外掛
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 樂觀鎖外掛
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 阻斷外掛
interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
return interceptor;
}
/**
* 分頁外掛,自動識別資料庫型別 https://baomidou.com/guide/interceptor-pagination.html
*/
public PaginationInnerInterceptor paginationInnerInterceptor () {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 設定資料庫型別為mysql
paginationInnerInterceptor.setDbType(DbType.MYSQL);
// 設定最大單頁限制數量,預設 500 條,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 樂觀鎖外掛 https://baomidou.com/guide/interceptor-optimistic-locker.html
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor () {
return new OptimisticLockerInnerInterceptor();
}
/**
* 如果是對全表的刪除或更新操作,就會終止該操作 https://baomidou.com/guide/interceptor-block-attack.html
*/
public BlockAttackInnerInterceptor blockAttackInnerInterceptor () {
return new BlockAttackInnerInterceptor();
}
}
建立dto資料夾
建立人臉返回實體 FaceSearchResDto
/**
* @author yangbuyi.top
*/
@Data
public class FaceSearchResDto {
/**
* 唯一人臉Id
*/
private String faceId;
/**
* 人臉名稱
*/
private String name;
private Integer similarValue;
/**
* 年齡
*/
private Integer age;
/**
* 性別
*/
private String gender;
/**
* 圖片
*/
private String image;
}
建立人臉對映 FaceUserInfo
/**
* @author yangbuyi.top
*/
@Data
public class FaceUserInfo {
private int id;
private int groupId;
private String faceId;
private String name;
private Integer similarValue;
private byte[] faceFeature;
}
建立年齡對映
/**
* @author yangbuyi.top
*/
public class ProcessInfo {
private Integer age;
private Integer gender;
public Integer getAge () {
return age;
}
public void setAge (Integer age) {
this.age = age;
}
public Integer getGender () {
return gender;
}
public void setGender (Integer gender) {
this.gender = gender;
}
}
建立enums資料夾
建立ErrorCodeEnum列舉類
/**
* @author yangbuyi.top
*/
public enum ErrorCodeEnum {
MOK(0, "成功"),
UNKNOWN(1, "未知錯誤"),
INVALID_PARAM(2, "無效引數"),
UNSUPPORTED(3, "引擎不支援"),
NO_MEMORY(4, "記憶體不足"),
BAD_STATE(5, "狀態錯誤"),
USER_CANCEL(6, "使用者取消相關操作"),
EXPIRED(7, "操作時間過期"),
USER_PAUSE(8, "使用者暫停操作"),
BUFFER_OVERFLOW(9, "緩衝上溢"),
BUFFER_UNDERFLOW(10, "緩衝下溢"),
NO_DISKSPACE(11, "存貯空間不足"),
COMPONENT_NOT_EXIST(12, "元件不存在"),
GLOBAL_DATA_NOT_EXIST(13, "全域性資料不存在"),
NO_FACE_DETECTED(14, "未檢出到人臉"),
FACE_DOES_NOT_MATCH(15, "人臉不匹配"),
INVALID_APP_ID(28673, "無效的AppId"),
INVALID_SDK_ID(28674, "無效的SdkKey"),
INVALID_ID_PAIR(28675, "AppId和SdkKey不匹配"),
MISMATCH_ID_AND_SDK(28676, "SdkKey 和使用的SDK 不匹配"),
SYSTEM_VERSION_UNSUPPORTED(28677, "系統版本不被當前SDK所支援"),
LICENCE_EXPIRED(28678, "SDK有效期過期,需要重新下載更新"),
APS_ENGINE_HANDLE(69633, "引擎控制代碼非法"),
APS_MEMMGR_HANDLE(69634, "記憶體控制代碼非法"),
APS_DEVICEID_INVALID(69635, " Device ID 非法"),
APS_DEVICEID_UNSUPPORTED(69636, "Device ID 不支援"),
APS_MODEL_HANDLE(69637, "模板資料指標非法"),
APS_MODEL_SIZE(69638, "模板資料長度非法"),
APS_IMAGE_HANDLE(69639, "影象結構體指標非法"),
APS_IMAGE_FORMAT_UNSUPPORTED(69640, "影象格式不支援"),
APS_IMAGE_PARAM(69641, "影象引數非法"),
APS_IMAGE_SIZE(69642, "影象尺寸大小超過支援範圍"),
APS_DEVICE_AVX2_UNSUPPORTED(69643, "處理器不支援AVX2指令"),
FR_INVALID_MEMORY_INFO(73729, "無效的輸入記憶體"),
FR_INVALID_IMAGE_INFO(73730, "無效的輸入影象引數"),
FR_INVALID_FACE_INFO(73731, "無效的臉部資訊"),
FR_NO_GPU_AVAILABLE(73732, "當前裝置無GPU可用"),
FR_MISMATCHED_FEATURE_LEVEL(73733, "待比較的兩個人臉特徵的版本不一致"),
FACEFEATURE_UNKNOWN(81921, "人臉特徵檢測錯誤未知"),
FACEFEATURE_MEMORY(81922, "人臉特徵檢測記憶體錯誤"),
FACEFEATURE_INVALID_FORMAT(81923, "人臉特徵檢測格式錯誤"),
FACEFEATURE_INVALID_PARAM(81924, "人臉特徵檢測引數錯誤"),
FACEFEATURE_LOW_CONFIDENCE_LEVEL(81925, "人臉特徵檢測結果置信度低"),
ASF_EX_BASE_FEATURE_UNSUPPORTED_ON_INIT(86017, "Engine不支援的檢測屬性"),
ASF_EX_BASE_FEATURE_UNINITED(86018, "需要檢測的屬性未初始化"),
ASF_EX_BASE_FEATURE_UNPROCESSED(86019, "待獲取的屬性未在process中處理過"),
ASF_EX_BASE_FEATURE_UNSUPPORTED_ON_PROCESS(86020, "PROCESS不支援的檢測屬性,例如FR,有自己獨立的處理函式"),
ASF_EX_BASE_INVALID_IMAGE_INFO(86021, "無效的輸入影象"),
ASF_EX_BASE_INVALID_FACE_INFO(86022, "無效的臉部資訊"),
ASF_BASE_ACTIVATION_FAIL(90113, "人臉比對SDK啟用失敗,請開啟讀寫許可權"),
ASF_BASE_ALREADY_ACTIVATED(90114, "人臉比對SDK已啟用"),
ASF_BASE_NOT_ACTIVATED(90115, "人臉比對SDK未啟用"),
ASF_BASE_SCALE_NOT_SUPPORT(90116, "detectFaceScaleVal 不支援"),
ASF_BASE_VERION_MISMATCH(90117, "SDK版本不匹配"),
ASF_BASE_DEVICE_MISMATCH(90118, "裝置不匹配"),
ASF_BASE_UNIQUE_IDENTIFIER_MISMATCH(90119, "唯一標識不匹配"),
ASF_BASE_PARAM_NULL(90120, "引數為空"),
ASF_BASE_SDK_EXPIRED(90121, "SDK已過期"),
ASF_BASE_VERSION_NOT_SUPPORT(90122, "版本不支援"),
ASF_BASE_SIGN_ERROR(90123, "簽名錯誤"),
ASF_BASE_DATABASE_ERROR(90124, "資料庫插入錯誤"),
ASF_BASE_UNIQUE_CHECKOUT_FAIL(90125, "唯一識別符號校驗失敗"),
ASF_BASE_COLOR_SPACE_NOT_SUPPORT(90126, "輸入的顏色空間不支援"),
ASF_BASE_IMAGE_WIDTH_NOT_SUPPORT(90127, "輸入影象的byte資料長度不正確"),
ASF_NETWORK_BASE_COULDNT_RESOLVE_HOST(94209, "無法解析主機地址"),
ASF_NETWORK_BASE_COULDNT_CONNECT_SERVER(94210, "無法連線伺服器"),
ASF_NETWORK_BASE_CONNECT_TIMEOUT(94211, "網路連線超時"),
ASF_NETWORK_BASE_UNKNOWN_ERROR(94212, "未知錯誤");
private Integer code;
private String description;
ErrorCodeEnum (Integer code, String description) {
this.code = code;
this.description = description;
}
public Integer getCode () {
return code;
}
public void setCode (Integer code) {
this.code = code;
}
public String getDescription () {
return description;
}
public void setDescription (String description) {
this.description = description;
}
public static ErrorCodeEnum getDescriptionByCode (Integer code) {
for (ErrorCodeEnum errorCodeEnum : ErrorCodeEnum.values()) {
if (code.equals(errorCodeEnum.getCode())) {
return errorCodeEnum;
}
}
return ErrorCodeEnum.UNKNOWN;
}
}
建立utils資料夾
建立Base64DecodeMultipartFile
package top.yangbuyi.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
/**
* @author yangbuyi.top
* @program: yangbuyi-rbac
* @ClassName: Base64DecodeMultipartFile
* @create: 2022-04-25 16:25
* @since: JDK1.8
* @Base64DecodeMultipartFile: $
**/
public class Base64DecodeMultipartFile implements MultipartFile {
private final byte[] imgContent;
private final String header;
public Base64DecodeMultipartFile (byte[] imgContent, String header) {
this.imgContent = imgContent;
this.header = header.split(";")[0];
}
@Override
public String getName () {
return System.currentTimeMillis() + Math.random() + "." + header.split("/")[1];
}
@Override
public String getOriginalFilename () {
return System.currentTimeMillis() + (int) Math.random() * 10000 + "." + header.split("/")[1];
}
@Override
public String getContentType () {
return header.split(":")[1];
}
@Override
public boolean isEmpty () {
return imgContent == null || imgContent.length == 0;
}
@Override
public long getSize () {
return imgContent.length;
}
@Override
public byte[] getBytes () throws IOException {
return imgContent;
}
@Override
public InputStream getInputStream () throws IOException {
return new ByteArrayInputStream(imgContent);
}
@Override
public void transferTo (File dest) throws IOException, IllegalStateException {
new FileOutputStream(dest).write(imgContent);
}
}
建立ImageUtils
package top.yangbuyi.utils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
/**
* 圖片處理工具類
*
* @author yangbuyi.top
*/
public class ImageUtils {
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
public static MultipartFile base64ToMultipartFile (String base64) {
//base64編碼後的圖片有頭資訊所以要分離出來 [0]data:image/png;base64, 圖片內容為索引[1]
String[] baseStrs = base64.split(",");
//取索引為1的元素進行處理
byte[] b = Base64.decodeBase64(baseStrs[1]);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
//處理過後的資料通過Base64DecodeMultipartFile轉換為MultipartFile物件
return new Base64DecodeMultipartFile(b, baseStrs[0]);
}
}
專案基本需要的配置與工具已經建立完畢接下來我們開始人臉識別業務編寫
實現BasePooledObjectFactory自定義引擎工廠
建立factory資料夾->建立FaceEngineFactory類
import com.arcsoft.face.EngineConfiguration;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
/**
* 引擎工廠
* @author yangbuyi.top
*/
@Slf4j
public class FaceEngineFactory extends BasePooledObjectFactory<FaceEngine> {
private final String appId;
private final String sdkKey;
private final String sdkLibPath;
private final EngineConfiguration engineConfiguration;
private final Integer detectFaceMaxNum = 10;
private final Integer detectFaceScaleVal = 16;
private final DetectMode detectMode = DetectMode.ASF_DETECT_MODE_IMAGE;
private final DetectMode detectVideo = DetectMode.ASF_DETECT_MODE_VIDEO;
private final DetectOrient detectFaceOrientPriority = DetectOrient.ASF_OP_0_ONLY;
public FaceEngineFactory (String sdkLibPath, String appId, String sdkKey, EngineConfiguration engineConfiguration) {
this.sdkLibPath = sdkLibPath;
this.appId = appId;
this.sdkKey = sdkKey;
this.engineConfiguration = engineConfiguration;
}
@Override
public FaceEngine create () throws Exception {
FaceEngine faceEngine = new FaceEngine(sdkLibPath);
// 用於線上啟用SDK---
int activeCode = faceEngine.activeOnline(appId, sdkKey);
log.info("線上啟用SDK完畢!,{}", activeCode);
int initCode = faceEngine.init(engineConfiguration);
log.info("初始化功能引擎完畢!,{}", initCode);
return faceEngine;
}
@Override
public PooledObject<FaceEngine> wrap (FaceEngine faceEngine) {
return new DefaultPooledObject<>(faceEngine);
}
@Override
public void destroyObject (PooledObject<FaceEngine> p) throws Exception {
FaceEngine faceEngine = p.getObject();
int unInitCode = faceEngine.unInit();
super.destroyObject(p);
log.info("銷燬物件完畢! faceEngineUnInitCode: {}", unInitCode);
}
}
一、編寫人臉識別業務介面
在service目錄下建立-> FaceEngineService
package top.yangbuyi.service;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.toolkit.ImageInfo;
import top.yangbuyi.dto.FaceUserInfo;
import top.yangbuyi.dto.ProcessInfo;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* 人臉識別介面
*/
public interface FaceEngineService {
/**
* 人臉檢測
* @param imageInfo
* @return
*/
List<FaceInfo> detectFaces (ImageInfo imageInfo);
/**
* 提取年齡-性別
* @param imageInfo
* @return
*/
List<ProcessInfo> process (ImageInfo imageInfo);
/**
* 人臉特徵
* @param imageInfo
* @return
*/
byte[] extractFaceFeature (ImageInfo imageInfo) throws InterruptedException;
/**
* 人臉比對
* @param groupId
* @param faceFeature
* @return
*/
List<FaceUserInfo> compareFaceFeature (byte[] faceFeature, Integer groupId) throws InterruptedException, ExecutionException;
}
service.impl 包下建立 FaceEngineServiceImpl 實現類
import cn.hutool.core.collection.CollectionUtil;
import com.arcsoft.face.*;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.toolkit.ImageInfo;
import com.google.common.collect.Lists;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.yangbuyi.dto.FaceUserInfo;
import top.yangbuyi.dto.ProcessInfo;
import top.yangbuyi.factory.FaceEngineFactory;
import top.yangbuyi.mapper.SysUserFaceInfoMapper;
import top.yangbuyi.service.FaceEngineService;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @author yangbuyi.top
*/
@Service
public class FaceEngineServiceImpl implements FaceEngineService {
public final static Logger logger = LoggerFactory.getLogger(FaceEngineServiceImpl.class);
@Value("${config.sdk-lib-path}")
public String sdkLibPath;
@Value("${config.app-id}")
public String appId;
@Value("${config.sdk-key}")
public String sdkKey;
@Value("${config.thread-pool-size}")
public Integer threadPoolSize;
/**
* 人臉識別引擎
*/
private static FaceEngine faceEngine;
/**
* 相似度
*/
private final Integer passRate = 95;
private ExecutorService executorService;
@Autowired
private SysUserFaceInfoMapper userFaceInfoMapper;
/**
* 執行緒池
*/
private GenericObjectPool<FaceEngine> faceEngineObjectPool;
/**
* 專案啟動時初始化執行緒池與人臉識別引擎配置
*/
@PostConstruct
public void init () {
executorService = Executors.newFixedThreadPool(threadPoolSize);
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxIdle(threadPoolSize);
poolConfig.setMaxTotal(threadPoolSize);
poolConfig.setMinIdle(threadPoolSize);
poolConfig.setLifo(false);
//引擎配置
EngineConfiguration engineConfiguration = new EngineConfiguration();
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);
//功能配置 對應的功能請檢視文件
FunctionConfiguration functionConfiguration = new FunctionConfiguration();
functionConfiguration.setSupportAge(true);
functionConfiguration.setSupportFace3dAngle(true);
functionConfiguration.setSupportFaceDetect(true);
functionConfiguration.setSupportFaceRecognition(true);
functionConfiguration.setSupportGender(true);
functionConfiguration.setSupportLiveness(true);
functionConfiguration.setSupportIRLiveness(true);
engineConfiguration.setFunctionConfiguration(functionConfiguration);
// 底層庫演算法物件池
faceEngineObjectPool = new GenericObjectPool(new FaceEngineFactory(sdkLibPath, appId, sdkKey, engineConfiguration), poolConfig);
try {
faceEngine = faceEngineObjectPool.borrowObject();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 確保精度
* @param value
* @return
*/
private int plusHundred (Float value) {
BigDecimal target = new BigDecimal(value);
BigDecimal hundred = new BigDecimal(100f);
return target.multiply(hundred).intValue();
}
}
人臉識別邏輯
1、必須進行人臉特徵獲取 -> 特徵獲取成功 -> 進入人臉對比 -> 人臉檢測 -> 返回人臉資料
2、必須進行人臉特徵獲取 -> 特徵獲取失敗 -> 直接跳出返回 未檢出到人臉
編寫人臉特徵獲取邏輯
/**
* 人臉特徵
*
* @param imageInfo
* @return
*/
@Override
public byte[]extractFaceFeature(ImageInfo imageInfo)throws InterruptedException{
FaceEngine faceEngine=null;
try{
//獲取引擎物件
faceEngine=faceEngineObjectPool.borrowObject();
//人臉檢測得到人臉列表
List<FaceInfo> faceInfoList=new ArrayList<FaceInfo>();
//人臉檢測
int i=faceEngine.detectFaces(imageInfo.getImageData(),imageInfo.getWidth(),imageInfo.getHeight(),imageInfo.getImageFormat(),faceInfoList);
if(CollectionUtil.isNotEmpty(faceInfoList)){
FaceFeature faceFeature=new FaceFeature();
//提取人臉特徵
faceEngine.extractFaceFeature(imageInfo.getImageData(),imageInfo.getWidth(),imageInfo.getHeight(),imageInfo.getImageFormat(),faceInfoList.get(0),faceFeature);
return faceFeature.getFeatureData();
}
}catch(Exception e){
logger.error("",e);
}finally{
if(faceEngine!=null){
//釋放引擎物件
faceEngineObjectPool.returnObject(faceEngine);
}
}
return null;
}
編寫人臉對比邏輯
/**
* 人臉比對
* @param groupId
* @param faceFeature
* @return 人臉組
*/
@Override
public List<FaceUserInfo> compareFaceFeature(byte[]faceFeature,Integer groupId)throws InterruptedException,ExecutionException{
// 識別到的人臉列表
List<FaceUserInfo> resultFaceInfoList=Lists.newLinkedList();
// 建立人臉特徵物件
FaceFeature targetFaceFeature=new FaceFeature();
targetFaceFeature.setFeatureData(faceFeature);
// 根據分組拿人臉庫,從資料庫中取出人臉庫
List<FaceUserInfo> faceInfoList=userFaceInfoMapper.getUserFaceInfoByGroupId(groupId);
// 分成50一組,多執行緒處理, 資料量大1000組
List<List<FaceUserInfo>>faceUserInfoPartList=Lists.partition(faceInfoList,50);
// 多執行緒
CompletionService<List<FaceUserInfo>>completionService=new ExecutorCompletionService(executorService);
for(List<FaceUserInfo> part:faceUserInfoPartList){
// 開始執行緒掃描人臉匹配度
completionService.submit(new CompareFaceTask(part,targetFaceFeature));
}
// 獲取執行緒任務引數
for(List<FaceUserInfo> faceUserInfos:faceUserInfoPartList){
List<FaceUserInfo> faceUserInfoList=completionService.take().get();
if(CollectionUtil.isNotEmpty(faceInfoList)){
resultFaceInfoList.addAll(faceUserInfoList);
}
}
// 從大到小排序
resultFaceInfoList.sort((h1,h2)->h2.getSimilarValue().compareTo(h1.getSimilarValue()));
return resultFaceInfoList;
}
/**
* 多執行緒跑人臉對比邏輯
*/
private class CompareFaceTask implements Callable<List<FaceUserInfo>> {
private final List<FaceUserInfo> faceUserInfoList;
private final FaceFeature targetFaceFeature;
public CompareFaceTask (List<FaceUserInfo> faceUserInfoList, FaceFeature targetFaceFeature) {
this.faceUserInfoList = faceUserInfoList;
this.targetFaceFeature = targetFaceFeature;
}
@Override
public List<FaceUserInfo> call () throws Exception {
FaceEngine faceEngine = null;
//識別到的人臉列表
List<FaceUserInfo> resultFaceInfoList = Lists.newLinkedList();
try {
faceEngine = faceEngineObjectPool.borrowObject();
for (FaceUserInfo faceUserInfo : faceUserInfoList) {
FaceFeature sourceFaceFeature = new FaceFeature();
// 設定人臉特徵
sourceFaceFeature.setFeatureData(faceUserInfo.getFaceFeature());
FaceSimilar faceSimilar = new FaceSimilar();
faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
//獲取相似值
Integer similarValue = plusHundred(faceSimilar.getScore());
//相似值大於配置預期,加入到識別到人臉的列表
if (similarValue > passRate) {
FaceUserInfo info = new FaceUserInfo();
info.setName(faceUserInfo.getName());
info.setFaceId(faceUserInfo.getFaceId());
info.setSimilarValue(similarValue);
resultFaceInfoList.add(info);
}
}
} catch (Exception e) {
logger.error("", e);
} finally {
if (faceEngine != null) {
faceEngineObjectPool.returnObject(faceEngine);
}
}
return resultFaceInfoList;
}
}
編寫根據根據分組ID獲取人臉庫資料
在mapper下SysUserFaceInfoMapper -> 建立介面 getUserFaceInfoByGroupId
/**
* 根據分組Id
* 從資料庫中取出人臉庫
*
* @param groupId
* @return 人臉庫
*/
List<FaceUserInfo> getUserFaceInfoByGroupId(Integer groupId);
sql實現
<resultMap id="userFace2" type="top.yangbuyi.dto.FaceUserInfo">
<id column="id" property="id" javaType="int"/>
<result column="group_id" property="groupId" javaType="java.lang.Integer"/>
<result column="name" property="name" javaType="java.lang.String"/>
<result column="face_id" property="faceId" javaType="String"/>
<result column="face_feature" property="faceFeature"/>
</resultMap>
<
select id = "getUserFaceInfoByGroupId" resultMap="userFace2" parameterType="java.lang.Integer"
resultType="top.yangbuyi.dto.FaceUserInfo">
select id, group_id, face_id, name, face_feature
from `sys_user_face_info`
where group_id = #{groupId}
< /
select>
二、編寫人臉新增業務介面
userFaceInfoService 新增人臉業務介面
/**
* 新增使用者人臉識別
*
* @param sysUserFaceInfo 使用者人臉識別
* @return 結果
*/
public int insertSysUserFaceInfo(SysUserFaceInfo sysUserFaceInfo);
就是一共簡簡單單的新增插入資料 這個就不用我教了吧?????????
三、 編寫Controller業務控制層
import top.yangbuyi.domain.AjaxResult;
/**
* (sys_user_face_info)表控制層
*
* @author yangbuyi.top
*/
@RestController
@Slf4j
@RequestMapping("face")
@RequiredArgsConstructor
public class SysUserFaceInfoController {
public final static Logger logger = LoggerFactory.getLogger(SysUserFaceInfoController.class);
private final FaceEngineService faceEngineService;
private final SysUserFaceInfoService userFaceInfoService;
/**
* 人臉新增
*
* @param file 人臉附件
* @param groupId 分組id
* @param name 使用者登入名稱
*/
@RequestMapping(value = "/faceAdd", method = RequestMethod.POST)
public AjaxResult faceAdd (@RequestBody Map<String, Object> map) {
// 業務.... yangbuyi.top 版權所有
return AjaxResult.success();
}
/**
* 人臉識別
*
* @param file 人臉附件
* @param groupId 分組ID 方便快速識別
*/
@RequestMapping(value = "/faceSearch", method = RequestMethod.POST)
public AjaxResult faceSearch (@RequestBody Map<String, Object> map) throws Exception {
// 業務... yangbuyi.top 版權所有
return AjaxResult.success();
}
faceAdd 具體業務編寫
- 根據前端傳遞的
file -> 人臉圖片
groupId -> 使用者分組(用於縮小範圍查詢該人員的位置有效減少資料量大的問題)
name -> 當前使用者名稱稱(實際開發當中應該是儲存id)
String file = String.valueOf(map.get("file"));
String groupId = String.valueOf(map.get("groupId"));
String name = String.valueOf(map.get("name"));
try {
if (file == null) {
return AjaxResult.error("file is null");
}
if (groupId == null) {
return AjaxResult.error("file is null");
}
if (name == null) {
return AjaxResult.error("file is null");
}
// 轉換實體
byte[] decode = Base64.decode(base64Process(file));
ImageInfo imageInfo = ImageFactory.getRGBData(decode);
//人臉特徵獲取
byte[] bytes = faceEngineService.extractFaceFeature(imageInfo);
if (bytes == null) {
return AjaxResult.error(ErrorCodeEnum.NO_FACE_DETECTED.getDescription());
}
List<ProcessInfo> processInfoList = faceEngineService.process(imageInfo);
// 開始將人臉識別Base64轉換檔案流->用於檔案上傳
// final MultipartFile multipartFile = ImageUtils.base64ToMultipartFile(file);
final SysUserFaceInfo one = this.userFaceInfoService.lambdaQuery().eq(SysUserFaceInfo::getName, name).one();
// 如果存在則更新人臉和特徵
if (null != one) {
this.userFaceInfoService.lambdaUpdate().set(SysUserFaceInfo::getFaceFeature, bytes)
.set(SysUserFaceInfo::getFpath, "儲存頭像地址")
.eq(SysUserFaceInfo::getFaceId, name).update();
} else {
// 組裝人臉實體
SysUserFaceInfo userFaceInfo = new SysUserFaceInfo();
userFaceInfo.setName(name);
userFaceInfo.setAge(processInfoList.get(0).getAge());
userFaceInfo.setGender(processInfoList.get(0).getGender().shortValue());
userFaceInfo.setGroupId(Integer.valueOf(groupId));
userFaceInfo.setFaceFeature(bytes);
userFaceInfo.setFpath("儲存頭像地址");
// 儲存使用者ID -> 我這裡先使用name代替 -> 假如是唯一性
userFaceInfo.setFaceId(name);
//人臉特徵插入到資料庫
userFaceInfoService.insertSysUserFaceInfo(userFaceInfo);
}
logger.info("faceAdd:" + name);
return AjaxResult.success("人臉繫結成功!");
} catch (Exception e) {
logger.error("", e);
}
// 錯誤返回
return AjaxResult.error(ErrorCodeEnum.UNKNOWN.getDescription());
faceSearch 具體業務編寫
- 根據前端傳遞的
file -> 人臉圖片
groupId -> 使用者分組(用於縮小範圍查詢該人員的位置有效減少資料量大的問題)
String file = String.valueOf(map.get("file"));
String groupId = String.valueOf(map.get("groupId"));
if (groupId == null) {
return AjaxResult.error("groupId is null");
}
byte[] decode = Base64.decode(base64Process(file));
BufferedImage bufImage = ImageIO.read(new ByteArrayInputStream(decode));
ImageInfo imageInfo = ImageFactory.bufferedImage2ImageInfo(bufImage);
//人臉特徵獲取
byte[] bytes = faceEngineService.extractFaceFeature(imageInfo);
// 校驗是否顯示出人臉
if (bytes == null) {
return AjaxResult.error(ErrorCodeEnum.NO_FACE_DETECTED.getDescription());
}
//人臉比對,獲取比對結果
List<FaceUserInfo> userFaceInfoList = faceEngineService.compareFaceFeature(bytes, Integer.valueOf(groupId));
if (CollectionUtil.isNotEmpty(userFaceInfoList)) {
FaceUserInfo faceUserInfo = userFaceInfoList.get(0);
FaceSearchResDto faceSearchResDto = new FaceSearchResDto();
BeanUtil.copyProperties(faceUserInfo, faceSearchResDto);
List<ProcessInfo> processInfoList = faceEngineService.process(imageInfo);
if (CollectionUtil.isNotEmpty(processInfoList)) {
//人臉檢測
List<FaceInfo> faceInfoList = faceEngineService.detectFaces(imageInfo);
int left = faceInfoList.get(0).getRect().getLeft();
int top = faceInfoList.get(0).getRect().getTop();
int width = faceInfoList.get(0).getRect().getRight() - left;
int height = faceInfoList.get(0).getRect().getBottom() - top;
Graphics2D graphics2D = bufImage.createGraphics();
// 紅色
graphics2D.setColor(Color.RED);
BasicStroke stroke = new BasicStroke(5f);
graphics2D.setStroke(stroke);
graphics2D.drawRect(left, top, width, height);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(bufImage, "jpg", outputStream);
byte[] bytes1 = outputStream.toByteArray();
faceSearchResDto.setImage("data:image/jpeg;base64," + Base64Utils.encodeToString(bytes1));
faceSearchResDto.setAge(processInfoList.get(0).getAge());
faceSearchResDto.setGender(processInfoList.get(0).getGender().equals(1) ? "女" : "男");
}
return AjaxResult.success(faceSearchResDto);
}
return AjaxResult.error(ErrorCodeEnum.FACE_DOES_NOT_MATCH.getDescription());
測試介面流程
準備兩張圖片
百度隨便搜尋個線上轉換 Base64
1、人臉新增
新增有臉的
新增測試無臉的
2、人臉識別
有臉的
無臉的
結尾線上演示
(前往享受人臉識別)