http://blog.csdn.net/lyq8479/article/details/17362685
為了防止網頁丟失還是自己保存一份安全一點
人臉檢測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
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對象,對應的代碼如下:
package org.liufeng.course.pojo; /** * Face Model * * @author liufeng * @date 2013-12-18 */ public class Face implements Comparable<Face> { // 被檢測出的每一張人臉都在Face++系統中的標識符 private String faceId; // 年齡估計值 private int ageValue; // 年齡估計值的正負區間 private int ageRange; // 性別:Male/Female private String genderValue; // 性別分析的可信度 private double genderConfidence; // 人種:Asian/White/Black private String raceValue; // 人種分析的可信度 private double raceConfidence; // 微笑程度 private double smilingValue; // 人臉框的中心點坐標 private double centerX; private double centerY; public String getFaceId() { return faceId; } public void setFaceId(String faceId) { this.faceId = faceId; } public int getAgeValue() { return ageValue; } public void setAgeValue(int ageValue) { this.ageValue = ageValue; } public int getAgeRange() { return ageRange; } public void setAgeRange(int ageRange) { this.ageRange = ageRange; } public String getGenderValue() { return genderValue; } public void setGenderValue(String genderValue) { this.genderValue = genderValue; } public double getGenderConfidence() { return genderConfidence; } public void setGenderConfidence(double genderConfidence) { this.genderConfidence = genderConfidence; } public String getRaceValue() { return raceValue; } public void setRaceValue(String raceValue) { this.raceValue = raceValue; } public double getRaceConfidence() { return raceConfidence; } public void setRaceConfidence(double raceConfidence) { this.raceConfidence = raceConfidence; } public double getSmilingValue() { return smilingValue; } public void setSmilingValue(double smilingValue) { this.smilingValue = smilingValue; } public double getCenterX() { return centerX; } public void setCenterX(double centerX) { this.centerX = centerX; } public double getCenterY() { return centerY; } public void setCenterY(double centerY) { this.centerY = centerY; } // 根據人臉中心點坐標從左至右排序 @Override public int compareTo(Face face) { int result = 0; if (this.getCenterX() > face.getCenterX()) result = 1; else result = -1; return result; } }
與普通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;
/**
* 人臉檢測服務
*
* @author liufeng
* @date 2013-12-18
*/
public class FaceService {
/**
* 發送http請求
*
* @param requestUrl 請求地址
* @return String
*/
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();
}
/**
* 調用Face++ API實現人臉檢測
*
* @param picUrl 待檢測圖片的訪問地址
* @return List<Face> 人臉列表
*/
private static List<Face> faceDetect(String picUrl) {
List<Face> faceList = new ArrayList<Face>();
try {
// 拼接Face++人臉檢測的請求地址
String queryUrl = "http://apicn.faceplusplus.com/v2/detection/detect?url=URL&api_secret=API_SECRET&api_key=API_KEY";
// 對URL進行編碼
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);
// 解析返回json中的Face列表
JSONArray jsonArray = JSONObject.fromObject(json).getJSONArray("face");
// 遍歷檢測到的人臉
for (int i = 0; i < jsonArray.size(); i++) {
// face
JSONObject faceObject = (JSONObject) jsonArray.get(i);
// attribute
JSONObject attrObject = faceObject.getJSONObject("attribute");
// position
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);
}
// 將檢測出的Face按從左至右的順序排序
Collections.sort(faceList);
} catch (Exception e) {
faceList = null;
e.printStackTrace();
}
return faceList;
}
/**
* 性別轉換(英文->中文)
*
* @param gender
* @return
*/
private static String genderConvert(String gender) {
String result = "男性";
if ("Male".equals(gender))
result = "男性";
else if ("Female".equals(gender))
result = "女性";
return result;
}
/**
* 人種轉換(英文->中文)
*
* @param race
* @return
*/
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;
}
/**
* 根據人臉識別結果組裝消息
*
* @param faceList 人臉列表
* @return
*/
private static String makeMessage(List<Face> faceList) {
StringBuffer buffer = new StringBuffer();
// 檢測到1張臉
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");
}
}
// 檢測到2-10張臉
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");
}
}
// 檢測到10張臉以上
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();
}
/**
* 提供給外部調用的人臉檢測方法
*
* @param picUrl 待檢測圖片的訪問地址
* @return String
*/
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;
/**
* 核心服務類
*
* @author liufeng
* @date 2013-12-19
*/
public class CoreService {
/**
* 處理微信發來的請求
*/
public static String processRequest(HttpServletRequest request) {
// 返回給微信服務器的xml消息
String respXml = null;
try {
// xml請求解析
Map<String, String> requestMap = MessageUtil.parseXml(request);
// 發送方帳號(open_id)
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實現更多實用、有趣的功能。應用開發不是簡單的接口調用!
