

### 実現したいこと

リアルタイム顔検出して、輪郭を四角く囲う

### 前提

KotlinとML Kitを使用して、リアルタイムで顔検出をして輪郭を四角く囲おうとしています。  
一通り実装し終わったので実行したのですが、輪郭を四角く囲えていませんでした。

### 発生している問題

analyzeメソッドにあるログだと輪郭の座標は取得できて変数に代入できているのですが、  
onDrawメソッドにあるログだと代入した数字がもってこれていないことが分かりました。(変数は持ってこれています。)

また、onDrawメソッドが三回呼び出されてからanalyzeメソッドにいっています。(理由がわからない)

座標を指定(100, 200, 100,
200)みたいに指定したところ、線が表示されたので座標をonDrawメソッドにもってくることができると解決するかと思われます。

### 試したこと

  * 同期処理になっていないのかと思い、analyzeメソッドに同期処理を実装しました。
  * 座標を指定したら線を表示させることに成功した。
  * invalidate()の位置を調整した。
  * setWillNotDraw()も試した

### 補足情報（FW/ツールのバージョンなど）

minSdk 21  
targetSdk 32  
versionCode 1  
versionName "1.0"

### 該当のソースコード

MainActivity

[code]

    import android.Manifest
    import android.content.Context
    import android.content.pm.PackageManager
    import android.graphics.Bitmap
    import android.graphics.Canvas
    import android.graphics.Color
    import android.graphics.Paint
    import android.media.FaceDetector
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.util.Log
    import android.util.Size
    import android.widget.ImageView
    import androidx.camera.core.*
    import androidx.camera.lifecycle.ProcessCameraProvider
    import com.google.common.util.concurrent.ListenableFuture
    import androidx.camera.view.PreviewView
    import androidx.core.app.ActivityCompat
    import androidx.core.content.ContextCompat
    import androidx.lifecycle.LifecycleOwner
    import java.util.concurrent.Executors
    
    
    class MainActivity : AppCompatActivity() {
    
        private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    
        companion object {
            const val TAG = "CameraXBasic"
            private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 10)
                startCamera()
            }
        }
    
        private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
            ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
        }
    
    
        fun startCamera() {
            val finder = findViewById<PreviewView>(R.id.viewFinder)
            cameraProviderFuture = ProcessCameraProvider.getInstance(this)
            cameraProviderFuture.addListener(Runnable {
                val cameraProvider = cameraProviderFuture.get()
    
                var preview: Preview = Preview.Builder()
                    .build()
    
                var cameraSelector: CameraSelector = CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build()
    
                val imageAnalysis = ImageAnalysis.Builder()
                    .setTargetResolution(Size(640, 480))
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()
    
                val context = this
                imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), ImageAnalyzer(context = context, attrs = null))
    
                try {
                    cameraProvider.unbindAll()
                    var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview,)
                    Log.d(TAG, "camera set.")
                    preview.setSurfaceProvider(finder.createSurfaceProvider(camera.cameraInfo))
                } catch (exc: Exception) {
                    Log.e(TAG, "Use case binding failed", exc)
                }
            }, ContextCompat.getMainExecutor(this))
        }
    }
    
[/code]

ImageAnalyzer

[code]

    import android.annotation.SuppressLint
    import android.content.ContentValues.TAG
    import android.content.Context
    import android.graphics.*
    import android.util.AttributeSet
    import android.util.Log
    import android.view.View
    import android.widget.ImageView
    import androidx.camera.core.ImageAnalysis
    import androidx.camera.core.ImageProxy
    import com.google.mlkit.vision.common.InputImage
    import com.google.mlkit.vision.face.FaceContour
    import com.google.mlkit.vision.face.FaceDetection
    import com.google.mlkit.vision.face.FaceDetectorOptions
    import kotlinx.coroutines.*
    import okhttp3.Dispatcher
    import java.util.*
    import kotlin.collections.ArrayList
    
    class ImageAnalyzer(context: Context?, attrs: AttributeSet?) : ImageAnalysis.Analyzer,
        View(context, attrs) {
    
        private val paint = Paint().apply {
            color = Color.GREEN
            style = Paint.Style.STROKE
            strokeWidth = 5f
        }
    
        private var bounds = Rect(0, 0, 0, 0)
    
        @SuppressLint("UnsafeOptInUsageError")
        override fun analyze(imageProxy: ImageProxy) {
            Log.d(MainActivity.TAG, "analyze start.")
            val mediaImage = imageProxy.image
    
            if (mediaImage != null) {
                Log.d(MainActivity.TAG, "image true.")
                val image =
                    InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
                val realTimeOpts = FaceDetectorOptions.Builder()
                    .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
                    .build()
                val detector = FaceDetection.getClient(realTimeOpts)
                detector.process(image)
                    .addOnSuccessListener { faces ->
                        for (face in faces) {
                            bounds.set(
                                face.boundingBox.left,
                                face.boundingBox.top,
                                face.boundingBox.right,
                                face.boundingBox.bottom
                            )
                            invalidate()
                            Log.d(TAG, "Face detected:$bounds")
                        }
                        imageProxy.close()
                        Log.d(MainActivity.TAG, "imagePrroxy closed.")
                    }
                    .addOnFailureListener { e ->
                        Log.d(MainActivity.TAG, "process failed.")
                    }
            } else {
                Log.d(MainActivity.TAG, "image false.")
            }
            Log.d(TAG, "Analyzer finish")
        }
    
    
        override fun onDraw(canvas: Canvas?) {
    
            Log.d(TAG, "bounds:$bounds")
            canvas?.drawRect(
                bounds.left.toFloat(),
                bounds.top.toFloat(),
                bounds.right.toFloat(),
                bounds.bottom.toFloat(),
                paint
            )
            Log.d(TAG, "onDraw called")
        }
    }
    
