上一年也就是這個時候微軟根據自己的人臉識別API推出了一個識別照片中人臉年齡和性別的網站——http://how-old.net,小伙伴們各種玩耍,一年后的今天突發“奇想”地想測試一下這個網站的識別情況。正好手里有3萬多份標識有身份證信息、性別及照片拍攝時間的證件照(別問我從哪兒弄的,這玩意兒你懂的)。今天就寫了個腳本來測試一下。測試識別的目標有兩個:
- 性別
- 年齡
提交數據獲得識別結果
尋找接口
首先,查看一下how-old.net的提交接口。
用Chrome查看一下網絡請求的情況
查看一下前三個請求的數據情況:
第一個:
第二個:
第三個:
很奇怪有沒有,第一個是一個bolb地址,第二個是圖片的base64編碼后的字符,第三個倒像是真正的請求,可查看請求中,盡然找不到對應圖片的參數。再查看一下第三個請求的響應:
嗯,一個添加轉移符號的json數據,我們想要的識別結果確實在里面。這就確定這個請求就是我們需要的請求接口,現在的問題是怎樣上傳圖片數據呢?
我們不妨從頭看一下這三個請求。第一個中的bolb地址和第二個請求中的base64數據是怎么個情況呢?在Stack Overflow上查找到了下面的信息:
簡單來說就是,在二進制數據以流式方式提交的時候,有這樣一個模式:生成一個bolb地址做本機數據訪問 -> 訪問具體的信息是是base64編碼的的文件 -> 對指定接口以流式上傳數據。也就是說前兩個請求時發生在本機的,是對本地資源的訪問,第三個請求才是真正的請求,只不過數據是前兩個“本機請求”生成的流式數據。
上傳數據獲得識別結果
這樣我們就得到了我們需要的訪問接口及數據提交方式:
- 接口:
- 提交方式:POST流式提交
我們可以在上面第三個請求圖中查看到請求參數及header,cookies等信息。使用requests庫能很容易做到數據流式提交,針對此接口請求代碼如下:
#訪問主頁獲得cookie
t = requests.get("http://how-old.net",timeout=60)
_cookies = t.cookies
t.close()
#構建請求頭
headers = {
"Content-Type": "application/octet-stream",
"Referer": "http://how-old.net/",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"
}
info = None
#POST方式流式提交(pic_name是圖片地址)
with open(pic_name, 'rb') as f:
r = requests.post("http://how-old.net/Home/Analyze?isTest=False&source=&version=how-old.net",
data=f,
headers=headers,
cookies=_cookies,
timeout=10)
info = r.content
將返回的識別數據存儲在info
中,其樣式像下面這樣:
"{\"AnalyticsEvent\":\"[\\r\\n {\\r\\n \\\"face\\\": {\\r\\n \\\"age\\\": 16.0,\\r\\n \\\"gender\\\": \\\"Male\\\"\\r\\n },\\r\\n \\\"event_datetime\\\": \\\"2016-04-30T11:39:30.4786437Z\\\",\\r\\n \\\"user_id\\\": \\\"ab85e356-6638-41e7-a46f-be54c1f94f97\\\",\\r\\n \\\"session_id\\\": \\\"ba5ec8e4-65e0-481d-b034-970494680bca\\\",\\r\\n \\\"submission_method\\\": \\\"Upload\\\",\\r\\n \\\"user_agent\\\": \\\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36\\\",\\r\\n \\\"location\\\": {\\r\\n \\\"latitude\\\": 35.71,\\r\\n \\\"longitude\\\": 115.23\\r\\n },\\r\\n \\\"location_city\\\": {\\r\\n \\\"latitude\\\": 35.7,\\r\\n \\\"longitude\\\": 115.2\\r\\n },\\r\\n \\\"is_mobile_device\\\": false,\\r\\n \\\"browser_type\\\": \\\"Chrome\\\",\\r\\n \\\"platform\\\": \\\"Windows\\\",\\r\\n \\\"mobile_device_model\\\": \\\"Unknown\\\"\\r\\n }\\r\\n]\",\"Faces\":[{\"faceId\":null,\"faceRectangle\":{\"top\":29,\"left\":49,\"width\":51,\"height\":51},\"attributes\":{\"gender\":\"Male\",\"age\":16.0}}]}"
正想我們在Chrome中觀測到的返回數據一樣,這樣通過Python提交圖片並獲得識別數據就成功了。
但是這樣的數據我們很難使用,因為里面數據很多且有很多的轉義,所以先把\r``\n``\
這樣的數據清洗掉,並選取其中最后面的一部分,獲得下面的結構數據:
{
"Faces": [
{
"faceId": null,
"faceRectangle": {
"top": 29,
"left": 49,
"width": 51,
"height": 51
},
"attributes": {
"gender": "Male",
"age": 16
}
}
]
}
faceId
是圖片中識別出的臉的標號,faceRectangle
是將臉部框前來的矩形左上坐標及寬高,attributes
中是識別出的性別和年齡。由於證件照都是標准的一個人,網站基本都能識別出來,所以只考慮一張圖片對應的一個attributes
。將照片對應的信息存在一個persons列表中,樣式如下:
persons = [
{
"num":num,
"real_age":real_age,
"real_gender":real_gender,
"rec_age":rec_age,
"rec_gender":rec_gender
}
]
識別結果統計
性別識別
性別識別統計很容易,直接比對一張照片對應的實際性別和識別:
toatal = len(persons)
right = 0
wrong_fm = 0
wrong_mf = 0
for person in persons:
if person["real_gender"] == person["rec_gender"]:
right += 1
elif person["real_gender"] == "Female":
wrong_fm += 1
else:
wrong_mf +=1
最終的結果是:
年齡識別
年齡的識別統計采用一個字典記錄,其結構是識別{某年齡差:識別為該年齡差的個數}:
age_rec = {}
for person in persons:
tmp = person["rec_age"] - person["real_age"]
try:
age_rec[tmp] += 1
except:
pass
finally:
age_rec[tmp] = 1
最終的統計結果是:
結語
本實踐統計了HOW-OLD對兩千多份圖片樣本的識別結果,性別識別正確率很高,而年齡識別錯誤范圍較大,且識別結果偏大的居多。我甚至覺得,這東西可以用來檢測攝影師的拍照技術,識別結果越小,人物攝影技術越好:)(開個玩笑)。整個實踐最麻煩的地方是找接口及上傳數據的方法,最費時間的是上傳數據獲得結果這個過程(受網絡IO的限制,用家里的小破wifi,使用多線程也沒多大用,而且線程一多,就會掉線:()。