1. 程式人生 > >Python公眾號開發:顏值檢測

Python公眾號開發:顏值檢測

效果圖

效果1.png

效果2.jpg

效果3.jpg

一. 接入騰訊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, 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。

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
'''
Python學習資料或者需要程式碼、視訊加這個群548377875 都在這裡了
'''

# 一.計算介面鑑權,構造請求引數

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):
    print(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':
            print('hello')
            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)  # 下載圖片
            print(name)
            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. 無法自定義選單,一旦啟用了自定義開發,選單也需要自定義配置,但是未認證的公眾號沒有許可權通過程式來配置選單,只能在微信後臺配置。