本文轉自柳峰的專欄:http://blog.csdn.net/lyq8479/article/details/17362685
本文將為讀者介紹人臉檢測應用的完整實現過程。
人臉檢測屬於人臉識別的范疇,它是一個復雜的具有挑戰性的模式匹配問題,國內外許多組織、科研機構都在專門研究該問題。國內的Face++團隊專注於研發人臉檢測、識別、分析和重建技術,並且向廣大開發者開放了人臉識別API,本文介紹的“人臉檢測”應用正是基於Face++ API進行開發。
Face++簡介
Face++是北京曠視科技有限公司旗下的人臉識別雲服務平台,Face++平台通過提供雲端API、離線SDK、以及面向用戶的自主研發產品等形式,將人臉識別技術廣泛應用到互聯網及移動應用場景中。Face++為廣大開發者提供了簡單易用的API,開發者可以輕松搭建屬於自己的雲端身份認證、用戶興趣挖掘、移動體感交互、社交娛樂分享等多種類型的應用。
Face++提供的技術服務包括人臉檢測、人臉分析和人臉識別,主要說明如下:
1)人臉檢測:可以從圖片中快速、准確的定位面部的關鍵區域位置,包括眉毛、眼睛、鼻子、嘴巴等。
2)人臉分析:可以從圖片或實時視頻流中分析出人臉的性別(准確度達96%)、年齡、種族等多種屬性。
3)人臉識別:可以快速判定兩張照片是否為同一個人,或者快速判定視頻中的人像是否為某一位特定的人。
Face++的中文網址為http://cn.faceplusplus.com/,要使用Face++ API,需要注冊成為Face++開發者,也就是要注冊一個Face++賬號。注冊完成后,先創建應用,創建應用時需要填寫“應用名稱”、“應用描述”、“API服務器”、“應用類型”和“應用平台”,讀者可以根據實際情況填寫。應用創建完成后,可以看到應用的詳細信息,如下圖所示。

