一、编写人脸识别业务接口
在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 yby6.com */ @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 yby6.com */ @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) { // 业务.... yby6.com 版权所有 return AjaxResult.success(); } /** * 人脸识别 * * @param file 人脸附件 * @param groupId 分组ID 方便快速识别 */ @RequestMapping(value = "/faceSearch", method = RequestMethod.POST) public AjaxResult faceSearch (@RequestBody Map<String, Object> map) throws Exception { // 业务... yby6.com 版权所有 return AjaxResult.success(); }
faceAdd 具体业务编写
1.根据前端传递的
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 具体业务编写
1.根据前端传递的
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
(在线图片转Base64)
1、人脸添加
新增有脸的
新增测试无脸的
2、人脸识别
有脸的
无脸的
结尾在线演示
(前往享受人脸识别)