Python人臉檢測(初級)
前言
忙了一段時間,少有時間寫文,前連天公司的一個web專案需要使用者上傳自拍照然後檢查自拍照裡面的人臉狀態(人臉數量,是否正臉,是否戴眼鏡,是否戴帽子等等),如果狀態合適就將人臉提取出來,最後與設定的前景圖片進行融合。
這其中涉及到的關鍵點在於人臉的檢測,人臉的提取,至於人臉與前景的融合則可以使用前端js實現,對人臉的處理一般使用濾鏡處理,前端可以使用騰訊AlloyTeam團隊開源的專案AlloyImage( ofollow,noindex" href="http://alloyteam.github.io/AlloyImage/" target="_blank">http://alloyteam.github.io/AlloyImage/ )處理圖片,擁有豐富的濾鏡。當然也可以在後端處理好傳給前端,後端處理可以選擇使用OpenCV,後端處理的缺點在於多使用者同時上傳的時候伺服器壓力較大,交給前端處理比較妥當。
關於濾鏡處理圖片這部分不多說了,仔細研讀騰訊的開源專案文件就能搞定,今天說說人臉檢測和提取部分,注意是“檢測”,不是“識別”。
檢測的意思是隻需要檢查是人臉和人臉的狀態以及人臉的位置,識別則是根據人臉特徵匹配到人的姓名,這是兩種不同的概念,千萬不要混淆。
分析
既然是入門級的人臉檢測,當然是從易到難,首先考慮的是有沒有第三方人臉檢測API,直接呼叫第三方介面,也是一種快速實現的方法。說到第三方介面,自然要貨比三家,誰好用用誰。
經過搜尋引擎的篩選,博主選擇了四家服務商的介面,分別是百度、騰訊、阿里雲、Face++。當然了,如果這篇文章只是單純的描述如何呼叫第三方介面的話,估計各位看官也不會買賬,不就是一個requests請求的事,還需要寫個文?
為了體現從易到難循序漸進的思想,博主增加了本地檢測的方式進行對比,本地Python配合OpenCV也可以實現人臉的檢測,把這幾種進行對比,自然就能看出各自的優劣。
當然了,本地OpenCV檢測的速度和效率是第三方服務商介面呼叫無法比的,這也是為下一篇進階(監控流媒體畫面實時檢測)打基礎。
好了,廢話不多說,開始幾種方案的對比。
介面
首先對四家的介面文件進行分析,對接難度從易到難分別是 Face++、百度、騰訊、阿里雲,參考標準是實現相同功能需要的程式碼行數(手動滑稽!)。
給出四家服務商的介面文件地址供大家自行參考:
- Face++ 文件地址:https://console.faceplusplus.com.cn/documents/4888373
- 騰訊人臉檢測 文件地址:https://cloud.tencent.com/document/product/867/17588
- 百度 人臉檢測 文件地址:http://ai.baidu.com/docs#/Face-Detect-V3/top
- 阿里雲 人臉檢測 文件地址:https://help.aliyun.com/knowledge_detail/53399.html
Face++和百度雲不需要生成簽名,只需要傳入key和id即可,騰訊和阿里雲需要生成對應的簽名做認證才能呼叫成功,其中阿里雲的簽名生成是對所有的請求內容進行簽名,稍有錯誤就導致簽名失敗,所以除錯起來稍微有點複雜,博主也是在這裡被坑的很慘。
Face++ 支援三種圖片的傳遞方式,url、二進位制、base64,檔案大小為最大2M。
騰訊 支援兩種圖片的傳遞方式,url和二進位制。
百度 支援兩種圖片的傳遞方式,url和Base64。
阿里雲 支援兩種圖片的傳遞方式,url和Base64。
阿里雲返回的結果中資料量最小,很多檢測結果是沒有的,比如是否戴眼鏡,戴帽子等,當然了,這裡的重點是人臉檢測,只需要檢測到人臉的位置資訊就OK了,其他的都是次要資訊。
針對專案需求,url傳圖的方式因為有圖片下載的時間不確定性,而且使用者上傳圖片後臺轉換url也是一道程式,還不如直接傳圖,四家服務商除了騰訊不支援base64以外,其他三家都支援base64,而且前端呼叫微信的圖片上傳方法可以直接將圖片在前端就轉換成base64,通過介面上傳給服務端處理,呼叫騰訊雲的時候可以將base64編碼的圖片直接decode成二進位制檔案,這樣騰訊雲就可以支援了。
實戰
博主首選選取一張帶有明顯人臉標誌的圖片進行測試,上測試圖(在此感謝迪麗熱巴)
然後根據需求編寫程式碼,先給出測試結果的對比圖,再來根據各自的結果進行分析
從對比圖可以看出,Face++和百度的識別結果基本一致,騰訊和本地OpenCV識別出來的結果非常接近,而阿里雲識別出來的結果則是相對比較完整的人臉。
當然了,僅僅依靠這一張圖很難判斷出來誰更厲害,那咱們多來幾個測試,首先來一張胡歌的側臉特寫(在此感謝胡歌)
這組檢測結果裡面依然是阿里雲表現最佳,而百度則有點離譜,OpenCV雖然檢測到的位置是準確的,但是檢測人臉的範圍出現了失誤,顯示為正方形。
下面再來一張,這張測試圖是一張面部被頭髮遮擋並且不是正面,看下各自的識別結果(在此感謝袁姍姍)

這裡得說明一下,OpenCV識別失敗並不是因為它就一定很垃圾,OpenCV的識別率是根據給定的人臉模型而來的,OpenCV自帶了很多識別的模型,這些模型都是經過多次卷積網路演算法訓練得來的,而博主這裡使用的是人臉最為嚴格的模型,也就是說識別的條件更為苛刻,所以在這次測試中OpenCV沒有檢測到人臉,比較尷尬。
所以,綜上一些簡單的測試後發現如果要做人臉的提取,還是選擇阿里雲的人臉檢測介面比較合適,下面來看看各家API的收費情況,這裡僅僅比對免費額度。
Face++提供免費的使用API key,不限制呼叫次數,但是一些高階的功能無法使用。
百度 對於個人使用者提供QOS 為2 企業使用者QOS 為10,呼叫次數不限的免費服務。
騰訊 提供不限次數的呼叫請求,但是高階功能需要付費。
阿里雲 提供免費5000次的免費呼叫額度,5000次用完自動切換到付費模式。
這裡放上一張前兩天專案中的效果圖吧
程式碼
根據各家提供的API文件和測試demo,編寫各自的測試程式碼,下面提供程式碼供大家參考。
注意:這裡程式碼僅僅作為快速除錯的參考,沒有做異常處理,正式開發請根據自身開發環境和生產環境自行考慮各種異常處理,請不要直接使用我的程式碼。
如果有不懂的地方可以發郵件詢問。
Face++(圖片以二進位制上傳)
# -*- coding: utf-8 -*- import requests, sys from PIL import Image, ImageDraw source_path = sys.path[0] + '/shanshan.jpg' with open(source_path, "rb") as f: file_data = f.read() params = { "api_key": "xxxxxx", "api_secret": "xxxxxxx" } f = {"image_file": file_data} request_url = "https://api-cn.faceplusplus.com/facepp/v3/detect" decode_result = requests.post(url=request_url, data=params, files=f, timeout=30).json() current_face = decode_result.get('faces')[0] face_location = current_face.get('face_rectangle') face_x = int(face_location.get("left")) face_y = int(face_location.get("top")) face_width = face_x + int(face_location.get("width")) face_height = face_y + int(face_location.get("height")) old_img = Image.open(source_path) draw_instance = ImageDraw.Draw(old_img) draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0)) old_img.save(sys.path[0] + '/i-face++.jpg')
百度
# -*- coding: utf-8 -*- import requests, sys, base64 from PIL import Image, ImageDraw source_path = sys.path[0] + '/shanshan.jpg' # client_id 為官網獲取的AK, client_secret 為官網獲取的SK host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=*********&client_secret=**********' session = requests.get(url=host, timeout=30).json() access_token = session.get('access_token') with open(source_path, "rb") as f: base64_data = base64.b64encode(f.read()) params = { "image": base64_data, "image_type": "BASE64", "face_field": "beauty,age,glasses,face_type,quality" } request_url = "https://aip.baidubce.com/rest/2.0/face/v3/detect" request_url = request_url + "?access_token=" + access_token decode_result = requests.post(url=request_url, data=params, timeout=30).json() face_num = decode_result.get('result').get('face_num') current_face = decode_result.get('result').get('face_list')[0] face_location = current_face.get('location') face_glasses = current_face.get('glasses') face_real = current_face.get('face_type') face_quality = current_face.get('quality') face_probability = current_face.get('face_probability') face_angel = current_face.get('angel') face_x = int(face_location.get("left")) face_y = int(face_location.get("top")) face_width = face_x + int(face_location.get("width")) face_height = face_y + int(face_location.get("height")) old_img = Image.open(source_path) draw_instance = ImageDraw.Draw(old_img) draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0)) old_img.save(sys.path[0] + '/i-baidu.jpg')
騰訊
# -*- coding: utf-8 -*- import requests, sys, base64, time, random, hashlib, hmac from PIL import Image, ImageDraw source_path = sys.path[0] + '/shanshan.jpg' orignal = "a=******&b=&k=*******&e=******&t=%d&r=%d&f=" % (int(time.time()), random.randint(1, 1000)) SignTmp = hmac.new("**********".encode('utf8'), orignal.encode('utf8'), hashlib.sha1).digest() before_string = SignTmp + orignal.encode('utf8') Sign = base64.b64encode(before_string).decode('utf8') request_url = "https://recognition.image.myqcloud.com/face/detect" headers = {"authorization": Sign, "host": "recognition.image.myqcloud.com"} s = requests.session() with open(source_path, "rb") as f: img_file = f.read() params = {"appid": "*********", "mode": 1} f = {"image": open(source_path, "rb").read()} decode_result = s.post(url=request_url, headers=headers, data=params, files=f, timeout=30).json() if decode_result.get('code') == 0: face_data = decode_result.get('data') current_face = face_data.get('face')[0] face_x = int(current_face.get("x")) face_y = int(current_face.get("y")) face_width = face_x + int(current_face.get("width")) face_height = face_y + int(current_face.get("height")) old_img = Image.open(source_path) draw_instance = ImageDraw.Draw(old_img) draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0)) old_img.save(sys.path[0] + '/i-tencent.jpg')
阿里雲
# -*- coding: utf-8 -*- import requests, sys, base64, hashlib, hmac, json, datetime from PIL import Image, ImageDraw source_path = sys.path[0] + '/shanshan.jpg' id = "*******" key = "***********" request_url = "https://dtplus-cn-shanghai.data.aliyuncs.com/face/detect" with open(source_path, "rb") as f: base64_data = base64.b64encode(f.read()).decode('utf8') params = {"type": 1, "content": base64_data} def get_current_date(): date = datetime.datetime.strftime(datetime.datetime.utcnow(), "%a, %d %b %Y %H:%M:%S GMT") return date def to_md5_base64(string_body): hash_obj = hashlib.md5() hash_obj.update(string_body.encode('utf8')) return base64.b64encode(hash_obj.digest()).decode('utf8') def to_sha1_base64(string_to_sign, secret): hmac_sha1 = hmac.new(secret.encode('utf8'), string_to_sign.encode('utf8'), hashlib.sha1) return base64.b64encode(hmac_sha1.digest()).decode('utf8') options = { 'method': 'POST', 'body': json.dumps(params), 'headers': { 'accept': 'application/json', 'content-type': 'application/json', 'date':get_current_date() } } body_md5 = to_md5_base64(options.get('body')) string_sign = '%s\n%s\n%s\n%s\n%s\n%s' % ( options.get('method'), options.get('headers').get('accept'), body_md5, options.get('headers').get('content-type'), options.get('headers').get('date'), "/face/detect" ) signature = to_sha1_base64(string_sign, key) headers = { 'Accept': 'application/json', 'Content-type': 'application/json', "Date": options.get('headers').get('date'), "Authorization": "Dataplus %s:%s" % (id, signature) } s = requests.session() decode_result = s.post(url=request_url, headers=headers, data=json.dumps(params), timeout=30).json() if decode_result.get('errno') == 0: face_data = decode_result.get('face_rect') face_x = int(face_data[0]) face_y = int(face_data[1]) face_width = face_x + int(face_data[2]) face_height = face_y + int(face_data[3]) old_img = Image.open(source_path) draw_instance = ImageDraw.Draw(old_img) draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0)) old_img.save(sys.path[0] + '/i-aliyun.jpg')
OpenCV
# -*- coding: utf-8 -*- import cv2, sys from PIL import Image, ImageDraw def test_face(): source_path = sys.path[0] + '/shanshan.jpg' img = cv2.imread(source_path) face_cascade = cv2.CascadeClassifier("/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.3, 4) if len(faces) > 1: print("不止一個人臉") else: print("一個人臉") face_x = faces[0][0] face_y = faces[0][1] face_width = face_x + faces[0][2] face_height = face_y + faces[0][3] old_img = Image.open(source_path) draw_instance = ImageDraw.Draw(old_img) draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0)) old_img.save(sys.path[0] + '/i-opencv.jpg') if __name__ == '__main__': test_face()
請根據自己的環境自行替換程式碼中的引數進行測試。
如果需要對圖片中的人臉進行提取也非常簡單,增加下面的程式碼即可
image_name = sys.path[0] + '/cut-opencv.jpg' Image.open(source_path).crop((face_x, face_y, face_width, face_height)).save(image_name)
用途
寫了這麼多,到底都有啥用途呢,很常見的那種顏值測試就可以用這種api直接實現,api裡面都可以直接返回顏值分數,非常簡單。
本文連結:https://www.92ez.com/?action=show&id=23472
!!! 轉載請先聯絡[email protected]授權並在顯著位置註明作者和原文連結 !!!小黑屋
提示:技術文章有一定的時效性,請先確認是否適用你當前的系統環境。