zoukankan      html  css  js  c++  java
  • FACE++学习二、获得face属性

    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中了解到人脸检测接口的详细信息,该接口的请求地址如下:

    1. 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("
    ");
    			for (Face face : faceList) {
    				buffer.append(face.getRaceValue()).append("人种,");
    				buffer.append(face.getGenderValue()).append(",");
    				buffer.append(face.getAgeValue()).append("岁左右").append("
    ");
    			}
    		}
    		// 检测到2-10张脸
    		else if (faceList.size() > 1 && faceList.size() <= 10) {
    			buffer.append("共检测到 ").append(faceList.size()).append(" 张人脸,按脸部中心位置从左至右依次为:").append("
    ");
    			for (Face face : faceList) {
    				buffer.append(face.getRaceValue()).append("人种,");
    				buffer.append(face.getGenderValue()).append(",");
    				buffer.append(face.getAgeValue()).append("岁左右").append("
    ");
    			}
    		}
    		// 检测到10张脸以上
    		else if (faceList.size() > 10) {
    			buffer.append("共检测到 ").append(faceList.size()).append(" 张人脸").append("
    ");
    			// 统计各人种、性别的人数
    			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("
    ");
    			if (0 != whiteMale || 0 != whiteFemale)
    				buffer.append("白色人种:").append(whiteMale).append("男").append(whiteFemale).append("女").append("
    ");
    			if (0 != blackMale || 0 != blackFemale)
    				buffer.append("黑色人种:").append(blackMale).append("男").append(blackFemale).append("女").append("
    ");
    		}
    		// 移除末尾空格
    		buffer = new StringBuffer(buffer.substring(0, buffer.lastIndexOf("
    ")));
    		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("
    
    ");
    		buffer.append("发送一张清晰的照片,就能帮你分析出种族、年龄、性别等信息").append("
    ");
    		buffer.append("快来试试你是不是长得太着急");
    		return buffer.toString();
    	}
    }

    到这里,人脸检测应用就全部开发完成了,整个项目的完整结构如下:

            

    运行结果如下:

             

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

  • 相关阅读:
    HDU2363 最短路+贪心
    stl-----map去重,排序,计数
    STL------sort三种比较算子定义
    栈------表达式求值
    踩水坑系列一
    第一周 动态规划Dynamic Programming(一)
    模拟递归回溯贪心专题入门
    HDU1013,1163 ,2035九余数定理 快速幂取模
    HDU1005 找规律 or 循环点 or 矩阵快速幂
    入门基础常识
  • 原文地址:https://www.cnblogs.com/Anita9002/p/4057151.html
Copyright © 2011-2022 走看看