如何修复人脸检测框的准确率?
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 上的项目
我原本以为人脸检测器能够准确检测出整个脸部,而不会误认非面部物体。然而,检测器仍然只能识别出半张脸,有时还会将非面部物体检测为人脸。
如何修复人脸检测框的准确率?
下载声明: 本站所有软件和资料均为软件作者提供或网友推荐发布而来,仅供学习和研究使用,不得用于任何商业用途。如本站不慎侵犯你的版权请联系我,我将及时处理,并撤下相关内容!
下载声明: 本站所有软件和资料均为软件作者提供或网友推荐发布而来,仅供学习和研究使用,不得用于任何商业用途。如本站不慎侵犯你的版权请联系我,我将及时处理,并撤下相关内容!
收藏的用户(0)
X
正在加载信息~