3

To implement face detection expressed in this blog with Camera X and ML Kit, with custom overlay. That enables the shutter button only when the face is in the bounding box.

Expected result video and Starter source code with CameraX included

https://medium.com/onfido-tech/face-detection-and-tracking-on-android-using-ml-kit-part-1-fbee4200d174

enter image description here

Following the Android camera X code labs, I could capture images and video. Though ML Kit bounding box implementation requires a Graphic Overlay.

class OverlayPosition(var x: Float, var y: Float, var r: Float)

class OverlayView @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint: Paint = Paint()
    private var holePaint: Paint = Paint()
    private var bitmap: Bitmap? = null
    private var layer: Canvas? = null
    private var border: Paint = Paint()

    //position of hole
    var holePosition: OverlayPosition = OverlayPosition(0.0f, 0.0f, 0.0f)
        set(value) {
            field = value
            //redraw
            this.invalidate()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (bitmap == null) {
            configureBitmap()
        }


        //draw background
        layer?.drawRect(0.0f, 0.0f, width.toFloat(), height.toFloat(), paint)
        //draw hole
        layer?.drawCircle((width / 2).toFloat(), (height / 4).toFloat(), 400f, border)
        layer?.drawCircle((width / 2).toFloat(), (height / 4).toFloat(), 400f, holePaint)
        //draw bitmap
        canvas.drawBitmap(bitmap!!, 0.0f, 0.0f, paint);
    }

    private fun configureBitmap() {
        //create bitmap and layer
        bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        layer = Canvas(bitmap!!)
    }

    init {
        //configure background color
        val backgroundAlpha = 0.8
        paint.color = ColorUtils.setAlphaComponent(context?.let {
            ContextCompat.getColor(
                it,
                R.color.overlay
            )
        }!!, (255 * backgroundAlpha).toInt())

        border.color = Color.parseColor("#FFFFFF")
        border.strokeWidth = 30F
        border.style = Paint.Style.STROKE
        border.isAntiAlias = true
        border.isDither = true

        //configure hole color & mode
        holePaint.color = ContextCompat.getColor(context, android.R.color.transparent)

        holePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
    }
}
Sharan
  • 1,055
  • 3
  • 21
  • 38

2 Answers2

2

The CameraX team provides a GitHub code sample on how to detect objects with ML Kit and draw overlay on Preview. Please take a look and let me know if this works for you. The API is called MLKitAnalyzer.

Xi 张熹
  • 10,492
  • 18
  • 58
  • 86
  • 1
    I'm unsure how to use MLKitAnalyzer to draw an overlay for a front-facing lens. https://drive.google.com/file/d/1rddiuQOv6TaCyDAS1dzrmTceKd6OXX-r/view?usp=share_link – Sharan Nov 12 '22 at 03:02
  • 1
    I've attached a started code with Camera X front-facing lens with image and video capture to test the implementation. – Sharan Nov 12 '22 at 03:04
  • 2
    If you use CameraController, use the setCameraSelector API to switch back/front camera: https://developer.android.com/reference/androidx/camera/view/CameraController#setCameraSelector(androidx.camera.core.CameraSelector) – Xi 张熹 Nov 13 '22 at 04:51
  • Can you help me with the implementation? – Sharan Nov 14 '22 at 14:34
0

In the layout file, we will define it as follows:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.camera.view.PreviewView
            android:id="@+id/viewFinder"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:scaleType="fillStart" />

        <com.example.facedetection.utils.OvalOverlayView
            android:id="@+id/oval_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>
// other codes
</androidx.constraintlayout.widget.ConstraintLayout>

And here is the definition of the custom view Oval:

class OvalOverlayView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private val paint = Paint()
private val ovalRect = RectF()
private val blurPaint = Paint()
private val ovalPath = Path()

init {
    paint.apply {
        color = Color.RED
        style = Paint.Style.STROKE
        strokeWidth = 15f
    }
    blurPaint.apply {
        isAntiAlias = true
        color = Color.BLACK
        style = Paint.Style.FILL
        alpha = 128
    }

}

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

    val centerX = width / 2
    val centerY = height / 2.8
    val radiusX = width / 2.8
    val radiusY = height / 3.4

    ovalRect.set(
        (centerX - radiusX).toFloat(),
        (centerY - radiusY).toFloat(),
        (centerX + radiusX).toFloat(),
        (centerY + radiusY).toFloat()
    )

    // The following code adjusts the outer part of the oval shape to be slightly blurred and the inner part to be transparent.
    ovalPath.addOval(ovalRect, Path.Direction.CCW)
    if (Build.VERSION.SDK_INT >= 26) {
        canvas?.clipOutPath(ovalPath)
    } else {
        @Suppress("DEPRECATION") canvas?.clipPath(ovalPath, Region.Op.DIFFERENCE)
    }
    canvas?.drawPaint(blurPaint)
}
}

Next, you pass the received image to the image analyzer (here I'm using MediaPipe, but ML Kit would be similar :)) ). After obtaining the facial landmarks, you can select four facial landmarks (such as left/right cheeks, forehead, and chin). If these four landmarks are within the oval shape (you can check using RectF.contains), it can be determined that the face is placed within the oval shape.

I have also developed a similar application, and you can refer to it in this repository.

This is my first answer on the forum, and hopefully it won't be considered a low-quality answer.

  • Answer needs supporting information Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](https://stackoverflow.com/help/how-to-answer). – moken Jul 14 '23 at 08:55