[/code]

Log

[code]

    2023-03-08 14:04:42.097 5600-5760/com.example.facedetection V/FaceDetectorV2Jni: detectFacesImageByteBufferMultiPlanes.start()
    2023-03-08 14:04:42.108 5600-5740/com.example.facedetection D/TransportRuntime.SQLiteEventStore: Storing event with priority=DEFAULT, name=FIREBASE_ML_SDK for destination cct
    2023-03-08 14:04:42.112 5600-5740/com.example.facedetection D/TransportRuntime.JobInfoScheduler: Upload for context TransportContext(cct, DEFAULT, MSRodHRwczovL2ZpcmViYXNlbG9nZ2luZy5nb29nbGVhcGlzLmNvbS92MGNjL2xvZy9iYXRjaD9mb3JtYXQ9anNvbl9wcm90bzNc) is already scheduled. Returning...
    2023-03-08 14:04:42.128 5600-5760/com.example.facedetection V/FaceDetectorV2Jni: detectFacesImageByteBuffer.end()
    2023-03-08 14:04:42.129 5600-5600/com.example.facedetection D/ContentValues: Face detected:Rect(370, 95 - 890, 679)
    2023-03-08 14:04:42.130 5600-5600/com.example.facedetection D/CameraXBasic: imagePrroxy closed.
    2023-03-08 14:04:42.130 5600-5739/com.example.facedetection D/CameraXBasic: analyze start.
    2023-03-08 14:04:42.130 5600-5739/com.example.facedetection D/CameraXBasic: image true.
    2023-03-08 14:04:42.131 5600-5739/com.example.facedetection D/ContentValues: Analyzer finish
    2023-03-08 14:04:42.131 5600-5744/com.example.facedetection V/FaceDetectorV2Jni: detectFacesImageByteBufferMultiPlanes.start()
    2023-03-08 14:04:42.136 5600-5740/com.example.facedetection D/TransportRuntime.SQLiteEventStore: Storing event with priority=DEFAULT, name=FIREBASE_ML_SDK for destination cct
    2023-03-08 14:04:42.139 5600-5740/com.example.facedetection D/TransportRuntime.JobInfoScheduler: Upload for context TransportContext(cct, DEFAULT, MSRodHRwczovL2ZpcmViYXNlbG9nZ2luZy5nb29nbGVhcGlzLmNvbS92MGNjL2xvZy9iYXRjaD9mb3JtYXQ9anNvbl9wcm90bzNc) is already scheduled. Returning...
    2023-03-08 14:04:42.161 5600-5744/com.example.facedetection V/FaceDetectorV2Jni: detectFacesImageByteBuffer.end()
    2023-03-08 14:04:42.163 5600-5600/com.example.facedetection D/ContentValues: Face detected:Rect(376, 98 - 892, 696)
    2023-03-08 14:04:42.163 5600-5600/com.example.facedetection D/CameraXBasic: imagePrroxy closed.
    2023-03-08 14:04:42.169 5600-5739/com.example.facedetection D/CameraXBasic: analyze start.
    2023-03-08 14:04:42.169 5600-5739/com.example.facedetection D/CameraXBasic: image true.
    2023-03-08 14:04:42.170 5600-5739/com.example.facedetection D/ContentValues: Analyzer finish
    
[/code]


CC BY-SA 4.0
2023-03-08T05:08:30.950
94098
2023-03-11T06:52:30.237
2023-03-11T06:52:30.237
3060
45538
question
java android kotlin
Drawメソッドが呼び出されないです。おそらくinvalidate()の位置が関係していると思うけど分かりません