手把手教你集成華為機器學習服務(ML Kit)人臉檢測功能

當給自己拍一張美美的自拍照時,卻發現照片中自己的臉不夠瘦、眼睛不夠大、表情不夠豐富可愛…如果此時能夠一鍵美顏瘦臉並且添加可愛的貼紙的話,是不是很棒?

當家裡的小孩觀看iPad螢幕時間過長或者眼睛離螢幕距離過近,家長沒能時刻關注到時,如果有一款可以實現parent control的應用,那是不是很方便?面對以上問題,華為機器學習服務(ML Kit)的人臉檢測功能輕鬆幫你搞定!

華為機器學習服務的人臉檢測功能可以對人臉多達855個關鍵點進行檢測,從而返回人臉的輪廓、眉毛、眼睛、鼻子、嘴巴、耳朵等部位的坐標以及人臉偏轉角度等資訊。集成人臉檢測服務後開發者可以根據這些資訊快速構建人臉美化的應用,或者在臉上加一些有趣可愛的貼紙元素,增加圖片的趣味性。除了這個強大的功能外,人臉檢測服務還可以識別人臉中包括眼睛是否睜開、是否戴眼鏡或帽子、性別、年齡、是否有鬍子等特徵。除此之外,人臉檢測功能可以識別人臉多達七種表情,包括微笑、無表情、憤怒、厭惡、驚恐、悲傷和驚訝。

在這裡插入圖片描述
「瘦臉大眼」開發實戰

1. 開發準備

詳細的準備步驟可以參考華為開發者聯盟

這裡列舉關鍵的開發步驟。

1.1 項目級gradle里配置Maven倉地址

buildscript {
    repositories {
            ...
        maven {url '//developer.huawei.com/repo/'}
    }
}
 dependencies {
                              ...
        classpath 'com.huawei.agconnect:agcp:1.3.1.300'
    }
allprojects {
    repositories {
            ...
        maven {url '//developer.huawei.com/repo/'}
    }
}

1.2 文件頭增加配置

集成SDK後,在文件頭添加配置

apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'

1.3 應用級gradle里配置SDK依賴

dependencies{ 
    // 引入基礎SDK
    implementation 'com.huawei.hms:ml-computer-vision-face:2.0.1.300'
    // 引入人臉輪廓+關鍵點檢測模型包
    implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:2.0.1.300'
    // 引入表情檢測模型包
    implementation 'com.huawei.hms:ml-computer-vision-face-emotion-model:2.0.1.300'
    // 引入特徵檢測模型包
    implementation 'com.huawei.hms:ml-computer-vision-face-feature-model:2.0.1.300'
}

1.4 將以下語句添加到AndroidManifest.xml文件中,用於自動更新機器學習模型

<manifest
    ...
    <meta-data
        android:name="com.huawei.hms.ml.DEPENDENCY" 
        android:value= "face"/>
    ...
</manifest>
 
1.3   申請攝影機許可權
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

2. 程式碼開發

2.1 使用默認參數配置,創建人臉分析器

analyzer =   MLAnalyzerFactory.getInstance().getFaceAnalyzer();
 
2.2  通過android.graphics.Bitmap創建MLFrame對象用於分析器檢測圖片
MLFrame frame = MLFrame.fromBitmap(bitmap);
 
2.3  調用「asyncAnalyseFrame」方法進行人臉檢測
Task<List<MLFace>> task = analyzer.asyncAnalyseFrame(frame);
task.addOnSuccessListener(new OnSuccessListener<List<MLFace>>() {
     @Override
     public void onSuccess(List<MLFace> faces) {
         // 檢測成功,獲取臉部關鍵點資訊。
     }
 }).addOnFailureListener(new OnFailureListener() {
     @Override
     public void onFailure(Exception e) {
         // 檢測失敗。
    }
 });

2.4 通過進度條進行不同程度的大眼瘦臉處理。分別調用magnifyEye方法和smallFaceMesh方法實現大眼演算法和瘦臉演算法

private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        switch (seekBar.getId()) {
            case R.id.seekbareye: // 當大眼進度條變化時,…
            case R.id.seekbarface: // 當瘦臉進度條變化時,…
        }
    }
}
2.5 檢測完成,釋放分析器
try {
    if (analyzer != null) {
        analyzer.stop();
    }
} catch (IOException e) {
    Log.e(TAG, "e=" + e.getMessage());
}

Demo效果

在這裡插入圖片描述

「有趣可愛貼紙」開發實戰

開發前準備

在項目級gradle里添加華為maven倉

打開AndroidStudio項目級build.gradle文件

在這裡插入圖片描述

增量添加如下maven地址:

buildscript {
     {        
        maven {url '//developer.huawei.com/repo/'}
    }    
 }
 allprojects {
    repositories {       
        maven { url '//developer.huawei.com/repo/'}
    }
 }

在應用級的build.gradle裡面加上SDK依賴

在這裡插入圖片描述

