今天給大家帶來一個通過使用Face++來實現人臉識別的功能。
我們先去這個Face++官網看看:http://www.faceplusplus.com.cn
我們點開案例可以看到眾多我們熟知的軟件都是使用的這個公司所提供的SDK。
然后我們點擊開發者中心中的開發工具與sdk下載我們所需要的sdk。
之后再點擊我的應用中的創建應用之后他會給我們兩個密鑰。
要保存這兩個值我們在程序中要用到它們
我今天實現的是實現面部捕捉並且識別性別和年齡來看一下效果圖
閑話不多說我們來看看實現
1.工具類Constant用來存放密鑰
public class Constant { //設置兩個之前獲取的兩個常量 public static final String Key="2029451928755e97039b8138cfa8f8ca"; public static final String Secret="5RNoYATB9vqpA5kerpmo6bp2Aw9fxMl0"; }
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() { @Override public 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; @Override protected 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); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.get_image: //獲取系統選擇圖片intent Intent intent=new Intent(Intent.ACTION_PICK); intent.setType("image/*"); //開啟選擇圖片功能響應碼為PICK_CODE startActivityForResult(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() { @Override public void success(JSONObject jsonObject) { Message message=Message.obtain(); message.what=MSG_SUCESS; message.obj=jsonObject; myhandler.sendMessage(message); } @Override public void error(FaceppParseException exception) { Message message=Message.obtain(); message.what=MSG_ERROR; message.obj=exception; myhandler.sendMessage(message); } }); break; } } //設置響應intent請求 @Override protected 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); //計算寬高要盡可能小於1024 double ratio=Math.max(options.outWidth*1.0d/1024f,options.outHeight*1.0d/1024f); //設置圖片縮放的倍數。假如設為 4 ,則寬和高都為原來的 1/4 ,則圖是原來的 1/16 。 options.inSampleSize=(int)Math.ceil(ratio); //我們這里並想讓他顯示圖片所以這里要置為false options.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() { @Override public 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); //得到新的bitmap myBitmapImage=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://www.bubblyyi.com