8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何修复人脸检测框的准确率?

Bmroxx Roxx 2月前

16 0

我正在开发一款人脸识别考勤应用程序。该应用程序成功显示了人脸检测框,但人脸检测的准确性并不令人满意。检测器经常

我正在开发一款人脸识别考勤应用程序。该应用程序成功显示了人脸检测框,但人脸检测的准确性不令人满意。检测器经常会错误识别人脸或在某些情况下无法检测到人脸。我正在寻找方法来提高应用程序中人脸检测的准确性。以下是一些具体内容:

面部检测器有时会将非面部物体检测为面部。面部检测框朝向面部,但只能检测到一半面部。我正在使用 TensorFlow 和 ML Kit 面部检测器进行面部检测。

我将非常感激任何关于如何提高我的应用程序中人脸检测功能的准确性的建议或解决方案。

我尝试实现以下代码来提高人脸检测的准确性:

以下是 XML:

        <!-- PreviewView for camera -->
        <androidx.camera.view.PreviewView
            android:id="@+id/previewView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.example.absensiapp.Drawing.OverlayView
            android:id="@+id/tracking_overlay"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/transparent" />

以下是后端代码:

class RegistrasiFaceActivity : AppCompatActivity() {

    private lateinit var previewView: PreviewView
    private lateinit var registerButton: Button
    private lateinit var switchCameraButton: Button
    private lateinit var overlayView: OverlayView
    private lateinit var cameraExecutor: ExecutorService
    private var isUsingFrontCamera = true

    private val detector: FaceDetector by lazy {
        val highAccuracyOpts = FaceDetectorOptions.Builder()
            .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
            .build()
        FaceDetection.getClient(highAccuracyOpts)
    }

    private var faceClassifier: FaceClassifier? = null
    private var registerFace = false
    private var isProcessingFrame = false
    private lateinit var tracker: MultiBoxTracker

    companion object {
        private const val PERMISSIONS_REQUEST_CODE = 121
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_registrasi_face)

        previewView = findViewById(R.id.previewView)
        overlayView = findViewById(R.id.tracking_overlay)
        registerButton = findViewById(R.id.registerButton)
        switchCameraButton = findViewById(R.id.switchCameraButton)

        cameraExecutor = Executors.newSingleThreadExecutor()
        tracker = MultiBoxTracker(this)

        // Initialize FACE Recognition
        CoroutineScope(Dispatchers.Main).launch {
            try {
                faceClassifier = TFLiteFaceRecognition.create(
                    assets,
                    "mobile_face_net.tflite",
                    112, // pastikan input size sesuai
                    false,
                    applicationContext
                )
            } catch (e: IOException) {
                e.printStackTrace()
                Toast.makeText(applicationContext, "Classifier could not be initialized", Toast.LENGTH_SHORT).show()
                finish()
            }
        }

        checkAndRequestPermissions()

        registerButton.setOnClickListener {
            registerFace = true
        }

