效果如上,我用的是阿里云的人脸识别。首先,我们先封装一个阿里云的请求js文件
faceRecognition.js
typescript
import CryptoJS from 'crypto-js'
//SignatureNonce随机数字
function signNRandom() {
const Rand = Math.random()
const mineId = Math.round(Rand * 100000000000000)
return mineId;
};
//Timestamp
function getTimestamp() {
let date = new Date();
let YYYY = pad2(date.getUTCFullYear());
let MM = pad2(date.getUTCMonth() + 1);
let DD = pad2(date.getUTCDate());
let HH = pad2(date.getUTCHours());
let mm = pad2(date.getUTCMinutes());
let ss = pad2(date.getUTCSeconds());
return `${YYYY}-${MM}-${DD}T${HH}:${mm}:${ss}Z`;
}
//补位占位
function pad2(num) {
if (num < 10) {
return '0' + num;
}
return '' + num;
};
// 排序
function ksort(params) {
let keys = Object.keys(params).sort();
let newParams = {};
keys.forEach((key) => {
newParams[key] = params[key];
});
return newParams;
};
// HmacSHA1加密+base64
function createHmac(stringToSign, key) {
const CrypStringToSign = CryptoJS.HmacSHA1(stringToSign, key);
const base64 = CryptoJS.enc.Base64.stringify(CrypStringToSign);
return base64;
};
//编码
function encode(str) {
var result = encodeURIComponent(str);
return result.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
};
function sha1(stringToSign, key) {
return createHmac(stringToSign, key);
};
function getSignature(signedParams, method, secret) {
var stringToSign = `${method}&${encode('/')}&${encode(signedParams)}`;
const key = secret + "&";
return sha1(stringToSign, key);
};
//参数拼接 &
function objToParam(param) {
if (Object.prototype.toString.call(param) !== '[object Object]') {
return '';
}
let queryParam = '';
for (let key in param) {
if (param.hasOwnProperty(key)) {
let value = param[key];
queryParam += toQueryPair(key, value);
}
}
return queryParam;
};
function toQueryPair(key, value) {
if (typeof value == 'undefined') {
return `&${key}=`;
}
return `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
};
function generateUrl(request, httpMethod, endpoint, accessKeySecret) {
//参数中key排序
const sortParams = ksort(request);
//拼成参数
const sortQueryStringTmp = objToParam(sortParams);
const sortedQueryString = sortQueryStringTmp.substring(1);// 去除第一个多余的&符号
//构造待签名的字符串
const Signature = getSignature(sortedQueryString, httpMethod, accessKeySecret)
//签名最后也要做特殊URL编码
request["Signature"] = encodeURIComponent(Signature);
//最终生成出合法请求的URL
const finalUrl = "https://" + endpoint + "/?Signature=" + encodeURIComponent(Signature) + sortQueryStringTmp;
return finalUrl;
}
const callRecognizeBankCard = function (ImageURL,callback) {
const accessKeyId = '';
const accessKeySecret = '';
const endpoint = "facebody.cn-shanghai.aliyuncs.com";
const Action = "DetectFace";
// API_HTTP_METHOD推荐使用POST
const API_HTTP_METHOD = "POST";
const API_VERSION = "2019-12-30";
const request_ = {};
//系统参数
request_["SignatureMethod"] = "HMAC-SHA1";
request_["SignatureNonce"] = signNRandom();
request_["AccessKeyId"] = accessKeyId;
request_["SignatureVersion"] = "1.0";
request_["Timestamp"] = getTimestamp();
request_["Format"] = "JSON";
request_["RegionId"] = "cn-shanghai";
request_["Version"] = API_VERSION;
request_["Action"] = Action;
request_["ImageURL"] = ImageURL
callApiRequest(request_, API_HTTP_METHOD, endpoint, accessKeySecret, callback);
}
var http = {};
http.request = function (option, callback) {
var url = option.url;
var method = option.method;
var data = option.data;
var timeout = option.timeout || 0;
//创建XMLhttpRequest对象
var xhr = new XMLHttpRequest();
// var xhr = new plus.net.XMLHttpRequest()
// return
(timeout > 0) && (xhr.timeout = timeout);
//使用open方法设置和服务器的交互信息
xhr.open(method, url, true);
if (typeof data === 'object') {
try {
data = JSON.stringify(data);
} catch (e) { }
}
//发送请求
xhr.send(data);
//如果请求完成,并响应完成,获取到响应数据
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var result = xhr.responseText;
try { result = JSON.parse(xhr.responseText); } catch (e) { }
callback && callback(null, result);
}
}.bind(this);
//延时处理
xhr.ontimeout = function () {
callback && callback('timeout');
console.log('error', '连接超时');
};
};
// post请求
http.post = function (option, callback) {
option.method = 'post';
option.contentType = 'application/json;charset=UTF-8'
this.request(option, callback);
};
// 封装请求方法
function httpRequest(url, data, method, callback) {
uni.request({
url: url,
method: method,
data: data,
header: {
'content-type': 'application/json'
},
success: function (res) {
callback(null, res.data);
},
fail: function (err) {
callback(err, null);
}
});
}
//请求数据
const callApiRequest = (request_, API_HTTP_METHOD, endpoint, accessKeySecret, callback) => {
const url = generateUrl(request_, API_HTTP_METHOD, endpoint, accessKeySecret);
httpRequest(url, null, 'POST', function (err, result) {
if (err) {
console.error('Error:', err);
callback(null);
} else {
console.log('Result:', result);
callback(result);
}
});
}
export default callRecognizeBankCard;
请求之后的响应参数都在文档里面有,https://help.aliyun.com/zh/viapi/developer-reference/api-i5236v?spm=a2c4g.11186623.0.i2
然后开始使用
typescript
<view class="re-upload" @click="uploadImage">上传图片</view>
<u-modal :show="identifyShow" title="" :showConfirmButton="false">
<view class="slot-content" style="width: 100%;position: relative;display: flex;justify-content: center;background-color: #00112b;">
<image class="ai-bg-top" src="" mode=""></image>
<view class="ai-image" style="width: 330px !important;height: 350px;margin-top: 200rpx;position: relative;">
<view class="animation" v-show="scanShow">
<view class="animation-list"></view></view>
<image :src="aiAvatar" mode="" style="width: 100% !important;height: 100%;"></image>
<view class="faceRectangles"
v-for="(item, index) in avatarPostionList"
:key="index"
:style="handlefaceRectanglesStyle(item, index)"
>
</view>
</view>
<image class="ai-bg-bottom" src="" mode=""></image>
</view>
</u-modal>
import faceRecognition from '@/utils/faceRecognition.js'
const identifyShow = ref(false)
const aiAvatar = ref('')
const scanShow = ref(false)
const studentForm = ref({ avatar: '' })
const avatarPostionList = ref([])
const handlefaceRectanglesStyle = (item, index) => {
return {
'position': 'absolute',
'left': `${item[0]}px`,
'top': `${item[1]}px`,
'width': `${item[2]}px`,
'height': `${item[3]}px`
}
}
const chunkArray = (array, chunkSize) => {
const groupedArray = [];
for (let i = 0; i < array.length; i += chunkSize) {
groupedArray.push(array.slice(i, i + chunkSize));
}
return groupedArray;
}
const handleClear = (boolean) => {
avatarPostionList.value = []
scanShow.value = boolean
identifyShow.value = boolean
}
const handleFaceRecognition = (path) => {
faceRecognition(path, avatar => {
if(avatar.Code) {
uni.showToast({
title: avatar.Message,
icon: 'none',
duration: 5000
})
identifyShow.value = false
return;
}
if(avatar.Data.FaceCount !== 1) {
const avatarList = chunkArray(avatar.Data.FaceRectangles, 4)
avatarPostionList.value = avatarList
scanShow.value = false
setTimeout(() => {
uni.showModal({
title: '提示',
content: '当前照片可能存在多张人脸,是否继续上传?',
success: res => {
if(res.confirm) {
studentForm.value.avatar = path
}else {
uni.showToast({
title: '已取消上传',
icon: 'none'
})
}
identifyShow.value = false
}
})
}, 1000)
}else {
avatarPostionList.value = [avatar.Data.FaceRectangles]
setTimeout(() => {
handleClear(false)
studentForm.value.avatar = path
}, 1000)
}
})
}
const uploadImage = () => {
uni.chooseImage({
count: 1,
success: (res) => {
//uploadFile,封装的上传方法
uploadFile(res.tempFilePaths[0], 'avatar', (path:string) => {
// path为图片线上地址
const newPath = `${path}?x-oss-process=image/resize,limit_0,m_fill,w_330,h_350/quality,q_100`
// newPath 加宽高之后的图片
aiAvatar.value = newPath
handleClear(true)
setTimeout(() => {
handleFaceRecognition(newPath)
}, 5000)
},()=>{})
}
});
}
css
typescript
:deep(.u-popup__content){
width: 350px !important;
// border-radius: 0 !important;
}
:deep(.u-modal) {
width: 100% !important;
}
.ai-bg-top {
position: absolute;
width: 100%;
height: 200rpx;
top: 0;
left: 0;
}
.ai-bg-bottom {
position: absolute;
width: 100%;
height: 200rpx;
bottom: 0;
left: 0;
}
.animation{
position: absolute;
top: 350rpx;
left: 0;
right: 0;
height: 700rpx;
}
.animation-list{
width: 100%;
height: 450rpx;
background: linear-gradient(to bottom,rgba(216,179,255,0),rgba(216,179,255,1));
position: relative;
top: 0;
animation: myfist 2s linear 1s infinite alternate;
}
/* 开始执行动画 */
@keyframes myfist{
0%{
background: linear-gradient(to bottom,rgba(216,179,255,0),rgba(216,179,255,1));
left: 0;
top: -400rpx;
}
25%{
left: 0;
top: 100rpx;
}
50%{
left: 0;
top: 100rpx;
}
75%{
left: 0;
top: 100rpx;
}
100%{
left: 0;
top: -400rpx;
}
}
.faceRectangles {
border: 4rpx solid red;
}
:deep(.u-modal__content){
padding: 0 !important;
height: 1100rpx;
width: 100%;
}
:deep(.u-line){
display: none !important;
}