From 7056393f5612de5f76d170c575e91cc972424251 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Nicolas <6371790+pynicolas@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:24:01 +0200 Subject: [PATCH] Save about 400ms at capture by delegating the rotation to OpenCV --- .../mydomain/myscan/DocumentDetectionTest.kt | 2 +- .../org/mydomain/myscan/DocumentDetection.kt | 23 +++++++++++++++---- .../java/org/mydomain/myscan/MainViewModel.kt | 11 ++------- .../java/org/mydomain/myscan/view/Camera.kt | 2 ++ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/app/src/androidTest/java/org/mydomain/myscan/DocumentDetectionTest.kt b/app/src/androidTest/java/org/mydomain/myscan/DocumentDetectionTest.kt index 0c683be..3a5792b 100644 --- a/app/src/androidTest/java/org/mydomain/myscan/DocumentDetectionTest.kt +++ b/app/src/androidTest/java/org/mydomain/myscan/DocumentDetectionTest.kt @@ -57,7 +57,7 @@ class DocumentDetectionTest { if (quad != null) { val resizedQuad = quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height) - outputBitmap = extractDocument(bitmap, resizedQuad) + outputBitmap = extractDocument(bitmap, resizedQuad, 0) val file = File(context.getExternalFilesDir(null), imageFileName) FileOutputStream(file).use { outputBitmap.compress(Bitmap.CompressFormat.JPEG, 95, it) diff --git a/app/src/main/java/org/mydomain/myscan/DocumentDetection.kt b/app/src/main/java/org/mydomain/myscan/DocumentDetection.kt index e7647d0..7a32a59 100644 --- a/app/src/main/java/org/mydomain/myscan/DocumentDetection.kt +++ b/app/src/main/java/org/mydomain/myscan/DocumentDetection.kt @@ -15,7 +15,9 @@ package org.mydomain.myscan import android.graphics.Bitmap +import androidx.core.graphics.createBitmap import org.opencv.android.Utils +import org.opencv.core.Core import org.opencv.core.Mat import org.opencv.core.MatOfPoint import org.opencv.core.MatOfPoint2f @@ -24,7 +26,6 @@ import org.opencv.imgproc.Imgproc import kotlin.math.abs import kotlin.math.max import kotlin.math.sqrt -import androidx.core.graphics.createBitmap fun detectDocumentQuad(mask: Bitmap): Quad? { val mat = Mat() @@ -65,7 +66,7 @@ fun detectDocumentQuad(mask: Bitmap): Quad? { return createQuad(vertices) } -fun extractDocument(originalBitmap: Bitmap, quad: Quad): Bitmap { +fun extractDocument(originalBitmap: Bitmap, quad: Quad, rotationDegrees: Int): Bitmap { val widthTop = norm(quad.topLeft, quad.topRight) val widthBottom = norm(quad.bottomLeft, quad.bottomRight) val maxWidth = max(widthTop, widthBottom).toInt() @@ -96,11 +97,23 @@ fun extractDocument(originalBitmap: Bitmap, quad: Quad): Bitmap { val enhanced = enhanceCapturedImage(outputMat) - return toBitmap(enhanced, maxWidth, maxHeight) + return toBitmap(rotate(enhanced, rotationDegrees)) } -private fun toBitmap(mat: Mat, width: Int, height: Int): Bitmap { - val outputBitmap = createBitmap(width, height) +fun rotate(input: Mat, degrees: Int): Mat { + val output = Mat() + when ((degrees % 360 + 360) % 360) { + 0 -> input.copyTo(output) + 90 -> Core.rotate(input, output, Core.ROTATE_90_CLOCKWISE) + 180 -> Core.rotate(input, output, Core.ROTATE_180) + 270 -> Core.rotate(input, output, Core.ROTATE_90_COUNTERCLOCKWISE) + else -> throw IllegalArgumentException("Only 0, 90, 180, 270 degrees are supported") + } + return output +} + +private fun toBitmap(mat: Mat): Bitmap { + val outputBitmap = createBitmap(mat.cols(), mat.rows()) Utils.matToBitmap(mat, outputBitmap) return outputBitmap } diff --git a/app/src/main/java/org/mydomain/myscan/MainViewModel.kt b/app/src/main/java/org/mydomain/myscan/MainViewModel.kt index d0f7c2d..479c58b 100644 --- a/app/src/main/java/org/mydomain/myscan/MainViewModel.kt +++ b/app/src/main/java/org/mydomain/myscan/MainViewModel.kt @@ -17,7 +17,6 @@ package org.mydomain.myscan import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.graphics.Matrix import androidx.camera.core.ImageProxy import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -109,25 +108,19 @@ class MainViewModel( private suspend fun processCapturedImage(imageProxy: ImageProxy): Bitmap? = withContext(Dispatchers.IO) { var corrected: Bitmap? = null - val bitmap = imageProxy.toBitmap().rotate(imageProxy.imageInfo.rotationDegrees) + var bitmap = imageProxy.toBitmap() val segmentation = imageSegmentationService.runSegmentationAndReturn(bitmap, 0) if (segmentation != null) { val mask = segmentation.segmentation.toBinaryMask() val quad = detectDocumentQuad(mask) if (quad != null) { val resizedQuad = quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height) - corrected = extractDocument(bitmap, resizedQuad) + corrected = extractDocument(bitmap, resizedQuad, imageProxy.imageInfo.rotationDegrees) } } return@withContext corrected } - fun Bitmap.rotate(degrees: Int): Bitmap { - if (degrees == 0) return this - val matrix = Matrix().apply { postRotate(degrees.toFloat()) } - return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true) - } - fun addPage(bitmap: Bitmap, quality: Int = 75) { val resized = resizeImage(bitmap) val outputStream = ByteArrayOutputStream() diff --git a/app/src/main/java/org/mydomain/myscan/view/Camera.kt b/app/src/main/java/org/mydomain/myscan/view/Camera.kt index 2b2c4c1..3ecab16 100644 --- a/app/src/main/java/org/mydomain/myscan/view/Camera.kt +++ b/app/src/main/java/org/mydomain/myscan/view/Camera.kt @@ -131,6 +131,7 @@ fun CameraScreen( pageCount = viewModel.pageCount(), liveAnalysisState, onCapture = { + Log.i("MyScan", "Pressed ") viewModel.liveAnalysisEnabled = false showPageDialog.value = true isProcessing.value = true @@ -140,6 +141,7 @@ fun CameraScreen( viewModel.processCapturedImageThen(imageProxy) { isProcessing.value = false viewModel.liveAnalysisEnabled = true + Log.i("MyScan", "Capture process finished") } } else { Log.e("MyScan", "Error during image capture")