作者:reggie1996
連結:https://www.jianshu.com/p/920b9c525d2f
最近公司項目比較空,花了點時間寫了個人臉識別的app,可以查看你的性別、年齡、顏值、情緒等信息,利用的是 Face++ 的人臉識別API。本項目採用了 MVP 的架構,使用了 Retrofit、RxJava、Dagger、EventBus 等框架進行開發和解耦,利用 MaterialDesign 進行UI上的布局設計。
主要的功能就是拍照,然後將照片傳至 Face++ 伺服器,進行人臉識別,獲取返回的信息,對信息進行處理。將人臉在照片上標出,並將信息展示出來。
話不多說,先來看一下 app 的效果(吳彥祖還是帥啊,哈哈)。
面部識別主界面面部識別詳情界面多人臉識別項目我已經放在 github 上,clone 下來即可編譯運行。github 地址: reggie1996 - FaceDetect 。下面文章主要介紹的是本項目的開發過程和碰到的坑。
過程項目的整個流程很簡單無非就是三步,拍照片,傳照片獲取數據,然後對數據進行處理展示。
拍照獲取照片
拍照需要獲取系統權限,我封裝了一個方法,來判斷App是否有拍照相關的權限,如果沒有就去動態請求權限,並返回 false,如果有就返回 true。
public static boolean checkAndRequestPermission(Context context, int requestCode) {
if (context.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| context.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
((Activity) context).requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, requestCode);
return false;
}else {
return true;
}
}
獲取到拍照權限後就可以拍照了,但是拍照得到的照片我們需要通過 FileProvider 獲取。FileProvider 相關的內容就不作介紹了,Android 7.0 之後都得用這個。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.chaochaowu.facedetect.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
拍照之後從文件中讀取照片,我們可以得到一個 BitMap 對象。這裡就有一個很大的坑,如果手機是三星的話,照片從文件裡讀出來,最後得到的照片會被旋轉 90°!!!,這個賊坑啊,調了我好久,以為是自己手機的故障,後來網上查了一下,也請教了一下前輩,原來三星的手機都有這個問題,所以說我們要對文件中取出來的照片進行一下處理。
public static int getBitmapDegree(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
default:
degree = 0;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {
Bitmap returnBm = null;
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
} catch (OutOfMemoryError | Exception e) {
e.printStackTrace();
}
if (returnBm == null) {
returnBm = bm;
}
if (bm != returnBm) {
bm.recycle();
}
return returnBm;
}
封裝了兩個方法,依次調用可以解決三星手機照片的問題。兩個方法主要的工作就是,得到取出來的照片被旋轉的角度,然後再將角度旋轉回去,就可以得到原來的照片。因為並不是所有的手機在獲取照片時,照片都會被旋轉,所以得先判斷一下照片有沒有被旋轉,再決定是否需要將它旋轉調整。
行,這樣最後就獲得到了正確的 BitMap 照片,可以進行下一步了。
傳照片獲取數據
傳照片獲取數據,主要是運用了 Retrofit 和 RxJava 的封裝。請求的參數可以參考 Face++ 的官方文檔。
public interface FaceppService {
@POST("facepp/v3/detect")
@FormUrlEncoded
Observable<FaceppBean> getFaceInfo(@Field("api_key") String apikey,
@Field("api_secret") String apiSecret,
@Field("image_base64") String imageBase64,
@Field("return_landmark") int returnLandmark,
@Field("return_attributes") String returnAttributes);
}
照片需要進行 base64 轉碼後上傳至伺服器,封裝了一個照片base64轉碼方法。
public static String base64(Bitmap bitmap){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bytes = baos.toByteArray();
return Base64.encodeToString(bytes, Base64.DEFAULT);
}
處理完成之後就可以進行網絡請求獲取數據。
@Override
public void getDetectResultFromServer(final Bitmap photo) {
String s = Utils.base64(photo);
faceppService.getFaceInfo(BuildConfig.API_KEY, BuildConfig.API_SECRET, s, 1, "gender,age,smiling,emotion,beauty")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<FaceppBean>() {
@Override
public void onSubscribe(Disposable d) {
mView.showProgress();
}
@Override
public void onNext(FaceppBean faceppBean) {
handleDetectResult(photo,faceppBean);
}
@Override
public void onError(Throwable e) {
mView.hideProgress();
}
@Override
public void onComplete() {
mView.hideProgress();
}
});
}
Face++ 伺服器會對我們上傳的照片進行處理,分析照片中的人臉信息,並以 json 形式返回,返回的數據將被放入我們定義的bean類中。
public class FaceppBean {
private String image_id;
private String request_id;
private int time_used;
private List<FacesBean> faces;
...顯示部分內容
bean 類中有人臉識別得到的 性別、年齡、顏值、情緒等信息,還有每張人臉在照片中的坐標位置。接下來的工作就是對這些數據進行處理。
獲取信息後的數據處理
數據的處理主要就兩件事,一個是將數據以文字的形式展現,這個很簡單,就不介紹了,還有一個就是將人臉在照片中標示出來,這個需要對 BitMap 進行處理,利用數據中人臉在照片中的坐標位置,我們用方框將人臉標識出來。
private Bitmap markFacesInThePhoto(Bitmap bitmap, List<FaceppBean.FacesBean> faces) {
Bitmap tempBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(tempBitmap);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
for (FaceppBean.FacesBean face : faces) {
FaceppBean.FacesBean.FaceRectangleBean faceRectangle = face.getFace_rectangle();
int top = faceRectangle.getTop();
int left = faceRectangle.getLeft();
int height = faceRectangle.getHeight();
int width = faceRectangle.getWidth();
canvas.drawRect(left, top, left + width, top + height, paint);
}
return tempBitmap;
}
封裝了一個方法,運用 Canvas 在照片上進行繪製,因為照片中的人臉可能不止一個,所以用for循環遍歷。獲取人臉在照片中的坐標,利用人臉左上角的坐標以及人臉的寬高,在照片中繪製一個方框將人臉標出。
剩餘信息我這邊採用 RecyclerView 來展示。左右滑動可以查看每張人臉的信息。RecyclerView 的 item 上展示的是簡要信息,可以點擊 item 進入詳情頁面查看面部識別的詳細信息。RecyclerView 以及詳情界面的實現就不作介紹了,很基本的操作。我這邊也就只使用了 SharedElement 讓界面切換看起來舒服一點。具體的實現可以看 github 上的代碼。
其他就沒什麼操作了,還可以看一下我的項目架構。由於用了各種框架進行解耦,所以代碼文件數量變多了,但是單個文件中的代碼會變少一點,清晰易讀一點,這也是解耦的目的,也方便之後的維護。
具體實現的細節可以看 github 上面的代碼~
最後寫完這個APP後,我一直在思考一個問題,APP給吳彥祖的顏值打分80多,那100分的顏值會是怎樣?
感興趣的朋友可以把代碼下載下來玩一下,測一下自己或者是朋友的顏值,嘿嘿。github 地址:https://github.com/reggie1996/FaceDetect
【點擊成為Java大神】