        switchCameraButton.setOnClickListener {
            isUsingFrontCamera = !isUsingFrontCamera
            bindCameraUseCases()
        }
    }

    private fun checkAndRequestPermissions() {
        val permissionsNeeded = mutableListOf<String>()
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.CAMERA)
        }
        if (permissionsNeeded.isNotEmpty()) {
            ActivityCompat.requestPermissions(this, permissionsNeeded.toTypedArray(), PERMISSIONS_REQUEST_CODE)
        } else {
            bindCameraUseCases()
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSIONS_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
                bindCameraUseCases()
            } else {
                Toast.makeText(this, "Permissions denied", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun bindCameraUseCases() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(previewView.surfaceProvider)
                }

            val cameraSelector = CameraSelector.Builder()
                .requireLensFacing(if (isUsingFrontCamera) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK)
                .build()

            val imageAnalyzer = ImageAnalysis.Builder()
                .setTargetResolution(Size(640, 480))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, { imageProxy -> processImageProxy(imageProxy) })
                }

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalyzer)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }, ContextCompat.getMainExecutor(this))
    }

    @SuppressLint("UnsafeOptInUsageError")
    private fun processImageProxy(imageProxy: ImageProxy) {
        if (isProcessingFrame) {
            imageProxy.close()
            return
        }
        isProcessingFrame = true

        val rotationDegrees = imageProxy.imageInfo.rotationDegrees
        val image = imageProxy.image ?: run {
            imageProxy.close()
            isProcessingFrame = false
            return
        }

        val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        val paint = android.graphics.Paint().apply {
            color = android.graphics.Color.RED
            style = android.graphics.Paint.Style.STROKE
            strokeWidth = 5.0f
        }
        canvas.drawBitmap(bitmap, 0f, 0f, paint)

        val inputImage = InputImage.fromMediaImage(image, rotationDegrees)
        detector.process(inputImage)
            .addOnSuccessListener { faces ->
                processFaces(faces, bitmap, image.width, image.height)
                imageProxy.close()
                isProcessingFrame = false
            }
            .addOnFailureListener { e ->
                e.printStackTrace()
                imageProxy.close()
                isProcessingFrame = false
            }
    }

    private fun processFaces(faces: List<Face>, bitmap: Bitmap, imageWidth: Int, imageHeight: Int) {
        if (faces.isEmpty()) {
            return
        }

        val face = faces.first()
        val bounds = face.boundingBox

        // Pastikan kotak batas berada dalam dimensi bitmap
        val left = bounds.left.coerceAtLeast(0)
        val top = bounds.top.coerceAtLeast(0)
        val right = bounds.right.coerceAtMost(bitmap.width)
        val bottom = bounds.bottom.coerceAtMost(bitmap.height)

        val width = right - left
        val height = bottom - top

        if (width <= 0 || height <= 0) {
            return
        }

        // Potong wajah dari bitmap asli
        val crop = Bitmap.createBitmap(bitmap, left, top, width, height)

        // Ubah ukuran bitmap sesuai dengan ukuran yang diharapkan oleh model (112x112 dalam kasus ini)
        val scaledBitmap = Bitmap.createScaledBitmap(crop, 112, 112, false)

        // Gunakan bitmap yang telah diubah ukurannya langsung dengan fungsi recognizeImage
        val result = faceClassifier?.recognizeImage(scaledBitmap, registerFace)

        val rectF = RectF(bounds)

        // Calculate scale to transform bounding box to screen coordinates
        val scaleX = overlayView.width / imageWidth.toFloat()
        val scaleY = overlayView.height / imageHeight.toFloat()

        // Apply scale to bounding box
        rectF.left *= scaleX
        rectF.top *= scaleY
        rectF.right *= scaleX
        rectF.bottom *= scaleY

        val recognition = FaceClassifier.Recognition(face.trackingId.toString(), "", 0f, rectF)

        tracker.setFrameConfiguration(imageWidth, imageHeight, 0)

        // Bersihkan callback sebelumnya sebelum menambahkan yang baru
        overlayView.clearCallbacks()

        tracker.trackResults(listOf(recognition), System.currentTimeMillis())
        overlayView.addCallback(object : OverlayView.DrawCallback {
            override fun drawCallback(canvas: Canvas) {
                tracker.draw(canvas)
            }
        })
        overlayView.postInvalidate()

        if (registerFace) {
            showRegisterDialog(scaledBitmap, result!!)
        }
    }

    private fun showRegisterDialog(bitmap: Bitmap, recognition: FaceClassifier.Recognition) {
        val dialog = androidx.appcompat.app.AlertDialog.Builder(this)
            .setView(R.layout.dialog_register)
            .create()

        dialog.setOnShowListener {
            val imageView: ImageView = dialog.findViewById(R.id.dialog_image)!!
            val registerButton: Button = dialog.findViewById(R.id.dialog_register_button)!!

            imageView.setImageBitmap(bitmap)
            registerButton.setOnClickListener {
                CoroutineScope(Dispatchers.IO).launch {
                    faceClassifier?.register("Face", recognition)
                    withContext(Dispatchers.Main) {
                        Toast.makeText(this@RegistrasiFaceActivity, "Face Registered Successfully", Toast.LENGTH_SHORT).show()
                        dialog.dismiss()
                    }
                }
            }
        }

        dialog.show()
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
        detector.close()
    }
}

这是 TFLiteFaceRecognition 代码:

class TFLiteFaceRecognition private constructor(context: Context) : FaceClassifier {

