-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add face detection sample using CameraX + MLKit * Resize art * Update README.md
- Loading branch information
1 parent
1a294e8
commit 3729180
Showing
23 changed files
with
833 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
*.iml | ||
.gradle | ||
/local.properties | ||
/.idea/* | ||
.DS_Store | ||
/build | ||
/captures | ||
.externalNativeBuild | ||
.cxx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
apply plugin: "com.android.application" | ||
apply plugin: "kotlin-android" | ||
apply plugin: "kotlin-android-extensions" | ||
|
||
android { | ||
compileSdkVersion 30 | ||
buildToolsVersion "29.0.3" | ||
|
||
defaultConfig { | ||
applicationId "com.husaynhakeem.facedetectorsample" | ||
minSdkVersion 21 | ||
targetSdkVersion 30 | ||
versionCode 1 | ||
} | ||
|
||
// CameraX uses java8 features | ||
compileOptions { | ||
sourceCompatibility JavaVersion.VERSION_1_8 | ||
targetCompatibility JavaVersion.VERSION_1_8 | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" | ||
implementation "androidx.core:core-ktx:1.3.0" | ||
implementation "androidx.appcompat:appcompat:1.1.0" | ||
implementation "androidx.constraintlayout:constraintlayout:1.1.3" | ||
implementation "androidx.activity:activity-ktx:1.2.0-alpha06" | ||
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha06" | ||
|
||
// CameraX | ||
implementation "androidx.camera:camera-camera2:1.0.0-beta06" | ||
implementation "androidx.camera:camera-lifecycle:1.0.0-beta06" | ||
implementation "androidx.camera:camera-view:1.0.0-alpha13" | ||
|
||
// MLKit | ||
implementation "com.google.mlkit:face-detection:16.0.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.husaynhakeem.facedetectorsample"> | ||
|
||
<!-- For using the camera --> | ||
<uses-permission android:name="android.permission.CAMERA" /> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:label="@string/app_name" | ||
android:supportsRtl="true" | ||
android:theme="@style/AppTheme"> | ||
<activity android:name=".MainActivity"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
|
||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
</manifest> |
84 changes: 84 additions & 0 deletions
84
...ectorSample/app/src/main/java/com/husaynhakeem/facedetectorsample/AnalysisFaceDetector.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.husaynhakeem.facedetectorsample | ||
|
||
import android.annotation.SuppressLint | ||
import android.graphics.Rect | ||
import android.graphics.RectF | ||
import androidx.camera.core.ImageAnalysis | ||
import androidx.camera.core.ImageProxy | ||
import com.google.mlkit.vision.common.InputImage | ||
import com.google.mlkit.vision.face.Face | ||
import com.google.mlkit.vision.face.FaceDetection | ||
import com.google.mlkit.vision.face.FaceDetector | ||
|
||
/** CameraX Analyzer that wraps MLKit's FaceDetector. */ | ||
internal class AnalysisFaceDetector( | ||
private val previewWidth: Int, | ||
private val previewHeight: Int, | ||
private val isFrontLens: Boolean | ||
) : ImageAnalysis.Analyzer { | ||
|
||
private val faceDetector: FaceDetector = FaceDetection.getClient() | ||
|
||
/** Listener to receive callbacks for when faces are detected, or an error occurs. */ | ||
var listener: Listener? = null | ||
|
||
@SuppressLint("UnsafeExperimentalUsageError") | ||
override fun analyze(imageProxy: ImageProxy) { | ||
val image = imageProxy.image | ||
if (image == null) { | ||
imageProxy.close() | ||
return | ||
} | ||
|
||
val rotation = imageProxy.imageInfo.rotationDegrees | ||
val inputImage = InputImage.fromMediaImage(image, rotation) | ||
faceDetector | ||
.process(inputImage) | ||
.addOnSuccessListener { faces: List<Face> -> | ||
val listener = listener ?: return@addOnSuccessListener | ||
|
||
// In order to correctly display the face bounds, the orientation of the analyzed | ||
// image and that of the viewfinder have to match. Which is why the dimensions of | ||
// the analyzed image are reversed if its rotation information is 90 or 270. | ||
val reverseDimens = rotation == 90 || rotation == 270 | ||
val width = if (reverseDimens) imageProxy.height else imageProxy.width | ||
val height = if (reverseDimens) imageProxy.width else imageProxy.height | ||
|
||
val faceBounds = faces.map { it.boundingBox.transform(width, height) } | ||
listener.onFacesDetected(faceBounds) | ||
imageProxy.close() | ||
} | ||
.addOnFailureListener { exception -> | ||
listener?.onError(exception) | ||
imageProxy.close() | ||
} | ||
} | ||
|
||
private fun Rect.transform(width: Int, height: Int): RectF { | ||
val scaleX = previewWidth / width.toFloat() | ||
val scaleY = previewHeight / height.toFloat() | ||
|
||
// If the front camera lens is being used, reverse the right/left coordinates | ||
val flippedLeft = if (isFrontLens) width - right else left | ||
val flippedRight = if (isFrontLens) width - left else right | ||
|
||
// Scale all coordinates to match preview | ||
val scaledLeft = scaleX * flippedLeft | ||
val scaledTop = scaleY * top | ||
val scaledRight = scaleX * flippedRight | ||
val scaledBottom = scaleY * bottom | ||
return RectF(scaledLeft, scaledTop, scaledRight, scaledBottom) | ||
} | ||
|
||
/** | ||
* Interface to register callbacks for when the face detector provides detected face bounds, or | ||
* when it encounters an error. | ||
*/ | ||
internal interface Listener { | ||
/** Callback that receives face bounds that can be drawn on top of the viewfinder. */ | ||
fun onFacesDetected(faceBounds: List<RectF>) | ||
|
||
/** Invoked when an error is encounter during face detection. */ | ||
fun onError(exception: Exception) | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
FaceDetectorSample/app/src/main/java/com/husaynhakeem/facedetectorsample/CameraViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.husaynhakeem.facedetectorsample | ||
|
||
import android.app.Application | ||
import androidx.camera.lifecycle.ProcessCameraProvider | ||
import androidx.core.content.ContextCompat | ||
import androidx.lifecycle.AndroidViewModel | ||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import java.util.concurrent.ExecutionException | ||
|
||
/** ViewModel that provides a [ProcessCameraProvider] to interact with the camera. */ | ||
class CameraViewModel(application: Application) : AndroidViewModel(application) { | ||
|
||
private val _getProcessCameraProvider by lazy { | ||
MutableLiveData<ProcessCameraProvider>().apply { | ||
val cameraProviderFuture = ProcessCameraProvider.getInstance(getApplication()) | ||
cameraProviderFuture.addListener( | ||
Runnable { | ||
try { | ||
value = cameraProviderFuture.get() | ||
} catch (exception: ExecutionException) { | ||
throw IllegalStateException( | ||
"Failed to retrieve a ProcessCameraProvider instance", exception | ||
) | ||
} catch (exception: InterruptedException) { | ||
throw IllegalStateException( | ||
"Failed to retrieve a ProcessCameraProvider instance", exception | ||
) | ||
} | ||
}, | ||
ContextCompat.getMainExecutor(getApplication()) | ||
) | ||
} | ||
} | ||
val processCameraProvider: LiveData<ProcessCameraProvider> | ||
get() = _getProcessCameraProvider | ||
} |
32 changes: 32 additions & 0 deletions
32
...DetectorSample/app/src/main/java/com/husaynhakeem/facedetectorsample/FaceBoundsOverlay.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.husaynhakeem.facedetectorsample | ||
|
||
import android.content.Context | ||
import android.graphics.Canvas | ||
import android.graphics.Paint | ||
import android.graphics.RectF | ||
import android.util.AttributeSet | ||
import android.view.View | ||
import androidx.core.content.ContextCompat | ||
|
||
/** Overlay where face bounds are drawn. */ | ||
class FaceBoundsOverlay constructor(context: Context?, attributeSet: AttributeSet?) : | ||
View(context, attributeSet) { | ||
|
||
private val faceBounds: MutableList<RectF> = mutableListOf() | ||
private val paint = Paint().apply { | ||
style = Paint.Style.STROKE | ||
color = ContextCompat.getColor(context!!, android.R.color.black) | ||
strokeWidth = 10f | ||
} | ||
|
||
override fun onDraw(canvas: Canvas) { | ||
super.onDraw(canvas) | ||
faceBounds.forEach { canvas.drawRect(it, paint) } | ||
} | ||
|
||
fun drawFaceBounds(faceBounds: List<RectF>) { | ||
this.faceBounds.clear() | ||
this.faceBounds.addAll(faceBounds) | ||
invalidate() | ||
} | ||
} |
Oops, something went wrong.