Save about 400ms at capture by delegating the rotation to OpenCV
This commit is contained in:
@@ -57,7 +57,7 @@ class DocumentDetectionTest {
|
|||||||
if (quad != null) {
|
if (quad != null) {
|
||||||
val resizedQuad =
|
val resizedQuad =
|
||||||
quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height)
|
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)
|
val file = File(context.getExternalFilesDir(null), imageFileName)
|
||||||
FileOutputStream(file).use {
|
FileOutputStream(file).use {
|
||||||
outputBitmap.compress(Bitmap.CompressFormat.JPEG, 95, it)
|
outputBitmap.compress(Bitmap.CompressFormat.JPEG, 95, it)
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
package org.mydomain.myscan
|
package org.mydomain.myscan
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import androidx.core.graphics.createBitmap
|
||||||
import org.opencv.android.Utils
|
import org.opencv.android.Utils
|
||||||
|
import org.opencv.core.Core
|
||||||
import org.opencv.core.Mat
|
import org.opencv.core.Mat
|
||||||
import org.opencv.core.MatOfPoint
|
import org.opencv.core.MatOfPoint
|
||||||
import org.opencv.core.MatOfPoint2f
|
import org.opencv.core.MatOfPoint2f
|
||||||
@@ -24,7 +26,6 @@ import org.opencv.imgproc.Imgproc
|
|||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
import androidx.core.graphics.createBitmap
|
|
||||||
|
|
||||||
fun detectDocumentQuad(mask: Bitmap): Quad? {
|
fun detectDocumentQuad(mask: Bitmap): Quad? {
|
||||||
val mat = Mat()
|
val mat = Mat()
|
||||||
@@ -65,7 +66,7 @@ fun detectDocumentQuad(mask: Bitmap): Quad? {
|
|||||||
return createQuad(vertices)
|
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 widthTop = norm(quad.topLeft, quad.topRight)
|
||||||
val widthBottom = norm(quad.bottomLeft, quad.bottomRight)
|
val widthBottom = norm(quad.bottomLeft, quad.bottomRight)
|
||||||
val maxWidth = max(widthTop, widthBottom).toInt()
|
val maxWidth = max(widthTop, widthBottom).toInt()
|
||||||
@@ -96,11 +97,23 @@ fun extractDocument(originalBitmap: Bitmap, quad: Quad): Bitmap {
|
|||||||
|
|
||||||
val enhanced = enhanceCapturedImage(outputMat)
|
val enhanced = enhanceCapturedImage(outputMat)
|
||||||
|
|
||||||
return toBitmap(enhanced, maxWidth, maxHeight)
|
return toBitmap(rotate(enhanced, rotationDegrees))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toBitmap(mat: Mat, width: Int, height: Int): Bitmap {
|
fun rotate(input: Mat, degrees: Int): Mat {
|
||||||
val outputBitmap = createBitmap(width, height)
|
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)
|
Utils.matToBitmap(mat, outputBitmap)
|
||||||
return outputBitmap
|
return outputBitmap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ package org.mydomain.myscan
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Matrix
|
|
||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
@@ -109,25 +108,19 @@ class MainViewModel(
|
|||||||
|
|
||||||
private suspend fun processCapturedImage(imageProxy: ImageProxy): Bitmap? = withContext(Dispatchers.IO) {
|
private suspend fun processCapturedImage(imageProxy: ImageProxy): Bitmap? = withContext(Dispatchers.IO) {
|
||||||
var corrected: Bitmap? = null
|
var corrected: Bitmap? = null
|
||||||
val bitmap = imageProxy.toBitmap().rotate(imageProxy.imageInfo.rotationDegrees)
|
var bitmap = imageProxy.toBitmap()
|
||||||
val segmentation = imageSegmentationService.runSegmentationAndReturn(bitmap, 0)
|
val segmentation = imageSegmentationService.runSegmentationAndReturn(bitmap, 0)
|
||||||
if (segmentation != null) {
|
if (segmentation != null) {
|
||||||
val mask = segmentation.segmentation.toBinaryMask()
|
val mask = segmentation.segmentation.toBinaryMask()
|
||||||
val quad = detectDocumentQuad(mask)
|
val quad = detectDocumentQuad(mask)
|
||||||
if (quad != null) {
|
if (quad != null) {
|
||||||
val resizedQuad = quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height)
|
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
|
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) {
|
fun addPage(bitmap: Bitmap, quality: Int = 75) {
|
||||||
val resized = resizeImage(bitmap)
|
val resized = resizeImage(bitmap)
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ fun CameraScreen(
|
|||||||
pageCount = viewModel.pageCount(),
|
pageCount = viewModel.pageCount(),
|
||||||
liveAnalysisState,
|
liveAnalysisState,
|
||||||
onCapture = {
|
onCapture = {
|
||||||
|
Log.i("MyScan", "Pressed <Capture>")
|
||||||
viewModel.liveAnalysisEnabled = false
|
viewModel.liveAnalysisEnabled = false
|
||||||
showPageDialog.value = true
|
showPageDialog.value = true
|
||||||
isProcessing.value = true
|
isProcessing.value = true
|
||||||
@@ -140,6 +141,7 @@ fun CameraScreen(
|
|||||||
viewModel.processCapturedImageThen(imageProxy) {
|
viewModel.processCapturedImageThen(imageProxy) {
|
||||||
isProcessing.value = false
|
isProcessing.value = false
|
||||||
viewModel.liveAnalysisEnabled = true
|
viewModel.liveAnalysisEnabled = true
|
||||||
|
Log.i("MyScan", "Capture process finished")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e("MyScan", "Error during image capture")
|
Log.e("MyScan", "Error during image capture")
|
||||||
|
|||||||
Reference in New Issue
Block a user