    companion object {
        private const val OUTPUT_SIZE = 192
        private const val IMAGE_MEAN = 127.5f
        private const val IMAGE_STD = 127.5f
        private const val WIDTH = 112
        private const val HEIGHT = 112

        @Throws(IOException::class)
        private fun loadModelFile(assets: AssetManager, modelFilename: String): MappedByteBuffer {
            val fileDescriptor = assets.openFd(modelFilename)
            val inputStream = FileInputStream(fileDescriptor.fileDescriptor)
            val fileChannel = inputStream.channel
            val startOffset = fileDescriptor.startOffset
            val declaredLength = fileDescriptor.declaredLength
            return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
        }

        @Throws(IOException::class)
        suspend fun create(
            assetManager: AssetManager,
            modelFilename: String,
            inputSize: Int,
            isQuantized: Boolean,
            context: Context
        ): FaceClassifier {
            return TFLiteFaceRecognition(context).apply {
                this.inputSize = inputSize

                try {
                    tfLite = Interpreter(loadModelFile(assetManager, modelFilename))
                } catch (e: Exception) {
                    throw RuntimeException(e)
                }

                this.isModelQuantized = isQuantized
                val numBytesPerChannel = if (isQuantized) 1 else 4
                imgData = ByteBuffer.allocateDirect(1 * inputSize * inputSize * 3 * numBytesPerChannel)
                imgData.order(ByteOrder.nativeOrder())
                intValues = IntArray(inputSize * inputSize)
            }
        }
    }

    private var isModelQuantized = false
    private var inputSize = 0
    private lateinit var intValues: IntArray
    private lateinit var embeddings: Array<FloatArray>
    private lateinit var imgData: ByteBuffer
    private lateinit var tfLite: Interpreter
    private val registered = HashMap<String, FaceClassifier.Recognition>()

    interface ApiService {
        @POST("api/update-profile")
        suspend fun insertFace(@Body requestBody: FaceRequest)
    }

    data class FaceRequest(val name: String, val embedding: String)

    private val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl(BuildConfig.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    private val apiService: ApiService = retrofit.create(ApiService::class.java)

    override fun register(name: String, rec: FaceClassifier.Recognition) {
        CoroutineScope(Dispatchers.IO).launch {
            insertFaceToDatabase(name, rec.embedding as FloatArray)
            registered[name] = rec
        }
    }

    private fun findNearest(emb: FloatArray): Pair<String, Float>? {
        var ret: Pair<String, Float>? = null
        for ((name, value) in registered) {
            val knownEmb = (value.embedding as Array<FloatArray>)[0]
            var distance = 0f
            for (i in emb.indices) {
                val diff = emb[i] - knownEmb[i]
                distance += diff * diff
            }
            distance = kotlin.math.sqrt(distance)
            if (ret == null || distance < ret.second) {
                ret = Pair(name, distance)
            }
        }
        return ret
    }

    private fun imageToArray(bitmap: Bitmap): Array<Array<Array<FloatArray>>> {
        val resizedBitmap = Bitmap.createScaledBitmap(bitmap, WIDTH, HEIGHT, true)
        val intValues = IntArray(WIDTH * HEIGHT)
        resizedBitmap.getPixels(intValues, 0, WIDTH, 0, 0, WIDTH, HEIGHT)
        val floatValues = FloatArray(WIDTH * HEIGHT * 3)
        for (i in intValues.indices) {
            val value = intValues[i]
            floatValues[i * 3 + 0] = ((value shr 16 and 0xFF) - IMAGE_MEAN) / IMAGE_STD
            floatValues[i * 3 + 1] = ((value shr 8 and 0xFF) - IMAGE_MEAN) / IMAGE_STD
            floatValues[i * 3 + 2] = ((value and 0xFF) - IMAGE_MEAN) / IMAGE_STD
        }
        return Array(1) { Array(WIDTH) { Array(HEIGHT) { FloatArray(3) } }.also { array ->
            for (y in 0 until HEIGHT) {
                for (x in 0 until WIDTH) {
                    for (c in 0 until 3) {
                        array[y][x][c] = floatValues[(y * WIDTH + x) * 3 + c]
                    }
                }
            }
        }}
    }

    override fun recognizeImage(bitmap: Bitmap, storeExtra: Boolean): FaceClassifier.Recognition {
        val inputArray = imageToArray(bitmap)
        val outputArray = Array(1) { FloatArray(OUTPUT_SIZE) }

        tfLite.run(inputArray, outputArray)

        var distance = Float.MAX_VALUE
        var id = "0"
        var label = "?"

        if (registered.isNotEmpty()) {
            val nearest = findNearest(outputArray[0])
            if (nearest != null) {
                val name = nearest.first
                label = name
                distance = nearest.second
            }
        }

        val rec = FaceClassifier.Recognition(
            id,
            label,
            distance,
            RectF()
        )

        if (storeExtra) {
            rec.embedding = outputArray
        }

        return rec
    }

    private suspend fun insertFaceToDatabase(name: String, embedding: FloatArray) {
        withContext(Dispatchers.IO) {
            val embeddingString = embedding.joinToString(",")
            val request = FaceRequest(name, embeddingString)
            apiService.insertFace(request)
        }
    }
}

这是 ImageUtils 代码

/** Utility class for manipulating images. */
object ImageUtils {
    // This value is 2 ^ 18 - 1, and is used to clamp the RGB values before their ranges
    // are normalized to eight bits.
    private const val kMaxChannelValue = 262143

