前幾段微軟推出的大數據人臉識別年齡應用how-old.net在微博火了一把,它能夠經過照片快速得到照片上人物的年齡,系統會對瞳孔、眼角、鼻子等27個「面部地標點"展開分析,進而得出你的「顏齡"。html
來看下關於這款應用的截圖:java
昨晚閒着沒事,在網上查閱了點資料仿寫了一款相似功能的APP,看下截圖:android
關於人臉識別技術本想去使用微軟給開發人員提供的SDK,但因爲天朝巨坑的網絡,我連How-old.net官網都登不上,只能繞道去找找其餘地方有沒相似功能的SDK。後來想起以前在搞O2O的時候,看到過一則關於支付寶"刷臉支付"功能的新聞,查找了相關資料發現他們的"刷臉技術"是Face++提供的,也就這樣找到了個好東西。json
這是Face++的官方網站:http://www.faceplusplus.com.cn/,在網站裏能夠找到它爲開發者提供了一部分功能的SDK(須要註冊),其中就有人臉識別,判斷年齡性別種族這個功能。canvas
咱們註冊個帳號,而後建立個應用就能夠獲得官方給咱們提供的APIKey和APISecret,記錄下來,而後到到開發者中心(http://www.faceplusplus.com.cn/dev-tools-sdks/)就能夠下載到對應版本的SDK,就一個Jar包直接導入項目就能夠,這是官方給咱們提供的API參考文檔(http://www.faceplusplus.com.cn/api-overview/),這樣子準備工做就作好了,能夠開始進入軟件編碼階段了。api
先來看下佈局文件:數組
很簡單的佈局文件,這裏就直接貼代碼了:網絡
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#ffffff" 6 android:orientation="vertical" > 7 8 <LinearLayout 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:layout_margin="5dp" 12 android:orientation="horizontal" > 13 14 <TextView 15 android:id="@+id/tv_tip" 16 android:layout_width="0dp" 17 android:layout_height="wrap_content" 18 android:layout_weight="1" 19 android:text="請選擇圖片" 20 android:textColor="#000000" 21 android:textSize="14dp" /> 22 23 <Button 24 android:id="@+id/bt_getImage" 25 android:layout_width="0dp" 26 android:layout_height="wrap_content" 27 android:layout_weight="1" 28 android:text="選擇圖片" 29 android:textSize="16dp" /> 30 31 <Button 32 android:id="@+id/bt_doAction" 33 android:layout_width="0dp" 34 android:layout_height="wrap_content" 35 android:layout_weight="1" 36 android:enabled="false" 37 android:text="識別操做" 38 android:textSize="16dp" /> 39 </LinearLayout> 40 41 <ImageView 42 android:id="@+id/iv_image" 43 android:layout_width="match_parent" 44 android:layout_height="match_parent" 45 android:layout_marginBottom="5dp" 46 android:src="@drawable/ic_launcher" /> 47 48 <FrameLayout 49 android:id="@+id/fl_view" 50 android:layout_width="wrap_content" 51 android:layout_height="wrap_content" 52 android:visibility="invisible" > 53 54 <TextView 55 android:id="@+id/tv_info" 56 android:layout_width="wrap_content" 57 android:layout_height="wrap_content" 58 android:background="@drawable/hint" 59 android:drawableLeft="@drawable/female" 60 android:gravity="center" 61 android:text="18" 62 android:textColor="#ff0044" 63 android:textSize="22sp" 64 android:visibility="invisible" /> 65 </FrameLayout> 66 67 </LinearLayout>
再來講下主程序類,關於程序的實現,基本能夠分爲這幾步:app
一、進入程序,點擊按鈕跳轉相冊選取一張圖片,並在程序主界面顯示。ide
這裏要注意的一些地方:
根據開發者API的/detection/detect(http://www.faceplusplus.com.cn/detection_detect/),咱們能夠知道,在設定APIKEY和APISECRERT的同時,咱們須要指定一張圖片的Url地址或者是把圖片轉爲二進制數據向服務端進行POST提交,這裏須要注意的是圖片的大小不能超過1M,而如今智能手機的像素很高,隨便拍一張照片都會超出這個限制範圍,因此咱們在獲取到圖片的同時須要對圖片進行壓縮處理。
二、封裝所須要的參數,並把圖片轉爲二進制數據提交到服務端獲取識別結果(Json數據)。
三、根據服務端所返回的數據,設置顯示到圖像上。
大概實現就是這樣子,下面直接上的代碼吧,大部分註釋都很詳細,我不一個個講了,會挑一些重點出來講。
這是一個網絡請求工具類:
1 package com.lcw.rabbit.face; 2 3 import java.io.ByteArrayOutputStream; 4 5 import org.json.JSONObject; 6 7 import android.graphics.Bitmap; 8 9 import com.facepp.error.FaceppParseException; 10 import com.facepp.http.HttpRequests; 11 import com.facepp.http.PostParameters; 12 13 /** 14 * Face++ 幫助類,執行網絡請求耗時操做 15 * 16 * @author Rabbit_Lee 17 * 18 */ 19 public class FaceHelper { 20 21 private static final String TAG = FaceHelper.class.getName(); 22 23 /** 24 * 建立網絡 25 * 26 * @param bitmap 27 * @param callBack 28 */ 29 public static void uploadFaces(final Bitmap bitmap, final CallBack callBack) { 30 new Thread(new Runnable() { 31 32 @Override 33 public void run() { 34 try { 35 // 將Bitmap對象轉換成byte數組 36 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 37 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); 38 byte[] data = stream.toByteArray(); 39 40 // 建立鏈接請求 41 HttpRequests requests = new HttpRequests(MainActivity.APIKey, MainActivity.APISecret, true, true); 42 // 封裝參數 43 PostParameters params = new PostParameters(); 44 params.setImg(data); 45 // 提交網絡請求 46 JSONObject jsonObject = requests.detectionDetect(params); 47 // 設置回調數據 48 callBack.success(jsonObject); 49 } catch (FaceppParseException e) { 50 // 設置回調數據 51 callBack.error(e); 52 e.printStackTrace(); 53 } 54 55 } 56 }).start(); 57 58 } 59 60 /** 61 * 數據回調接口,方便主類獲取數據 62 * 63 * @author Rabbit_Lee 64 * 65 */ 66 public interface CallBack { 67 68 void success(JSONObject jsonObject); 69 70 void error(FaceppParseException exception); 71 } 72 }
既然是網絡請求,那確定屬於耗時操做,那麼咱們須要在子線程裏去完成,而後官方給咱們提供的SDK裏幫咱們封裝了一些工具類
例如:
HttpRequests類,它幫咱們封裝好了HTTP請求,咱們能夠直接設置參數去訪問服務端。
打開源碼咱們能夠發現除了無參構造,它還有2個構造方法,分別是2個參數和4個參數的,其實咱們仔細看下源碼即可以很輕鬆的發現,這些參數只不過是用來提交服務端的URL
分別是:(無疑咱們是要選擇CN+HTTP),因此後兩個參數咱們直接置爲TRUE就能夠了。
1 static final private String WEBSITE_CN = "https://apicn.faceplusplus.com/v2/"; 2 static final private String DWEBSITE_CN = "http://apicn.faceplusplus.com/v2/"; 3 static final private String WEBSITE_US = "https://apius.faceplusplus.com/v2/"; 4 static final private String DWEBSITE_US = "http://apius.faceplusplus.com/v2/";
PostParameters類,用來設置參數的具體值的,這裏提供了一個setImg方法,咱們只須要把咱們圖片轉爲字節數組的變量直接存入便可。
爲了方便主程序類方便獲取到返回數據,這裏採用了接口回調方法CallBack,設置了success(當數據正常返回時),error(當數據不正常返回時)。
這是主程序類:
1 package com.lcw.rabbit.face; 2 3 import org.json.JSONArray; 4 import org.json.JSONException; 5 import org.json.JSONObject; 6 7 import android.app.Activity; 8 import android.app.ProgressDialog; 9 import android.content.Intent; 10 import android.database.Cursor; 11 import android.graphics.Bitmap; 12 import android.graphics.BitmapFactory; 13 import android.graphics.Canvas; 14 import android.graphics.Paint; 15 import android.net.Uri; 16 import android.os.Bundle; 17 import android.os.Handler; 18 import android.os.Message; 19 import android.provider.MediaStore; 20 import android.view.View; 21 import android.view.View.MeasureSpec; 22 import android.view.View.OnClickListener; 23 import android.widget.Button; 24 import android.widget.ImageView; 25 import android.widget.TextView; 26 import android.widget.Toast; 27 28 import com.facepp.error.FaceppParseException; 29 30 public class MainActivity extends Activity implements OnClickListener { 31 32 // 聲明控件 33 private TextView mTip; 34 private Button mGetImage; 35 private Button mDoAction; 36 private ImageView mImageView; 37 private View mView; 38 private ProgressDialog mDialog; 39 40 // Face++關鍵數據 41 public static final String APIKey = "你的APPKEY"; 42 public static final String APISecret = "你的APPSERCRET"; 43 44 // 標誌變量 45 private static final int REQUEST_CODE = 89757; 46 private static final int SUCCESS = 1; 47 private static final int ERROR = 0; 48 49 // 圖片路徑 50 private String mPicStr; 51 // Bitmap對象 52 private Bitmap mBitmap; 53 54 private Handler handler = new Handler() { 55 public void handleMessage(android.os.Message msg) { 56 switch (msg.what) { 57 case SUCCESS: 58 // 成功 59 JSONObject object = (JSONObject) msg.obj; 60 // 解析Json數據,重構Bitmap對象 61 reMakeBitmap(object); 62 mImageView.setImageBitmap(mBitmap); 63 break; 64 case ERROR: 65 // 失敗 66 String errorInfo = (String) msg.obj; 67 if (errorInfo == null || "".equals(errorInfo)) { 68 Toast.makeText(MainActivity.this, "Error", Toast.LENGTH_LONG).show(); 69 } else { 70 Toast.makeText(MainActivity.this, errorInfo, Toast.LENGTH_LONG).show(); 71 } 72 break; 73 74 default: 75 break; 76 } 77 } 78 79 private void reMakeBitmap(JSONObject json) { 80 81 mDialog.dismiss(); 82 83 // 拷貝原Bitmap對象 84 Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), mBitmap.getConfig()); 85 Canvas canvas = new Canvas(bitmap); 86 canvas.drawBitmap(mBitmap, 0, 0, null); 87 88 Paint paint = new Paint(); 89 paint.setColor(0xffffffff); 90 paint.setStrokeWidth(3); 91 92 try { 93 JSONArray faces = json.getJSONArray("face"); 94 // 檢測照片有多少張人臉 95 int facesCount = faces.length(); 96 mTip.setText("識別到"+facesCount+"張人臉"); 97 for (int i = 0; i < facesCount; i++) { 98 JSONObject position = faces.getJSONObject(i).getJSONObject("position"); 99 // position屬性下的所需數據,定位人臉位置 100 Float x = (float) position.getJSONObject("center").getDouble("x"); 101 Float y = (float) position.getJSONObject("center").getDouble("y"); 102 Float width = (float) position.getDouble("width"); 103 Float height = (float) position.getDouble("height"); 104 105 // 把百分比轉化爲實際像素值 106 x = x / 100 * bitmap.getWidth(); 107 y = y / 100 * bitmap.getHeight(); 108 width = width / 100 * bitmap.getWidth(); 109 height = height / 100 * bitmap.getHeight(); 110 // 繪製矩形人臉識別框 111 canvas.drawLine(x - width / 2, y - height / 2, x - width / 2, y + height / 2, paint); 112 canvas.drawLine(x - width / 2, y - height / 2, x + width / 2, y - height / 2, paint); 113 canvas.drawLine(x + width / 2, y - height / 2, x + width / 2, y + height / 2, paint); 114 canvas.drawLine(x - width / 2, y + height / 2, x + width / 2, y + height / 2, paint); 115 116 // 獲取年齡,性別 117 JSONObject attribute = faces.getJSONObject(i).getJSONObject("attribute"); 118 Integer age = attribute.getJSONObject("age").getInt("value"); 119 String sex = attribute.getJSONObject("gender").getString("value"); 120 121 // 獲取顯示年齡性別的Bitmap對象 122 Bitmap infoBm = makeView(age, sex); 123 124 canvas.drawBitmap(infoBm, x - infoBm.getWidth() / 2, y - height / 2-infoBm.getHeight(), null); 125 126 mBitmap = bitmap; 127 128 } 129 130 } catch (JSONException e) { 131 e.printStackTrace(); 132 } 133 134 } 135 136 /** 137 * 構建一個顯示年齡,性別的Bitmap 138 * 139 * @param age 140 * @param sex 141 */ 142 private Bitmap makeView(Integer age, String sex) { 143 mView.setVisibility(View.VISIBLE); 144 TextView tv_info = (TextView) mView.findViewById(R.id.tv_info); 145 tv_info.setText(age.toString()); 146 147 if (sex.equals("Female")) { 148 //女性 149 tv_info.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.female), null, null, null); 150 } else { 151 //男性 152 tv_info.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.male), null, null, null); 153 } 154 // 經過cache機制將View保存爲Bitmap 155 tv_info.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 156 tv_info.layout(0, 0, tv_info.getMeasuredWidth(), tv_info.getMeasuredHeight()); 157 tv_info.buildDrawingCache(); 158 tv_info.setDrawingCacheEnabled(true); 159 Bitmap bitmap = Bitmap.createBitmap(tv_info.getDrawingCache()); 160 tv_info.destroyDrawingCache(); 161 162 163 return bitmap; 164 } 165 }; 166 167 @Override 168 protected void onCreate(Bundle savedInstanceState) { 169 super.onCreate(savedInstanceState); 170 setContentView(R.layout.activity_main); 171 // 對控件進行初始化操做 172 initViews(); 173 // 對控件進行監聽操做 174 initActions(); 175 } 176 177 private void initActions() { 178 mGetImage.setOnClickListener(this); 179 mDoAction.setOnClickListener(this); 180 } 181 182 private void initViews() { 183 mTip = (TextView) findViewById(R.id.tv_tip); 184 mGetImage = (Button) findViewById(R.id.bt_getImage); 185 mDoAction = (Button) findViewById(R.id.bt_doAction); 186 mImageView = (ImageView) findViewById(R.id.iv_image); 187 mView=findViewById(R.id.fl_view); 188 mDialog = new ProgressDialog(MainActivity.this); 189 mDialog.setMessage("系統檢測識別中,請稍後.."); 190 191 } 192 193 @Override 194 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 195 super.onActivityResult(requestCode, resultCode, intent); 196 if (requestCode == REQUEST_CODE) { 197 if (intent != null) { 198 Uri uri = intent.getData(); 199 Cursor cursor = getContentResolver().query(uri, null, null, null, null); 200 if (cursor.moveToFirst()) { 201 int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 202 mPicStr = cursor.getString(index); 203 cursor.close(); 204 205 // 根據獲取到的圖片路徑,獲取圖片並進行圖片壓縮 206 BitmapFactory.Options options = new BitmapFactory.Options(); 207 // 當Options的inJustDecodeBounds屬性設置爲true時,不會顯示圖片,只會返回該圖片的具體數據 208 options.inJustDecodeBounds = true; 209 210 // 根據所選實際的寬高計算壓縮比例並將圖片壓縮 211 BitmapFactory.decodeFile(mPicStr, options); 212 Double reSize = Math.max(options.outWidth * 1.0 / 1024f, options.outHeight * 1.0 / 1024f); 213 options.inSampleSize = (int) Math.ceil(reSize); 214 options.inJustDecodeBounds = false; 215 216 // 建立Bitmap 217 mBitmap = BitmapFactory.decodeFile(mPicStr, options); 218 mImageView.setImageBitmap(mBitmap); 219 220 mTip.setText("點擊識別==》"); 221 mDoAction.setEnabled(true); 222 223 } 224 } 225 226 } 227 } 228 229 @Override 230 public void onClick(View v) { 231 switch (v.getId()) { 232 case R.id.bt_getImage: 233 // 點擊獲取圖片按鈕跳轉相冊選取圖片 234 Intent intent = new Intent(Intent.ACTION_PICK); 235 // 獲取手機圖庫信息 236 intent.setType("image/*"); 237 startActivityForResult(intent, REQUEST_CODE); 238 break; 239 case R.id.bt_doAction: 240 // 點擊識別按鈕進行圖片識別操做 241 mDialog.show(); 242 FaceHelper.uploadFaces(mBitmap, new FaceHelper.CallBack() { 243 244 @Override 245 public void success(JSONObject result) { 246 // 人臉識別成功,回調獲取數據 247 Message msg = Message.obtain(); 248 msg.what = SUCCESS; 249 msg.obj = result; 250 handler.sendMessage(msg); 251 } 252 253 @Override 254 public void error(FaceppParseException e) { 255 // 人臉識別失敗,回調獲取錯誤信息 256 Message msg = Message.obtain(); 257 msg.what = ERROR; 258 msg.obj = e.getErrorMessage(); 259 handler.sendMessage(msg); 260 } 261 }); 262 break; 263 264 default: 265 break; 266 } 267 268 } 269 270 }
因爲註釋很全,這裏我就挑幾個地方出來說,有其餘不清楚的朋友能夠在文章評論裏留言,我會答覆的。
一、當點擊選擇圖片按鈕時,經過Intent去訪問系統圖片,並根據返回的圖片進行圖片壓縮,由於官方文檔很明確的告訴咱們圖片大小不能超過1MB。
壓縮圖片有2種方式,一種是經過壓縮圖片的質量大小,另外一種則是根據比例來壓縮圖片大小(這裏我選擇第二種壓縮方式)。
根據獲得的Bitmap對象,咱們能夠先將inJustDecodeBounds先設置成true,這樣咱們就不會獲得具體的圖片,只會獲得該圖片的獲取到真實圖片的寬和高,而後咱們讓其去除以1024,取較大的一個數做爲壓縮比例,取整利用inSampleSize去對Bitmap進行壓縮,而後再把inJustDecodeBounds設置爲false。
二、當咱們提交圖片而且服務端向咱們返回數據時,因爲咱們是在子線程中去執行網絡請求的,因此這邊咱們經過Handler機制來傳輸數據,使得主線程能夠拿到數據並更新UI。
服務端給咱們返回的是一個Json的數據,因此咱們須要在這裏將它進行解析(關於Json解析,我這裏用到的是安卓官方自帶的Json幫助類,想用谷歌提供的Gson工具類也是能夠的,這邊是工具類使用方法:《Gson簡要使用筆記》)拿到咱們所須要的數據,這裏咱們須要的數據有
具體數值各表明什麼意思,這個你們查閱下官方給定的API文檔就能夠知道了,這裏就再也不詳細去寫了,而後根據所給的這些數值咱們會圈出所對應人臉的位置,方法能夠有不少,形狀也無所謂,這裏我給出的是矩形方案。
三、再來就是對隱藏佈局TextView進行顯示,因爲咱們能夠在服務端給咱們返回的數據裏知道性別年齡等數據,這裏就很好辦了。
這裏我經過經過cache機制將View轉爲Bitmap而後進行顯示,固然不止是這種方法,用自定義View去進行佈局也是能夠的,這裏你們靈活操做,我就很少說了。
到這裏,文章就結束了,你們有什麼疑問能夠在文章評論下面給我留言。
做者:李晨瑋
出處:http://www.cnblogs.com/lichenwei/本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。正在看本人博客的這位童鞋,我看你氣度不凡,談吐間隱隱有王者之氣,往後必有一番做爲!旁邊有「推薦」二字,你就順手把它點了吧,相得準,我分文不收;相不許,你也好回來找我!