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