2

I have following code in place

GraphicOverlay.kt

open class GraphicOverlay(context: Context?, attrs: AttributeSet?) :
    View(context, attrs) {

    private val lock = Any()
    private val graphics: MutableList<Graphic> = ArrayList()
    var mScale: Float? = null
    var mOffsetX: Float? = null
    var mOffsetY: Float? = null
    var cameraSelector: Int = CameraSelector.LENS_FACING_FRONT

    abstract class Graphic(private val overlay: GraphicOverlay) {

        abstract fun draw(canvas: Canvas?)

        fun calculateRect(height: Float, width: Float, boundingBoxT: Rect): RectF {

            // for land scape
            fun isLandScapeMode(): Boolean {
                return overlay.context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
            }

            fun whenLandScapeModeWidth(): Float {
                return when (isLandScapeMode()) {
                    true -> width
                    false -> height
                }
            }

            fun whenLandScapeModeHeight(): Float {
                return when (isLandScapeMode()) {
                    true -> height
                    false -> width
                }
            }

            val scaleX = overlay.width.toFloat() / whenLandScapeModeWidth()
            val scaleY = overlay.height.toFloat() / whenLandScapeModeHeight()
            val scale = scaleX.coerceAtLeast(scaleY)
            overlay.mScale = scale

            // Calculate offset (we need to center the overlay on the target)
            val offsetX = (overlay.width.toFloat() - ceil(whenLandScapeModeWidth() * scale)) / 2.0f
            val offsetY =
                (overlay.height.toFloat() - ceil(whenLandScapeModeHeight() * scale)) / 2.0f

            overlay.mOffsetX = offsetX
            overlay.mOffsetY = offsetY

            val mappedBox = RectF().apply {
                left = boundingBoxT.right * scale + offsetX
                top = boundingBoxT.top * scale + offsetY
                right = boundingBoxT.left * scale + offsetX
                bottom = boundingBoxT.bottom * scale + offsetY
            }

            // for front mode
            if (overlay.isFrontMode()) {
                val centerX = overlay.width.toFloat() / 2
                mappedBox.apply {
                    left = centerX + (centerX - left)
                    right = centerX - (right - centerX)
                }
            }
            return mappedBox
        }
    }

    fun isFrontMode() = cameraSelector == CameraSelector.LENS_FACING_FRONT

    fun toggleSelector() {
        cameraSelector =
            if (cameraSelector == CameraSelector.LENS_FACING_BACK) CameraSelector.LENS_FACING_FRONT
            else CameraSelector.LENS_FACING_BACK
    }

    fun clear() {
        synchronized(lock) { graphics.clear() }
        postInvalidate()
    }

    fun add(graphic: Graphic) {
        synchronized(lock) { graphics.add(graphic) }
    }

    fun remove(graphic: Graphic) {
        synchronized(lock) { graphics.remove(graphic) }
        postInvalidate()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        synchronized(lock) {
            for (graphic in graphics) {
                graphic.draw(canvas)
            }
        }
    }

}

FaceContourGraphic.kt

class FaceContourGraphic(
    overlay: GraphicOverlay,
    private val face: Face,
    private val imageRect: Rect
) : GraphicOverlay.Graphic(overlay) {

    private val facePositionPaint: Paint
    private val idPaint: Paint
    private val boxPaint: Paint

    init {
        val selectedColor = Color.WHITE

        facePositionPaint = Paint()
        facePositionPaint.color = selectedColor

        idPaint = Paint()
        idPaint.color = selectedColor

        boxPaint = Paint()
        boxPaint.color = selectedColor
        boxPaint.style = Paint.Style.STROKE
        boxPaint.strokeWidth = BOX_STROKE_WIDTH
    }

    override fun draw(canvas: Canvas?) {
        val rect = calculateRect(
            imageRect.height().toFloat(),
            imageRect.width().toFloat(),
            face.boundingBox
        )
        canvas?.drawRect(rect, boxPaint)
    }

    companion object {
        private const val BOX_STROKE_WIDTH = 5.0f
    }

}

SelfieAnalyzer.kt

abstract class SelfieAnalyzer<T> : ImageAnalysis.Analyzer {

    abstract val graphicOverlay: GraphicOverlay

    @SuppressLint("UnsafeExperimentalUsageError")
    override fun analyze(imageProxy: ImageProxy) {

        val mediaImage = imageProxy.image
        mediaImage?.let { image ->
            detectInImage(InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees))
                .addOnSuccessListener { results ->
                    onSuccess(
                        results,
                        graphicOverlay,
                        image.cropRect
                    )
                    imageProxy.close()
                }
                .addOnFailureListener {
                    onFailure(it)
                    imageProxy.close()
                }
        }
    }

    protected abstract fun detectInImage(image: InputImage): Task<T>

    abstract fun stop()

    protected abstract fun onSuccess(
        results: T,
        graphicOverlay: GraphicOverlay,
        rect: Rect
    )

    protected abstract fun onFailure(e: Exception)

    companion object {
        const val TAG: String = "SelfieAnalyzer"
    }
}

FaceContourDetectionProcessor.kt