// Face detection SDK.
 implementation 'com.huawei.hms:ml-computer-vision-face:2.0.1.300'
 // Face detection model.
 implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:2.0.1.300'

在AndroidManifest.xml文件裡面申請相機、訪問網路和存儲許可權

<!--相機許可權--> 
 <uses-feature android:name="android.hardware.camera" />
 <uses-permission android:name="android.permission.CAMERA" />
 <!--寫許可權--> 
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <!--讀許可權--> 
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

程式碼開發關鍵步驟

設置人臉檢測器

MLFaceAnalyzerSetting detectorOptions;
 detectorOptions = new MLFaceAnalyzerSetting.Factory()
        .setFeatureType(MLFaceAnalyzerSetting.TYPE_UNSUPPORT_FEATURES)
        .setShapeType(MLFaceAnalyzerSetting.TYPE_SHAPES)
        .allowTracing(MLFaceAnalyzerSetting.MODE_TRACING_FAST)
        .create();
 detector = MLAnalyzerFactory.getInstance().getFaceAnalyzer(detectorOptions);

這裡我們通過相機回調拿到相機幀數據,並通過調用人臉檢測器拿到人臉輪廓點後寫入FacePointEngine供貼紙濾鏡使用.

@Override
 public void onPreviewFrame(final byte[] imgData, final Camera camera) {
    int width = mPreviewWidth;
    int height = mPreviewHeight;
 
    long startTime = System.currentTimeMillis();
    //設置前後攝方向一致
    if (isFrontCamera()){
        mOrientation = 0;
    }else {
        mOrientation = 2;
    }
    MLFrame.Property property =
            new MLFrame.Property.Creator()
                    .setFormatType(ImageFormat.NV21)
                    .setWidth(width)
                    .setHeight(height)
                    .setQuadrant(mOrientation)
                    .create();
 
    ByteBuffer data = ByteBuffer.wrap(imgData);
    // 調用人臉檢測介面
    SparseArray<MLFace> faces = detector.analyseFrame(MLFrame.fromByteBuffer(data,property));
    //判斷是否獲取到人臉資訊
    if(faces.size()>0){
        MLFace mLFace = faces.get(0);
        EGLFace EGLFace = FacePointEngine.getInstance().getOneFace(0);
        EGLFace.pitch = mLFace.getRotationAngleX();
        EGLFace.yaw = mLFace.getRotationAngleY();
        EGLFace.roll = mLFace.getRotationAngleZ() - 90;
        if (isFrontCamera())
            EGLFace.roll = -EGLFace.roll;
        if (EGLFace.vertexPoints == null) {
            EGLFace.vertexPoints = new PointF[131];
        }
        int index = 0;
        // 獲取一個人的輪廓點坐標並轉化到openGL歸一化坐標系下的浮點值
        for (MLFaceShape contour : mLFace.getFaceShapeList()) {
            if (contour == null) {
                continue;
            }
            List<MLPosition> points = contour.getPoints();
 
            for (int i = 0; i < points.size(); i++) {
                MLPosition point = points.get(i);
                float x = ( point.getY() / height) * 2 - 1;
                float y = ( point.getX() / width ) * 2 - 1;
                if (isFrontCamera())
                    x = -x;
                PointF Point = new PointF(x,y);
                EGLFace.vertexPoints[index] = Point;
                index++;
            }
        }
        // 插入人臉對象
        FacePointEngine.getInstance().putOneFace(0, EGLFace);
        // 設置人臉個數
        FacePointEngine.getInstance().setFaceSize(faces!= null ? faces.size() : 0);
    }else{
        FacePointEngine.getInstance().clearAll();
    }
    long endTime = System.currentTimeMillis();
    Log.d("TAG","Face detect time: " + String.valueOf(endTime - startTime));
 }

ML kit介面返回的人臉輪廓點情況如圖所示:

在這裡插入圖片描述

介紹如何設計貼紙,首先看一下貼紙數JSON數據定義介紹如何設計貼紙,首先看一下貼紙數JSON數據定義

public class FaceStickerJson {
 
    public int[] centerIndexList;   // 中心坐標索引列表,有可能是多個關鍵點計算中心點
    public float offsetX;           // 相對於貼紙中心坐標的x軸偏移像素
    public float offsetY;           // 相對於貼紙中心坐標的y軸偏移像素
    public float baseScale;         // 貼紙基準縮放倍數
    public int startIndex;          // 人臉起始索引,用於計算人臉的寬度
    public int endIndex;            // 人臉結束索引,用於計算人臉的寬度
    public int width;               // 貼紙寬度
    public int height;              // 貼紙高度
    public int frames;              // 貼紙幀數
    public int action;              // 動作,0表示默認顯示,這裡用來處理貼紙動作等
    public String stickerName;      // 貼紙名稱,用於標記貼紙所在文件夾以及png文件的
    public int duration;            // 貼紙幀顯示間隔
    public boolean stickerLooping;  // 貼紙是否循環渲染
    public int maxCount;            // 最大貼紙渲染次數
 ...
 }

