赞
踩
最近在Android的课程设计,老师要求一定要用手机传感器,并自己设计一个题目。我思来想去,最后想着用人脸识别做一个登录模块,一开始没有头绪嘛,但是无意中进到百度智能云的界面,了解到百度智能云有提供第三方的API,因此,我就用Spring Boot整合了第三方的API(百度智能云人脸识别API)完成这个功能模块的开发。
注:此处只提供人脸识别的后台接口的实现,读者可将此模块加到自己的项目中,由Web前端或者App端传递一张人脸照片进行验证,或者使用项目中的单元测试进行验证。
创建应用
可以选择自己想要使用的接口:比如人脸识别、语音技术等,本次项目采用的是人脸识别,官方也默认选择了人脸识别的全部接口,所以也不需要做改动,只需要填写应用名称即可!
获取密钥
官网文档:人脸识别-百度智能云
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.9.0</version>
</dependency>
/** * 人脸注册测试 * * @throws IOException */ @Test public void testFaceRegister() throws IOException { String appId = "26358250"; String apiKey = "你的百度云APIKey"; String secretKey = "你的百度云secretKey"; String imageType = "BASE64"; String groupId = "1"; String userId = "face_test"; /* * 1. 创建Java代码和百度云交互的Client对象 */ AipFace client = new AipFace(appId, apiKey, secretKey); /* * 2. 参数设置 */ HashMap<String, String> map = new HashMap<>(); //图片质量 map.put("quality_control", "NORMAL"); //活体检测 map.put("liveness_control", "LOW"); /* * 3. 构造图片 * 上传的图片 两种格式:url地址 Base64字符串形式 */ String path = "本地照片的路径"; byte[] bytes = Files.readAllBytes(Paths.get(path)); String encode = Base64Util.encode(bytes); /* * 4. 调用api方法完成人脸注册 * 参数1:图片的url或者base64字符串 * 参数2:图片形式(URL,BASE64) * 参数3:组Id(固定一个字符串) * 参数4:用户Id * 参数5:hashMap基本参数配置 */ JSONObject res = client.addUser(encode, imageType, groupId, "1001", map); System.out.println(res.toString()); }
运行后,res这个JSON字符中中会有一个“error_code”的字段,其值为0则表示成功。
其他字段的详细意思见官方文档。
/** * 人脸检测 判断图片中是否有面部信息 */ @Test public void testFaceCheck() throws IOException { String appId = "26358250"; String apiKey = "你的百度云APIKey"; String secretKey = "你的百度云secretKey"; String imageType = "BASE64"; String groupId = "1001"; String userId = "face_test"; //1. 创建Java代码和百度云交互的Client对象 AipFace client = new AipFace(appId, apiKey, secretKey); //2. 构造图片 String path = "本地照片的路径"; //上传的图片 两种格式:url地址 Base64字符串形式 byte[] bytes = Files.readAllBytes(Paths.get(path)); String encode = Base64Util.encode(bytes); //调用Api方法进行人脸检测 /* * 参数1:图片的url或者base64字符串 * 参数2:图片形式(URL,BASE64) * 参数3:hashMap中的基本参数配置(null:使用默认配置) */ JSONObject result = client.detect(encode, imageType, null); System.out.println(result.toString(2)); }
响应结果:
/** * 人脸搜索:根据用户上传的图片和指定人脸库中的所有人脸进行比较 * score:相似度评分(80分以上可以认为是同一个人) */ @Test public void testFaceSearch() throws IOException { String appId = "26358250"; String apiKey = "你的百度云APIKey"; String secretKey = "你的百度云secretKey"; String imageType = "BASE64"; String groupId = "1"; String userId = "face_test"; //1. 创建Java代码和百度云交互的Client对象 AipFace client = new AipFace(appId, apiKey, secretKey); //2. 构造图片 String path = "本地照片的路径"; //上传的图片 两种格式:url地址 Base64字符串形式ee byte[] bytes = Files.readAllBytes(Paths.get(path)); String encode = Base64Util.encode(bytes); //人脸搜索 JSONObject res = client.search(encode, imageType, groupId, null); System.out.println(res.toString(2)); }
我们对百度智能云提供的第三方API进行二次封装,封装成一个BaidiAiUtils.java工具类,并且加上@Component注解,将其注册到Spring容器中,在Spring Boot应用启动后将自动实例化该类,我们只需要在需要时注入并使用即可,BaiduAiUtils.java的源码如下:
package edu.zhku.utils; import com.baidu.aip.face.AipFace; import edu.zhku.common.Const; import lombok.extern.slf4j.Slf4j; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; /** * @author Zhang * @date 2022-06-08 17:05 * @description 百度智能云人脸识别API工具类 */ @Slf4j @Component public class BaiduAiUtils { @Value("${ai.appId}") private String appId; @Value("${ai.apiKey}") private String apiKey; @Value("${ai.secretKey}") private String secretKey; @Value("${ai.imageType}") private String imageType; @Value("${ai.groupId}") private String groupId; /** * 客户端 */ private AipFace client; private HashMap<String, String> option; /** * Spring容器初始化自动执行该方法 */ @PostConstruct public void init() { client = new AipFace(appId, apiKey, secretKey); option = new HashMap<>(16); //图片质量 option.put("quality_control", "NORMAL"); //活体检测 option.put("liveness_control", "LOW"); } /** * 人脸注册,将用户照片存入人脸库中 * * @param userId 用户id * @param image 图片的URL或者BASE64字符串 * @return true-成功,反之 */ public Boolean faceRegister(String userId, String image) { /* * 人脸注册 * image:图片的URL或者BASE64字符串 * imageType:图片形式(URL,BASE64) * groupId:组Id(固定一个字符串) * userId:用户Id * option:hashMap基本参数配置 */ JSONObject res = client.addUser(image, imageType, groupId, userId, option); log.debug("BaiduAiUtils--faceRegister"); log.debug(res.toString()); return res.getInt("error_code") == 0; } /** * 人脸更新,更新人脸库中的用户照片 * * @param userId 用户id * @param image 图片的URL或者BASE64字符串 * @return true-成功,反之 */ public Boolean faceUpdate(String userId, String image) { /* * 人脸更新 * image:图片的URL或者BASE64字符串 * imageType:图片形式(URL,BASE64) * groupId:组Id(固定一个字符串) * userId:用户Id * option:hashMap基本参数配置 */ JSONObject res = client.updateUser(image, imageType, groupId, userId, null); //获取error_code,其值为0则表示测试成功 return res.getInt("error_code") == 0; } /** * 人脸检测。判断上传的图片中是否具有面部信息 * * @param image 图片的URL或者BASE64字符串 * @return true-成功,反之 */ public Boolean faceCheck(String image) { JSONObject res = client.detect(image, imageType, null); if (res.has(Const.ERROR_CODE) && res.getInt(Const.ERROR_CODE) == 0) { //获取"result"域的数据 JSONObject resultObject = res.getJSONObject("result"); //result域的face_num为1,则表示检验成功 return resultObject.getInt("face_num") == 1; } else { return false; } } /** * 人脸查找:查找人脸库中最相似的人脸并返回数据 * 处理:用户的匹配得分(score)大于80分,即可认为是同一个人 * * @param image 图片的URL或者BASE64字符串 * @return userId */ public JSONObject faceSearch(String image) { JSONObject res = client.search(image, imageType, groupId, null); log.info("BaiduAiUtils--faceSearch"); return res; // if (res.has(Const.ERROR_CODE) && res.getInt(Const.ERROR_CODE) == 0) { // //获取"result"域数据 // JSONObject resultObject = res.getJSONObject("result"); // //获取"result"下"user_list"的数据 // JSONArray userList = resultObject.getJSONArray("user_list"); // if (userList.length() > 0) { // JSONObject user = userList.getJSONObject(0); // double score = user.getDouble("score"); // if (score >= Const.USER_CHECKED_SCORE) { // return user.getString("user_id"); // } // } // } } }
/** * 人脸识别登录 * @param file * @return * @throws IOException */ @PostMapping("/upload/image") public String login(@RequestParam("file") MultipartFile file) throws I log.info("[{}]", file.getName()); log.info("FaceLoginController--login3"); log.info("文件不为空"); log.info("文件名[{}]", file.getOriginalFilename()); log.info("文件大小[{}]", file.getSize()); byte[] bytes = file.getBytes(); Map<String, Object> resultMap = new HashMap<>(16); String imageBase64 = null; try { //压缩文件并转换成Base64编码 imageBase64 = Base64Util.encode(ImageUtils.imageSet(bytes)); } catch (Exception e) { e.printStackTrace(); } JSONObject res = baiduAiUtils.faceSearch(imageBase64); log.info("识别结果:[{}]", res.toString(2)); if (res.has(Const.ERROR_CODE) && res.getInt(Const.ERROR_CODE) == 0 JSONObject resultObject = res.getJSONObject("result"); // 获取 "result" 下 "user_list" 的数据 JSONArray userList = resultObject.getJSONArray("user_list"); if (userList.length() > 0) { JSONObject user = userList.getJSONObject(0); double score = user.getDouble("score"); if (score >= Const.USER_CHECKED_SCORE) { User login = userService.login(user.getInt("group_id") resultMap.put("msg", "人脸识别成功"); resultMap.put("code", "200"); resultMap.put("userName", login.getAccount()); resultMap.put("password", login.getPassword()); } } } else { resultMap.put("msg", "人脸识别失败"); resultMap.put("code", "500"); } log.info("响应结果:[{}]", JSON.toJSONString(resultMap)); return JSON.toJSONString(resultMap); }
日志打印:
在调用第三方API进行人脸识别时,报出以下错误:
0 [main] WARN com.baidu.aip.client.BaseClient - oauth get error, current state: STATE_TRUE_CLOUD_USER
{
"error_msg": "IAM Certification failed",
"error_code": 14
}
解决方法:仔细检查APP_ID、API_KEY、SECRET_KEY字符串,一般都是直接复制所以复制出错的概率很低,但是字符串首尾中是否有空格这一点很容易忽视。
我们在接收App端传过来的人脸图像,编码后调用百度智能云的API进行人脸识别时,会显示图片过大,因为我们一般照片都是在几十到几百KB,但是因为现在相机的像素比较高(我的应用场景是调用手机设摄像头拍摄进行人脸识别登录),一张照片基本都在几MB左右,我们要需要对照片进行压缩处理,然后再将图片转为Base64编码,再调用第三方接口。
这里提供一个工具ImageUtils.java,传入图像的字节数组即可完成压缩,返回图像压缩后的字节数组,ImageUtils.java工具类源码如下:
package edu.zhku.utils; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; /** * @author Zhang * @date 2022/6/24 14:06 * @description 图像处理工具类 */ public class ImageUtils { /** * 压缩图片 * * @param fromPath1 byte直接文件图片 * @return 剪切后的byte文件 */ public static byte[] imageSet(byte[] fromPath1) throws Exception { return imageChangeSize(fromPath1, 470, 635); } /** * @param data byte类型图片 * @param max_wi 图片修改后最大的宽 * @param max_he 图片修改后最大的高 * @return * @throws Exception * @Description 更改图片内容的大小 byte【】 类型 */ public static byte[] imageChangeSize(byte[] data, int max_wi, int max_he) throws Exception { ImageIO io = null; float rate = 1; /*图片的原始宽 120*/ int oldwi; /*图片的原始高 120*/ int oldhe; /*图片修改后的宽 0*/ int new_wi = 0; /*图片修改后的高 0*/ int new_he = 0; /*拿到byte图片*/ InputStream is = new ByteArrayInputStream(data); BufferedImage bufImg = ImageIO.read(is); /*图片的原始宽度*/ oldwi = bufImg.getWidth(); /*图片的原始高度*/ oldhe = bufImg.getHeight(); // rate = (float) oldwi / (float) oldhe; /* * 如果图片的原宽大于最大宽度,并且原高小于等于最大高度。 * 则证明图片过宽了,将图片宽度设置为最大宽度,此时需要等比例减小高度 */ if (oldwi > max_wi && oldhe <= max_he) { new_wi = max_wi; new_he = new Float((float) new_wi / rate).intValue(); /* * 如果图片的原宽和原高都大于或者都小于其所对应的最大值, * 则以任意一方为主(此处以最大高度为主) */ } else if (oldwi >= max_wi && oldhe >= max_he || oldwi <= max_wi && oldhe <= max_he) { new_he = max_he; new_wi = new Float(new_he * rate).intValue(); /* * 如果图片的原宽小于于最大宽度,并且原高大于等于最大高度。 * 则证明图片过高了,将图片宽度设置为最大高度,此时需要等比例减小宽度 */ } else if (oldwi <= max_wi && oldhe > max_he) { new_he = max_he; new_wi = new Float(new_he * rate).intValue(); } /* * 开始改变大小 */ ImageObserver ser = null; BufferedImage bf = new BufferedImage(new_wi, new_he, BufferedImage.TYPE_INT_RGB); bf.getGraphics().drawImage(bufImg, 0, 0, new_wi, new_he, null); ByteArrayOutputStream out = new ByteArrayOutputStream(); //转换编码格式JPEG ImageIO.write(bf, "jpeg", out); return out.toByteArray(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。