本文详细介绍了 java 集成免费虹软人脸识别的详细流程,从 SDK 下载到 SDK 的集成,以及 API 的封装策略。
将将压缩包中的 jar 包,放置在 resource 文件夹下的 lib 文件夹中。
在 pom.xml 中引入本地依赖:
<dependency>
<groupId>com.arcsoftgroupId>
<artifactId>jsonartifactId>
<version>1.0version>
<scope>systemscope>
<systemPath>${project.basedir}/src/main/resources/lib/arcsoft-sdk-face-3.0.0.0.jarsystemPath>
dependency>
关于本地 jar 包的引用方式说明,请转 引入本地 jar 包教程 https://blog.csdn.net/demo_yo/article/details/132495029
将压缩包中的 WIN64 dll 动态链接文件,放置在项目根目录下创建的 lib 文件夹下。
将在人脸识别引擎工厂类中引用改路径,用于引擎加载。如下:
// 加载引擎
FaceEngine faceEngine = new FaceEngine(System.getProperty("user.dir") + File.separator + "lib" + File.separator + "WIN64");
# 人脸认证引擎配置
face-engine:
# 应用id
app-id: xxxxxxxxxxxxxxxxx
# sdk密匙
sdk-key: xxxxxxxxxxxxxxxxx
# 人脸对比阀值(建议0.8)
face-similar-score: 0.8
# RGB活体检测阀值(建议0.5)
rgb-threshold: 0.5
# IR活体检测阀值(建议0.7)
ir-threshold: 0.7
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 人脸认证引擎配置
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "face-engine")
public class FaceEngineProperties {
/**
* appId
*/
private String appId;
/**
* sdkKey
*/
private String sdkKey;
/**
* 人脸对比阀值
*/
private Float faceSimilarScore;
/**
* RGB活体检测阀值
*/
private Float rgbThreshold;
/**
* IR活体检测阀值
*/
private Float irThreshold;
}
import com.arcsoft.face.ActiveFileInfo;
import com.arcsoft.face.EngineConfiguration;
import com.arcsoft.face.FaceEngine;
import com.arcsoft.face.FunctionConfiguration;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.enums.ErrorInfo;
import com.whitemeen.magic.modules.demo.faceengine.config.FaceEngineProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@RequiredArgsConstructor
public class FaceEngineFactory {
private static final String FACE_ENGINE_KEY = "face_engine_key";
private static final String FUNCTION_CONFIGURATION_KEY = "function_configuration_key";
private ConcurrentHashMap<String, FaceEngine> faceEngineMap = new ConcurrentHashMap<>(); // 引擎Bean
private ConcurrentHashMap<String, FunctionConfiguration> functionConfigurationMap = new ConcurrentHashMap<>(); // 引擎配置
private final FaceEngineProperties faceEngineProperties;
/**
* 在bean初始化后进行引擎初始化
*/
@PostConstruct
public void init() {
log.info("进入人脸引擎初始化");
// 加载引擎
FaceEngine faceEngine = new FaceEngine(System.getProperty("user.dir") + File.separator + "lib" + File.separator + "WIN64");
// 激活引擎
int errorCode = faceEngine.activeOnline(faceEngineProperties.getAppId(), faceEngineProperties.getSdkKey());
if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
log.error("人脸引擎激活失败");
return;
}
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
errorCode = faceEngine.getActiveFileInfo(activeFileInfo);
if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
log.error("获取激活文件信息失败");
return;
}
// 引擎配置
EngineConfiguration engineConfiguration = new EngineConfiguration();
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
engineConfiguration.setDetectFaceMaxNum(10);
engineConfiguration.setDetectFaceScaleVal(16);
// 功能配置
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);
// 初始化引擎
errorCode = faceEngine.init(engineConfiguration);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.error("初始化人脸引擎失败");
return;
}
// 将引擎放入map
faceEngineMap.put(FACE_ENGINE_KEY, faceEngine);
// 将引擎配置放入map
functionConfigurationMap.put(FUNCTION_CONFIGURATION_KEY, functionConfiguration);
log.info("人脸引擎初始化完成");
}
/**
* 工厂方法
*
* @return 人脸引擎
*/
public FaceEngine getFaceEngine() {
return faceEngineMap.get(FACE_ENGINE_KEY);
}
/**
* 工厂方法
*
* @return 引擎配置
*/
public FunctionConfiguration getFunctionConfiguration() {
return functionConfigurationMap.get(FUNCTION_CONFIGURATION_KEY);
}
}
import com.arcsoft.face.*;
import com.arcsoft.face.enums.ErrorInfo;
import com.arcsoft.face.toolkit.ImageInfo;
import com.whitemeen.magic.modules.demo.faceengine.factory.FaceEngineFactory;
import com.whitemeen.magic.modules.demo.faceengine.model.FaceEngineR;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import static com.arcsoft.face.toolkit.ImageFactory.getRGBData;
@Slf4j
@Component
@RequiredArgsConstructor
public class FaceEngineService {
private final FaceEngineFactory faceEngineFactory;
/**
* 人脸认证
*
* 建议待检测的图像人脸角度上、下、左、右转向小于30度;
* 图片中人脸尺寸不小于50 x 50像素;
* 图片大小小于10MB;
*
* @param authImageBytes 认证的图片字节
* @param localImageBytes 人脸库的图片字节
* @param faceSimilarScore 人脸对比阀值(范围为[0~1],生活照推荐:0.80,证件照推荐:0.82)
* @param rgbThreshold RGB活体检测阀值(范围为[0~1],推荐:0.5)
* @param irThreshold IR活体检测阀值 (范围为[0~1],推荐:0.7)
* @return 认证结果
*/
public FaceEngineR compareFace(byte[] authImageBytes, byte[] localImageBytes, float faceSimilarScore, float rgbThreshold, float irThreshold) {
// 获取引擎
FaceEngine faceEngine = faceEngineFactory.getFaceEngine();
int errorCode = 0; // 错误码
//人脸检测
ImageInfo imageInfo = getRGBData(authImageBytes);
List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
log.info("人脸检测数据:{}", faceInfoList);
log.info("[errorCode] = {}", errorCode);
if (errorCode == ErrorInfo.MERR_ASF_EX_INVALID_FACE_INFO.getValue()) {
log.info("无效的脸部信息:[errorCode] = {}", errorCode);
return FaceEngineR.failed("未检测到人脸");
} else if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸检测异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸检测异常:[errorCode] = " + errorCode);
}
//特征提取
FaceFeature faceFeature = new FaceFeature();
errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
log.info("人脸特征值大小:{}", faceFeature.getFeatureData().length);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸特征提取异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸特征提取异常:[errorCode] = " + errorCode);
}
//人脸检测2
ImageInfo imageInfo2 = getRGBData(localImageBytes);
List<FaceInfo> faceInfoList2 = new ArrayList<FaceInfo>();
errorCode = faceEngine.detectFaces(imageInfo2.getImageData(), imageInfo2.getWidth(), imageInfo2.getHeight(), imageInfo2.getImageFormat(), faceInfoList2);
log.info("人脸库人脸检测数据:{}", faceInfoList2);
log.info("[errorCode] = {}", errorCode);
if (errorCode == ErrorInfo.MERR_ASF_EX_INVALID_FACE_INFO.getValue()) {
log.info("人脸库无效的脸部信息:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸库未检测到人脸");
} else if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸库人脸检测异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸库人脸检测异常:[errorCode] = " + errorCode);
}
//特征提取2
FaceFeature faceFeature2 = new FaceFeature();
errorCode = faceEngine.extractFaceFeature(imageInfo2.getImageData(), imageInfo2.getWidth(), imageInfo2.getHeight(), imageInfo2.getImageFormat(), faceInfoList2.get(0), faceFeature2);
log.info("人脸库特征值大小:{}", faceFeature2.getFeatureData().length);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸库人脸特征提取异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸库人脸特征提取异常:[errorCode] = " + errorCode);
}
//特征比对
FaceFeature targetFaceFeature = new FaceFeature();
targetFaceFeature.setFeatureData(faceFeature.getFeatureData());
FaceFeature sourceFaceFeature = new FaceFeature();
sourceFaceFeature.setFeatureData(faceFeature2.getFeatureData());
FaceSimilar faceSimilar = new FaceSimilar();
errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
log.info("相似度:{}", faceSimilar.getScore());
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸特征对比异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸特征对比异常:[errorCode] = " + errorCode);
}
//相识度分数对比
if (faceSimilar.getScore() < faceSimilarScore) {
return FaceEngineR.failed("人脸对比未通过");
}
//设置活体测试
errorCode = faceEngine.setLivenessParam(rgbThreshold, irThreshold);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("活体测试异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("活体测试异常:[errorCode] = " + errorCode);
}
//人脸属性检测
FunctionConfiguration configuration = new FunctionConfiguration();
configuration.setSupportAge(true);
configuration.setSupportFace3dAngle(true);
configuration.setSupportGender(true);
configuration.setSupportLiveness(true);
errorCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, configuration);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸属性检测异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸属性检测异常:[errorCode] = " + errorCode);
}
//3D信息检测
List<Face3DAngle> face3DAngleList = new ArrayList<Face3DAngle>();
errorCode = faceEngine.getFace3DAngle(face3DAngleList);
log.info("3D角度:{}", face3DAngleList.get(0).getPitch() + ",{}", face3DAngleList.get(0).getRoll() + ",{}", face3DAngleList.get(0).getYaw());
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("3D信息检测异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("3D信息检测异常:[errorCode] = " + errorCode);
}
//活体检测
List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
errorCode = faceEngine.getLiveness(livenessInfoList);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("活体检测异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("活体检测异常:[errorCode] = " + errorCode);
}
log.info("活体:{}", livenessInfoList.get(0).getLiveness());
if (livenessInfoList.get(0).getLiveness() != 1) {
return FaceEngineR.failed("未检测到活体");
}
return FaceEngineR.ok("人脸认证通过");
}
/**
* 人脸认证
*
* @param authImageBytes 认证的图片字节
*/
public FaceEngineR detectFace(byte[] authImageBytes) {
// 获取引擎
FaceEngine faceEngine = faceEngineFactory.getFaceEngine();
int errorCode = 0; // 错误码
//人脸检测
ImageInfo imageInfo = getRGBData(authImageBytes);
List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
log.info("人脸检测数据:{}", faceInfoList);
if (errorCode != ErrorInfo.MERR_ASF_EX_INVALID_FACE_INFO.getValue()) {
log.info("无效的脸部信息:[errorCode] = {}", errorCode);
return FaceEngineR.failed("未检测到人脸");
} else if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸检测异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸检测异常:[errorCode] = " + errorCode);
}
//特征提取
FaceFeature faceFeature = new FaceFeature();
errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
log.info("特征值大小:{}", faceFeature.getFeatureData().length);
if (errorCode != ErrorInfo.MOK.getValue()) {
log.info("人脸特征提取异常:[errorCode] = {}", errorCode);
return FaceEngineR.failed("人脸特征提取异常:[errorCode] = " + errorCode);
}
return FaceEngineR.ok(faceFeature.getFeatureData().length, "人脸检测通过");
}
}
import lombok.Data;
import java.io.Serializable;
@Data
public class FaceEngineR<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功标记
*/
private static Integer SUCCESS = 0;
/**
* 失败标记
*/
private static Integer FAIL = 1;
/**
* 返回标记:成功标记=0,失败标记=1
*/
private int code;
/**
* 返回信息
*/
private String msg;
/**
* 数据
*/
private T data;
public static <T> FaceEngineR<T> ok() {
return restResult(null, SUCCESS, "成功");
}
public static <T> FaceEngineR<T> ok(T data) {
return restResult(data, SUCCESS, "成功");
}
public static <T> FaceEngineR<T> ok(T data, String msg) {
return restResult(data, SUCCESS, msg);
}
public static <T> FaceEngineR<T> failed() {
return restResult(null, FAIL, "失败");
}
public static <T> FaceEngineR<T> failed(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> FaceEngineR<T> failed(T data) {
return restResult(data, FAIL, "失败");
}
public static <T> FaceEngineR<T> failed(T data, String msg) {
return restResult(data, FAIL, msg);
}
static <T> FaceEngineR<T> restResult(T data, int code, String msg) {
FaceEngineR<T> apiResult = new FaceEngineR<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
}
import com.whiemeen.magic.common.core.exception.FcApiException;
import com.whiemeen.magic.common.core.fc.kit.FcFileKit;
import com.whiemeen.magic.modules.dome.faceengine.config.FaceEngineProperties;
import com.whiemeen.magic.modules.dome.faceengine.model.FaceEngineR;
import com.whiemeen.magic.modules.dome.faceengine.service.FaceEngineService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* 人脸认证api
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class FaceEngineApi {
private final FaceEngineService faceEngineService;
private final FaceEngineProperties faceEngineProperties;
/**
* 本地人脸检测
*/
public boolean detectLocalFace(String imagePath) {
log.info("检测人脸:{}", imagePath);
// 将图片url转换为文件字节
byte[] authImageBytes = FcFileKit.imagePathToBytes(imagePath);
Optional.ofNullable(authImageBytes).orElseThrow(() -> new FcApiException("文件读取异常"));
// 人脸检测
FaceEngineR faceEngineR = faceEngineService.detectFace(authImageBytes);
if (faceEngineR.getCode() != 0) {
log.info("认证失败:[code] {} [msg] {}", faceEngineR.getCode(), faceEngineR.getMsg());
throw new FcApiException(faceEngineR.getMsg());
log.info("检测通过");
return true;
}
/**
* 人脸检测
*/
public boolean detectFace(String imageUrl) {
log.info("检测人脸:{}", imageUrl);
// 将图片url转换为文件字节
byte[] authImageBytes = FcFileKit.imageUrlToBytes(imageUrl);
Optional.ofNullable(authImageBytes).orElseThrow(() -> new FcApiException("文件读取异常"));
// 人脸检测
FaceEngineR faceEngineR = faceEngineService.detectFace(authImageBytes);
if (faceEngineR.getCode() != 0) {
log.info("认证失败:[code] {} [msg] {}", faceEngineR.getCode(), faceEngineR.getMsg());
throw new FcApiException(faceEngineR.getMsg());
}
log.info("检测通过");
return true;
}
/**
* 本地人脸对比认证
*
* @param authImagePath 待认证的人脸
* @param localImagePath 人脸库中的人脸
* @return 认证结果
*/
public boolean compareLocalFace(String authImagePath, String localImagePath) {
log.info("认证人脸:{}", authImagePath);
log.info("人脸库人脸:{}", localImagePath);
// 将图片path转换为文件字节
byte[] authImageBytes = FcFileKit.imagePathToBytes(authImagePath);
Optional.ofNullable(authImageBytes).orElseThrow(() -> new FcApiException("文件读取异常"));
byte[] localImageBytes = FcFileKit.imagePathToBytes(localImagePath);
Optional.ofNullable(localImageBytes).orElseThrow(() -> new FcApiException("人脸库文件读取异常"));
log.info("相识度阀值:{}", faceEngineProperties.getFaceSimilarScore());
log.info("RGB活体检测阀值:{}", faceEngineProperties.getRgbThreshold());
log.info("IR活体检测阀值:{}", faceEngineProperties.getIrThreshold());
FaceEngineR faceEngineR = faceEngineService.compareFace(authImageBytes, localImageBytes,
faceEngineProperties.getFaceSimilarScore(),
faceEngineProperties.getRgbThreshold(),
faceEngineProperties.getIrThreshold());
if (faceEngineR.getCode() != 0) {
log.info("认证失败:[code] {} [msg] {}", faceEngineR.getCode(), faceEngineR.getMsg());
throw new FcApiException(faceEngineR.getMsg());
}
log.info("认证通过");
return true;
}
/**
* 人脸对比认证
*
* @param authImageUrl 待认证的人脸
* @param localImageUrl 人脸库中的人脸
* @return 认证结果
*/
public boolean compareFace(String authImageUrl, String localImageUrl) {
log.info("认证人脸:{}", authImageUrl);
log.info("人脸库人脸:{}", localImageUrl);
// 将图片url转换为文件字节
byte[] authImageBytes = FcFileKit.imageUrlToBytes(authImageUrl);
Optional.ofNullable(authImageBytes).orElseThrow(() -> new FcApiException("文件读取异常"));
byte[] localImageBytes = FcFileKit.imageUrlToBytes(localImageUrl);
Optional.ofNullable(localImageBytes).orElseThrow(() -> new FcApiException("人脸库文件读取异常"));
log.info("相识度阀值:{}", faceEngineProperties.getFaceSimilarScore());
log.info("RGB活体检测阀值:{}", faceEngineProperties.getRgbThreshold());
log.info("IR活体检测阀值:{}", faceEngineProperties.getIrThreshold());
FaceEngineR faceEngineR = faceEngineService.compareFace(authImageBytes, localImageBytes,
faceEngineProperties.getFaceSimilarScore(),
faceEngineProperties.getRgbThreshold(),
faceEngineProperties.getIrThreshold());
if (faceEngineR.getCode() != 0) {
log.info("认证失败:[code] {} [msg] {}", faceEngineR.getCode(), faceEngineR.getMsg());
throw new FcApiException(faceEngineR.getMsg());
}
log.info("认证通过");
return true;
}
}
import cn.hutool.core.img.Img;
import cn.hutool.core.io.FileUtil;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FcFileKit {
/**
* 本地文件path转化为二进制文件
*
* @param imagePath 本地文件path
* @return 二进制文件
*/
public static byte[] imagePathToBytes(String imagePath) {
try {
return return FileUtil.readBytes(imagePath);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 远程文件Url转化为二进制文件
*
* @param imageUrl 文件Url
* @return 二进制文件
*/
public static byte[] imageUrlToBytes(String imageUrl) {
try {
URL url = new URL(imageUrl);
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Img.from(url).write(outputStream); // 写文件
return outputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}