    /**
     * Utility method to compute the allocated size in bytes of a YUV420SP image of the given
     * dimensions.
     */
    fun getYUVByteSize(width: Int, height: Int): Int {
        // The luminance plane requires 1 byte per pixel.
        val ySize = width * height

        // The UV plane works on 2x2 blocks, so dimensions with odd size must be rounded up.
        // Each 2x2 block takes 2 bytes to encode, one each for U and V.
        val uvSize = ((width + 1) / 2) * ((height + 1) / 2) * 2

        return ySize + uvSize
    }

    /**
     * Saves a Bitmap object to disk for analysis.
     *
     * @param bitmap The bitmap to save.
     */
    fun saveBitmap(bitmap: Bitmap) {
        saveBitmap(bitmap, "preview.png")
    }

    /**
     * Saves a Bitmap object to disk for analysis.
     *
     * @param bitmap The bitmap to save.
     * @param filename The location to save the bitmap to.
     */
    fun saveBitmap(bitmap: Bitmap, filename: String) {
        val root = Environment.getExternalStorageDirectory().absolutePath + File.separator + "tensorflow"
        //LOGGER.i("Saving %dx%d bitmap to %s.", bitmap.getWidth(), bitmap.getHeight(), root)
        val myDir = File(root)

        if (!myDir.mkdirs()) {
            //LOGGER.i("Make dir failed")
        }

        val fname = filename
        val file = File(myDir, fname)
        if (file.exists()) {
            file.delete()
        }
        try {
            val out = FileOutputStream(file)
            bitmap.compress(Bitmap.CompressFormat.PNG, 99, out)
            out.flush()
            out.close()
        } catch (e: Exception) {
            //LOGGER.e(e, "Exception!")
        }
    }

    fun convertYUV420SPToARGB8888(input: ByteArray, width: Int, height: Int, output: IntArray) {
        val frameSize = width * height
        for (j in 0 until height) {
            var uvp = frameSize + (j shr 1) * width
            var u = 0
            var v = 0

            for (i in 0 until width) {
                val y = 0xff and input[j * width + i].toInt()
                if (i and 1 == 0) {
                    v = 0xff and input[uvp++].toInt()
                    u = 0xff and input[uvp++].toInt()
                }

                output[j * width + i] = YUV2RGB(y, u, v)
            }
        }
    }

    private fun YUV2RGB(y: Int, u: Int, v: Int): Int {
        var y = y
        var u = u
        var v = v
        y = if (y - 16 < 0) 0 else y - 16
        u -= 128
        v -= 128

        val y1192 = 1192 * y
        var r = y1192 + 1634 * v
        var g = y1192 - 833 * v - 400 * u
        var b = y1192 + 2066 * u

        r = if (r > kMaxChannelValue) kMaxChannelValue else if (r < 0) 0 else r
        g = if (g > kMaxChannelValue) kMaxChannelValue else if (g < 0) 0 else g
        b = if (b > kMaxChannelValue) kMaxChannelValue else if (b < 0) 0 else b

        return -0x1000000 or (r shl 6 and 0xff0000) or (g shr 2 and 0xff00) or (b shr 10 and 0xff)
    }

