歡迎光臨
每天分享高質量文章

Android人臉識別app——基於Face++,MVP+Retofit+RxJava+Dagger高度解耦

作者: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°!!!,這個賊坑啊,調了我好久,以為是自己手機的故障,後來網上查了一下,也請教了一下前輩,原來三星的手機都有這個問題,所以說我們要對檔案中取出來的照片進行一下處理。

/**
     * 讀取圖片的旋轉的角度
     *
     * @param path 圖片絕對路徑
     * @return 圖片的旋轉角度
     */

    public static int getBitmapDegree(String path) {
        int degree = 0;
        try {
            // 從指定路徑下讀取圖片,並獲取其EXIF資訊
            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;
    }

    /**
     * 將圖片按照某個角度進行旋轉
     *
     * @param bm     需要旋轉的圖片
     * @param degree 旋轉角度
     * @return 旋轉後的圖片
     */

    public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {
        Bitmap returnBm = null;

        // 根據旋轉角度,生成旋轉矩陣
        Matrix matrix = new Matrix();
        matrix.postRotate(degree);
        try {
            // 將原始圖片按照旋轉矩陣進行旋轉,並得到新的圖片
            returnBm = Bitmap.createBitmap(bm, 00, 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++ 的官方檔案。

/**
 * retrofit 面部識別請求的網路服務
 * @author chaochaowu
 */

public interface FaceppService {

    /**
     * @param apikey
     * @param apiSecret
     * @param imageBase64
     * @param returnLandmark
     * @param returnAttributes
     * @return
     */

    @POST("facepp/v3/detect")
    @FormUrlEncoded
    Observable 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() {
                    @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類中。

/**
 * 面部識別結果的bean
 * @author chaochaowu
 */

public class FaceppBean {
    /**
     * image_id : Dd2xUw9S/7yjr0oDHHSL/Q==
     * request_id : 1470472868,dacf2ff1-ea45-4842-9c07-6e8418cea78b
     * time_used : 752
     * faces : [{"landmark":{"mouth_upper_lip_left_contour2":{"y":185,"x":146},"contour_chin":{"y":231,"x":137},"right_eye_pupil":{"y":146,"x":205},"mouth_upper_lip_bottom":{"y":195,"x":159}},"attributes":{"gender":{"value":"Female"},"age":{"value":21},"glass":{"value":"None"},"headpose":{"yaw_angle":-26.625063,"pitch_angle":12.921974,"roll_angle":22.814377},"smile":{"threshold":30.1,"value":2.566890001296997}},"face_rectangle":{"width":140,"top":89,"left":104,"height":141},"face_token":"ed319e807e039ae669a4d1af0922a0c8"}]
     */


    private String image_id;
    private String request_id;
    private int time_used;
    private List faces;
    ...顯示部分內容

bean 類中有人臉識別得到的 性別、年齡、顏值、情緒等資訊,還有每張人臉在照片中的坐標位置。接下來的工作就是對這些資料進行處理。

獲取資訊後的資料處理

資料的處理主要就兩件事,一個是將資料以文字的形式展現,這個很簡單,就不介紹了,還有一個就是將人臉在照片中標示出來,這個需要對 BitMap 進行處理,利用資料中人臉在照片中的坐標位置,我們用方框將人臉標識出來。

private Bitmap markFacesInThePhoto(Bitmap bitmap, List 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

最後祝大家生活愉快~


●編號358,輸入編號直達本文

●輸入m獲取到文章目錄

推薦↓↓↓

Java程式設計

更多推薦18個技術類公眾微信

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