class FaceContourDetectionProcessor(private val view: GraphicOverlay) :
    SelfieAnalyzer<List<Face>>() {


    private val realTimeOpts = FaceDetectorOptions.Builder()
        .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
        .setContourMode(FaceDetectorOptions.CONTOUR_MODE_NONE)
        .build()

    private val detector = FaceDetection.getClient(realTimeOpts)

    override val graphicOverlay: GraphicOverlay
        get() = view

    override fun detectInImage(image: InputImage): Task<List<Face>> {
        return detector.process(image)
    }

    override fun stop() {
        try {
            detector.close()
        } catch (e: IOException) {
            Log.e(TAG, "Exception thrown while trying to close Face Detector: $e")
        }
    }

    override fun onSuccess(
        results: List<Face>,
        graphicOverlay: GraphicOverlay,
        rect: Rect
    ) {
        graphicOverlay.clear()
        results.forEach { face ->
            val faceGraphic = FaceContourGraphic(graphicOverlay, face, rect)

            graphicOverlay.add(faceGraphic)
        }

        graphicOverlay.postInvalidate()
    }

    override fun onFailure(e: Exception) {
        Log.w(TAG, "Face Detector failed.$e")
    }

    companion object {
        private const val TAG = "FaceDetectionProcessor"
    }

}

SelfieFragment.kt

class SelfieFragment : Fragment() {

 // runs on camera permission grant success
 private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder().build()
            preview.setSurfaceProvider(binding.viewFinder.surfaceProvider)

            imageCapture = ImageCapture.Builder().build()

            val imageAnalyzer = ImageAnalysis.Builder()
                .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
                .build()
                .also {
                    it.setAnalyzer(
                        cameraExecutor,
                        selectAnalyzer()
                    )
                }

            // Select front camera as a default
            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA


            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture, imageAnalyzer
                )

            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(context))
    }
  }
}

fragment_selfie.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.fragments.SelfieFragment">

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

    <com.example.appname.customviews.GraphicOverlay
        android:id="@+id/graphic_overlay"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="@id/viewFinder"
        app:layout_constraintLeft_toLeftOf="@id/viewFinder"
        app:layout_constraintRight_toRightOf="@id/viewFinder"
        app:layout_constraintTop_toTopOf="@id/viewFinder" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btnCamera"
        style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
        android:layout_width="wrap_content"
        android:layout_height="56dp"
        android:layout_marginBottom="16dp"
        android:background="@android:color/transparent"
        android:insetLeft="0dp"
        android:insetTop="0dp"
        android:insetRight="0dp"
        android:insetBottom="0dp"
        app:icon="@drawable/ic_photo_camera_48"
        app:iconGravity="textStart"
        app:iconPadding="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

I want to create bitmap of detected faces but I do not know how, because Face api does not provide an way.

Zoe
  • 27,060
  • 21
  • 118
  • 148
user158
  • 12,852
  • 7
  • 62
  • 94
  • Did you verify that the detected face result has been fed into your graphic class? https://github.com/googlesamples/mlkit/blob/1e79fb6ba939b5dc8ce620a791cd9d5556f34e6f/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/kotlin/facedetector/FaceGraphic.kt#L35. is the sample code to follow after you get the detected face. – Chenxi Song Mar 31 '21 at 16:22
  • I suppose you mean `val faceGraphic = FaceContourGraphic(graphicOverlay, face, rect)` which is in `FaceContourDetectionProcessor.kt` – user158 Mar 31 '21 at 16:45
  • I see, looks like you have changed that class. It is hard to tell why the drawing is not appearing on your screen with the code snippet you provided. Within your code, you may want to print some log in order to detect what goes wrong. you can even check whether the layout is reflecting your code when you apply a color to the whole canvas regardless of the face info. – Chenxi Song Mar 31 '21 at 17:00
  • I really appreciate taking time to look into this. I think my question was not clear enough. what I want is to store detected faces as Bitmaps. – user158 Mar 31 '21 at 17:24
  • I think when you detected a face, you can get the bounding box of the face. First, convert the image to RGB bitmap, then crop the bitmap corresponds with the face bounding box. Here is the class that convert the image to bitmap [https://github.com/android/camera-samples/blob/master/CameraUtils/lib/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt](https://github.com/android/camera-samples/blob/master/CameraUtils/lib/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt) – Thành Thỏ Apr 07 '21 at 07:26
  • converting is easier, `Face` bounding box do not have frame data – user158 Apr 07 '21 at 11:52

1 Answers1

0

GraphicOverlay extends View and View can be rendered to Bitmap... here's an example. When wanting to render individual FaceContourGraphic, one may have to use a temporary View - so that one can add one FaceContourGraphic, render the output to Bitmap and then proceed with the next one FaceContourGraphic.

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • Could you please click on the link I've already provided? It's pointless to produce duplicate content and a `View` is a `View` - there is no difference at all, while the base-class is identical.... just create a `Canvas`, then draw the `View` and then create the `Bitmap`. And the question doesn't even specify if you want to draw them all on the same `Bitmap` or on individual `Bitmap` - so why should I be any more specific? – Martin Zeitler Apr 02 '21 at 15:54
  • I get the part of converting view to bitmap, My problem is getting the detected face? since `FaceContourGraphic` is drawn on top of the camera frame. (Frame and Overlay are separate) So How to extract detected faces from the frame? Please consider adding a sample code to that part. – user158 Apr 06 '21 at 11:47