Python公衆號開發(二)—顏值檢測

上篇文章,咱們把本身的程序接入了微信公衆號,而且能把用戶發送的文本及圖片文件原樣返回。今天咱們把用戶的圖片經過騰訊的AI平臺分析後再返回給用戶。python

爲了防止個人文章被處處轉載,貼一下個人公衆號【智能製造社區】,歡迎你們關注。git

github倉庫地址https://github.com/injetlee/Python/tree/master/wechatgithub

效果圖

圖片描述

圖片描述

圖片描述

一. 接入騰訊AI平臺

咱們先看一下官方人臉檢測與分析接口的描述:算法

檢測給定圖片(Image)中的全部人臉(Face)的位置和相應的面部屬性。位置包括(x, y, w, h),面部屬性包括性別(gender), 年齡(age), 表情(expression), 魅力(beauty), 眼鏡(glass)和姿態(pitch,roll,yaw)。express

請求參數包括下面幾個:json

  • app_id 應用標識,咱們在AI平臺註冊後就能夠獲得app_id
  • time_stamp 時間戳
  • nonce_str 隨機字符串
  • sign 簽名信息,須要咱們本身去計算
  • image 須要檢測的圖片(上限1M)
  • mode 檢測模式

1.接口鑑權,構造請求參數

官方給了咱們接口鑑權的計算方法。segmentfault

  1. 將<key, value>請求參數對按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 install requests安裝requests。api

3.處理返回的信息

處理返回的信息,把信息展現在圖片上,再把處理後的圖片保存。這裏咱們用到 opencv ,和 pillow 兩個庫pip install pillowpip install opencv-python來安裝。服務器

開始編寫代碼,咱們新建一個face_id.py 文件來對接AI平臺,而且返回檢測後的圖像數據。微信

import time
import random
import base64
import hashlib
import requests
from urllib.parse import urlencode
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import os


# 一.計算接口鑑權,構造請求參數

def random_str():
    '''獲得隨機字符串nonce_str'''
    str = 'abcdefghijklmnopqrstuvwxyz'
    r = ''
    for i in range(15):
        index = random.randint(0,25)
        r += str[index]
    return r


def image(name):
    with open(name, 'rb') as f:
        content = f.read()
    return base64.b64encode(content)


def get_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=lambda item: item[0], 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  # 添加到請求參數列表中
    return params

# 二.請求接口URL


def access_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信息
    # 把信息顯示到圖片上
    if res['ret'] == 0:  # 0表明請求成功
        pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))  # 把opencv格式轉換爲PIL格式,方便寫漢字
        draw = ImageDraw.Draw(pil_img)
        for obj in res['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']  # 人臉框高度
            # 根據返回的值,自定義一下顯示的文字內容
            if obj['glass'] == 1:  # 眼鏡
                glass = '有'
            else:
                glass = '無'
            if obj['gender'] >= 70:  # 性別值從0-100表示從女性到男性
                gender = '男'
            elif 50 <= obj['gender'] < 70:
                gender = "娘"
            elif obj['gender'] < 30:
                gender = '女'
            else:
                gender = '女漢子'
            if 90 < obj['expression'] <= 100:  # 表情從0-100,表示笑的程度
                expression = '一笑傾城'
            elif 80 < obj['expression'] <= 90:
                expression = '心花盛開'
            elif 70 < obj['expression'] <= 80:
                expression = '興高采烈'
            elif 60 < obj['expression'] <= 70:
                expression = '眉開眼笑'
            elif 50 < obj['expression'] <= 60:
                expression = '喜上眉梢'
            elif 40 < obj['expression'] <= 50:
                expression = '喜氣洋洋'
            elif 30 < obj['expression'] <= 40:
                expression = '笑逐顏開'
            elif 20 < obj['expression'] <= 30:
                expression = '似笑非笑'
            elif 10 < obj['expression'] <= 20:
                expression = '半嗔半喜'
            elif 0 <= obj['expression'] <= 10:
                expression = '黯然傷神'
            delt = h // 5  # 肯定文字垂直距離
            # 寫入圖片
            if len(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)
            elif img_width - x - w < 170:  # 避免圖片太窄,致使文字顯示不徹底
                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 '檢測失敗'