我們製作貓耳貼紙JSON文件,通過人臉索引找到眉心84號點和鼻尖85號點分別貼上耳朵和鼻子,然後把它和圖片都放在assets目錄下

{
    "stickerList": [{
        "type": "sticker",
        "centerIndexList": [84],
        "offsetX": 0.0,
        "offsetY": 0.0,
        "baseScale": 1.3024,
        "startIndex": 11,
        "endIndex": 28,
        "width": 495,
        "height": 120,
        "frames": 2,
        "action": 0,
        "stickerName": "nose",
        "duration": 100,
        "stickerLooping": 1,
        "maxcount": 5
    }, {
    "type": "sticker",
        "centerIndexList": [83],
        "offsetX": 0.0,
        "offsetY": -1.1834,
        "baseScale": 1.3453,
        "startIndex": 11,
        "endIndex": 28,
        "width": 454,
        "height": 150,
        "frames": 2,
        "action": 0,
        "stickerName": "ear",
        "duration": 100,
        "stickerLooping": 1,
        "maxcount": 5
    }]
 }

這裡渲染貼紙紋理我們使用GLSurfaceView,使用起來比TextureView簡單, 首先在onSurfaceChanged實例化貼紙濾鏡,傳入貼紙路徑並開啟相機

@Override
 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
 
    GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    mTextures = new int[1];
    mTextures[0] = OpenGLUtils.createOESTexture();
    mSurfaceTexture = new SurfaceTexture(mTextures[0]);
    mSurfaceTexture.setOnFrameAvailableListener(this);
 
    //將samplerExternalOES 輸入到紋理中
    cameraFilter = new CameraFilter(this.context);
 
    //設置assets目錄下人臉貼紙路徑
    String folderPath ="cat";
    stickerFilter = new FaceStickerFilter(this.context,folderPath);
 
    //創建螢幕濾鏡對象
    screenFilter = new BaseFilter(this.context);
 
    facePointsFilter = new FacePointsFilter(this.context);
    mEGLCamera.openCamera();
 }

然後在onSurfaceChanged初始化貼紙濾鏡

@Override
 public void onSurfaceChanged(GL10 gl, int width, int height) {
    Log.d(TAG, "onSurfaceChanged. width: " + width + ", height: " + height);
    int previewWidth = mEGLCamera.getPreviewWidth();
    int previewHeight = mEGLCamera.getPreviewHeight();
    if (width > height) {
        setAspectRatio(previewWidth, previewHeight);
    } else {
        setAspectRatio(previewHeight, previewWidth);
    }
    // 設置畫面的大小,創建FrameBuffer,設置顯示尺寸
    cameraFilter.onInputSizeChanged(previewWidth, previewHeight);
    cameraFilter.initFrameBuffer(previewWidth, previewHeight);
    cameraFilter.onDisplaySizeChanged(width, height);
 
    stickerFilter.onInputSizeChanged(previewHeight, previewWidth);
    stickerFilter.initFrameBuffer(previewHeight, previewWidth);
    stickerFilter.onDisplaySizeChanged(width, height);
 
    screenFilter.onInputSizeChanged(previewWidth, previewHeight);
    screenFilter.initFrameBuffer(previewWidth, previewHeight);
    screenFilter.onDisplaySizeChanged(width, height);
 
    facePointsFilter.onInputSizeChanged(previewHeight, previewWidth);
    facePointsFilter.onDisplaySizeChanged(width, height);
    mEGLCamera.startPreview(mSurfaceTexture);
 }

最後通過onDrawFrame把貼紙繪製到螢幕

@Override
 public void onDrawFrame(GL10 gl) {
    int textureId;
    // 清除螢幕和深度快取
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
    //更新獲取一張圖
    mSurfaceTexture.updateTexImage();
    //獲取SurfaceTexture轉化矩陣
    mSurfaceTexture.getTransformMatrix(mMatrix);
    //設置相機顯示轉化矩陣
    cameraFilter.setTextureTransformMatrix(mMatrix);
 
    //繪製相機紋理
    textureId = cameraFilter.drawFrameBuffer(mTextures[0],mVertexBuffer,mTextureBuffer);
    //繪製貼紙紋理
    textureId = stickerFilter.drawFrameBuffer(textureId,mVertexBuffer,mTextureBuffer);
    //繪製到螢幕
    screenFilter.drawFrame(textureId , mDisplayVertexBuffer, mDisplayTextureBuffer);
    if(drawFacePoints){
        facePointsFilter.drawFrame(textureId, mDisplayVertexBuffer, mDisplayTextureBuffer);
    }
 }

這樣我們的貼紙就畫到人臉上了.

Demo效果

在這裡插入圖片描述

欲了解更多詳情,請參閱:華為開發者聯盟官網、開發指導文檔

參與開發者討論請到Reddit: //www.reddit.com/r/HuaweiDevelopers/

下載demo和示例程式碼請到Github://github.com/HMS-Core

解決集成問題請到Stack Overflow://stackoverflow.com/questions/tagged/huawei-mobile-services?tab=Newest

Tags: