人脸识别(Java+ Face++实现)
一. 概述
Face++的核心技术是基于深度学习的人脸识别技术,其算法在准确率和速度方面都处于领先地位。该公司的产品和服务包括人脸识别SDK、人脸识别API、人脸比对服务、人脸检测服务、活体检测服务等。这些产品和服务广泛应用于金融、公安、零售、物流等领域。并且,Face++提供免费的API接口供开发者进行使用,所以,我们可以极小的成本借用该开放API完成我们对于项目的功能需求设计!
官网地址 https://www.faceplusplus.com.cn/
API文档地址 https://console.faceplusplus.com.cn/documents/4887586
免费源码地址 https://download.csdn.net/download/weixin_53742691/87855700
二. 核心术语概念
人脸
人脸(Face)在人脸识别技术中特指图像中发现的人脸,当对一张图片进行人脸检测时,会将检测到的人脸记录下来,包括人脸在图片中的位置,用一个系统标识 face_token 来表示。注意:对同一张图片进行多次人脸检测,对同一个人脸会得到不同的 face_token。
人脸库
人脸库(FaceSet)是用来存储检测到人脸的存储对象。一个 FaceSet 内的 face_token 是不重复的。
人脸特征标识(face_token)
Face_token 是系统为人脸分配的唯一标识。当对一张图片进行人脸检测后,检测到的人脸以及人脸在图片中的位置会用一个用一个人脸特征标识 face_token 来表示。在进行人脸比对或人脸关键点检测时必须指定 face_token。
请注意:针对同一张图片进行多次人脸检测,同一个人脸会得到不同的人脸特征标识 (face_token) 。人脸比对/人脸搜索
计算机检测到图片中一个人脸之后,通过人脸判断人身份的过程被称为人脸比对/人脸搜索。
人脸比对指采集新的人脸,与一个已知身份用户的人脸进行比对,判断新的人脸是否属于该已知身份用户。人脸比对需要调用人脸比对 API。
人脸搜索是指采集用户新的人脸,在多个已知身份用户的人脸集合中进行搜索,找出新的人脸属于哪一个已知身份用户。人脸搜索需要调用人脸搜索API。
API Key
API Key 是使用公有云 API 和联网授权 SDK 服务的凭证。注册账号后,需要先创建一个 API Key,才能进行接口调用服务。
请注意:API Key 分为试用(免费)与正式(服务)两种类型,试用 API Key 在创建数量、使用的服务类型和并发 (QPS) 保障上均有限制。API Secret
API Secret 是在创建 API Key 时随机生成的一串密钥,需要和 API Key 搭配,获取使用 API 的权限,请您妥善保管记录。
Bundle ID(包名)
Bundle ID 是 APP 的唯一标识,如果需要在 APP 内集成 SDK,首先需要绑定Bundle ID(包名)。
请注意:如果仅使用 API 服务,不需要创建 Bundle ID。并发 (QPS)
并发 (QPS) 指每秒可以发起的 API 请求次数。调用同一个功能模块下的各个 API ,会统一计算 QPS。例如人脸识别并发 (QPS) 为 10 个,人脸识别包括人脸检测 API、人脸比对 API、人脸搜索API、人脸库管理 API 组、获取人脸信息 API 和自定义人脸信息 API,则每秒可以发起 10 次 API 调用请求,不限制具体调用了哪一个 API。超过 10 次请求,将返回 403 并发超限错误码 (CONCURRENCY_LIMIT_EXCEEDED)。
三. 接口调用说明
描述
将两个人脸进行比对,来判断是否为同一个人,返回比对结果置信度和不同误识率下的阈值。
支持传入图片或 face_token 进行比对。使用图片时会自动选取图片中检测到人脸尺寸最大的一个人脸。
图片要求
图片格式:JPG(JPEG),PNG
图片像素尺寸:最小 4848 像素,最大 40964096 像素
图片文件大小:2MB
最小人脸像素尺寸: 系统能够检测到的人脸框为一个正方形,正方形边长的最小值为 150 像素。
调用 URL
https://api-cn.faceplusplus.com/facepp/v3/compare
调用方法
POST
请求体格式
multipart/form-data
权限
所有 API Key 都可以调用本 API。其中 face_rectangle1 和 face_rectangle2 参数只有正式 API Key 可以使用,试用 API Key 无法使用。
请求参数
是否必选 | 参数名 | 类型 | 参数说明 |
---|---|---|---|
必选 | api_key | String | 调用此 API 的 API Key |
必选 | api_secret | String | 调用此 API 的 API Secret |
必选(四选一) | face_token1 | String | 第一个人脸标识 face_token,优先使用该参数 |
image_url1 | String | 第一张图片的 URL | |
image_file1 | File | 第一张图片,二进制文件,需要用 post multipart/form-data 的方式上传。 | |
image_base64_1 | String | base64 编码的二进制图片数据如果同时传入了 image_url1、image_file1 和 image_base64_1 参数,本 API 使用顺序为image_file1 优先,image_url1 最低。 | |
必选(四选一) | face_token2 | String | 第二个人脸标识 face_token,优先使用该参数 |
image_url2 | String | 第二张图片的 URL | |
image_file2 | File | 第二张图片,二进制文件,需要用 post multipart/form-data 的方式上传。 | |
image_base64_2 | String | base64 编码的二进制图片数据如果同时传入了 image_url2、image_file2 和 image_base64_2 参数,本API 使用顺序为 image_file2优先,image_url2 最低。 | |
可选(仅正式 API Key 可以使用) | face_rectangle1 | String | 当传入图片进行人脸检测时,是否指定人脸框位置进行检测。如果此参数传入值为空,或不传入此参数,则不使用此功能。本 API 会自动检测图片内所有区域的所有人脸。如果使用正式 API Key 对此参数传入符合格式要求的值,则使用此功能。需要传入一个字符串代表人脸框位置,系统会根据此坐标对框内的图像进行人脸检测,以及人脸关键点和人脸属性等后续操作。系统返回的人脸矩形框位置会与传入的 face_rectangle 完全一致。对于此人脸框之外的区域,系统不会进行人脸检测,也不会返回任何其他的人脸信息。参数规格:四个正整数,用逗号分隔,依次代表人脸框左上角纵坐标(top),左上角横坐标(left),人脸框宽度(width),人脸框高度(height)。例如:70,80,100,100注:只有在传入 image_url1、image_file1 和 image_base64_1 三个参数中任意一个时,本参数才生效。 |
可选(仅正式 API Key 可以使用) | face_rectangle2 | String | 当传入图片进行人脸检测时,是否指定人脸框位置进行检测。如果此参数传入值为空,或不传入此参数,则不使用此功能。本 API 会自动检测图片内所有区域的所有人脸。如果使用正式 API Key 对此参数传入符合格式要求的值,则使用此功能。需要传入一个字符串代表人脸框位置,系统会根据此坐标对框内的图像进行人脸检测,以及人脸关键点和人脸属性等后续操作。系统返回的人脸矩形框位置会与传入的 face_rectangle 完全一致。对于此人脸框之外的区域,系统不会进行人脸检测,也不会返回任何其他的人脸信息。参数规格:四个正整数,用逗号分隔,依次代表人脸框左上角纵坐标(top),左上角横坐标(left),人脸框宽度(width),人脸框高度(height)。例如:70,80,100,100注:只有在传入image_url2、image_file2 和image_base64_2 三个参数中任意一个后本参数才生效。 |
返回值说明
字段 | 类型 | 说明 |
---|---|---|
request_id | String | 用于区分每一次请求的唯一的字符串。 |
confidence | Float | 比对结果置信度,范围 [0,100],小数点后3位有效数字,数字越大表示两个人脸越可能是同一个人。注:如果传入图片但图片中未检测到人脸,则无法进行比对,本字段不返回。 |
thresholds | Object | 一组用于参考的置信度阈值,包含以下三个字段。每个字段的值为一个 [0,100] 的浮点数,小数点后 3 位有效数字。1e-3:误识率为千分之一的置信度阈值;1e-4:误识率为万分之一的置信度阈值;1e-5:误识率为十万分之一的置信度阈值;如果置信值低于“千分之一”阈值则不建议认为是同一个人;如果置信值超过“十万分之一”阈值,则是同一个人的几率非常高。请注意:阈值不是静态的,每次比对返回的阈值不保证相同,所以没有持久化保存阈值的必要,更不要将当前调用返回的 confidence 与之前调用返回的阈值比较。注:如果传入图片但图片中未检测到人脸,则无法进行比对,本字段不返回。 |
image_id1 | String | 通过 image_url1、image_file1 或 image_base64_1 传入的图片在系统中的标识。注:如果未传入图片,本字段不返回。 |
image_id2 | String | 通过 image_url2、image_file2 或 image_base64_2 传入的图片在系统中的标识。注:如果未传入图片,本字段不返回。 |
faces1 | Array | 通过 image_url1、image_file1 或 image_base64_1 传入的图片中检测出的人脸数组,采用数组中的第一个人脸进行人脸比对。注:如果未传入图片,本字段不返回。如果没有检测出人脸则为空数组 |
faces2 | Array | 通过 image_url2、image_file2 或 image_base64_2 传入的图片中检测出的人脸数组,采用数组中的第一个人脸进行人脸比对。注:如果未传入图片,本字段不返回。如果没有检测出人脸则为空数组 |
time_used | Int | 整个请求所花费的时间,单位为毫秒。 |
error_message | String | 当请求失败时才会返回此字符串,具体返回内容见后续错误信息章节。否则此字段不存在。 |
faces1和faces2数组中单个元素的结构
字段 | 类型 | 说明 |
---|---|---|
face_token | String | 人脸的标识 |
face_rectangle | Object | 人脸矩形框的位置,包括以下属性。每个属性的值都是整数:top:矩形框左上角像素点的纵坐标left:矩形框左上角像素点的横坐标width:矩形框的宽度height:矩形框的高度 |
返回值示例
成功请求返回值示例
{
"time_used": 473,
"confidence": 96.46,
"thresholds": {
"1e-3": 65.3,
"1e-5": 76.5,
"1e-4": 71.8
},
"request_id": "1469761507,07174361-027c-46e1-811f-ba0909760b18"
}
失败请求返回值示例
{
"time_used": 5,
"error_message": "INVALID_FACE_TOKEN:c2fc0ad7c8da3af5a34b9c70ff764da0",
"request_id": "1469761051,ec285c20-8660-47d3-8b91-5dc2bffa0049"
}
当前 API 特有的 ERROR_MESSAGE
HTTP状态代码 | 错误信息 | 说明 |
---|---|---|
400 | INVALID_FACE_TOKEN: <face_token> | 使用face_token作为参数时,所传的face_token不存在。 |
400 | IMAGE_ERROR_UNSUPPORTED_FORMAT: | 参数对应的图像无法正确解析,有可能不是一个图像文件、或有数据破损。 |
400 | INVALID_IMAGE_SIZE: | 参数对应的客户上传的图像像素尺寸太大或太小,图片要求请参照本API描述。对应图像太大的那个参数的名称 |
400 | INVALID_IMAGE_URL: | 无法从参数对应的image_url下载图片,图片URL错误或者无效。 |
400 | IMAGE_FILE_TOO_LARGE: | 参数对应的客户上传的图像文件太大。本 API 要求图片文件大小不超过 2 MB |
403 | INSUFFICIENT_PERMISSION: | 试用 API Key 无法使用 对应的参数。请勿传入此参数。或者使用正式 API Key 调用。 |
412 | IMAGE_DOWNLOAD_TIMEOUT: | 下载图片超时。 |
通用的ERROR_MESSAGE
HTTP 状态代码 | 错误信息 | 说明 |
---|---|---|
401 | AUTHENTICATION_ERROR | api_key和api_secret不匹配。 |
403 | AUTHORIZATION_ERROR: | api_key没有调用本API的权限,具体原因为:用户自己禁止该api_key调用、管理员禁止该api_key调用、由于账户余额不足禁止调用。目前的有:Denied by Client(用户自己禁止该api_key调用)Denied by Admin(管理员禁止该api_key调用)Insufficient Account Balance(由于账户余额不足禁止调用) |
403 | CONCURRENCY_LIMIT_EXCEEDED | 并发数超过限制。注:这里的并发控制数超出限制,是指该API Key的QPS已经达到上限。如需要提高API Key的QPS配额请查看价格方案或者联系我们。 |
400 | MISSING_ARGUMENTS: | 缺少某个必选参数。 |
400 | BAD_ARGUMENTS: | 某个参数解析出错(比如必须是数字,但是输入的是非数字字符串; 或者长度过长,etc.) |
400 | COEXISTENCE_ARGUMENTS | 同时传入了要求是二选一或多选一的参数。如有特殊说明则不返回此错误。 |
413 | Request Entity Too Large | 客户发送的请求大小超过了2MB限制。该错误的返回格式为纯文本,不是json格式。 |
404 | API_NOT_FOUND | 所调用的API不存在。 |
500 | INTERNAL_ERROR | 服务器内部错误,当此类错误发生时请再次请求,如果持续出现此类错误,请及时联系技术支持团队。 |
四. 后端代码设计
代码文件结构
引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- JSON工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.26</version>
</dependency>
<!-- io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- logback框架的依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- jwt鉴权相应依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
通用常量信息
package com.xiaohui.faceapi.utils;
import io.jsonwebtoken.Claims;
/**
* @Description 通用常量信息
* @Author IT小辉同学
* @Date 2023/06/01
*/
public class Constants
{
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
public static final String GBK = "GBK";
/**
* www主域
*/
public static final String WWW = "www.";
/**
* http请求
*/
public static final String HTTP = "http://";
/**
* https请求
*/
public static final String HTTPS = "https://";
/**
* 通用成功标识
*/
public static final String SUCCESS = "0";
/**
* 通用失败标识
*/
public static final String FAIL = "1";
/**
* 登录成功
*/
public static final String LOGIN_SUCCESS = "Success";
/**
* 注销
*/
public static final String LOGOUT = "Logout";
/**
* 注册
*/
public static final String REGISTER = "Register";
/**
* 登录失败
*/
public static final String LOGIN_FAIL = "Error";
/**
* 验证码有效期(分钟)
*/
public static final Integer CAPTCHA_EXPIRATION = 2;
/**
* 令牌
*/
public static final String TOKEN = "token";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 令牌前缀
*/
public static final String LOGIN_USER_KEY = "login_user_key";
/**
* 用户ID
*/
public static final String JWT_USERID = "userid";
/**
* 用户名称
*/
public static final String JWT_USERNAME = Claims.SUBJECT;
/**
* 用户头像
*/
public static final String JWT_AVATAR = "avatar";
/**
* 创建时间
*/
public static final String JWT_CREATED = "created";
/**
* 用户权限
*/
public static final String JWT_AUTHORITIES = "authorities";
/**
* 资源映射路径 前缀
*/
public static final String RESOURCE_PREFIX = "/profile";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi:";
/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap:";
/**
* LDAPS 远程方法调用
*/
public static final String LOOKUP_LDAPS = "ldaps:";
/**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
/**
* 定时任务违规的字符
*/
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
}
http请求状态
package com.xiaohui.faceapi.utils;
/**
* @Description http请求状态
* @Author IT小辉同学
* @Date 2023/06/01
*/
public class HttpStatus
{
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 对象创建成功
*/
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;
/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 资源没有被修改
*/
public static final int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少,格式不匹配)
*/
public static final int BAD_REQUEST = 400;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;
/**
* 不支持的数据,媒体类型
*/
public static final int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
/**
* 接口未实现
*/
public static final int NOT_IMPLEMENTED = 501;
/**
* 系统警告消息
*/
public static final int WARN = 601;
}
ajax请求结果封装
package com.xiaohui.faceapi.utils;
import java.util.HashMap;
/**
* @Description ajax结果
* @Author IT小辉同学
* @Date 2023/06/01
*/
public class AjaxResult extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult()
{
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (data!=null)
{
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult warn(String msg)
{
return AjaxResult.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data)
{
return new AjaxResult(HttpStatus.WARN, msg, data);
}
/**
* 返回错误消息
*
* @return 错误消息
*/
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(String msg)
{
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(int code, String msg)
{
return new AjaxResult(code, msg, null);
}
/**
* 方便链式调用
*
* @param key 键
* @param value 值
* @return 数据对象
*/
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
}
通用http发送方法
package com.xiaohui.faceapi.utils;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.*;
import javax.net.ssl.*;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.xiaohui.faceapi.utils.Constants;
/**
* @Description 通用http发送方法
* @Author IT小辉同学
* @Date 2023/06/01
*/
public class HttpUtils {
private final static int CONNECT_TIME_OUT = 30000;
private final static int READ_OUT_TIME = 50000;
private static String boundaryString = getBoundary();
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
/**
* @param url 发送请求的 URL
* @return {@link String }
* @Description 向指定 URL 发送GET方法的请求
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static String sendGet(String url) {
return sendGet(url, StringUtils.EMPTY);
}
/**
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return {@link String }
* @Description 向指定 URL 发送GET方法的请求
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static String sendGet(String url, String param) {
return sendGet(url, param, Constants.UTF8);
}
/**
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @param contentType 编码类型
* @return {@link String }
* @Description 向指定 URL 发送GET方法的请求
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static String sendGet(String url, String param, String contentType) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
log.info("sendGet - {}", urlNameString);
URL realUrl = new URL(urlNameString);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
log.info("recv - {}", result);
} catch (ConnectException e) {
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
/**
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return {@link String }
* @Description 向指定 URL 发送POST方法的请求
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
log.info("sendPost - {}", url);
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "multipart/form-data;");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
log.info("recv - {}", result);
} catch (ConnectException e) {
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
/**
* @param url 发送请求的 URL
* @param param 参数
* @return {@link String }
* @Description 向指定 URL 发送POST方法的请求(url形式)
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static String sendSSLPost(String url, String param) {
StringBuilder result = new StringBuilder();
String urlNameString = url + "?" + param;
try {
log.info("sendSSLPost - {}", urlNameString);
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
URL console = new URL(urlNameString);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ret = "";
while ((ret = br.readLine()) != null) {
if (ret != null && !"".equals(ret.trim())) {
result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
}
}
log.info("recv - {}", result);
conn.disconnect();
br.close();
} catch (ConnectException e) {
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
}
return result.toString();
}
/**
* @param url 发送请求的 URL
* @param map map集合
* @param fileMap 文件映射
* @return {@link byte[] }
* @Description 向指定 URL 发送POST方法的请求(路径形式)
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static byte[] FilePost(String url, HashMap<String, String> map, HashMap<String, byte[]> fileMap) throws Exception {
HttpURLConnection conne;
URL url1 = new URL(url);
conne = (HttpURLConnection) url1.openConnection();
conne.setDoOutput(true);
conne.setUseCaches(false);
conne.setRequestMethod("POST");
conne.setConnectTimeout(CONNECT_TIME_OUT);
conne.setReadTimeout(READ_OUT_TIME);
conne.setRequestProperty("accept", "*/*");
conne.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundaryString);
conne.setRequestProperty("connection", "Keep-Alive");
conne.setRequestProperty("user-agent", "Mozilla/4.0 (compatible;MSIE 6.0;Windows NT 5.1;SV1)");
DataOutputStream obos = new DataOutputStream(conne.getOutputStream());
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry) iter.next();
String key = entry.getKey();
String value = entry.getValue();
obos.writeBytes("--" + boundaryString + "\r\n");
obos.writeBytes("Content-Disposition: form-data; name=\"" + key
+ "\"\r\n");
obos.writeBytes("\r\n");
obos.writeBytes(value + "\r\n");
}
if (fileMap != null && fileMap.size() > 0) {
Iterator fileIter = fileMap.entrySet().iterator();
while (fileIter.hasNext()) {
Map.Entry<String, byte[]> fileEntry = (Map.Entry<String, byte[]>) fileIter.next();
obos.writeBytes("--" + boundaryString + "\r\n");
obos.writeBytes("Content-Disposition: form-data; name=\"" + fileEntry.getKey()
+ "\"; filename=\"" + encode(" ") + "\"\r\n");
obos.writeBytes("\r\n");
obos.write(fileEntry.getValue());
obos.writeBytes("\r\n");
}
}
obos.writeBytes("--" + boundaryString + "--" + "\r\n");
obos.writeBytes("\r\n");
obos.flush();
obos.close();
InputStream ins = null;
int code = conne.getResponseCode();
try {
if (code == 200) {
ins = conne.getInputStream();
} else {
ins = conne.getErrorStream();
}
} catch (SSLException e) {
e.printStackTrace();
return new byte[0];
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[4096];
int len;
while ((len = ins.read(buff)) != -1) {
baos.write(buff, 0, len);
}
byte[] bytes = baos.toByteArray();
ins.close();
return bytes;
}
/**
* @return {@link String }
* @Description 分割线
* @Author IT小辉同学
* @Date 2023/06/01
*/
private static String getBoundary() {
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 32; ++i) {
sb.append("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".charAt(random.nextInt("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_".length())));
}
return sb.toString();
}
/**
* @param value 传入的字符串
* @return {@link String }
* @Description 设置编码格式
* @Author IT小辉同学
* @Date 2023/06/01
*/
private static String encode(String value) throws Exception {
return URLEncoder.encode(value, "UTF-8");
}
/**
* @param f 文件
* @return {@link byte[] }
* @Description 文件转换为二进制
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static byte[] getBytesFromFile(File f) {
if (f == null) {
return null;
}
try {
FileInputStream stream = new FileInputStream(f);
ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
byte[] b = new byte[1000];
int n;
while ((n = stream.read(b)) != -1)
out.write(b, 0, n);
stream.close();
out.close();
return out.toByteArray();
} catch (IOException e) {
}
return null;
}
/**
* @param jsonObject json对象
* @return {@link String }
* @Description 转换为url参数
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static String convertToUrlParams(JSONObject jsonObject) throws UnsupportedEncodingException {
StringBuffer sb = new StringBuffer();
Set<String> keys = jsonObject.keySet();
for (String key : keys) {
Object value = jsonObject.get(key);
if (value instanceof String) {
sb.append(key).append("=").append(URLEncoder.encode(value.toString(), "UTF-8"));
} else if (value instanceof JSONObject) {
sb.append(key).append("=").append(convertToUrlParams((JSONObject) value));
} else if (value instanceof JSONArray) {
sb.append(key).append("=").append(convertToUrlParams(((JSONArray) value).addObject()));
}
if (keys.size() != 1 || value == null) {
sb.append("&");
}
}
return sb.toString();
}
/**
* @param fileName 文件名称
* @return {@link byte[] }
* @Description 读文件字节数组
* @Author IT小辉同学
* @Date 2023/06/01
*/
public static byte[] readFileToByteArray(String fileName) throws IOException {
File file = new File(fileName);
FileInputStream inputStream = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
inputStream.read(buffer);
inputStream.close();
return buffer;
}
private static class TrustAnyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
接口调用实现
package com.xiaohui.faceapi.controller;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xiaohui.faceapi.utils.AjaxResult;
import com.xiaohui.faceapi.utils.HttpUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
@RestController
@RequestMapping("/api")
public class FaceApi {
/**
* url
*/
private static String url = "https://api-cn.faceplusplus.com/facepp/v3/compare";
/**
* api密匙键
*/
private static String api_key = "LP0v9dZj***oBKYgiwMq-P_CIk";
/**
* api秘钥值
*/
private static String api_secret = "vg8UNGVMX****jmIpFw_wxaXTFCF4Xb5";
/**
* @param image1 image1
* @param image2 image2
* @return {@link AjaxResult }
* @Description 根据URL进行人脸对比
* @Author IT小辉同学
* @Date 2023/06/01
*/
@GetMapping("/faceUrl")
public AjaxResult getFaceConfidenceByUrl(@RequestParam String image1, @RequestParam String image2) throws UnsupportedEncodingException {
//数据填充
HashMap params = new HashMap();
params.put("api_key", api_key);
params.put("api_secret", api_secret);
params.put("image_url1", image1);
params.put("image_url2", image2);
//数据类型转换为object
JSONObject jsonObject = new JSONObject(params);
//转换为入参形式
String param = HttpUtils.convertToUrlParams(jsonObject);
//发起请求
String getResult = HttpUtils.sendPost(url, param);
//数据类型转换为object
JSONObject result = JSONObject.parseObject(getResult);
return AjaxResult.success(result);
}
/**
* @param image1 image1
* @param image2 image2
* @return {@link AjaxResult }
* @Description 根据文件流进行人脸对比
* @Author IT小辉同学
* @Date 2023/06/01
*/
@GetMapping("/faceFile")
public AjaxResult getFaceConfidenceByFile(@RequestParam(value = "image1") String image1, @RequestParam(value = "image2") String image2) throws Exception {
File file1 = new File(image1);
byte[] buff1 = HttpUtils.getBytesFromFile(file1);
File file2 = new File(image2);
byte[] buff2 = HttpUtils.getBytesFromFile(file2);
HashMap<String, String> map = new HashMap<String, String>();
HashMap<String, byte[]> byteMap = new HashMap<String, byte[]>();
map.put("api_key", api_key);
map.put("api_secret", api_secret);
byteMap.put("image_file1", buff1);
byteMap.put("image_file2", buff2);
byte[] bacd = HttpUtils.FilePost(url, map, byteMap);
String str = new String(bacd);
JSONObject result = JSONObject.parseObject(str);
return AjaxResult.success(result);
}
}
五. 结果演示
URL请求方式
路径请求方式文章来源:https://www.toymoban.com/news/detail-467965.html
文章来源地址https://www.toymoban.com/news/detail-467965.html
到了这里,关于人脸识别(Java+ Face++实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!