微信公众平台开发教程第19篇-应用实例之人脸检测


人脸检测属于人脸识别的范畴,它是一个复杂的具有挑战性的模式匹配问题,国内外许多组织、科研机构都在专门研究该问题。国内的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中了解到人脸检测接口的详细信息,该接口的请求地址如下:

[html] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 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格式数据如下:

[html] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. {
  2. "face":[
  3. {
  4. "attribute":{
  5. "age":{
  6. "range":5,
  7. "value":23
  8. },
  9. "gender":{
  10. "confidence":99.9999,
  11. "value":"Female"
  12. },
  13. "glass":{
  14. "confidence":99.945,
  15. "value":"None"
  16. },
  17. "pose":{
  18. "pitch_angle":{
  19. "value":17
  20. },
  21. "roll_angle":{
  22. "value":0.735735
  23. },
  24. "yaw_angle":{
  25. "value":-2
  26. }
  27. },
  28. "race":{
  29. "confidence":99.6121,
  30. "value":"Asian"
  31. },
  32. "smiling":{
  33. "value":4.86501
  34. }
  35. },
  36. "face_id":"17233b4b1b51ac91e391e5afe130eb78",
  37. "position":{
  38. "center":{
  39. "x":49.4,
  40. "y":37.6
  41. },
  42. "eye_left":{
  43. "x":43.3692,
  44. "y":30.8192
  45. },
  46. "eye_right":{
  47. "x":56.5606,
  48. "y":30.9886
  49. },
  50. "height":26.8,
  51. "mouth_left":{
  52. "x":46.1326,
  53. "y":44.9468
  54. },
  55. "mouth_right":{
  56. "x":54.2592,
  57. "y":44.6282
  58. },
  59. "nose":{
  60. "x":49.9404,
  61. "y":38.8484
  62. },
  63. "width":26.8
  64. },
  65. "tag":""
  66. }
  67. ],
  68. "img_height":500,
  69. "img_id":"22fd9efc64c87e00224c33dd8718eec7",
  70. "img_width":500,
  71. "session_id":"38047ad0f0b34c7e8c6efb6ba39ed355",
  72. "url":"http://cn.faceplusplus.com/wp-content/themes/faceplusplus.zh/assets/img/demo/1.jpg?v=4"
  73. }
这里只对本文将要实现的“人脸检测”功能中主要用到的参数进行说明,参数说明如下:

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] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. packageorg.liufeng.course.pojo;
  2. /**
  3. *FaceModel
  4. *
  5. *@authorliufeng
  6. *@date2013-12-18
  7. */
  8. publicclassFaceimplementsComparable<Face>{
  9. //被检测出的每一张人脸都在Face++系统中的标识符
  10. privateStringfaceId;
  11. //年龄估计值
  12. privateintageValue;
  13. //年龄估计值的正负区间
  14. privateintageRange;
  15. //性别:Male/Female
  16. privateStringgenderValue;
  17. //性别分析的可信度
  18. privatedoublegenderConfidence;
  19. //人种:Asian/White/Black
  20. privateStringraceValue;
  21. //人种分析的可信度
  22. privatedoubleraceConfidence;
  23. //微笑程度
  24. privatedoublesmilingValue;
  25. //人脸框的中心点坐标
  26. privatedoublecenterX;
  27. privatedoublecenterY;
  28. publicStringgetFaceId(){
  29. returnfaceId;
  30. }
  31. publicvoidsetFaceId(StringfaceId){
  32. this.faceId=faceId;
  33. }
  34. publicintgetAgeValue(){
  35. returnageValue;
  36. }
  37. publicvoidsetAgeValue(intageValue){
  38. this.ageValue=ageValue;
  39. }
  40. publicintgetAgeRange(){
  41. returnageRange;
  42. }
  43. publicvoidsetAgeRange(intageRange){
  44. this.ageRange=ageRange;
  45. }
  46. publicStringgetGenderValue(){
  47. returngenderValue;
  48. }
  49. publicvoidsetGenderValue(StringgenderValue){
  50. this.genderValue=genderValue;
  51. }
  52. publicdoublegetGenderConfidence(){
  53. returngenderConfidence;
  54. }
  55. publicvoidsetGenderConfidence(doublegenderConfidence){
  56. this.genderConfidence=genderConfidence;
  57. }
  58. publicStringgetRaceValue(){
  59. returnraceValue;
  60. }
  61. publicvoidsetRaceValue(StringraceValue){
  62. this.raceValue=raceValue;
  63. }
  64. publicdoublegetRaceConfidence(){
  65. returnraceConfidence;
  66. }
  67. publicvoidsetRaceConfidence(doubleraceConfidence){
  68. this.raceConfidence=raceConfidence;
  69. }
  70. publicdoublegetSmilingValue(){
  71. returnsmilingValue;
  72. }
  73. publicvoidsetSmilingValue(doublesmilingValue){
  74. this.smilingValue=smilingValue;
  75. }
  76. publicdoublegetCenterX(){
  77. returncenterX;
  78. }
  79. publicvoidsetCenterX(doublecenterX){
  80. this.centerX=centerX;
  81. }
  82. publicdoublegetCenterY(){
  83. returncenterY;
  84. }
  85. publicvoidsetCenterY(doublecenterY){
  86. this.centerY=centerY;
  87. }
  88. //根据人脸中心点坐标从左至右排序
  89. @Override
  90. publicintcompareTo(Faceface){
  91. intresult=0;
  92. if(this.getCenterX()>face.getCenterX())
  93. result=1;
  94. else
  95. result=-1;
  96. returnresult;
  97. }
  98. }
