欢迎转载,转载时请标明出处:http://blog.csdn.net/android_for_james/article/details/51016170
今天给大家带来一个通过使用Face++来实现人脸识别的功能
我们先去这个Face++官网看看:http://www.faceplusplus.com.cn
我们点开案例可以看到众多我们熟知的软件都是使用的这个公司所提供的SDK
然后我们点击开发者中心中的开发工具与sdk下载我们所需要的sdk
之后再点击我的应用中的创建应用之后他会给我们两个密钥
要保存这两个值我们在程序中要用到它们
我今天实现的是实现面部捕捉并且识别性别和年龄来看一下效果图
闲话不多说我们来看看实现
1.工具类Constant用来存放密钥
public class Constant {//设置两个之前获取的两个常量public static final String Key="你的key";public static final String Secret="你的secret";
}
2.工具类InternetDetect
import android.graphics.Bitmap;import com.facepp.error.FaceppParseException;
import com.facepp.http.HttpRequests;
import com.facepp.http.PostParameters;import org.json.JSONObject;import java.io.ByteArrayOutputStream;public class InternetDetect {public interface CallBack{void success(JSONObject jsonObject);void error(FaceppParseException exception);}public static void dectect(final Bitmap bitmap, final CallBack callBack){//因为这里要向网络发送数据是耗时操作所以要在新线程中执行new Thread(new Runnable() {@Overridepublic void run() {/*1.设置请求* 2.创建一个Bitmap* 3.创建字符数组流* 4.将bitmap转换为字符并传入流中* 5.新建字符数组接受流* 6.创建发送数据包* 7.创建接受数据包** */try {HttpRequests httpRequests=new HttpRequests(Constant.Key,Constant.Secret,true,true);//从0,0点挖取整个视图,后两个参数是目标大小Bitmap bitmapsmall = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight());//这里api要求传入一个字节数组数据,因此要用字节数组输出流ByteArrayOutputStream stream=new ByteArrayOutputStream();/*Bitmap.compress()方法可以用于将Bitmap-->byte[]既将位图的压缩到指定的OutputStream。如果返回true,位图可以通过传递一个相应的InputStream BitmapFactory.decodeStream(重建)第一个参数可设置JPEG或PNG格式,第二个参数是图片质量,第三个参数是一个流信息*/bitmapsmall.compress(Bitmap.CompressFormat.JPEG, 100, stream);byte[] arrays=stream.toByteArray();//实现发送参数功能PostParameters parameters=new PostParameters();//发送数据parameters.setImg(arrays);//服务器返回一个JSONObject的数据JSONObject jsonObject=httpRequests.detectionDetect(parameters);System.out.println("jsonObject:"+jsonObject.toString());if(callBack!=null){//设置回调callBack.success(jsonObject);}} catch (FaceppParseException e) {System.out.println("error");e.printStackTrace();if(callBack!=null){callBack.error(e);}}}}).start();}}
3.MainActivity
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.support.v7.app.ActionBarActivity;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;import com.facepp.error.FaceppParseException;import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;public class MainActivity extends ActionBarActivity implements View.OnClickListener {private static final int PICK_CODE =1;private ImageView myPhoto;private Button getImage;private Button detect;private TextView tip;private View mWaitting;private String ImagePath=null;private Paint mypaint;private Bitmap myBitmapImage;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mypaint=new Paint();initViews();initEvent();}private void initViews(){myPhoto=(ImageView)findViewById(R.id.id_photo);getImage=(Button)findViewById(R.id.get_image);detect=(Button)findViewById(R.id.detect);tip=(TextView)findViewById(R.id.id_Tip);mWaitting=findViewById(R.id.id_waitting);tip.setMovementMethod(ScrollingMovementMethod.getInstance());}private void initEvent(){getImage.setOnClickListener(this);detect.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.get_image://获取系统选择图片intentIntent intent=new Intent(Intent.ACTION_PICK);intent.setType("image/*");//开启选择图片功能响应码为PICK_CODEstartActivityForResult(intent,PICK_CODE);break;case R.id.detect://显示进度条圆形mWaitting.setVisibility(View.VISIBLE);//这里需要注意判断用户是否没有选择图片直接点击了detect按钮//否则会报一个空指针异常而造成程序崩溃if(ImagePath!=null&&!ImagePath.trim().equals("")){//如果不是直接点击的图片则压缩当前选中的图片resizePhoto();}else{//否则将默认的背景图作为bitmap传入myBitmapImage=BitmapFactory.decodeResource(getResources(),R.drawable.test1);}//设置回调InternetDetect.dectect(myBitmapImage, new InternetDetect.CallBack() {@Overridepublic void success(JSONObject jsonObject) {Message message=Message.obtain();message.what=MSG_SUCESS;message.obj=jsonObject;myhandler.sendMessage(message);}@Overridepublic void error(FaceppParseException exception) {Message message=Message.obtain();message.what=MSG_ERROR;message.obj=exception;myhandler.sendMessage(message);}});break;}}//设置响应intent请求@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent intent) {super.onActivityResult(requestCode, resultCode, intent);if(requestCode==PICK_CODE){if(intent!=null){//获取图片路径//获取所有图片资源Uri uri=intent.getData();//设置指针获得一个ContentResolver的实例Cursor cursor=getContentResolver().query(uri,null,null,null,null);cursor.moveToFirst();//返回索引项位置int index=cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);//返回索引项路径ImagePath=cursor.getString(index);cursor.close();//这个jar包要求请求的图片大小不得超过3m所以要进行一个压缩图片操作resizePhoto();myPhoto.setImageBitmap(myBitmapImage);tip.setText("Click Detect==>");}}}private void resizePhoto() {//得到BitmapFactory的操作权BitmapFactory.Options options = new BitmapFactory.Options();// 如果设置为 true ,不获取图片,不分配内存,但会返回图片的高宽度信息。options.inJustDecodeBounds = true;BitmapFactory.decodeFile(ImagePath,options);//计算宽高要尽可能小于1024double ratio=Math.max(options.outWidth*1.0d/1024f,options.outHeight*1.0d/1024f);//设置图片缩放的倍数。假如设为 4 ,则宽和高都为原来的 1/4 ,则图是原来的 1/16 。options.inSampleSize=(int)Math.ceil(ratio);//我们这里并想让他显示图片所以这里要置为falseoptions.inJustDecodeBounds=false;//利用Options的这些值就可以高效的得到一幅缩略图。myBitmapImage=BitmapFactory.decodeFile(ImagePath,options);}private static final int MSG_SUCESS=11;private static final int MSG_ERROR=22;private Handler myhandler=new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what){case MSG_SUCESS://关闭缓冲条mWaitting.setVisibility(View.GONE);//拿到新线程中返回的JSONObject数据JSONObject rsobj= (JSONObject) msg.obj;//准备Bitmap,这里会解析JSONObject传回的数据prepareBitmap(rsobj);//让主线程的相框刷新myPhoto.setImageBitmap(myBitmapImage);break;case MSG_ERROR:mWaitting.setVisibility(View.GONE);String errormsg= (String) msg.obj;break;}}};private void prepareBitmap(JSONObject JS) {//新建一个Bitmap使用它作为Canvas操作的对象Bitmap bitmap=Bitmap.createBitmap(myBitmapImage.getWidth(),myBitmapImage.getHeight(),myBitmapImage.getConfig());//实例化一块画布Canvas canvas=new Canvas(bitmap);//把原图先画到画布上面canvas.drawBitmap(myBitmapImage, 0, 0, null);//解析传回的JSONObject数据try {//JSONObject中包含着众多JSONArray,但是我们这里需要关键字为face的数组中的信息JSONArray faces=JS.getJSONArray("face");//获取得到几个人脸int faceCount=faces.length();//让提示文本显示人脸数tip.setText("find"+faceCount);//下面对每一张人脸都进行单独的信息绘制for(int i=0;i<faceCount;i++){//拿到每张人脸的信息JSONObject face=faces.getJSONObject(i);//拿到人脸的详细位置信息JSONObject position=face.getJSONObject("position");float x=(float)position.getJSONObject("center").getDouble("x");float y=(float)position.getJSONObject("center").getDouble("y");float w=(float)position.getDouble("width");float h=(float)position.getDouble("height");//注意这里拿到的各个参数并不是实际的像素值,而是一个比例,都是相对于整个屏幕而言的比例信息//因此我们使用的时候要进行一下数据处理x=x/100*bitmap.getWidth();y=y/100*bitmap.getHeight();w=w/100*bitmap.getWidth();h=h/100*bitmap.getHeight();//设置画笔颜色mypaint.setColor(0xffffffff);//设置画笔宽度mypaint.setStrokeWidth(3);//绘制一个矩形框canvas.drawLine(x-w/2,y-h/2,x-w/2,y+h/2,mypaint);canvas.drawLine(x-w/2,y-h/2,x+w/2,y-h/2,mypaint);canvas.drawLine(x+w/2,y-h/2,x+w/2,y+h/2,mypaint);canvas.drawLine(x-w/2,y+h/2,x+w/2,y+h/2,mypaint);//得到年龄信息int age=face.getJSONObject("attribute").getJSONObject("age").getInt("value");//得到性别信息String gender=face.getJSONObject("attribute").getJSONObject("gender").getString("value");System.out.println("age:"+age);System.out.println("gender:"+gender);//现在要把得到的文字信息转化为一个图像信息,我们写一个专门的函数来处理Bitmap ageBitmap=buildAgeBitmap(age,("Male").equals(gender));//进行图片提示气泡的缩放,这个很有必要,当人脸很小的时候我们需要把提示气泡也变小int agewidth=ageBitmap.getWidth();int agehight=ageBitmap.getHeight();if(bitmap.getWidth()<myPhoto.getWidth()&&bitmap.getHeight()<myPhoto.getHeight()){//设置缩放比float ratio=Math.max(bitmap.getWidth()*1.0f/myPhoto.getWidth(),bitmap.getHeight()*1.0f/myPhoto.getHeight());//完成缩放ageBitmap=Bitmap.createScaledBitmap(ageBitmap,(int)(agewidth*ratio*0.8),(int)(agehight*ratio*0.5),false);}//在画布上画出提示气泡canvas.drawBitmap(ageBitmap,x-ageBitmap.getWidth()/2,y-h/2-ageBitmap.getHeight(),null);//得到新的bitmapmyBitmapImage=bitmap;}} catch (JSONException e) {e.printStackTrace();}}private Bitmap buildAgeBitmap(int age, boolean isMale) {//这里要将文字信息转化为图像信息,如果拿Canvas直接画的话操作量太大//因此这里有一些技巧,将提示气泡设置成一个TextView,他的背景就是气泡的背景//他的内容左侧是显示性别的图片右侧是年龄TextView tv= (TextView) mWaitting.findViewById(R.id.id_age_and_gender);//这里要记得显示数字的时候后面最好跟一个""不然有时候会显示不出来tv.setText(age + "");if(isMale){//判断性别tv.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.male),null,null,null);}else{tv.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.female),null,null,null);}//使用setDrawingCacheEnabled(boolean flag)提高绘图速度//View组件显示的内容可以通过cache机制保存为bitmap//这里要获取它的cache先要通过setDrawingCacheEnable方法把cache开启,// 然后再调用getDrawingCache方法就可 以获得view的cache图片了。tv.setDrawingCacheEnabled(true);Bitmap bitmap=Bitmap.createBitmap(tv.getDrawingCache());//关闭许可tv.destroyDrawingCache();return bitmap;}}
布局文件比较简单因此代码我就不上传了
这里我用红色字体标出我在实现的过程中遇到的问题希望大家可以避免:
1.在写按键响应的时候要注意判断用户是否没有选择图片而直接点击了detect按钮,这样的话避免了获取图片时存在空指针异常的问题
2.上传图片时要保证图片小于3M因此注意图片的压缩
3.气泡处理有技巧,把它当作Textview更方便
4.在绘制气泡的时候也要注意缩放不然的话当人脸很小的时候气泡会占据整个图片导致用户体验降低
希望对大家有所帮助,欢迎转载但要标明出处,谢谢!
有什么不足的地方可以留言给我我会尽快回复并改正!
欢迎关注我的博客:http://blog.csdn.net/android_for_james
源码下载网址(点开后面链接后在文章末尾有点击下载按钮):点击打开链接