基于安卓高仿how-old.net实现人脸识别估算年龄与性别
前几段微软推出的大数据人脸识别年龄应用how-old.net在微博火了一把,它可以通过照片快速获得照片上人物的年龄,系统会对瞳孔、眼角、鼻子等27个“面部地标点"展开分析,进而得出你的“颜龄"。
来看下关于这款应用的截图:
昨晚闲着没事,在网上查阅了点资料仿写了一款类似功能的APP,看下截图:
关于人脸识别技术本想去使用微软给开发人员提供的SDK,但由于天朝巨坑的网络,我连How-old.net官网都登不上,只能绕道去找找其他地方有没类似功能的SDK。后来想起之前在搞O2O的时候,看到过一则关于支付宝"刷脸支付"功能的新闻,查找了相关资料发现他们的"刷脸技术"是Face++提供的,也就这样找到了个好东西。
这是Face++的官方网站:http://www.faceplusplus.com.cn/,在网站里可以找到它为开发者提供了一部分功能的SDK(需要注册),其中就有人脸识别,判断年龄性别种族这个功能。
我们注册个账号,然后创建个应用就可以得到官方给我们提供的APIKey和APISecret,记录下来,然后到到开发者中心(http://www.faceplusplus.com.cn/dev-tools-sdks/)就可以下载到对应版本的SDK,就一个Jar包直接导入项目就可以,这是官方给我们提供的API参考文档(http://www.faceplusplus.com.cn/api-overview/),这样子准备工作就做好了,可以开始进入软件编码阶段了。
先来看下布局文件:
很简单的布局文件,这里就直接贴代码了:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="horizontal" > <TextView
android:id="@+id/tv_tip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="请选择图片"
android:textColor="#000000"
android:textSize="14dp" /> <Button
android:id="@+id/bt_getImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="选择图片"
android:textSize="16dp" /> <Button
android:id="@+id/bt_doAction"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:text="识别操作"
android:textSize="16dp" />
</LinearLayout> <ImageView
android:id="@+id/iv_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:src="@drawable/ic_launcher" /> <FrameLayout
android:id="@+id/fl_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible" > <TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/hint"
android:drawableLeft="@drawable/female"
android:gravity="center"
android:text="18"
android:textColor="#ff0044"
android:textSize="22sp"
android:visibility="invisible" />
</FrameLayout> </LinearLayout>
再来说下主程序类,关于程序的实现,基本可以分为这几步:
1、进入程序,点击按钮跳转相册选取一张图片,并在程序主界面显示。
这里要注意的一些地方:
根据开发者API的/detection/detect(http://www.faceplusplus.com.cn/detection_detect/),我们可以知道,在设定APIKEY和APISECRERT的同时,我们需要指定一张图片的Url地址或者是把图片转为二进制数据向服务端进行POST提交,这里需要注意的是图片的大小不能超过1M,而现在智能手机的像素很高,随便拍一张照片都会超出这个限制范围,所以我们在获取到图片的同时需要对图片进行压缩处理。
2、封装所需要的参数,并把图片转为二进制数据提交到服务端获取识别结果(Json数据)。
3、根据服务端所返回的数据,设置显示到图像上。
大概实现就是这样子,下面直接上的代码吧,大部分注释都很详细,我不一个个讲了,会挑一些重点出来说。
这是一个网络请求工具类:
package com.lcw.rabbit.face; import java.io.ByteArrayOutputStream; import org.json.JSONObject; import android.graphics.Bitmap; import com.facepp.error.FaceppParseException;
import com.facepp.http.HttpRequests;
import com.facepp.http.PostParameters; /**
* Face++ 帮助类,执行网络请求耗时操作
*
* @author Rabbit_Lee
*
*/
public class FaceHelper { private static final String TAG = FaceHelper.class.getName(); /**
* 创建网络
*
* @param bitmap
* @param callBack
*/
public static void uploadFaces(final Bitmap bitmap, final CallBack callBack) {
new Thread(new Runnable() { @Override
public void run() {
try {
// 将Bitmap对象转换成byte数组
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] data = stream.toByteArray(); // 创建连接请求
HttpRequests requests = new HttpRequests(MainActivity.APIKey, MainActivity.APISecret, true, true);
// 封装参数
PostParameters params = new PostParameters();
params.setImg(data);
// 提交网络请求
JSONObject jsonObject = requests.detectionDetect(params);
// 设置回调数据
callBack.success(jsonObject);
} catch (FaceppParseException e) {
// 设置回调数据
callBack.error(e);
e.printStackTrace();
} }
}).start(); } /**
* 数据回调接口,方便主类获取数据
*
* @author Rabbit_Lee
*
*/
public interface CallBack { void success(JSONObject jsonObject); void error(FaceppParseException exception);
}
}
既然是网络请求,那肯定属于耗时操作,那么我们需要在子线程里去完成,然后官方给我们提供的SDK里帮我们封装了一些工具类
例如:
HttpRequests类,它帮我们封装好了HTTP请求,我们可以直接设置参数去访问服务端。
打开源码我们可以发现除了无参构造,它还有2个构造方法,分别是2个参数和4个参数的,其实我们仔细看下源码便可以很轻松的发现,这些参数只不过是用来提交服务端的URL
分别是:(无疑我们是要选择CN+HTTP),所以后两个参数我们直接置为TRUE就可以了。
static final private String WEBSITE_CN = "https://apicn.faceplusplus.com/v2/";
static final private String DWEBSITE_CN = "http://apicn.faceplusplus.com/v2/";
static final private String WEBSITE_US = "https://apius.faceplusplus.com/v2/";
static final private String DWEBSITE_US = "http://apius.faceplusplus.com/v2/";
PostParameters类,用来设置参数的具体值的,这里提供了一个setImg方法,我们只需要把我们图片转为字节数组的变量直接存入即可。
为了方便主程序类方便获取到返回数据,这里采用了接口回调方法CallBack,设置了success(当数据正常返回时),error(当数据不正常返回时)。
这是主程序类:
package com.lcw.rabbit.face; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast; import com.facepp.error.FaceppParseException; public class MainActivity extends Activity implements OnClickListener { // 声明控件
private TextView mTip;
private Button mGetImage;
private Button mDoAction;
private ImageView mImageView;
private View mView;
private ProgressDialog mDialog; // Face++关键数据
public static final String APIKey = "你的APPKEY";
public static final String APISecret = "你的APPSERCRET"; // 标志变量
private static final int REQUEST_CODE = 89757;
private static final int SUCCESS = 1;
private static final int ERROR = 0; // 图片路径
private String mPicStr;
// Bitmap对象
private Bitmap mBitmap; private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case SUCCESS:
// 成功
JSONObject object = (JSONObject) msg.obj;
// 解析Json数据,重构Bitmap对象
reMakeBitmap(object);
mImageView.setImageBitmap(mBitmap);
break;
case ERROR:
// 失败
String errorInfo = (String) msg.obj;
if (errorInfo == null || "".equals(errorInfo)) {
Toast.makeText(MainActivity.this, "Error", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, errorInfo, Toast.LENGTH_LONG).show();
}
break; default:
break;
}
} private void reMakeBitmap(JSONObject json) { mDialog.dismiss(); // 拷贝原Bitmap对象
Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), mBitmap.getConfig());
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(mBitmap, 0, 0, null); Paint paint = new Paint();
paint.setColor(0xffffffff);
paint.setStrokeWidth(3); try {
JSONArray faces = json.getJSONArray("face");
// 检测照片有多少张人脸
int facesCount = faces.length();
mTip.setText("识别到"+facesCount+"张人脸");
for (int i = 0; i < facesCount; i++) {
JSONObject position = faces.getJSONObject(i).getJSONObject("position");
// position属性下的所需数据,定位人脸位置
Float x = (float) position.getJSONObject("center").getDouble("x");
Float y = (float) position.getJSONObject("center").getDouble("y");
Float width = (float) position.getDouble("width");
Float height = (float) position.getDouble("height"); // 把百分比转化为实际像素值
x = x / 100 * bitmap.getWidth();
y = y / 100 * bitmap.getHeight();
width = width / 100 * bitmap.getWidth();
height = height / 100 * bitmap.getHeight();
// 绘制矩形人脸识别框
canvas.drawLine(x - width / 2, y - height / 2, x - width / 2, y + height / 2, paint);
canvas.drawLine(x - width / 2, y - height / 2, x + width / 2, y - height / 2, paint);
canvas.drawLine(x + width / 2, y - height / 2, x + width / 2, y + height / 2, paint);
canvas.drawLine(x - width / 2, y + height / 2, x + width / 2, y + height / 2, paint); // 获取年龄,性别
JSONObject attribute = faces.getJSONObject(i).getJSONObject("attribute");
Integer age = attribute.getJSONObject("age").getInt("value");
String sex = attribute.getJSONObject("gender").getString("value"); // 获取显示年龄性别的Bitmap对象
Bitmap infoBm = makeView(age, sex); canvas.drawBitmap(infoBm, x - infoBm.getWidth() / 2, y - height / 2-infoBm.getHeight(), null); mBitmap = bitmap; } } catch (JSONException e) {
e.printStackTrace();
} } /**
* 构建一个显示年龄,性别的Bitmap
*
* @param age
* @param sex
*/
private Bitmap makeView(Integer age, String sex) {
mView.setVisibility(View.VISIBLE);
TextView tv_info = (TextView) mView.findViewById(R.id.tv_info);
tv_info.setText(age.toString()); if (sex.equals("Female")) {
//女性
tv_info.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.female), null, null, null);
} else {
//男性
tv_info.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.male), null, null, null);
}
// 通过cache机制将View保存为Bitmap
tv_info.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
tv_info.layout(0, 0, tv_info.getMeasuredWidth(), tv_info.getMeasuredHeight());
tv_info.buildDrawingCache();
tv_info.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(tv_info.getDrawingCache());
tv_info.destroyDrawingCache(); return bitmap;
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 对控件进行初始化操作
initViews();
// 对控件进行监听操作
initActions();
} private void initActions() {
mGetImage.setOnClickListener(this);
mDoAction.setOnClickListener(this);
} private void initViews() {
mTip = (TextView) findViewById(R.id.tv_tip);
mGetImage = (Button) findViewById(R.id.bt_getImage);
mDoAction = (Button) findViewById(R.id.bt_doAction);
mImageView = (ImageView) findViewById(R.id.iv_image);
mView=findViewById(R.id.fl_view);
mDialog = new ProgressDialog(MainActivity.this);
mDialog.setMessage("系统检测识别中,请稍后.."); } @Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == REQUEST_CODE) {
if (intent != null) {
Uri uri = intent.getData();
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
mPicStr = cursor.getString(index);
cursor.close(); // 根据获取到的图片路径,获取图片并进行图片压缩
BitmapFactory.Options options = new BitmapFactory.Options();
// 当Options的inJustDecodeBounds属性设置为true时,不会显示图片,只会返回该图片的具体数据
options.inJustDecodeBounds = true; // 根据所选实际的宽高计算压缩比例并将图片压缩
BitmapFactory.decodeFile(mPicStr, options);
Double reSize = Math.max(options.outWidth * 1.0 / 1024f, options.outHeight * 1.0 / 1024f);
options.inSampleSize = (int) Math.ceil(reSize);
options.inJustDecodeBounds = false; // 创建Bitmap
mBitmap = BitmapFactory.decodeFile(mPicStr, options);
mImageView.setImageBitmap(mBitmap); mTip.setText("点击识别==》");
mDoAction.setEnabled(true); }
} }
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_getImage:
// 点击获取图片按钮跳转相册选取图片
Intent intent = new Intent(Intent.ACTION_PICK);
// 获取手机图库信息
intent.setType("image/*");
startActivityForResult(intent, REQUEST_CODE);
break;
case R.id.bt_doAction:
// 点击识别按钮进行图片识别操作
mDialog.show();
FaceHelper.uploadFaces(mBitmap, new FaceHelper.CallBack() { @Override
public void success(JSONObject result) {
// 人脸识别成功,回调获取数据
Message msg = Message.obtain();
msg.what = SUCCESS;
msg.obj = result;
handler.sendMessage(msg);
} @Override
public void error(FaceppParseException e) {
// 人脸识别失败,回调获取错误信息
Message msg = Message.obtain();
msg.what = ERROR;
msg.obj = e.getErrorMessage();
handler.sendMessage(msg);
}
});
break; default:
break;
} } }
由于注释很全,这里我就挑几个地方出来讲,有其他不清楚的朋友可以在文章评论里留言,我会答复的。
1、当点击选择图片按钮时,通过Intent去访问系统图片,并根据返回的图片进行图片压缩,因为官方文档很明确的告诉我们图片大小不能超过1MB。
压缩图片有2种方式,一种是通过压缩图片的质量大小,另一种则是根据比例来压缩图片大小(这里我选择第二种压缩方式)。
根据得到的Bitmap对象,我们可以先将inJustDecodeBounds先设置成true,这样我们就不会得到具体的图片,只会得到该图片的获取到真实图片的宽和高,然后我们让其去除以1024,取较大的一个数作为压缩比例,取整利用inSampleSize去对Bitmap进行压缩,然后再把inJustDecodeBounds设置为false。
2、当我们提交图片并且服务端向我们返回数据时,由于我们是在子线程中去执行网络请求的,所以这边我们通过Handler机制来传输数据,使得主线程可以拿到数据并更新UI。
服务端给我们返回的是一个Json的数据,所以我们需要在这里将它进行解析(关于Json解析,我这里用到的是安卓官方自带的Json帮助类,想用谷歌提供的Gson工具类也是可以的,这边是工具类使用方法:《Gson简要使用笔记》)拿到我们所需要的数据,这里我们需要的数据有
具体数值各代表什么意思,这个大家查阅下官方给定的API文档就可以知道了,这里就不再详细去写了,然后根据所给的这些数值我们会圈出所对应人脸的位置,方法可以有很多,形状也无所谓,这里我给出的是矩形方案。
3、再来就是对隐藏布局TextView进行显示,由于我们可以在服务端给我们返回的数据里知道性别年龄等数据,这里就很好办了。
这里我通过通过cache机制将View转为Bitmap然后进行显示,当然不止是这种方法,用自定义View去进行布局也是可以的,这里大家灵活操作,我就不多说了。
到这里,文章就结束了,大家有什么疑问可以在文章评论下面给我留言。
作者:李晨玮
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!
基于安卓高仿how-old.net实现人脸识别估算年龄与性别的更多相关文章
- 基于MTCNN多任务级联卷积神经网络进行的人脸识别 世纪晟人脸检测
神经网络和深度学习目前为处理图像识别的许多问题提供了最佳解决方案,而基于MTCNN(多任务级联卷积神经网络)的人脸检测算法也解决了传统算法对环境要求高.人脸要求高.检测耗时高的弊端. 基于MTCNN多 ...
- 安卓高仿QQ头像截取升级版
观看此篇文章前,请先阅读上篇文章:高仿QQ头像截取: 本篇之所以为升级版,是在截取头像界面添加了与qq类似的阴影层(裁剪区域以外的部分),且看效果图: 为了适应大家不同需求,这次打了两个包,及上图 ...
- 主成分分析 (PCA) 与其高维度下python实现(简单人脸识别)
Introduction 主成分分析(Principal Components Analysis)是一种对特征进行降维的方法.由于观测指标间存在相关性,将导致信息的重叠与低效,我们倾向于用少量的.尽可 ...
- 基于人脸识别+IMDB-WIFI+Caffe的性别识别
本文用记录基于Caffe的人脸性别识别过程.基于imdb-wiki模型做finetune,imdb-wiki数据集合模型可从这里下载:https://data.vision.ee.ethz.ch/cv ...
- 基于虹软 2.0 人脸识别猜年龄 java版 demo
首先感谢虹软,是你们提供这么好的SDK支撑了我们的想象力! 这是一个用javav编写的可视化应用,用户通过自己的脸和计算机进行交互,计算机则通过萌萌女孩的语音和用户对话.核心程序就是利用ArcFace ...
- 基于虹软人脸识别,实现RTMP直播推流追踪视频中所有人脸信息(C#)
前言 大家应该都知道几个很常见的例子,比如在张学友的演唱会,在安检通道检票时,通过人像识别系统成功识别捉了好多在逃人员,被称为逃犯克星:人行横道不遵守交通规则闯红灯的路人被人脸识别系统抓拍放在大屏上以 ...
- 安卓开发笔记——Fragment+ViewPager组件(高仿微信界面)
什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再 ...
- Android 高仿微信实时聊天 基于百度云推送
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天终于有幸利用百 ...
- 基于Node.js+MySQL开发的开源微信小程序B2C商城(页面高仿网易严选)
界面高仿网易严选商城(主要是2016年wap版) 测试数据采集自网易严选商城 功能和数据库参考ecshop 服务端api基于Node.js+ThinkJS+MySQL 计划添加基于Vue.js的后台管 ...
随机推荐
- java Unicode转UTF-8代码
在做http请求时,有时候服务器传回的数据中会遇到传回数据为Unicode的情况,为此需要进行Unicode转UTF-8的转化,代码: public class StringTest { /** * ...
- 简单理解ECMAScript2015中的Promise
ECMAScript6中新增了Promise对象, 所谓Promise对象,即代表着一个还未完成,但将来某时会完成的操作(通常是异步操作).使用Promise对象,我们就可以避免陷入函数层层嵌套的‘回 ...
- Java-认识字符集-转载
问题起源 对于计算机而言,它仅认识两个0和1,不管是在内存中还是外部存储设备上,我们所看到的文字.图片.视频等等“数据”在计算机中都是已二进制形式存在的.不同字符对应二进制数的规则,就是字符的编码.字 ...
- [读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器
前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭 ...
- paip. java的 函数式编程 大法
paip. java的 函数式编程 大法 Java 语言中常被忽视的一个方面是它被归类为一种命令式(imperative)编程语言.命令式编程虽然由于与 Java 语言的关联而相当普及,但是并不是惟一 ...
- js系列(8)简介
JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HT ...
- 比较用decodeFileDescriptor和decodeFile的区别
从本地中读取图片,可以用decodeFileDescriptor和decodeFile,至于哪一种方式的耗内存情况作了一次简单对比,可能一次选取6张图片数量过少,貌似区别不大,decodeFileDe ...
- ES5 数组方法map
概述 map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组. 语法 array.map(callback[, thisArg]) 参数 callback 原数组中的元素经 ...
- 第50讲:Scala中Variance变化点
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
- Android定位&地图&导航——自定义公交路线代码
一.问题描述 基于百度地图实现检索指定城市指定公交的交通路线图,效果如图所示 二.通用组件Application类,主要创建并初始化BMapManager public class App exten ...