HMS ML Kitの顔検出機能の実装入門とFirebase ML Kitの顔検出機能との比較

# HMSのML Kitについて
HMS ML Kitはファーウェイが提供しているAndroid用の機械学習キットです。ARMのAndroid端末であれば、ファーウェイの端末じゃなくても使えます。機能についてはすでに[HMSとFirebaseの機械学習（ML Kit）の比較](https://qiita.com/Rei_2020/items/3960530fd554af96b242)で紹介したので、そちらをご参照いただければ幸いです。

# HMSの顔検出とFirebaseの顔検出の比較
最近の顔検出SDKは単なる顔を検出し、その領域を特定するだけでなく、目や鼻の位置を特定したり、笑顔を検出したりもします。当然、SDKによって、含まれている機能が異なるので、そこをしっかり把握したうえで、SDKを選定したほうがおすすめです。

HMS ML KitとFirebase ML KitはAndroidで簡単に導入できる顔検出SDKです。それらの機能を次のようにまとめました。

※Googleは2020年6月3日にFirebaseに依存しないGoogle ML Kitをリリースしましたが、機能的にFirebaseのML Kitと同じです。

### 検出対象
|機能|HMS ML Kit|Firebase ML Kit|
|:---:|:---:|:---:|
|顔検出（静止画）|〇|〇|
|顔検出（動画）|〇|〇|

### 顔のパーツ
|機能|HMS ML Kit|Firebase ML Kit|
|:---:|:---:|:---:|
|顔の領域（長方形）|〇|〇|
|顔の輪郭|〇|〇|
|左目の輪郭|〇|〇|
|右目の輪郭|〇|〇|
|左眉の輪郭（上部）|〇|〇|
|左眉の輪郭（下部）|〇|〇|
|右眉の輪郭（上部）|〇|〇|
|右眉の輪郭（下部）|〇|〇|
|上唇の輪郭（上部）|〇|〇|
|上唇の輪郭（下部）|〇|〇|
|下唇の輪郭（上部）|〇|〇|
|下唇の輪郭（下部）|〇|〇|
|鼻柱の位置|〇|〇|
|鼻の下部の輪郭|〇|〇|
|顔の三次元向き|〇|〇|

### 表情
|機能|HMS ML Kit|Firebase ML Kit|
|:---:|:---:|:---:|
|笑う|〇|〇|
|怒る|〇|×|
|嫌がる|〇|×|
|怖がる|〇|×|
|悲しむ|〇|×|
|驚く|〇|×|
|無表情|〇|×|

### 顔の特徴
|機能|HMS ML Kit|Firebase ML Kit|
|:---:|:---:|:---:|
|左目の開き具合|〇|〇|
|右目の開き具合|〇|〇|
|メガネの有無|〇|×|
|帽子の有無|〇|×|
|ひげの有無|〇|×|
|性別|〇|×|
|年齢|〇|×|

HMS ML Kitのほうが機能数が多いです。

# HMSの顔検出の実装（ステップ１：カメラ関連の実装）
[Androidカメラのプレビュー表示（Camera API + SurfaceView）](https://qiita.com/Rei_2020/items/b1bb2e9fbec7d9cc0830)で紹介した実装の一部を利用します。

## カメラのパーミッション
### AndroidManifest.xml
カメラを使うのに、カメラのパーミッションが必要です。AndroidManifest.xmlにandroid.permission.CAMERAを追加します。

```xml:AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxx">
    ...
    <uses-permission android:name="android.permission.CAMERA" />
    ...
</manifest>
```

### パーミッション要請
ユーザーにカメラ権限を要請します。

```kotlin:MyActivity.kt
// カメラ権限があるかどうかを確認し、ある場合はカメラを起動し、ない場合はユーザーに要請します。
private fun checkAndAskCameraPermission(context: Context) {
	if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
		// ユーザーにカメラ権限を要請
		requestPermissions(arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
	} else {
		// カメラ権限があるので、カメラを起動
		openCamera()
	} 
}

// カメラ権限要請のコールバック
override fun onRequestPermissionsResult(
		requestCode: Int,
		permissions: Array<String>, grantResults: IntArray
) {
	when (requestCode) {
		PERMISSION_REQUEST_CODE -> {
			if (permissions.isNotEmpty() && grantResults.isNotEmpty()) {
				val resultMap: MutableMap<String, Int> = mutableMapOf()
				for (i in 0..min(permissions.size - 1, grantResults.size - 1)) {
					resultMap[permissions[i]] = grantResults[i]
				}

				when {
					resultMap[Manifest.permission.CAMERA] == PackageManager.PERMISSION_GRANTED -> {
						// ユーザーがカメラ権限を許可したので、カメラを起動
						openCamera()
					}
				}
			}
		}
	}
}
```

### [注意点]：Surfaceサイズの調整
表示のアスペクト比を調整し、画面いっぱいに表示します。

```kotlin:MyActivity.kt
private var surfaceWidth = 1
private var surfaceHeight = 1

private fun updateSurfaceSize(view: View, width: Int, height: Int, surfaceWidth: Int, surfaceHeight: Int) {
	val param = view.layoutParams
	var newWidth = width
	var newHeight = height

	// [注意点]：端末が縦の場合、設定する横幅と縦幅は逆になる
	if (activity?.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
		newWidth = height
		newHeight = width
	}

	val scale1 = surfaceWidth / newWidth.toFloat()
	val scale2 = surfaceHeight / newHeight.toFloat()
	val scale = if (newWidth < surfaceWidth || newHeight < surfaceHeight) {
		max(scale1, scale2)
	} else if (newWidth < surfaceWidth) {
		scale1
	} else if (newHeight < surfaceHeight) {
		scale2
	} else {
		1.0f
	}

	// [注意点]：入力された横幅と縦幅を使う（SurfaceViewの幅と高さだと、アスペクト比が合わないことがある）
	param.width = (newWidth.toFloat() * scale).toInt()
	param.height = (newHeight.toFloat() * scale).toInt()
	view.layoutParams = param
}
```

# HMSの顔検出の実装（ステップ２：ML Kit関連の実装）

こちらはML Kit関連の実装になります。

## AppGallery Connectの作業
1. AppGallery Connectに入って、[My projects]を選びます。
2. リストから対象アプリに切り替えます。
3. [Project settings] -> [Manage APIs]に入って、ML Kitを有効にします。（デフォルトではすでに有効になっているはずです）

こちらもご参照ください。
[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/config-agc-0000001050990353](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/config-agc-0000001050990353)
[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/enable-service-0000001050038078](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/enable-service-0000001050038078)

## HMS SDKの導入
1. agconnect-services.jsonをプロジェクト内に配置します。
2. プロジェクトのbuild.gradleにSDKを追加します。
3. モジュールのbuild.gradleにSDKを追加します。

顔検出関連のライブラリはこちらです。

```gradle:build.gradle
    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'
```

こちらもご参照ください。
[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/add-appgallery-0000001050038080](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/add-appgallery-0000001050038080)
[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/config-maven-0000001050040031](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/config-maven-0000001050040031)
[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/face-sdk-0000001050038096](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/face-sdk-0000001050038096)

## AndroidManifest.xml
HMS ML Kitの顔検出機能の定義をAndroidManifest.xmlに追加しなければなりません。

```xml:AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxx">
    ...
    <meta-data
        android:name="com.huawei.hms.ml.DEPENDENCY"
        android:value= "face"/>
    ...
</manifest>
```

## レイアウト

顔のパーツなどを描画するため、Viewを継承したオーバーレイクラスを配置します。
オーバーレイに表示しない場合はオーバーレイクラスを追加不要です。

```xml:main_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:keepScreenOn="true">

        <SurfaceView
            android:id="@+id/surface_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            />

        <!-- オーバーレイのクラス -->
        <your.package.name.OverlayView
            android:id="@+id/overlay_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>
```

## カメラと顔検出の初期化

ML Kitのキーポイントが２つです。

* カメラである[LensEngine](https://developer.huawei.com/consumer/jp/doc/HMSCore-References-V5/lensengine-0000001050169367-V5)
* 画像解析処理である[MLAnalyzer&lt;T&gt;](https://developer.huawei.com/consumer/jp/doc/HMSCore-References-V5/mlanalyzer-0000001050169371-V5)

画像解析処理のMLAnalyzer&lt;T&gt;を作って、それをもとにカメラのLensEngineを作って、さらに解析結果のコールバックをMLAnalyzer&lt;T&gt;に設定すればよいです。

```kotlin:MainFragment.kt
private var faceAnalyzer: MLFaceAnalyzer? = null
private var lensEngine: LensEngine? = null

private fun openCamera() {
	// 顔分析オブジェクトを作成
	faceAnalyzer = MLAnalyzerFactory.getInstance().faceAnalyzer.apply {
		setTransactor(object : MLAnalyzer.MLTransactor<MLFace> {
			// 分析結果がtransactResultコールバックで渡される
			override fun transactResult(results: MLAnalyzer.Result<MLFace>?) {
				// このサンプルでは分析結果をオーバーレイに描画する
				binding.overlayView.setFaceResults(results)
			}

			override fun destroy() {
			}
		})
	}

	// ML Kit専用のカメラオブジェクトを作成
	// LensEngineの中身はCamera APIのラッパー
	lensEngine = LensEngine.Creator(context, faceAnalyzer)
		// フロントカメラ：LensEngine.FRONT_LENS
		// バックカメラ：LensEngine.BACK_LENS
		.setLensType(LensEngine.FRONT_LENS)
		// プレビューサイズ：1920x1920 - 320x320を設定する
		.applyDisplayDimension(1920, 1920)
		.applyFps(30.0f)
		.enableAutomaticFocus(true)
		.create()
		.apply {
			// カメラをSurfaceViewに出力
			run(surfaceHolder)
			// [カメラの出力]：SurfaceViewのアスペクト比とサイズを調整
			updateSurfaceSize(
				binding.surfaceView,
				this.displayDimension.width,
				this.displayDimension.height,
				surfaceWidth,
				surfaceHeight
			)
			// [顔検出結果の描画]：オーバーレイのアスペクト比とサイズの調整とカメラのパラメータの受け渡し
			updateOverlay(this)
		}
}
```

### オーバーレイのアスペクト比とサイズの調整とカメラのパラメータの受け渡し
オーバーレイのサイズをSurfaceViewのサイズと同じにします。描画するときに、カメラのサイズとカメラの種類が必要なので、ここで渡しておきます。

```kotlin:MainFragment.kt
private fun updateOverlay(lensEngine: LensEngine) {
	if (activity?.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
		binding.overlayView.setCameraWidth(lensEngine.displayDimension.height)
		binding.overlayView.setCameraHeight(lensEngine.displayDimension.width)
	} else {
		binding.overlayView.setCameraWidth(lensEngine.displayDimension.width)
		binding.overlayView.setCameraHeight(lensEngine.displayDimension.height)
	}

	binding.overlayView.layoutParams.width = binding.surfaceView.layoutParams.width
	binding.overlayView.layoutParams.height = binding.surfaceView.layoutParams.height
	binding.overlayView.setLensType(lensEngine.lensType)
}
```

### SurfaceView
```kotlin:MainFragment.kt
override fun onCreateView(
	inflater: LayoutInflater,
	container: ViewGroup?,
	savedInstanceState: Bundle?
): View? {
	binding = DataBindingUtil.inflate<MainFragmentBinding>(
		inflater,
		R.layout.main_fragment,
		container,
		false
	)
	binding.surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
		override fun surfaceCreated(holder: SurfaceHolder) {
			surfaceHolder = holder
			checkAndAskPermission()
		}

		override fun surfaceChanged(
			holder: SurfaceHolder,
			format: Int,
			width: Int,
			height: Int
		) {
			surfaceWidth = width
			surfaceHeight = height
			lensEngine?.let { lensEngine ->
				// [カメラの出力]：SurfaceViewのアスペクト比とサイズを調整
				updateSurfaceSize(
					binding.surfaceView,
					lensEngine.displayDimension.width,
					lensEngine.displayDimension.height,
					surfaceWidth,
					surfaceHeight
				)
				// [顔検出結果の描画]：オーバーレイのアスペクト比とサイズの調整とカメラのパラメータの受け渡し
				updateOverlay(lensEngine)
			}
		}

		override fun surfaceDestroyed(holder: SurfaceHolder) {
		}
	})

	return binding.root
}
```

### リソース解放
```kotlin:MainFragment.kt
override fun onStop() {
	super.onStop()
	faceAnalyzer?.let {
		try {
			it.stop()
		} catch (ioException: IOException) {
			ioException.printStackTrace()
		}
	}
	lensEngine?.let {
		it.release()
	}
}
```

### 分析結果の描画
顔検出結果はオーバーレイで描画します。ここでは注意点がいくつかあります。

<b>描画座標</b>
カメラのプレビューサイズとオーバーレイのサイズが一致しない場合、faceAnalyzerのコールバックから渡されたx座標をそのまま使うと、表示がずれてしまいます。

オーバーレイのx座標 = faceAnalyzerのx座標 x キャンバスの幅 / カメラの幅
オーバーレイのy座標 = faceAnalyzerのy座標 x キャンバスの高さ / カメラの高さ

<b>カメラの種類</b>
フロントカメラの場合、faceAnalyzerのx座標の起点は左ではなく、右からです。
フロントカメラの場合のオーバーレイのx座標 = View.width - (faceAnalyzerのx座標 x キャンバスの幅 / カメラの幅)


```kotlin:OverlayView.kt
class OverlayView : View {

    private val paint = Paint().apply {
        color = Color.RED
        style = Paint.Style.STROKE
        strokeWidth = 6.0f
    }

    private var faceResults: MLAnalyzer.Result<MLFace>? = null
    private var lensType = LensEngine.FRONT_LENS
    private var cameraWidth = 1
    private var cameraHeight = 1

    constructor(context: Context?): super(context)

    constructor(context: Context?, attrs: AttributeSet?): super(context, attrs)

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr)

    fun setFaceResults(results: MLAnalyzer.Result<MLFace>?) {
        faceResults = results
        invalidate()
    }

    fun setLensType(lensType: Int) {
        this.lensType = lensType
    }

    fun setCameraWidth(width: Int) {
        cameraWidth = width
    }

    fun setCameraHeight(height: Int) {
        cameraHeight = height
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        canvas?.let { canvas ->
            drawFaceResult(canvas, faceResults)
        }
    }

    private fun drawFaceResult(canvas: Canvas, results: MLAnalyzer.Result<MLFace>?) {
        val mlFaceList: SparseArray<MLFace> = results?.analyseList ?: return

        mlFaceList.forEach { key, value ->
            value.border?.let { border ->
                // 顔領域の描画
                canvas.drawRect(
                    translateX(canvas, border.left.toFloat()),
                    translateY(canvas, border.top.toFloat()),
                    translateX(canvas, border.right.toFloat()),
                    translateY(canvas, border.bottom.toFloat()),
                    paint)

                val x = translateX(canvas, if (lensType == LensEngine.FRONT_LENS) border.right.toFloat() else border.left.toFloat())
                var y = translateY(canvas, border.top.toFloat()) - 5.0f

                // 感情分析結果を描画
                value.emotions?.let { mlFaceEmotion ->
                    val map: MutableMap<String, Point> = mutableMapOf()
                    map[String.format("怒る:%.3f", mlFaceEmotion.angryProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())
                    map[String.format("嫌がる:%.3f", mlFaceEmotion.disgustProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())
                    map[String.format("怖がる:%.3f", mlFaceEmotion.fearProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())
                    map[String.format("無表情:%.3f", mlFaceEmotion.neutralProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())
                    map[String.format("悲しむ:%.3f", mlFaceEmotion.sadProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())
                    map[String.format("笑顔:%.3f", mlFaceEmotion.smilingProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())
                    map[String.format("驚く:%.3f", mlFaceEmotion.surpriseProbability)] = Point(x.toInt(), y.apply { y -= 50.0f }.toInt())

                    map.forEach { (string, point) ->
                        canvas.drawText(string, point.x.toFloat(), point.y.toFloat(), paint)
                    }
                }

                // 顔の特徴を描画
                value.features?.let { mlFaceFeature ->
                    if (mlFaceFeature.age >= 0) {
                        canvas.drawText(String.format("年齢:%d", mlFaceFeature.age), x, y.apply { y -= 50.0f }, paint)
                    }
                    if (mlFaceFeature.hatProbability >= 0.0f) {
                        canvas.drawText(String.format("帽子:%.3f", mlFaceFeature.hatProbability), x, y.apply { y -= 50.0f }, paint)
                    }
                    if (mlFaceFeature.leftEyeOpenProbability >= 0.0f) {
                        canvas.drawText(String.format("左目:%.3f", mlFaceFeature.leftEyeOpenProbability), x, y.apply { y -= 50.0f }, paint)
                    }
                    if (mlFaceFeature.rightEyeOpenProbability >= 0.0f) {
                        canvas.drawText(String.format("右目:%.3f", mlFaceFeature.rightEyeOpenProbability), x, y.apply { y -= 50.0f }, paint)
                    }
                    if (mlFaceFeature.moustacheProbability >= 0.0f) {
                        canvas.drawText(String.format("ひげ:%.3f", mlFaceFeature.moustacheProbability), x, y.apply { y -= 50.0f }, paint)
                    }
                    if (mlFaceFeature.sexProbability >= 0.0f) {
                        canvas.drawText(String.format("性別:%s、%.3f", if (mlFaceFeature.moustacheProbability <= 0.5f) "男" else "女", mlFaceFeature.moustacheProbability), x, y.apply { y -= 50.0f }, paint)
                    }
                    if (mlFaceFeature.sunGlassProbability >= 0.0f) {
                        canvas.drawText(String.format("メガネ:%.3f", mlFaceFeature.sunGlassProbability), x, y.apply { y -= 50.0f }, paint)
                    }
                }
            }

            // 顔の輪郭を描画
            value.faceShapeList.filter {
                it.faceShapeType == MLFaceShape.TYPE_FACE
            }.forEach { mlFaceShape ->
                mlFaceShape.points.forEach { point ->
                    canvas.drawPoint(translateX(canvas, point.x), translateY(canvas, point.y), paint)
                }
            }

            // 目の輪郭を描画
            value.faceShapeList.filter {
                it.faceShapeType == MLFaceShape.TYPE_LEFT_EYE || it.faceShapeType == MLFaceShape.TYPE_RIGHT_EYE
            }.forEach { mlFaceShape ->
                mlFaceShape.points.forEach { point ->
                    canvas.drawPoint(translateX(canvas, point.x), translateY(canvas, point.y), paint)
                }
            }

            // 眉の輪郭を描画
            value.faceShapeList.filter {
                it.faceShapeType == MLFaceShape.TYPE_TOP_OF_LEFT_EYEBROW
                        || it.faceShapeType == MLFaceShape.TYPE_BOTTOM_OF_LEFT_EYEBROW
                        || it.faceShapeType == MLFaceShape.TYPE_TOP_OF_RIGHT_EYEBROW
                        || it.faceShapeType == MLFaceShape.TYPE_BOTTOM_OF_RIGHT_EYEBROW
            }.forEach { mlFaceShape ->
                mlFaceShape.points.forEach { point ->
                    canvas.drawPoint(translateX(canvas, point.x), translateY(canvas, point.y), paint)
                }
            }

            // くちびるの輪郭を描画
            value.faceShapeList.filter {
                it.faceShapeType == MLFaceShape.TYPE_TOP_OF_UPPER_LIP
                        || it.faceShapeType == MLFaceShape.TYPE_BOTTOM_OF_UPPER_LIP
                        || it.faceShapeType == MLFaceShape.TYPE_TOP_OF_LOWER_LIP
                        || it.faceShapeType == MLFaceShape.TYPE_BOTTOM_OF_LOWER_LIP
            }.forEach { mlFaceShape ->
                mlFaceShape.points.forEach { point ->
                    canvas.drawPoint(translateX(canvas, point.x), translateY(canvas, point.y), paint)
                }
            }

            // 鼻の輪郭を描画
            value.faceShapeList.filter {
                it.faceShapeType == MLFaceShape.TYPE_BRIDGE_OF_NOSE || it.faceShapeType == MLFaceShape.TYPE_BOTTOM_OF_NOSE
            }.forEach { mlFaceShape ->
                mlFaceShape.points.forEach { point ->
                    canvas.drawPoint(translateX(canvas, point.x), translateY(canvas, point.y), paint)
                }
            }
        }
    }

    // [注意点]：カメラのプレビューサイズとオーバーレイのサイズが一致していない場合、
    // faceAnalyzerのコールバックから渡されたx座標をそのまま使うと、表示がずれてしまいます。
    // オーバーレイのx座標 = faceAnalyzerのx座標 x キャンバスの幅 / カメラの幅
    //
    // [注意点２]：フロントカメラの場合、faceAnalyzerのx座標の起点は左ではなく、右からです。
    // フロントカメラの場合のオーバーレイのx座標 = View.width - (faceAnalyzerのx座標 x キャンバスの幅 / カメラの幅)
    private fun translateX(canvas: Canvas, x: Float): Float {
        return if (lensType == LensEngine.FRONT_LENS) {
            width - x * canvas.width / cameraWidth
        } else {
            x * canvas.width / cameraWidth
        }
    }

    // [注意点]：カメラのプレビューサイズとオーバーレイのサイズが一致しない場合、
    // faceAnalyzerのコールバックから渡されたy座標をそのまま使うと、表示がずれてしまいます。
    // オーバーレイのy座標 = faceAnalyzerのy座標 x キャンバスの高さ / カメラの高さ
    private fun translateY(canvas: Canvas, y: Float): Float {
        return y * canvas.height / cameraHeight
    }
}
```

実装は以上になります。

# 最後
HMS ML Kitの顔検出機能がかなり強力です。また、FirebaseのML Kitよりも機能が多く、人のさまざまな感情も数値化でき、さらにメガネの有無も検出できるので、使い方によっていろいろなサービスが実現できそうです。しかも無料に使えます。みなさんもぜひお試しください。

# GitHub
* [https://github.com/Rei2020GitHub/MyPublicProject/tree/master/ml/FaceDetectionDemo](https://github.com/Rei2020GitHub/MyPublicProject/tree/master/ml/FaceDetectionDemo)

# APK
* [https://github.com/Rei2020GitHub/MyPublicProject/blob/master/APK/FaceDetectionDemo.apk](https://github.com/Rei2020GitHub/MyPublicProject/blob/master/APK/FaceDetectionDemo.apk)

# 参考
* HMS：[https://developer.huawei.com/consumer/jp/](https://developer.huawei.com/consumer/jp/)
* HMS ML Kitの紹介：[https://developer.huawei.com/consumer/jp/hms/huawei-mlkit](https://developer.huawei.com/consumer/jp/hms/huawei-mlkit)
* HMS ML Kitのドキュメント：[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/service-introduction-0000001050040017](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/service-introduction-0000001050040017)
* HMS ML Kitの顔検出の概要：[https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/face-detection-0000001050038170](https://developer.huawei.com/consumer/jp/doc/development/HMSCore-Guides/face-detection-0000001050038170)
* Huawei Developers：[https://forums.developer.huawei.com/forumPortal/en/home](https://forums.developer.huawei.com/forumPortal/en/home)
* Facebook Huawei Developersグループ：[https://www.facebook.com/Huaweidevs/](https://www.facebook.com/Huaweidevs/)