到這裏咱們的人臉檢測接口接入及圖片處理就完成了。以後在收到用戶發送的圖片信息後,調用這個函數,把處理後的圖片返回給用戶就能夠。

返回圖片給用戶

當收到用戶圖片時,須要如下幾個步驟:

保存圖片

當接收到用戶圖片後,咱們要先把圖片保存起來,以後才能去調用人臉分析接口,把圖片信息傳遞過去,咱們須要編寫一個 img_download 函數來下載圖片。詳見下方代碼

調用人臉分析接口

圖片下載後,調用 face_id.py 文件裏的接口函數,獲得處理後的圖片。

上傳圖片

檢測結果是一張新的圖片,要把圖片發送給用戶咱們須要一個 Media_ID,要獲取Media_ID必須先把圖片上傳爲臨時素材,因此這裏咱們須要一個img_upload函數來上傳圖片,而且在上傳時須要用到一個access_token,咱們經過一個函數來獲取. 獲取access_token必需要把咱們本身的IP地址加入白名單,不然是獲取不到的。請登陸「微信公衆平臺-開發-基本配置」提早將服務器IP地址添加到IP白名單中,能夠在http://ip.qq.com/查看本機的IP...

開始編寫代碼,咱們新建一個 utils.py 來下載、上傳圖片

import requests
import json
import threading
import time
import os

token = ''
app_id = 'wxfc6adcdd7593a712'
secret = '429d85da0244792be19e0deb29615128'


def img_download(url, name):
    r = requests.get(url)
    with open('images/{}-{}.jpg'.format(name, time.strftime("%Y_%m_%d%H_%M_%S", time.localtime())), 'wb') as fd:
        fd.write(r.content)
    if os.path.getsize(fd.name) >= 1048576:
        return 'large'
    # print('namename', os.path.basename(fd.name))
    return os.path.basename(fd.name)


def get_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)
    global token
    token = parse_json['access_token']
    global timer
    timer = threading.Timer(6000, get_access_token)
    timer.start()


def img_upload(mediaType, name):
    global token
    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)
    return parse_json['media_id']

get_access_token(app_id, secret)

返回給用戶

咱們簡單修改下收到圖片後的邏輯,收到圖片後通過人臉檢測,上傳得到Media_ID,咱們要作的就是把圖片返回給用戶便可。直接看connect.py的代碼

import falcon
from falcon import uri
from wechatpy.utils import check_signature
from wechatpy.exceptions import InvalidSignatureException
from wechatpy import parse_message
from wechatpy.replies import TextReply, ImageReply

from utils import img_download, img_upload
from face_id import access_api


class Connect(object):

    def on_get(self, req, resp):
        query_string = req.query_string
        query_list = query_string.split('&')
        b = {}
        for i in query_list:
            b[i.split('=')[0]] = i.split('=')[1]

        try:
            check_signature(token='lengxiao', signature=b['signature'], timestamp=b['timestamp'], nonce=b['nonce'])
            resp.body = (b['echostr'])
        except InvalidSignatureException:
            pass
        resp.status = falcon.HTTP_200

    def on_post(self, req, resp):
        xml = req.stream.read()
        msg = parse_message(xml)
        if msg.type == 'text':
            reply = TextReply(content=msg.content, message=msg)
            xml = reply.render()
            resp.body = (xml)
            resp.status = falcon.HTTP_200
        elif msg.type == 'image':
            name = img_download(msg.image, msg.source)  # 下載圖片
            r = access_api('images/' + name)
            if r == '檢測成功':
                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. 沒法自定義菜單,一旦啓用了自定義開發,菜單也須要自定義配置,可是未認證的公衆號沒有權限經過程序來配置菜單,只能在微信後臺配置。

因此,我並無在個人公衆號上啓用這個程序,可是若是有認證的公衆號,能夠嘗試開發各類好玩的功能。

上篇:Python微信公衆號開發—小白篇(一)

相關文章
相關標籤/搜索