首先祝大家粽子节快乐,多吃点粽子!
上篇文章,我们把自己的程序接入了微信公众号,并且能把用户发送的文本及图片文件原样返回。今天我们把用户的图片通过腾讯的AI平台分析后再返回给用户。
效果图
以上效果图是在测试账号 【尖叫的牙齿】进行测试,大家也可以去测试一下。
接入腾讯AI平台
我们先看一下官方人脸检测与分析接口的描述
检测给定图片(Image)中的所有人脸(Face)的位置和相应的面部属性。位置包括(x, y, w, h),面部属性包括性别(gender), 年龄(age), 表情(expression), 魅力(beauty), 眼镜(glass)和姿态(pitch,roll,yaw)。
请求参数包括下面几个:
app_id 应用标识,我们在AI平台注册后就可以得到app_id
time_stamp 时间戳
nonce_str 随机字符串
sign 签名信息,需要我们自己去计算
image 需要检测的图片(上限1M)
mode 检测模式
1.接口鉴权,构造请求参数
官方给了我们接口鉴权的计算方法。
1. 将请求参数对按key进行字典升序排序,得到有序的参数对列表N
2. 将列表N中的参数对按URL键值对的格式拼接成字符串,得到字符串T(如:key1=value1&key2=value2),URL键值拼接过程value部分需要URL编码,URL编码算法用大写字母,例如%E8,而不是小写%e8
3. 将应用密钥以app_key为键名,组成URL键值拼接到字符串T末尾,得到字符串S(如:key1=value1&key2=value2&app_key=密钥)
4. 对字符串S进行MD5运算,将得到的MD5值所有字符转换成大写,得到接口请求签名
2.请求接口地址
请求接口信息,我们用 requests 发送请求,会得到返回的 json 格式的图像信息,通过pip安装。
pipinstallrequests
3.处理返回的信息
处理返回的信息,把信息展示在图片上,再把处理后的图片保存。这里我们用到 opencv ,和 pillow 两个库,通过pip安装
pipinstallpillow
pipinstallopencv-python
开始编写代码,我们新建一个face_id.py 文件来对接AI平台,并且返回检测后的图像数据。
importtime
importrandom
importbase64
importhashlib
importrequests
fromurllib.parseimporturlencode
importcv2
importnumpyasnp
fromPILimportImage, ImageDraw, ImageFont
importos
# 一.计算接口鉴权,构造请求参数
defrandom_str():
'''得到随机字符串nonce_str'''
str ='abcdefghijklmnopqrstuvwxyz'
r =''
foriinrange(15):
index = random.randint(,25)
r += str[index]
returnr
defimage(name):
withopen(name,'rb')asf:
content = f.read()
returnbase64.b64encode(content)
defget_params(img):
'''组织接口请求的参数形式,并且计算sign接口鉴权信息,
最终返回接口请求所需要的参数字典'''
params = {
'app_id':'1106860829',
'time_stamp': str(int(time.time())),
'nonce_str': random_str(),
'image': img,
'mode':'0'
}
sort_dict = sorted(params.items(), key=lambdaitem: item[], reverse=False)# 排序
sort_dict.append(('app_key','P8Gt8nxi6k8vLKbS'))# 添加app_key
rawtext = urlencode(sort_dict).encode()# URL编码
sha = hashlib.md5()
sha.update(rawtext)
md5text = sha.hexdigest().upper()# 计算出sign,接口鉴权
params['sign'] = md5text# 添加到请求参数列表中
returnparams
# 二.请求接口URL
defaccess_api(img):
frame = cv2.imread(img)
nparry_encode = cv2.imencode('.jpg', frame)[1]
data_encode = np.array(nparry_encode)
img_encode = base64.b64encode(data_encode)# 图片转为base64编码格式
url ='https://api.ai.qq.com/fcgi-bin/face/face_detectface'
res = requests.post(url, get_params(img_encode)).json()# 请求URL,得到json信息
# 把信息显示到图片上
ifres['ret'] ==:# 0代表请求成功
pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))# 把opencv格式转换为PIL格式,方便写汉字
draw = ImageDraw.Draw(pil_img)
forobjinres['data']['face_list']:
img_width = res['data']['image_width']# 图像宽度
img_height = res['data']['image_height']# 图像高度
# print(obj)
x = obj['x']# 人脸框左上角x坐标
y = obj['y']# 人脸框左上角y坐标
w = obj['width']# 人脸框宽度
h = obj['height']# 人脸框高度
# 根据返回的值,自定义一下显示的文字内容
ifobj['glass'] ==1:# 眼镜
glass ='有'
else:
glass ='无'
ifobj['gender'] >=70:# 性别值从0-100表示从女性到男性
gender ='男'
elif50
gender ="娘"
elifobj['gender']
gender ='女'
else:
gender ='女汉子'
if90
expression ='一笑倾城'
elif80
expression ='心花怒放'
elif70
expression ='兴高采烈'
elif60
expression ='眉开眼笑'
elif50
expression ='喜上眉梢'
elif40
expression ='喜气洋洋'
elif30
expression ='笑逐颜开'
elif20
expression ='似笑非笑'
elif10
expression ='半嗔半喜'
elif
expression ='黯然伤神'
delt = h //5# 确定文字垂直距离
# 写入图片
iflen(res['data']['face_list']) >1:# 检测到多个人脸,就把信息写入人脸框内
font = ImageFont.truetype('yahei.ttf', w //8, encoding='utf-8')# 提前把字体文件下载好
draw.text((x +10, y +10),'性别 :'+ gender, (76,176,80), font=font)
draw.text((x +10, y +10+ delt *1),'年龄 :'+ str(obj['age']), (76,176,80), font=font)
draw.text((x +10, y +10+ delt *2),'表情 :'+ expression, (76,176,80), font=font)
draw.text((x +10, y +10+ delt *3),'魅力 :'+ str(obj['beauty']), (76,176,80), font=font)
draw.text((x +10, y +10+ delt *4),'眼镜 :'+ glass, (76,176,80), font=font)
elifimg_width - x - w
font = ImageFont.truetype('yahei.ttf', w //8, encoding='utf-8')
draw.text((x +10, y +10),'性别 :'+ gender, (76,176,80), font=font)
draw.text((x +10, y +10+ delt *1),'年龄 :'+ str(obj['age']), (76,176,80), font=font)
draw.text((x +10, y +10+ delt *2),'表情 :'+ expression, (76,176,80), font=font)
draw.text((x +10, y +10+ delt *3),'魅力 :'+ str(obj['beauty']), (76,176,80), font=font)
draw.text((x +10, y +10+ delt *4),'眼镜 :'+ glass, (76,176,80), font=font)
else:
font = ImageFont.truetype('yahei.ttf',20, encoding='utf-8')
draw.text((x + w +10, y +10),'性别 :'+ gender, (76,176,80), font=font)
draw.text((x + w +10, y +10+ delt *1),'年龄 :'+ str(obj['age']), (76,176,80), font=font)
draw.text((x + w +10, y +10+ delt *2),'表情 :'+ expression, (76,176,80), font=font)
draw.text((x + w +10, y +10+ delt *3),'魅力 :'+ str(obj['beauty']), (76,176,80), font=font)
draw.text((x + w +10, y +10+ delt *4),'眼镜 :'+ glass, (76,176,80), font=font)
draw.rectangle((x, y, x + w, y + h), outline="#4CB050")# 画出人脸方框
cv2img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)# 把 pil 格式转换为 cv
cv2.imwrite('faces/{}'.format(os.path.basename(img)), cv2img)# 保存图片到 face 文件夹下
return'检测成功'
else:
return'检测失败'
到这里我们的人脸检测接口接入及图片处理就完成了。之后在收到用户发送的图片信息后,调用这个函数,把处理后的图片返回给用户就可以。
返回图片给用户
当收到用户图片时,需要以下几个步骤:
1. 保存图片
当接收到用户图片后,我们要先把图片保存起来,之后才能去调用人脸分析接口,把图片信息传递过去,我们需要编写一个 img_download 函数来下载图片。详见下方代码
2. 调用人脸分析接口
图片下载后,调用 face_id.py 文件里的接口函数,得到处理后的图片。
3. 上传图片
检测结果是一张新的图片,要把图片发送给用户我们需要一个 Media_ID,要获取Media_ID必须先把图片上传为临时素材,所以这里我们需要一个img_upload函数来上传图片,并且在上传时需要用到一个access_token,我们通过一个函数来获取.获取access_token必须要把我们自己的IP地址加入白名单,否则是获取不到的。请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,可以在http://ip.qq.com/查看本机的IP地址
开始编写代码,我们新建一个 utils.py 来下载、上传图片
importrequests
importjson
importthreading
importtime
importos
token =''
app_id ='微信配置界面的AppID'
secret ='配置界面的开发者密码AppSecret'
defimg_download(url, name):
r = requests.get(url)
withopen('images/{}-{}.jpg'.format(name, time.strftime("%Y_%m_%d%H_%M_%S", time.localtime())),'wb')asfd:
fd.write(r.content)
ifos.path.getsize(fd.name) >=1048576:
return'large'
# print('namename', os.path.basename(fd.name))
returnos.path.basename(fd.name)
defget_access_token(appid, secret):
'''获取access_token,100分钟刷新一次'''
url ='https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}'.format(appid, secret)
r = requests.get(url)
parse_json = json.loads(r.text)
globaltoken
token = parse_json['access_token']
globaltimer
timer = threading.Timer(6000, get_access_token)
timer.start()
defimg_upload(mediaType, name):
globaltoken
url ="https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s"% (token, mediaType)
files = {'media': open('{}'.format(name),'rb')}
r = requests.post(url, files=files)
parse_json = json.loads(r.text)
returnparse_json['media_id']
get_access_token(app_id, secret)
返回给用户
我们简单修改下收到图片后的逻辑,收到图片后经过人脸检测,上传获得Media_ID,我们要做的就是把图片返回给用户即可。直接看connect.py的代码
importfalcon
fromfalconimporturi
fromwechatpy.utilsimportcheck_signature
fromwechatpy.exceptionsimportInvalidSignatureException
fromwechatpyimportparse_message
fromwechatpy.repliesimportTextReply, ImageReply
fromutilsimportimg_download, img_upload
fromface_idimportaccess_api
classConnect(object):
defon_get(self, req, resp):
query_string = req.query_string
query_list = query_string.split('&')
b = {}
foriinquery_list:
b[i.split('=')[]] = i.split('=')[1]
try:
check_signature(token='lengxiao', signature=b['signature'], timestamp=b['timestamp'], nonce=b['nonce'])
resp.body = (b['echostr'])
exceptInvalidSignatureException:
pass
resp.status = falcon.HTTP_200
defon_post(self, req, resp):
xml = req.stream.read()
msg = parse_message(xml)
ifmsg.type =='text':
reply = TextReply(content=msg.content, message=msg)
xml = reply.render()
resp.body = (xml)
resp.status = falcon.HTTP_200
elifmsg.type =='image':
name = img_download(msg.image, msg.source)# 下载图片
print(name)
r = access_api('images/'+ name)
ifr =='检测成功':
media_id = img_upload('image','faces/'+ name)# 上传图片,得到 media_id
reply = ImageReply(media_id=media_id, message=msg)
else:
reply = TextReply(content='人脸检测失败,请上传1M以下人脸清晰的照片', message=msg)
xml = reply.render()
resp.body = (xml)
resp.status = falcon.HTTP_200
app = falcon.API()
connect = Connect()
app.add_route('/connect', connect)
至此我们的工作就做完了,我们的公众号可以进行颜值检测了。本来我打算用在自己公众号上的,但是还存在下面几个问题,所以没有使用。
1. 微信的机制,我们的程序必须在5s内给出响应。不然就会报'公众号提供的服务出现故障'。然而处理图片有时会比较慢,经常会超过5s。所以正确的处理方式应该是拿到用户的请求后立即返回一个空字符串表示我们收到了,之后单独创建一个线程去处理图片,当图片处理完后通过客服接口发送给用户。可惜的是未认证的公众号没有客服接口,所以没办法,超过5s就会报错。
2. 无法自定义菜单,一旦启用了自定义开发,菜单也需要自定义配置,但是未认证的公众号没有权限通过程序来配置菜单,只能在微信后台配置。
所以,我并没有在这个公众号上启用这个程序,但是如果有认证的公众号,可以尝试开发各种好玩的功能。
github:https://github.com/injetlee/Python/tree/master/wechat
领取专属 10元无门槛券
私享最新 技术干货