diff --git a/app/src/main/java/org/fairscan/app/data/ImageRepository.kt b/app/src/main/java/org/fairscan/app/data/ImageRepository.kt index 4332877..6c6a0ed 100644 --- a/app/src/main/java/org/fairscan/app/data/ImageRepository.kt +++ b/app/src/main/java/org/fairscan/app/data/ImageRepository.kt @@ -33,6 +33,7 @@ import org.fairscan.app.domain.PageMetadata import org.fairscan.app.domain.PageViewKey import org.fairscan.app.domain.Rotation import org.fairscan.app.domain.ScanPage +import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.Point import org.fairscan.imageprocessing.Quad import java.io.File @@ -151,7 +152,7 @@ class ImageRepository( quad = metadata.normalizedQuad.toSerializable(), baseRotationDegrees = metadata.baseRotation.degrees, manualRotationDegrees = Rotation.R0.degrees, - isColored = metadata.isColored + isColored = metadata.autoColorMode == ColorMode.COLOR ) ) saveMetadata() @@ -313,6 +314,6 @@ fun PageV2.toMetadata(): PageMetadata? { return PageMetadata( quad.toQuad(), Rotation.fromDegrees(baseRotationDegrees), - isColored + if (isColored) ColorMode.COLOR else ColorMode.GRAYSCALE ) } diff --git a/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt b/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt index ea92511..3791b01 100644 --- a/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt +++ b/app/src/main/java/org/fairscan/app/domain/ExportPreparation.kt @@ -98,7 +98,7 @@ private fun prepareJpegForHigh( decoded, quad, pageMetadata.baseRotation.add(manualRotation).degrees, - pageMetadata.isColored, + pageMetadata.autoColorMode, exportQuality.maxPixels ) return Jpeg.fromMat(page, exportQuality.jpegQuality) diff --git a/app/src/main/java/org/fairscan/app/domain/Page.kt b/app/src/main/java/org/fairscan/app/domain/Page.kt index 0a197c2..80b1843 100644 --- a/app/src/main/java/org/fairscan/app/domain/Page.kt +++ b/app/src/main/java/org/fairscan/app/domain/Page.kt @@ -14,12 +14,13 @@ */ package org.fairscan.app.domain +import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.Quad data class PageMetadata( val normalizedQuad: Quad, val baseRotation: Rotation, - val isColored: Boolean, + val autoColorMode: ColorMode, ) data class ScanPage( diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt index 49b72b4..c447211 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraScreen.kt @@ -104,6 +104,7 @@ import org.fairscan.app.ui.components.pageCountText import org.fairscan.app.ui.dummyNavigation import org.fairscan.app.ui.fakeDocument import org.fairscan.app.ui.theme.FairScanTheme +import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.Point import org.fairscan.imageprocessing.Quad @@ -539,7 +540,7 @@ fun CameraScreenPreviewWithProcessedImage() { CapturedPage( debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"), CompletableDeferred(Jpeg(ByteArray(0))), - PageMetadata(quad, R0, false)))) + PageMetadata(quad, R0, ColorMode.COLOR)))) } @Preview(showBackground = true, widthDp = 640, heightDp = 320) diff --git a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt index fc0d6c3..3146c32 100644 --- a/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt +++ b/app/src/main/java/org/fairscan/app/ui/screens/camera/CameraViewModel.kt @@ -40,7 +40,7 @@ import org.fairscan.imageprocessing.Mask import org.fairscan.imageprocessing.Quad import org.fairscan.imageprocessing.detectDocumentQuad import org.fairscan.imageprocessing.extractDocument -import org.fairscan.imageprocessing.isColoredDocument +import org.fairscan.imageprocessing.autoColorMode import org.fairscan.imageprocessing.scaledTo import org.opencv.android.Utils import org.opencv.core.Mat @@ -230,16 +230,16 @@ fun extractDocumentFromBitmap( val bgr = Mat() Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGBA2BGR) // CV_8UC4 → CV_8UC3 rgba.release() - val isColored = isColoredDocument(bgr, mask, quad) + val colorMode = autoColorMode(bgr, mask, quad) val maxPixels = ExportQuality.BALANCED.maxPixels - val page = extractDocument(bgr, quad, rotationDegrees, isColored, maxPixels) + val page = extractDocument(bgr, quad, rotationDegrees, colorMode, maxPixels) val pageJpeg = Jpeg.fromMat(page, ExportQuality.BALANCED.jpegQuality) bgr.release() page.release() val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1) val baseRotation = Rotation.fromDegrees(rotationDegrees) - val metadata = PageMetadata(normalizedQuad, baseRotation, isColored) + val metadata = PageMetadata(normalizedQuad, baseRotation, colorMode) val sourceJpegDeferred = viewModelScope.async(Dispatchers.IO) { compressJpeg(source, 90) } diff --git a/app/src/test/java/org/fairscan/app/data/ImageRepositoryTest.kt b/app/src/test/java/org/fairscan/app/data/ImageRepositoryTest.kt index 2eb7215..4c9d636 100644 --- a/app/src/test/java/org/fairscan/app/data/ImageRepositoryTest.kt +++ b/app/src/test/java/org/fairscan/app/data/ImageRepositoryTest.kt @@ -26,6 +26,7 @@ import org.fairscan.app.domain.Rotation.R0 import org.fairscan.app.domain.Rotation.R180 import org.fairscan.app.domain.Rotation.R270 import org.fairscan.app.domain.Rotation.R90 +import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.Point import org.fairscan.imageprocessing.Quad import org.junit.Rule @@ -43,7 +44,7 @@ class ImageRepositoryTest { private val testScope = TestScope() val quad1 = Quad(Point(.01, .02), Point(.1, .03), Point(.11, .12), Point(.03, .09)) - val metadata1 = PageMetadata(quad1, R90, true) + val metadata1 = PageMetadata(quad1, R90, ColorMode.COLOR) fun getFilesDir(): File { if (_filesDir == null) { @@ -83,7 +84,7 @@ class ImageRepositoryTest { assertThat(metadata).isNotNull() assertThat(metadata!!.normalizedQuad).isEqualTo(quad1) assertThat(metadata.baseRotation).isEqualTo(metadata1.baseRotation) - assertThat(metadata.isColored).isEqualTo(metadata1.isColored) + assertThat(metadata.autoColorMode).isEqualTo(metadata1.autoColorMode) } @Test @@ -253,7 +254,9 @@ class ImageRepositoryTest { listOf(true, false).forEach { isColored -> val metadata = PageV2("1", 0, 0, quad, isColored).toMetadata() assertThat(metadata).isNotNull() - assertThat(metadata!!.isColored).isEqualTo(isColored) + assertThat(metadata!!.autoColorMode).isEqualTo( + if (isColored) ColorMode.COLOR else ColorMode.GRAYSCALE + ) } } diff --git a/evaluation/src/main/java/org/fairscan/evaluation/ColorDetectionEvaluator.kt b/evaluation/src/main/java/org/fairscan/evaluation/ColorDetectionEvaluator.kt index e6eb5d1..24ad4b8 100644 --- a/evaluation/src/main/java/org/fairscan/evaluation/ColorDetectionEvaluator.kt +++ b/evaluation/src/main/java/org/fairscan/evaluation/ColorDetectionEvaluator.kt @@ -14,9 +14,10 @@ */ package org.fairscan.evaluation +import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.detectDocumentQuad import org.fairscan.imageprocessing.extractDocument -import org.fairscan.imageprocessing.isColoredDocument +import org.fairscan.imageprocessing.autoColorMode import org.fairscan.imageprocessing.scaledTo import org.fairscan.imageprocessing.toImageSize import org.opencv.imgcodecs.Imgcodecs @@ -62,10 +63,10 @@ object ColorDetectionEvaluator { ?.scaledTo(mask.width, mask.height, mat.width(), mat.height()) if (quad == null) continue - val isColored = isColoredDocument(mat, mask, quad) - val extracted = extractDocument(mat, quad, 0, isColored, 2_000_000) + val colorMode = autoColorMode(mat, mask, quad) + val extracted = extractDocument(mat, quad, 0, colorMode, 2_000_000) - val detected = isColored + val detected = colorMode == ColorMode.COLOR nbProcessedImages++ diff --git a/evaluation/src/main/java/org/fairscan/evaluation/DatasetEvaluator.kt b/evaluation/src/main/java/org/fairscan/evaluation/DatasetEvaluator.kt index ceb91a0..22c0840 100644 --- a/evaluation/src/main/java/org/fairscan/evaluation/DatasetEvaluator.kt +++ b/evaluation/src/main/java/org/fairscan/evaluation/DatasetEvaluator.kt @@ -17,7 +17,7 @@ package org.fairscan.evaluation import org.fairscan.imageprocessing.Mask import org.fairscan.imageprocessing.detectDocumentQuad import org.fairscan.imageprocessing.extractDocument -import org.fairscan.imageprocessing.isColoredDocument +import org.fairscan.imageprocessing.autoColorMode import org.fairscan.imageprocessing.scaledTo import org.fairscan.imageprocessing.toImageSize import org.opencv.core.Mat @@ -74,8 +74,8 @@ object DatasetEvaluator { ?.scaledTo(mask.width, mask.height, inputMat.width(), inputMat.height()) val corrected: Mat? = if (quad != null) { - val isColored = isColoredDocument(inputMat, mask, quad) - extractDocument(inputMat, quad = quad, rotationDegrees = 0, isColored, 2_000_000) + val colorMode = autoColorMode(inputMat, mask, quad) + extractDocument(inputMat, quad = quad, rotationDegrees = 0, colorMode, 2_000_000) } else null val inputOut = File(outputDir, "${e.name}_input.jpg") diff --git a/evaluation/src/main/java/org/fairscan/evaluation/ExportQualityEvaluator.kt b/evaluation/src/main/java/org/fairscan/evaluation/ExportQualityEvaluator.kt index 8815156..4b4482c 100644 --- a/evaluation/src/main/java/org/fairscan/evaluation/ExportQualityEvaluator.kt +++ b/evaluation/src/main/java/org/fairscan/evaluation/ExportQualityEvaluator.kt @@ -16,7 +16,7 @@ package org.fairscan.evaluation import org.fairscan.imageprocessing.detectDocumentQuad import org.fairscan.imageprocessing.extractDocument -import org.fairscan.imageprocessing.isColoredDocument +import org.fairscan.imageprocessing.autoColorMode import org.fairscan.imageprocessing.scaledTo import org.fairscan.imageprocessing.toImageSize import org.opencv.core.MatOfInt @@ -65,13 +65,13 @@ object ExportQualityEvaluator { continue } - val isColored = isColoredDocument(sourceMat, mask, quad) + val colorMode = autoColorMode(sourceMat, mask, quad) for (quality in qualities) { for (maxPixels in maxPixelsList) { val outputMat = - extractDocument(sourceMat, quad, 0, isColored, maxPixels.toLong()) + extractDocument(sourceMat, quad, 0, colorMode, maxPixels.toLong()) val outputFile = File(outputDir, "$imgName-$quality-$maxPixels.jpg") val params = MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, quality) diff --git a/imageprocessing/src/main/java/org/fairscan/imageprocessing/ColorDetection.kt b/imageprocessing/src/main/java/org/fairscan/imageprocessing/ColorDetection.kt index 5d72e10..741f9d5 100644 --- a/imageprocessing/src/main/java/org/fairscan/imageprocessing/ColorDetection.kt +++ b/imageprocessing/src/main/java/org/fairscan/imageprocessing/ColorDetection.kt @@ -26,7 +26,7 @@ import org.opencv.imgproc.Imgproc import org.opencv.imgproc.Imgproc.fillConvexPoly import kotlin.math.roundToInt -fun isColoredDocument( +fun autoColorMode( img: Mat, mask: Mask, quad: Quad, @@ -34,7 +34,7 @@ fun isColoredDocument( proportionThreshold: Double = 0.0003, luminanceMin: Double = 40.0, luminanceMax: Double = 180.0 -): Boolean { +): ColorMode { // Work on a reasonable size (for correct performance) val resizedImg = resizeForMaxPixels(img, 1024.0 * 768.0) @@ -90,10 +90,13 @@ fun isColoredDocument( restrictedMask.release() docMask.release() - if (totalPixels == 0) return false + if (totalPixels == 0) return ColorMode.GRAYSCALE val proportion = coloredPixels.toDouble() / totalPixels.toDouble() - return proportion > proportionThreshold + return if (proportion > proportionThreshold) + ColorMode.COLOR + else + ColorMode.GRAYSCALE } private fun chroma(a: Mat, b: Mat): Mat { diff --git a/imageprocessing/src/main/java/org/fairscan/imageprocessing/DocumentDetection.kt b/imageprocessing/src/main/java/org/fairscan/imageprocessing/DocumentDetection.kt index f09c5fd..5d7b491 100644 --- a/imageprocessing/src/main/java/org/fairscan/imageprocessing/DocumentDetection.kt +++ b/imageprocessing/src/main/java/org/fairscan/imageprocessing/DocumentDetection.kt @@ -154,7 +154,7 @@ fun extractDocument( inputMat: Mat, quad: Quad, rotationDegrees: Int, - isColored: Boolean, + colorMode: ColorMode, maxPixels: Long, ): Mat { val widthTop = norm(quad.topLeft, quad.topRight) @@ -184,7 +184,7 @@ fun extractDocument( Imgproc.warpPerspective(inputMat, warped, transform, outputSize) val resized = resizeForMaxPixels(warped, maxPixels.toDouble()) - val enhanced = enhanceCapturedImage(resized, isColored) + val enhanced = enhanceCapturedImage(resized, colorMode) val rotated = rotate(enhanced, rotationDegrees) warped.release() diff --git a/imageprocessing/src/main/java/org/fairscan/imageprocessing/PostProcessing.kt b/imageprocessing/src/main/java/org/fairscan/imageprocessing/PostProcessing.kt index 6aedb86..9bb3eb0 100644 --- a/imageprocessing/src/main/java/org/fairscan/imageprocessing/PostProcessing.kt +++ b/imageprocessing/src/main/java/org/fairscan/imageprocessing/PostProcessing.kt @@ -25,11 +25,15 @@ import org.opencv.imgproc.Imgproc import kotlin.math.max import kotlin.math.min -fun enhanceCapturedImage(img: Mat, isColored: Boolean): Mat { - return if (isColored) { - multiScaleRetinexOnL(img) - } else { - enhanceGrayscaleImage(img) +enum class ColorMode { + COLOR, + GRAYSCALE, +} + +fun enhanceCapturedImage(img: Mat, colorMode: ColorMode): Mat { + return when (colorMode) { + ColorMode.COLOR -> multiScaleRetinexOnL(img) + ColorMode.GRAYSCALE -> enhanceGrayscaleImage(img) } }