    fun convertYUV420ToARGB8888(
        yData: ByteArray,
        uData: ByteArray,
        vData: ByteArray,
        width: Int,
        height: Int,
        yRowStride: Int,
        uvRowStride: Int,
        uvPixelStride: Int,
        out: IntArray
    ) {
        var yp = 0
        for (j in 0 until height) {
            val pY = yRowStride * j
            val pUV = uvRowStride * (j shr 1)

            for (i in 0 until width) {
                val uvOffset = pUV + (i shr 1) * uvPixelStride

                out[yp++] = YUV2RGB(
                    0xff and yData[pY + i].toInt(),
                    0xff and uData[uvOffset].toInt(),
                    0xff and vData[uvOffset].toInt()
                )
            }
        }
    }

    fun getTransformationMatrix(
        srcWidth: Int,
        srcHeight: Int,
        dstWidth: Int,
        dstHeight: Int,
        applyRotation: Int,
        maintainAspectRatio: Boolean
    ): Matrix {
        val matrix = Matrix()

        if (applyRotation != 0) {
            if (applyRotation % 90 != 0) {
                //LOGGER.w("Rotation of %d % 90 != 0", applyRotation)
            }

            // Translate so center of image is at origin.
            matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f)

            // Rotate around origin.
            matrix.postRotate(applyRotation.toFloat())
        }

        val transpose = (Math.abs(applyRotation) + 90) % 180 == 0

        val inWidth = if (transpose) srcHeight else srcWidth
        val inHeight = if (transpose) srcWidth else srcHeight

        if (inWidth != dstWidth || inHeight != dstHeight) {
            val scaleFactorX = dstWidth / inWidth.toFloat()
            val scaleFactorY = dstHeight / inHeight.toFloat()

            if (maintainAspectRatio) {
                val scaleFactor = Math.max(scaleFactorX, scaleFactorY)
                matrix.postScale(scaleFactor, scaleFactor)
            } else {
                matrix.postScale(scaleFactorX, scaleFactorY)
            }
        }

        if (applyRotation != 0) {
            matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f)
        }

        return matrix
    }
}

这是 FaceClassifier 代码:

interface FaceClassifier {

    fun register(name: String, recognition: Recognition)

    fun recognizeImage(bitmap: Bitmap, getExtra: Boolean): Recognition

    class Recognition(
        val id: String? = null,
        val title: String?,
        val distance: Float? = null,
        var location: RectF? = null,
        var embedding: Any? = null,
        var crop: Bitmap? = null
    ) {

        constructor(title: String?, embedding: Any?) : this(
            id = null,
            title = title,
            distance = null,
            location = null,
            embedding = embedding,
            crop = null
        )

        fun updateEmbedding(extra: Any?) {
            this.embedding = extra
        }

        override fun toString(): String {
            var resultString = ""
            if (id != null) {
                resultString += "[$id] "
            }

            if (title != null) {
                resultString += "$title "
            }

            if (distance != null) {
                resultString += String.format("(%.1f%%) ", distance * 100.0f)
            }

            if (location != null) {
                resultString += "$location "
            }

            return resultString.trim()
        }
    }
}

这是覆盖代码:

class OverlayView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val callbacks = mutableListOf<DrawCallback>()

    fun addCallback(callback: DrawCallback) {
        callbacks.add(callback)
    }

    fun clearCallbacks() {
        callbacks.clear()
    }

    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        for (callback in callbacks) {
            callback.drawCallback(canvas)
        }
    }

    /** Interface defining the callback for client classes. */
    interface DrawCallback {
        fun drawCallback(canvas: Canvas)
    }
}

这是 MultiboxTracker 代码:

class MultiBoxTracker(context: Context) {
    private val screenRects = mutableListOf<Pair<Float, RectF>>()
    private val availableColors = LinkedList<Int>()
    private val trackedObjects = mutableListOf<TrackedRecognition>()
    private val boxPaint = Paint()
    private var frameToCanvasMatrix: Matrix? = null
    private var frameWidth = 0
    private var frameHeight = 0
    private var sensorOrientation = 0

    init {
        for (color in COLORS) {
            availableColors.add(color)
        }

        boxPaint.color = Color.RED
        boxPaint.style = Paint.Style.STROKE
        boxPaint.strokeWidth = 10.0f
        boxPaint.strokeCap = Paint.Cap.ROUND
        boxPaint.strokeJoin = Paint.Join.ROUND
        boxPaint.strokeMiter = 100f
    }

    @Synchronized
    fun setFrameConfiguration(width: Int, height: Int, sensorOrientation: Int) {
        frameWidth = width
        frameHeight = height
        this.sensorOrientation = sensorOrientation
    }