上圖中,最重要的是API KEY和API SECRET,在調用Face++提供的API時,需要傳入這兩個參數。
人臉檢測API介紹
在Face++網站的“API文檔”中,能夠看到Face++提供的所有API,我們要使用的人臉檢測接口是detect分類下的“/detection/detect”,它能夠檢測出給定圖片(Image)中的所有人臉(Face)的位置和相應的面部屬性,目前面部屬性包括性別(gender)、年齡(age)、種族(race)、微笑程度(smiling)、眼鏡(glass)和姿勢(pose)。
讀者可以在http://cn.faceplusplus.com/uc/doc/home?id=69中了解到人臉檢測接口的詳細信息,該接口的請求地址如下:
- http://apicn.faceplusplus.com/v2/detection/detect?url=URL&api_secret=API_SECRET&api_key=API_KEY
調用上述接口,必須要傳入參數api_key、api_secret和待檢測的圖片。其中,待檢測的圖片可以是URL,也可以是POST方式提交的二進制數據。在微信公眾賬號后台,接收用戶發送的圖片,得到的是圖片的訪問路徑(PicUrl),因此,在本例中,直接使用待檢測圖片的URL是最方便的。調用人臉檢測接口返回的是JSON格式數據如下:
- {
- "face": [
- {
- "attribute": {
- "age": {
- "range": 5,
- "value": 23
- },
- "gender": {
- "confidence": 99.9999,
- "value": "Female"
- },
- "glass": {
- "confidence": 99.945,
- "value": "None"
- },
- "pose": {
- "pitch_angle": {
- "value": 17
- },
- "roll_angle": {
- "value": 0.735735
- },
- "yaw_angle": {
- "value": -2
- }
- },
- "race": {
- "confidence": 99.6121,
- "value": "Asian"
- },
- "smiling": {
- "value": 4.86501
- }
- },
- "face_id": "17233b4b1b51ac91e391e5afe130eb78",
- "position": {
- "center": {
- "x": 49.4,
- "y": 37.6
- },
- "eye_left": {
- "x": 43.3692,
- "y": 30.8192
- },
- "eye_right": {
- "x": 56.5606,
- "y": 30.9886
- },
- "height": 26.8,
- "mouth_left": {
- "x": 46.1326,
- "y": 44.9468
- },
- "mouth_right": {
- "x": 54.2592,
- "y": 44.6282
- },
- "nose": {
- "x": 49.9404,
- "y": 38.8484
- },
- "width": 26.8
- },
- "tag": ""
- }
- ],
- "img_height": 500,
- "img_id": "22fd9efc64c87e00224c33dd8718eec7",
- "img_width": 500,
- "session_id": "38047ad0f0b34c7e8c6efb6ba39ed355",
- "url": "http://cn.faceplusplus.com/wp-content/themes/faceplusplus.zh/assets/img/demo/1.jpg?v=4"
- }
這里只對本文將要實現的“人臉檢測”功能中主要用到的參數進行說明,參數說明如下:
1)face是一個數組,當一張圖片中包含多張人臉時,所有識別出的人臉信息都在face數組中。
2)age中的value表示估計年齡,range表示誤差范圍。例如,上述結果中value=23,range=5,表示人的真實年齡在18歲至28歲左右。
3)gender中的value表示性別,男性為Male,女性為Female;gender中的confidence表示檢測結果的可信度。
4)race中的value表示人種,黃色人種為Asian,白色人種為White,黑色人種為Black;race中的confidence表示檢測結果的可信度。
5)center表示人臉框中心點坐標,可以將x用於計算人臉的左右順序,即x坐標的值越小,人臉的位置越靠近圖片的左側。
人臉檢測API的使用方法
為了方便開發者調用人臉識別API,Face++團隊提供了基於Objective-C、Java(Android)、Matlab、Ruby、C#等多種語言的開發工具包,讀者可以在Face++網站的“工具下載”版塊下載相關的SDK。在本例中,筆者並不打算使用官方提供的SDK進行開發,主要原因如下:1)人臉檢測API的調用比較簡單,自己寫代碼實現也並不復雜;2)如果使用SDK進行開發,筆者還要花費大量篇幅介紹SDK的使用,這些並不是本文的重點;3)自己寫代碼實現比較靈活。當圖片中有多張人臉時,人臉檢測接口返回的數據是無序的,開發者可以按照實際使用需求進行排序,例如,將圖片中的人臉按照從左至右的順序進行排序。
編程調用人臉檢測API
首先,要對人臉檢測接口返回的結構進行封裝,建立與之對應的Java對象。由於人臉檢測接口返回的參數較多,筆者只是將本例中需要用到的參數抽取出來,封裝成Face對象,對應的代碼如下:
與普通Java類不同的是,
Face類實現了Comparable接口,並實現了該接口的compareTo()方法,這正是Java中對象排序的關鍵所在。
112-119行代碼是通過比較每個Face的臉部中心點的橫坐標來決定對象的排序方式,這樣能夠實現檢測出的多個Face按從左至右的先后順序進行排序。
接下來,是人臉檢測API的調用及相關處理邏輯,筆者將這些實現全部封裝在FaceService類中,該類的完整實現如下:
- package org.liufeng.course.service;
-
- import java.io.BufferedReader;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import org.liufeng.course.pojo.Face;
- import net.sf.json.JSONArray;
- import net.sf.json.JSONObject;
-
-
-
-
-
-
-
- public class FaceService {
-
-
-
-
-
-
- private static String httpRequest(String requestUrl) {
- StringBuffer buffer = new StringBuffer();
- try {
- URL url = new URL(requestUrl);
- HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
- httpUrlConn.setDoInput(true);
- httpUrlConn.setRequestMethod("GET");
- httpUrlConn.connect();
-
- InputStream inputStream = httpUrlConn.getInputStream();
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
-
- String str = null;
- while ((str = bufferedReader.readLine()) != null) {
- buffer.append(str);
- }
- bufferedReader.close();
- inputStreamReader.close();
-
- inputStream.close();
- inputStream = null;
- httpUrlConn.disconnect();
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- return buffer.toString();
- }
-
-
-
-
-
-
-
- private static List<Face> faceDetect(String picUrl) {
- List<Face> faceList = new ArrayList<Face>();
- try {
-
- String queryUrl = "http://apicn.faceplusplus.com/v2/detection/detect?url=URL&api_secret=API_SECRET&api_key=API_KEY";
-
- queryUrl = queryUrl.replace("URL", java.net.URLEncoder.encode(picUrl, "UTF-8"));
- queryUrl = queryUrl.replace("API_KEY", "替換成自己的API Key");
- queryUrl = queryUrl.replace("API_SECRET", "替換成自己的API Secret");
-
- String json = httpRequest(queryUrl);
-
- JSONArray jsonArray = JSONObject.fromObject(json).getJSONArray("face");
-
- for (int i = 0; i < jsonArray.size(); i++) {
-
- JSONObject faceObject = (JSONObject) jsonArray.get(i);
-
- JSONObject attrObject = faceObject.getJSONObject("attribute");
-
- JSONObject posObject = faceObject.getJSONObject("position");
- Face face = new Face();
- face.setFaceId(faceObject.getString("face_id"));
- face.setAgeValue(attrObject.getJSONObject("age").getInt("value"));
- face.setAgeRange(attrObject.getJSONObject("age").getInt("range"));
- face.setGenderValue(genderConvert(attrObject.getJSONObject("gender").getString("value")));
- face.setGenderConfidence(attrObject.getJSONObject("gender").getDouble("confidence"));
- face.setRaceValue(raceConvert(attrObject.getJSONObject("race").getString("value")));
- face.setRaceConfidence(attrObject.getJSONObject("race").getDouble("confidence"));
- face.setSmilingValue(attrObject.getJSONObject("smiling").getDouble("value"));
- face.setCenterX(posObject.getJSONObject("center").getDouble("x"));
- face.setCenterY(posObject.getJSONObject("center").getDouble("y"));
- faceList.add(face);
- }
-
- Collections.sort(faceList);
- } catch (Exception e) {
- faceList = null;
- e.printStackTrace();
- }
- return faceList;
- }
-
-
-
-
-
-
-
- private static String genderConvert(String gender) {
- String result = "男性";
- if ("Male".equals(gender))
- result = "男性";
- else if ("Female".equals(gender))
- result = "女性";
-
- return result;
- }
-
-
-
-
-
-
-
- private static String raceConvert(String race) {
- String result = "黃色";
- if ("Asian".equals(race))
- result = "黃色";
- else if ("White".equals(race))
- result = "白色";
- else if ("Black".equals(race))
- result = "黑色";
- return result;
- }
-
-
-
-
-
-
-
- private static String makeMessage(List<Face> faceList) {
- StringBuffer buffer = new StringBuffer();
-
- if (1 == faceList.size()) {
- buffer.append("共檢測到 ").append(faceList.size()).append(" 張人臉").append("\n");
- for (Face face : faceList) {
- buffer.append(face.getRaceValue()).append("人種,");
- buffer.append(face.getGenderValue()).append(",");
- buffer.append(face.getAgeValue()).append("歲左右").append("\n");
- }
- }
-
- else if (faceList.size() > 1 && faceList.size() <= 10) {
- buffer.append("共檢測到 ").append(faceList.size()).append(" 張人臉,按臉部中心位置從左至右依次為:").append("\n");
- for (Face face : faceList) {
- buffer.append(face.getRaceValue()).append("人種,");
- buffer.append(face.getGenderValue()).append(",");
- buffer.append(face.getAgeValue()).append("歲左右").append("\n");
- }
- }
-
- else if (faceList.size() > 10) {
- buffer.append("共檢測到 ").append(faceList.size()).append(" 張人臉").append("\n");
-
- int asiaMale = 0;
- int asiaFemale = 0;
- int whiteMale = 0;
- int whiteFemale = 0;
- int blackMale = 0;
- int blackFemale = 0;
- for (Face face : faceList) {
- if ("黃色".equals(face.getRaceValue()))
- if ("男性".equals(face.getGenderValue()))
- asiaMale++;
- else
- asiaFemale++;
- else if ("白色".equals(face.getRaceValue()))
- if ("男性".equals(face.getGenderValue()))
- whiteMale++;
- else
- whiteFemale++;
- else if ("黑色".equals(face.getRaceValue()))
- if ("男性".equals(face.getGenderValue()))
- blackMale++;
- else
- blackFemale++;
- }
- if (0 != asiaMale || 0 != asiaFemale)
- buffer.append("黃色人種:").append(asiaMale).append("男").append(asiaFemale).append("女").append("\n");
- if (0 != whiteMale || 0 != whiteFemale)
- buffer.append("白色人種:").append(whiteMale).append("男").append(whiteFemale).append("女").append("\n");
- if (0 != blackMale || 0 != blackFemale)
- buffer.append("黑色人種:").append(blackMale).append("男").append(blackFemale).append("女").append("\n");
- }
-
- buffer = new StringBuffer(buffer.substring(0, buffer.lastIndexOf("\n")));
- return buffer.toString();
- }
-
-
-
-
-
-
-
- public static String detect(String picUrl) {
-
- String result = "未識別到人臉,請換一張清晰的照片再試!";
- List<Face> faceList = faceDetect(picUrl);
- if (null != faceList) {
- result = makeMessage(faceList);
- }
- return result;
- }
-
- public static void main(String[] args) {
- String picUrl = "http://pic11.nipic.com/20101111/6153002_002722872554_2.jpg";
- System.out.println(detect(picUrl));
- }
- }
上述代碼雖然多,但條理很清晰,並不難理解,所以筆者只挑重點的進行講解,主要說明如下:
1)70行:參數url表示圖片的鏈接,由於鏈接中存在特殊字符,作為參數傳遞時必須進行URL編碼。請讀者記住:不管是什么應用,調用什么接口,凡是通過GET傳遞的參數中可能會包含特殊字符,都必須進行URL編碼,除了中文以外,特殊字符還包括等號“=”、與“&”、空格“ ”等。
2)76-97行:使用JSON-lib解析人臉檢測接口返回的JSON數據,並將解析結果存入List中。
3)99行:對集合中的對象進行排序,使用Collections.sort()方法排序的前提是集合中的Face對象實現了Comparable接口。
4)146-203行:組裝返回給用戶的消息內容。考慮到公眾平台的文本消息內容長度有限制,當一張圖片中識別出的人臉過多,則只返回一些匯總信息給用戶。
5)211-219行:detect()方法是public的,提供給其他類調用。筆者可以在本地的開發工具中運行上面的main()方法,測試detect()方法的輸出。
公眾賬號后台的實現
在公眾賬號后台的CoreService類中,需要對用戶發送的消息類型進行判斷,如果是圖片消息,則調用人臉檢測方法進行分析,如果是其他消息,則返回人臉檢測的使用指南。CoreService類的完整代碼如下:
- package org.liufeng.course.service;
-
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.liufeng.course.message.resp.TextMessage;
- import org.liufeng.course.util.MessageUtil;
-
-
-
-
-
-
-
- public class CoreService {
-
-
-
- public static String processRequest(HttpServletRequest request) {
-
- String respXml = null;
- try {
-
- Map<String, String> requestMap = MessageUtil.parseXml(request);
-
- String fromUserName = requestMap.get("FromUserName");
-
- String toUserName = requestMap.get("ToUserName");
-
- String msgType = requestMap.get("MsgType");
-
-
- TextMessage textMessage = new TextMessage();
- textMessage.setToUserName(fromUserName);
- textMessage.setFromUserName(toUserName);
- textMessage.setCreateTime(new Date().getTime());
- textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
-
-
- if (MessageUtil.REQ_MESSAGE_TYPE_IMAGE.equals(msgType)) {
-
- String picUrl = requestMap.get("PicUrl");
-
- String detectResult = FaceService.detect(picUrl);
- textMessage.setContent(detectResult);
- }
-
- else
- textMessage.setContent(getUsage());
-
- respXml = MessageUtil.textMessageToXml(textMessage);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return respXml;
- }
-
-
-
-
- public static String getUsage() {
- StringBuffer buffer = new StringBuffer();
- buffer.append("人臉檢測使用指南").append("\n\n");
- buffer.append("發送一張清晰的照片,就能幫你分析出種族、年齡、性別等信息").append("\n");
- buffer.append("快來試試你是不是長得太着急");
- return buffer.toString();
- }
- }
到這里,人臉檢測應用就全部開發完成了,整個項目的完整結構如下:

運行結果如下:

筆者用自己的相片測試了兩次,測試結果分別是26歲、30歲,這與筆者的實際年齡相差不大,可見,Face++的人臉檢測准確度還是比較高的。為了增加人臉檢測應用的趣味性和娛樂性,筆者忽略了年齡估計值的正負區間。讀者可以充分發揮自己的想像力和創造力,使用Face++ API實現更多實用、有趣的功能。應用開發不是簡單的接口調用!
(沒有展開的代碼統統和前面幾篇介紹開發模式、消息封裝的代碼相同,沒看的返回看前幾篇)