前段时间开发一个小程序需要使用到阿里云(1:N)人脸识别的服务,查询资料发现网上并没有详细的教程,而官方的api文档也写得很简略,于是就有了如下教程,希望能帮助到大家。
目录
服务开通
人脸识别服务开通
注册阿里云账户后,进入人脸人体控制台(阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台),进行对人脸人体API调用量的购买。目前阿里云上所有人脸人体的接口都是2QPS((Query Per Second):每秒请求数)免费,足够开发时学习使用。
oss存储服务开通
进入oss控制台(阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台),右侧有一块Bucket管理,点击创建Bucket来得到一块存储空间。个人对Bucket的理解就像是database中的一个个table,是用户用来管理所存储对象的存储空间。一个oss中可以拥有许多Bucket,但是每个Bucket的名称都是唯一的。由于人脸人体的url只支持上海的oss链接,所以创建华东2的Bucket。创建好之后便可以使用阿里云的人脸识别技术了。
代码部分
这里使用java代码作为实例,前端为需要人脸识别功能的小程序,后端使用Springboot框架。
人脸识别
①pom.xml
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.1.1</version>
</dependency>
②face_detect_util
需要先得到阿里云的access_key_id(如何创建AccessKey_访问控制-阿里云帮助中心);
package com.zhiqi.wayhome.Util;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhiqi.wayhome.Bean.Face_msg;
import java.util.Map;
public class face_detect_util {
//DefaultProfile.getProfile的参数分别是地域,access_key_id, access_key_secret
public static DefaultProfile profile = DefaultProfile.getProfile("cn-shanghai", "xxx", "xxx");//这里要改成自己的
public static DefaultAcsClient client = new DefaultAcsClient(profile);
public static ObjectMapper mapper=new ObjectMapper();
/**
* AddFaceEntity用于添加人脸样本 添加了人脸样本后才能添加人脸数据
* 只有在用户上传第一张照片时调用这个接口 之后就只需要添加人脸数据即可
* @param EntityId 用身份证号码来标识
*
*/
public static void AddFaceEntity(String EntityId){
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setVersion("2019-12-30");
request.setDomain("facebody.cn-shanghai.aliyuncs.com");
request.putBodyParameter("Action", "AddFaceEntity");
request.putBodyParameter("DbName", "Wayhome");
request.putBodyParameter("EntityId", EntityId);
CommonResponse response = null;
try {
response = client.getCommonResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
System.out.println(response.getData());
}
/**
* AddFace接口用于向人脸样本中添加人脸数据
* @param
* @param EntityId 添加人脸的身份证号
* @param imageUrl 检测图片的URL
*/
public static int AddFace(String EntityId,String imageUrl)
{
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setVersion("2019-12-30");
request.setDomain("facebody.cn-shanghai.aliyuncs.com");
request.setAction("AddFace");
request.putBodyParameter("DbName", "Wayhome");
request.putBodyParameter("EntityId", EntityId);
request.putBodyParameter("ImageUrl",imageUrl);
// request.putBodyParameter("Content", "/9j/4AAQSkZJRgABA..."); //检测图片的内容,Base64编码
CommonResponse response = null;
try {
response = client.getCommonResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
System.out.println(response.getData());
//得到这张图片的FaceId 并存到数据库中
Face_msg face_msg=null;
String s=response.getData();
String[] ss=s.split("FaceId");
String a=ss[1].substring(3,ss[1].length()-3);
return Integer.parseInt(a);
}
/**
* DeleteFace接口用于从人脸库中删除某张人脸数据
* @param FaceId 删除指定FaceId的图片
* @throws ClientException
*/
public static void DeleteFace(int FaceId) {
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setVersion("2019-12-30");
request.setDomain("facebody.cn-shanghai.aliyuncs.com");
request.setAction("DeleteFace");
request.putBodyParameter("DbName", "Wayhome");
request.putBodyParameter("FaceId", FaceId);
CommonResponse response = null;
try {
response = client.getCommonResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
System.out.println(response.getData());
}
/**
* DeleteFaceEntity接口用于从人脸库中删除人脸实体
* @param EntityId 在任务结束后清除该走失者的人脸实体
* @throws ClientException
*/
public static void DeleteFaceEntity(String EntityId) {
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setVersion("2019-12-30");
request.setDomain("facebody.cn-shanghai.aliyuncs.com");
request.setAction("DeleteFaceEntity");
request.putBodyParameter("DbName", "Wayhome");
request.putBodyParameter("EntityId", EntityId);
CommonResponse response = null;
try {
response = client.getCommonResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
System.out.println(response.getData());
}
/**
*
* @param ImageUrl
*/
public static String SearchFace(String ImageUrl) {
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setVersion("2019-12-30");
request.setDomain("facebody.cn-shanghai.aliyuncs.com");
request.setAction("SearchFace");
request.putBodyParameter("DbName", "Wayhome");
request.putBodyParameter("ImageUrl", ImageUrl);
request.putBodyParameter("Limit", 1);
CommonResponse response = null;
try {
response = client.getCommonResponse(request);
} catch (ClientException e) {
e.printStackTrace();
}
String s=response.getData();
System.out.println(s);
return s;
}
}
③代码讲解:
使用人脸识别的顺序-->创建人脸数据库(可手动也可代码,目前免费用户一人只能创建一个人脸库)-->创建人脸实体(一个人脸实体代表一个人,可以有几张照片)-->为人脸实体添加数据(一个实体最多用五张图片来训练)-->上传图片与人脸库中的所有实体进行比对(1:N)。
①创建人脸库:我是手动创建人脸库的,登录到人脸人体控制台(阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台)中,点击以下按钮,进入数据库管理中新建人脸库,我的取名为Wayhome。
②创建人脸实体:代码中的AddFaceEntity(String EntityId)函数。调用的也是AddFaceEntity接口,接口文档(AddFaceEntity_视觉智能开放平台-人脸人体_API文档-阿里云OpenAPI开发者门户),查看文档发现,请求的参数列表如下:
其中DbName为之前创建的人脸数据库名,我创建的为Wayhome;EntityId为创建的人脸实体的ID,一般使用该人的唯一标识。如身份证号、员工号等。Lables可传可不传,可以是该人的姓名作为备注。
注意:要用POST方法调用接口,传入的参数中要有SDK的版本:request.setVersion("2019-12-30");版本号从文档中查看(文档的网址中也有版本号)。
之后的接口就很类似了,这里不一一演示。
由于人脸识别传入的faceimage需要是自家oss的链接,我一开始自己搭建了上传图片的服务器,但是发现传入图片链接后报错,查阅资料传入的参数必须是oss的链接,接下来介绍一下小程序上传多张照片到oss并返回URL链接。
oss上传
①pom.xml
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.7.0</version>
</dependency>
②OSS_Util
package com.zhiqi.wayhome.Util;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Random;
public class OSS_Util {
protected static final Logger log = LoggerFactory.getLogger(OSS_Util.class);
private static String endpoint="oss-cn-shanghai.aliyuncs.com";
private static String accessKeyId="LTAI5tE4awy6GwWiHA4sHkd5";
private static String accessKeySecret="5E3X23XuLBO71YPW01ZXSdfgresG28";
private static String bucketName="wayhome";
//文件存储目录
private static String filedir = "userImg/";
/**
*
* 上传图片
* @param file
* @return
*/
public static String uploadImg2Oss(MultipartFile file) {
if (file.getSize() > 1024 * 1024 *20) {
return "图片太大";//RestResultGenerator.createErrorResult(ResponseEnum.PHOTO_TOO_MAX);
}
String originalFilename = file.getOriginalFilename();
String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
Random random = new Random();
String name = random.nextInt(10000) + System.currentTimeMillis() + substring;
try {
InputStream inputStream = file.getInputStream();
uploadFile2OSS(inputStream, name);
return name;//RestResultGenerator.createSuccessResult(name);
} catch (Exception e) {
return "上传失败";//RestResultGenerator.createErrorResult(ResponseEnum.PHOTO_UPLOAD);
}
}
/**
* 上传图片获取fileUrl
* @param instream
* @param fileName
* @return
*/
private static String uploadFile2OSS(InputStream instream, String fileName) {
String ret = "";
try {
//创建上传Object的Metadata
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(instream.available());
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma", "no-cache");
objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));
objectMetadata.setContentDisposition("inline;filename=" + fileName);
//上传文件
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
PutObjectResult putResult = ossClient.putObject(bucketName, filedir + fileName, instream, objectMetadata);
ret = putResult.getETag();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
if (instream != null) {
instream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return ret;
}
public static String getcontentType(String FilenameExtension) {
if (FilenameExtension.equalsIgnoreCase(".bmp")) {
return "image/bmp";
}
if (FilenameExtension.equalsIgnoreCase(".gif")) {
return "image/gif";
}
if (FilenameExtension.equalsIgnoreCase(".jpeg") ||
FilenameExtension.equalsIgnoreCase(".jpg") ||
FilenameExtension.equalsIgnoreCase(".png")) {
return "image/jpg";
}
if (FilenameExtension.equalsIgnoreCase(".html")) {
return "text/html";
}
if (FilenameExtension.equalsIgnoreCase(".txt")) {
return "text/plain";
}
if (FilenameExtension.equalsIgnoreCase(".vsd")) {
return "application/vnd.visio";
}
if (FilenameExtension.equalsIgnoreCase(".pptx") ||
FilenameExtension.equalsIgnoreCase(".ppt")) {
return "application/vnd.ms-powerpoint";
}
if (FilenameExtension.equalsIgnoreCase(".docx") ||
FilenameExtension.equalsIgnoreCase(".doc")) {
return "application/msword";
}
if (FilenameExtension.equalsIgnoreCase(".xml")) {
return "text/xml";
}
return "image/jpg";
}
/**
* 获取图片路径
* @param fileUrl
* @return
*/
public static String getImgUrl(String fileUrl) {
if (!StringUtils.isEmpty(fileUrl)) {
String[] split = fileUrl.split("/");
String url = getUrl(filedir + split[split.length - 1]);
return url;
}
return null;
}
/**
* 获得url链接
*
* @param key
* @return
*/
public static String getUrl(String key) {
// 设置URL过期时间为10年 3600l* 1000*24*365*10
// 如果不设置则URL的有效时间很短
Date expiration = new Date(new Date().getTime() + 3600L * 1000 * 24 * 365 * 10);
// 生成URL
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
URL url = ossClient.generatePresignedUrl(bucketName, key, expiration);
if (url != null) {
return url.toString();
}
return null;
}
/**
* 多图片上传
* @param fileList
* @return
*/
public static String checkList(List<MultipartFile> fileList) {
String fileUrl = "";
String str = "";
String photoUrl = "";
for(int i = 0;i< fileList.size();i++){
fileUrl = uploadImg2Oss(fileList.get(i));
str = getImgUrl(fileUrl);
if(i == 0){
photoUrl = str;
}else {
photoUrl += "," + str;
}
}
return photoUrl.trim();
}
/**
* 单个图片上传
* @param file
* @return
*/
public static String checkImage(MultipartFile file){
String fileUrl = uploadImg2Oss(file);
String str = getImgUrl(fileUrl);
return str.trim();
}
/**
* 删除oss内的文件
*
*/
public static void removeFile(String img_url) {
int i1=img_url.indexOf('/');
int i2=img_url.indexOf('/',i1+1);
int i3=img_url.indexOf('/',i2+1);
int i4=img_url.indexOf("?",i3+1);
String substring=img_url.substring(i3+1,i4);
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
try {
// 删除文件。如需删除文件夹,请将ObjectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。
ossClient.deleteObject(bucketName, substring);
} catch (Exception e) {
e.printStackTrace();
}
// 关闭OSSClient。
ossClient.shutdown();
}
}
使用代码时只需要修改以下部分:
protected static final Logger log = LoggerFactory.getLogger(OSS_Util.class);
private static String endpoint="oss-cn-shanghai.aliyuncs.com";
private static String accessKeyId="xxx";//人脸识别那讲的accessKeyId
private static String accessKeySecret="xxxxx";
private static String bucketName="wayhome";//之前创建的bucket名称
//文件存储目录
private static String filedir = "userImg/";//自定
单图片和多图片上传分别调用checkImage()和checkList()方法,返回图片的url。
删除文件部分代码的解释:
所以文件在Bucket中存储的的路径为,即第三个‘/’到‘?’之间的部分,所以获取删除文件的目录为以下代码。
public static void removeFile(String img_url) {
int i1=img_url.indexOf('/');
int i2=img_url.indexOf('/',i1+1);
int i3=img_url.indexOf('/',i2+1);
int i4=img_url.indexOf("?",i3+1);
String substring=img_url.substring(i3+1,i4);
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
try {
// 删除文件。如需删除文件夹,请将ObjectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。
ossClient.deleteObject(bucketName, substring);
} catch (Exception e) {
e.printStackTrace();
}
// 关闭OSSClient。
ossClient.shutdown();
}
③小程序前端
data: {
temp_img:null
}
uploadd:function(i){
var that=this;
wx.uploadFile({
url: '...',//输入你的上传接口
filePath: that.data.temp_img[i], //小程序的上传只能是一张一张图片上传 ,参数不能是一个数组。
name: 'fileList',
formData:{
//这里面放补充参数
},
//使用递归进行上传,会有错
success: function (res) {
console.log(res);
console.log(i);
i++;
if(i == length){ //当图片传完时,停止调用
console.log('成功');
wx.showToast({
title: '上传成功!',
duration: 1500,
success: function(){
that.hideModal();
}
});
}else {
that.uploadd(i);
}
//刷新页面
that.onLoad();
}, fail: function (err) {
console.log(err)
}
});
},
upload_img(){
//上传图片开始
var that = this;
wx.chooseImage({
count: 5, // 最多可以选择的图片张数,默认9(人脸库每个人最多五张照片)
sizeType: ['original'], // original 原图,compressed 压缩图,默认二者都有
sourceType: ['album', 'camera'], // album 从相册选图,camera 使用相机,默认二者都有
success: function (res) {
// 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
var tempFilePaths = res.tempFilePaths;
wx.showToast({
title: '正在上传',
icon: 'loading',
mask: true,
duration: 1000
});
console.log(tempFilePaths);
that.setData({
temp_img:tempFilePaths
})
var i=0;
that.uploadd(i);
},
});
},