    @Synchronized
    fun drawDebug(canvas: Canvas) {
        val boxPaint = Paint().apply {
            color = Color.RED
            alpha = 200
            style = Paint.Style.STROKE
        }

        for (detection in screenRects) {
            val rect = detection.second
            canvas.drawRect(rect, boxPaint)
        }
    }

    @Synchronized
    fun trackResults(results: List<FaceClassifier.Recognition>, timestamp: Long) {
        processResults(results)
    }

    private fun getFrameToCanvasMatrix(): Matrix {
        return frameToCanvasMatrix ?: Matrix()
    }

    @Synchronized
    fun draw(canvas: Canvas) {
        val rotated = sensorOrientation % 180 == 90
        val multiplier = minOf(
            canvas.height / (if (rotated) frameWidth else frameHeight).toFloat(),
            canvas.width / (if (rotated) frameHeight else frameWidth).toFloat()
        )
        frameToCanvasMatrix = ImageUtils.getTransformationMatrix(
            frameWidth,
            frameHeight,
            (multiplier * (if (rotated) frameHeight else frameWidth)).toInt(),
            (multiplier * (if (rotated) frameWidth else frameHeight)).toInt(),
            sensorOrientation,
            false
        )

        for (recognition in trackedObjects) {
            val trackedPos = RectF(recognition.location)

            getFrameToCanvasMatrix().mapRect(trackedPos)
            boxPaint.color = recognition.color

            // Gambar kotak deteksi wajah tanpa judul
            canvas.drawRect(trackedPos, boxPaint)
        }
    }

    private fun processResults(results: List<FaceClassifier.Recognition>) {
        val rectsToTrack = LinkedList<Pair<Float, FaceClassifier.Recognition>>()

        screenRects.clear()
        val rgbFrameToScreen = Matrix(getFrameToCanvasMatrix())

        for (result in results) {
            if (result.location == null) {
                continue
            }
            val detectionFrameRect = RectF(result.location)

            val detectionScreenRect = RectF()
            rgbFrameToScreen.mapRect(detectionScreenRect, detectionFrameRect)

            screenRects.add(Pair(result.distance ?: 0f, detectionScreenRect))

            if (detectionFrameRect.width() < MIN_SIZE || detectionFrameRect.height() < MIN_SIZE) {
                continue
            }

            rectsToTrack.add(Pair(result.distance ?: 0f, result))
        }

        trackedObjects.clear()
        if (rectsToTrack.isEmpty()) {
            return
        }

        for (potential in rectsToTrack) {
            val trackedRecognition = TrackedRecognition()
            trackedRecognition.detectionConfidence = potential.first
            trackedRecognition.location = RectF(potential.second.location)
            trackedRecognition.color = COLORS[trackedObjects.size]
            trackedObjects.add(trackedRecognition)

            if (trackedObjects.size >= COLORS.size) {
                break
            }
        }
    }

    private class TrackedRecognition {
        var location: RectF? = null
        var detectionConfidence: Float = 0f
        var color: Int = 0
    }

    companion object {
        private const val MIN_SIZE = 16.0f
        private val COLORS = intArrayOf(
            Color.BLUE,
            Color.RED,
            Color.GREEN,
            Color.YELLOW,
            Color.CYAN,
            Color.MAGENTA,
            Color.WHITE,
            Color.parseColor("#55FF55"),
            Color.parseColor("#FFA500"),
            Color.parseColor("#FF8888"),
            Color.parseColor("#AAAAFF"),
            Color.parseColor("#FFFFAA"),
            Color.parseColor("#55AAAA"),
            Color.parseColor("#AA33AA"),
            Color.parseColor("#0D0068")
        )
    }
}

尽管如此,准确率并没有像预期的那样提高。我还录制了应用程序的屏幕截图来说明这个问题:

点击观看我的视频

这是我在 GitHub 上的项目

我原本以为人脸检测器能够准确检测出整个脸部,而不会误认非面部物体。然而,检测器仍然只能识别出半张脸,有时还会将非面部物体检测为人脸。

帖子版权声明 1、本帖标题:如何修复人脸检测框的准确率?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Bmroxx Roxx在本站《tensorflow》版块原创发布, 转载请注明出处!
最新回复 (0)
返回
作者最近主题: