Introduce ColorMode enum
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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++
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user