Capture: reduce conversions between Mat/Bitmap/ByteArray
This commit is contained in:
@@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.fairscan.app.ui.screens.camera.extractDocumentFromBitmap
|
||||
import org.fairscan.imageprocessing.ImageSize
|
||||
import org.fairscan.imageprocessing.detectDocumentQuad
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -48,26 +49,24 @@ class DocumentDetectionTest {
|
||||
listOf("img01.jpg", "img02.jpg", "img03.jpg").forEach { imageFileName ->
|
||||
val inputStream = context.assets.open("uncropped/$imageFileName")
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
var outputBitmap: Bitmap? = null
|
||||
var outputJpeg: ByteArray? = null
|
||||
|
||||
val segmentationResult = runBlocking {
|
||||
segmentationService.runSegmentationAndReturn(bitmap, 0)
|
||||
segmentationService.runSegmentationAndReturn(bitmap)
|
||||
}
|
||||
if (segmentationResult != null) {
|
||||
val mask = segmentationResult.segmentation
|
||||
val quad = detectDocumentQuad(mask, false)
|
||||
val quad = detectDocumentQuad(mask, ImageSize(bitmap.width, bitmap.height),false)
|
||||
if (quad != null) {
|
||||
val resizedQuad =
|
||||
quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height)
|
||||
outputBitmap = extractDocumentFromBitmap(bitmap, resizedQuad, 0, mask).page
|
||||
outputJpeg = extractDocumentFromBitmap(bitmap, resizedQuad, 0, mask).pageJpeg
|
||||
val file = File(context.getExternalFilesDir(null), imageFileName)
|
||||
FileOutputStream(file).use {
|
||||
outputBitmap.compress(Bitmap.CompressFormat.JPEG, 95, it)
|
||||
}
|
||||
file.writeBytes(outputJpeg)
|
||||
Log.i("DocumentDetectionTest", "Image saved to ${file.absolutePath}")
|
||||
}
|
||||
}
|
||||
if (outputBitmap == null) {
|
||||
if (outputJpeg == null) {
|
||||
fail("Failed to extract document from image $imageFileName")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
|
||||
fun handleImageCaptured(capturedPage: CapturedPage) {
|
||||
viewModelScope.launch {
|
||||
imageRepository.add(
|
||||
compressJpeg(capturedPage.page, 75),
|
||||
capturedPage.pageJpeg,
|
||||
compressJpeg(capturedPage.source, 90),
|
||||
capturedPage.metadata,
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.fairscan.app.domain
|
||||
import android.graphics.Bitmap
|
||||
|
||||
data class CapturedPage(
|
||||
val page: Bitmap,
|
||||
val pageJpeg: ByteArray,
|
||||
val source: Bitmap,
|
||||
val metadata: PageMetadata,
|
||||
)
|
||||
|
||||
@@ -285,7 +285,7 @@ private fun CameraScreenScaffold(
|
||||
)
|
||||
}
|
||||
if (cameraUiState.captureState is CaptureState.CapturePreview) {
|
||||
val page = cameraUiState.captureState.capturedPage.page
|
||||
val page = bitmap(cameraUiState.captureState.capturedPage.pageJpeg)
|
||||
CapturedImage(page.asImageBitmap(), thumbnailCoords)
|
||||
}
|
||||
}
|
||||
@@ -533,10 +533,10 @@ fun CameraScreenPreviewWithProcessedImage() {
|
||||
val p = Point(0 , 0)
|
||||
val quad = Quad(p, p, p, p)
|
||||
ScreenPreview(CaptureState.CapturePreview(
|
||||
debugImage("uncropped/img01.jpg"),
|
||||
bitmap(debugImage("uncropped/img01.jpg")),
|
||||
CapturedPage(
|
||||
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||
bitmap(debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg")),
|
||||
PageMetadata(quad, R0, false))))
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ private fun ScreenPreview(captureState: CaptureState, rotationDegrees: Float = 0
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
Image(
|
||||
debugImage("uncropped/img01.jpg").asImageBitmap(),
|
||||
bitmap(debugImage("uncropped/img01.jpg")).asImageBitmap(),
|
||||
modifier=Modifier.rotate(rotationDegrees),
|
||||
contentDescription = null
|
||||
)
|
||||
@@ -591,9 +591,9 @@ private fun ScreenPreview(captureState: CaptureState, rotationDegrees: Float = 0
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun debugImage(imgName: String): Bitmap {
|
||||
private fun debugImage(imgName: String): ByteArray {
|
||||
val context = LocalContext.current
|
||||
return context.assets.open(imgName).use { input ->
|
||||
BitmapFactory.decodeStream(input)
|
||||
}
|
||||
return context.assets.open(imgName).readBytes()
|
||||
}
|
||||
|
||||
private fun bitmap(jpeg: ByteArray): Bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.size)
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.fairscan.imageprocessing.ImageSize
|
||||
import org.fairscan.imageprocessing.Mask
|
||||
import org.fairscan.imageprocessing.Quad
|
||||
import org.fairscan.imageprocessing.detectDocumentQuad
|
||||
import org.fairscan.imageprocessing.encodeJpeg
|
||||
import org.fairscan.imageprocessing.extractDocument
|
||||
import org.fairscan.imageprocessing.isColoredDocument
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
@@ -218,27 +219,14 @@ fun extractDocumentFromBitmap(
|
||||
val isColored = isColoredDocument(bgr, mask, quad)
|
||||
val maxPixels = ExportQuality.BALANCED.maxPixels
|
||||
val page = extractDocument(bgr, quad, rotationDegrees, isColored, maxPixels)
|
||||
val outBgr = page
|
||||
val pageJpeg = encodeJpeg(page, ExportQuality.BALANCED.jpegQuality)
|
||||
bgr.release()
|
||||
val outBitmap = toBitmap(outBgr)
|
||||
outBgr.release()
|
||||
page.release()
|
||||
|
||||
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
|
||||
val baseRotation = Rotation.fromDegrees(rotationDegrees)
|
||||
val metadata = PageMetadata(normalizedQuad, baseRotation, isColored)
|
||||
return CapturedPage(outBitmap, source, metadata)
|
||||
}
|
||||
|
||||
fun toBitmap(bgr: Mat): Bitmap {
|
||||
require(bgr.type() == CvType.CV_8UC3)
|
||||
|
||||
val rgba = Mat()
|
||||
Imgproc.cvtColor(bgr, rgba, Imgproc.COLOR_BGR2RGBA)
|
||||
|
||||
val bmp = createBitmap(bgr.cols(), bgr.rows(), Bitmap.Config.ARGB_8888)
|
||||
Utils.matToBitmap(rgba, bmp)
|
||||
|
||||
rgba.release()
|
||||
return bmp
|
||||
return CapturedPage(pageJpeg, source, metadata)
|
||||
}
|
||||
|
||||
fun rotateBitmap(source: Bitmap, angle: Float): Bitmap {
|
||||
|
||||
@@ -316,14 +316,11 @@ fun enhanceGrayscaleImage(img: Mat): Mat {
|
||||
val denoised = Mat()
|
||||
Imgproc.bilateralFilter(stretched8u, denoised, 9, 20.0, 10.0)
|
||||
|
||||
val finalBgr = Mat()
|
||||
Imgproc.cvtColor(denoised, finalBgr, Imgproc.COLOR_GRAY2BGR)
|
||||
|
||||
// -- Cleanup -----------
|
||||
gray.release(); imgFloat.release(); logImg.release()
|
||||
blur.release(); logBlur.release(); diff.release()
|
||||
retinex.release(); result8u.release()
|
||||
stretched8u.release(); denoised.release()
|
||||
stretched8u.release();
|
||||
|
||||
return finalBgr
|
||||
return denoised
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user