与普通Java类不同的是, Face类实现了Comparable接口,并实现了该接口的compareTo()方法,这正是Java中对象排序的关键所在。 112-119行代码是通过比较每个Face的脸部中心点的横坐标来决定对象的排序方式,这样能够实现检测出的多个Face按从左至右的先后顺序进行排序。

接下来,是人脸检测API的调用及相关处理逻辑,笔者将这些实现全部封装在FaceService类中,该类的完整实现如下:

[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. packageorg.liufeng.course.service;
  2. importjava.io.BufferedReader;
  3. importjava.io.InputStream;
  4. importjava.io.InputStreamReader;
  5. importjava.net.HttpURLConnection;
  6. importjava.net.URL;
  7. importjava.util.ArrayList;
  8. importjava.util.Collections;
  9. importjava.util.List;
  10. importorg.liufeng.course.pojo.Face;
  11. importnet.sf.json.JSONArray;
  12. importnet.sf.json.JSONObject;
  13. /**
  14. *人脸检测服务
  15. *
  16. *@authorliufeng
  17. *@date2013-12-18
  18. */
  19. publicclassFaceService{
  20. /**
  21. *发送http请求
  22. *
  23. *@paramrequestUrl请求地址
  24. *@returnString
  25. */
  26. privatestaticStringhttpRequest(StringrequestUrl){
  27. StringBufferbuffer=newStringBuffer();
  28. try{
  29. URLurl=newURL(requestUrl);
  30. HttpURLConnectionhttpUrlConn=(HttpURLConnection)url.openConnection();
  31. httpUrlConn.setDoInput(true);
  32. httpUrlConn.setRequestMethod("GET");
  33. httpUrlConn.connect();
  34. //将返回的输入流转换成字符串
  35. InputStreaminputStream=httpUrlConn.getInputStream();
  36. InputStreamReaderinputStreamReader=newInputStreamReader(inputStream,"utf-8");
  37. BufferedReaderbufferedReader=newBufferedReader(inputStreamReader);
  38. Stringstr=null;
  39. while((str=bufferedReader.readLine())!=null){
  40. buffer.append(str);
  41. }
  42. bufferedReader.close();
  43. inputStreamReader.close();
  44. //释放资源
  45. inputStream.close();
  46. inputStream=null;
  47. httpUrlConn.disconnect();
  48. }catch(Exceptione){
  49. e.printStackTrace();
  50. }
  51. returnbuffer.toString();
  52. }
  53. /**
  54. *调用Face++API实现人脸检测
  55. *
  56. *@parampicUrl待检测图片的访问地址
  57. *@returnList<Face>人脸列表
  58. */
  59. privatestaticList<Face>faceDetect(StringpicUrl){
  60. List<Face>faceList=newArrayList<Face>();
  61. try{
  62. //拼接Face++人脸检测的请求地址
  63. StringqueryUrl="http://apicn.faceplusplus.com/v2/detection/detect?url=URL&api_secret=API_SECRET&api_key=API_KEY";
  64. //对URL进行编码
  65. queryUrl=queryUrl.replace("URL",java.net.URLEncoder.encode(picUrl,"UTF-8"));
  66. queryUrl=queryUrl.replace("API_KEY","替换成自己的APIKey");
  67. queryUrl=queryUrl.replace("API_SECRET","替换成自己的APISecret");
  68. //调用人脸检测接口
  69. Stringjson=httpRequest(queryUrl);
  70. //解析返回json中的Face列表
  71. JSONArrayjsonArray=JSONObject.fromObject(json).getJSONArray("face");
  72. //遍历检测到的人脸
  73. for(inti=0;i<jsonArray.size();i++){
  74. //face
  75. JSONObjectfaceObject=(JSONObject)jsonArray.get(i);
  76. //attribute
  77. JSONObjectattrObject=faceObject.getJSONObject("attribute");
  78. //position
  79. JSONObjectposObject=faceObject.getJSONObject("position");
  80. Faceface=newFace();
  81. face.setFaceId(faceObject.getString("face_id"));
  82. face.setAgeValue(attrObject.getJSONObject("age").getInt("value"));
  83. face.setAgeRange(attrObject.getJSONObject("age").getInt("range"));
  84. face.setGenderValue(genderConvert(attrObject.getJSONObject("gender").getString("value")));
  85. face.setGenderConfidence(attrObject.getJSONObject("gender").getDouble("confidence"));
  86. face.setRaceValue(raceConvert(attrObject.getJSONObject("race").getString("value")));
  87. face.setRaceConfidence(attrObject.getJSONObject("race").getDouble("confidence"));
  88. face.setSmilingValue(attrObject.getJSONObject("smiling").getDouble("value"));
  89. face.setCenterX(posObject.getJSONObject("center").getDouble("x"));
  90. face.setCenterY(posObject.getJSONObject("center").getDouble("y"));
  91. faceList.add(face);
  92. }
  93. //将检测出的Face按从左至右的顺序排序
  94. Collections.sort(faceList);
  95. }catch(Exceptione){
  96. faceList=null;
  97. e.printStackTrace();
  98. }
  99. returnfaceList;
  100. }
  101. /**
  102. *性别转换(英文->中文)
  103. *
  104. *@paramgender
  105. *@return
  106. */
  107. privatestaticStringgenderConvert(Stringgender){
  108. Stringresult="男性";
  109. if("Male".equals(gender))
  110. result="男性";
  111. elseif("Female".equals(gender))
  112. result="女性";
  113. returnresult;
  114. }
  115. /**
  116. *人种转换(英文->中文)
  117. *
  118. *@paramrace
  119. *@return
  120. */
  121. privatestaticStringraceConvert(Stringrace){
  122. Stringresult="黄色";
  123. if("Asian".equals(race))
  124. result="黄色";
  125. elseif("White".equals(race))
  126. result="白色";
  127. elseif("Black".equals(race))
  128. result="黑色";
  129. returnresult;
  130. }
  131. /**
  132. *根据人脸识别结果组装消息
  133. *
  134. *@paramfaceList人脸列表
  135. *@return
  136. */
  137. privatestaticStringmakeMessage(List<Face>faceList){
  138. StringBufferbuffer=newStringBuffer();
  139. //检测到1张脸
  140. if(1==faceList.size()){
  141. buffer.append("共检测到").append(faceList.size()).append("张人脸").append("\n");
  142. for(Faceface:faceList){
  143. buffer.append(face.getRaceValue()).append("人种,");
  144. buffer.append(face.getGenderValue()).append(",");
  145. buffer.append(face.getAgeValue()).append("岁左右").append("\n");
  146. }
  147. }
  148. //检测到2-10张脸
  149. elseif(faceList.size()>1&&faceList.size()<=10){
  150. buffer.append("共检测到").append(faceList.size()).append("张人脸,按脸部中心位置从左至右依次为:").append("\n");
  151. for(Faceface:faceList){
  152. buffer.append(face.getRaceValue()).append("人种,");
  153. buffer.append(face.getGenderValue()).append(",");
  154. buffer.append(face.getAgeValue()).append("岁左右").append("\n");
  155. }
  156. }
  157. //检测到10张脸以上
  158. elseif(faceList.size()>10){
  159. buffer.append("共检测到").append(faceList.size()).append("张人脸").append("\n");
  160. //统计各人种、性别的人数
  161. intasiaMale=0;
  162. intasiaFemale=0;
  163. intwhiteMale=0;
  164. intwhiteFemale=0;
  165. intblackMale=0;
  166. intblackFemale=0;
  167. for(Faceface:faceList){
  168. if("黄色".equals(face.getRaceValue()))
  169. if("男性".equals(face.getGenderValue()))
  170. asiaMale++;
  171. else
  172. asiaFemale++;
  173. elseif("白色".equals(face.getRaceValue()))
  174. if("男性".equals(face.getGenderValue()))
  175. whiteMale++;
  176. else
  177. whiteFemale++;
  178. elseif("黑色".equals(face.getRaceValue()))
  179. if("男性".equals(face.getGenderValue()))
  180. blackMale++;
  181. else
  182. blackFemale++;
  183. }
  184. if(0!=asiaMale||0!=asiaFemale)
  185. buffer.append("黄色人种:").append(asiaMale).append("男").append(asiaFemale).append("女").append("\n");
  186. if(0!=whiteMale||0!=whiteFemale)
  187. buffer.append("白色人种:").append(whiteMale).append("男").append(whiteFemale).append("女").append("\n");
  188. if(0!=blackMale||0!=blackFemale)
  189. buffer.append("黑色人种:").append(blackMale).append("男").append(blackFemale).append("女").append("\n");
  190. }
  191. //移除末尾空格
  192. buffer=newStringBuffer(buffer.substring(0,buffer.lastIndexOf("\n")));
  193. returnbuffer.toString();
  194. }
  195. /**
  196. *提供给外部调用的人脸检测方法
  197. *
  198. *@parampicUrl待检测图片的访问地址
  199. *@returnString
  200. */
  201. publicstaticStringdetect(StringpicUrl){
  202. //默认回复信息
  203. Stringresult="未识别到人脸,请换一张清晰的照片再试!";
  204. List<Face>faceList=faceDetect(picUrl);
  205. if(null!=faceList){
  206. result=makeMessage(faceList);
  207. }
  208. returnresult;
  209. }
  210. publicstaticvoidmain(String[]args){
  211. StringpicUrl="http://pic11.nipic.com/20101111/6153002_002722872554_2.jpg";
  212. System.out.println(detect(picUrl));
  213. }
  214. }
上述代码虽然多,但条理很清晰,并不难理解,所以笔者只挑重点的进行讲解,主要说明如下:

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类的完整代码如下:

[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. packageorg.liufeng.course.service;
  2. importjava.util.Date;
  3. importjava.util.Map;
  4. importjavax.servlet.http.HttpServletRequest;
  5. importorg.liufeng.course.message.resp.TextMessage;
  6. importorg.liufeng.course.util.MessageUtil;
  7. /**
  8. *核心服务类
  9. *
  10. *@authorliufeng
  11. *@date2013-12-19
  12. */
  13. publicclassCoreService{
  14. /**
  15. *处理微信发来的请求
  16. */
  17. publicstaticStringprocessRequest(HttpServletRequestrequest){
  18. //返回给微信服务器的xml消息
  19. StringrespXml=null;
  20. try{
  21. //xml请求解析
  22. Map<String,String>requestMap=MessageUtil.parseXml(request);
  23. //发送方帐号(open_id)
  24. StringfromUserName=requestMap.get("FromUserName");
  25. //公众帐号
  26. StringtoUserName=requestMap.get("ToUserName");
  27. //消息类型
  28. StringmsgType=requestMap.get("MsgType");
  29. //回复文本消息
  30. TextMessagetextMessage=newTextMessage();
  31. textMessage.setToUserName(fromUserName);
  32. textMessage.setFromUserName(toUserName);
  33. textMessage.setCreateTime(newDate().getTime());
  34. textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
  35. //图片消息
  36. if(MessageUtil.REQ_MESSAGE_TYPE_IMAGE.equals(msgType)){
  37. //取得图片地址
  38. StringpicUrl=requestMap.get("PicUrl");
  39. //人脸检测
  40. StringdetectResult=FaceService.detect(picUrl);
  41. textMessage.setContent(detectResult);
  42. }
  43. //其它类型的消息
  44. else
  45. textMessage.setContent(getUsage());
  46. respXml=MessageUtil.textMessageToXml(textMessage);
  47. }catch(Exceptione){
  48. e.printStackTrace();
  49. }
  50. returnrespXml;
  51. }
  52. /**
  53. *人脸检测帮助菜单
  54. */
  55. publicstaticStringgetUsage(){
  56. StringBufferbuffer=newStringBuffer();
  57. buffer.append("人脸检测使用指南").append("\n\n");
  58. buffer.append("发送一张清晰的照片,就能帮你分析出种族、年龄、性别等信息").append("\n");
  59. buffer.append("快来试试你是不是长得太着急");
  60. returnbuffer.toString();
  61. }
  62. }
到这里,人脸检测应用就全部开发完成了,整个项目的完整结构如下:

运行结果如下:

笔者用自己的相片测试了两次,测试结果分别是26岁、30岁,这与实际年龄相差不大,可见,Face++的人脸检测准确度还是比较高的。为了增加人脸检测应用的趣味性和娱乐性,笔者忽略了年龄估计值的正负区间。读者可以充分发挥自己的想像力和创造力,使用Face++ API实现更多实用、有趣的功能。应用开发不是简单的接口调用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值