Extract module imageprocessing (#76)
This commit is contained in:
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -11,6 +11,7 @@
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/imageprocessing" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
|
||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.1.0" />
|
||||
<option name="version" value="2.2.21" />
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -88,8 +88,10 @@ android {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
@@ -101,6 +103,10 @@ apply(from = "download-tflite.gradle.kts")
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(project(":imageprocessing")) {
|
||||
exclude(group = "org.openpnp", module = "opencv")
|
||||
}
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
@@ -29,6 +29,7 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fairscan.app.data.Logger
|
||||
import org.fairscan.imageprocessing.Mask
|
||||
import org.opencv.core.CvType
|
||||
import org.opencv.core.Mat
|
||||
import org.tensorflow.lite.DataType
|
||||
@@ -132,7 +133,11 @@ class ImageSegmentationService(private val context: Context, private val logger:
|
||||
return maskFloats
|
||||
}
|
||||
|
||||
data class Segmentation(private val probmap: FloatArray, val width: Int, val height: Int) {
|
||||
data class Segmentation(
|
||||
private val probmap: FloatArray,
|
||||
override val width: Int,
|
||||
override val height: Int
|
||||
): Mask {
|
||||
fun get(x: Int, y: Int): Float = probmap[y * width + x]
|
||||
fun toBinaryMask(): Bitmap {
|
||||
val bmp = createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
@@ -144,7 +149,8 @@ class ImageSegmentationService(private val context: Context, private val logger:
|
||||
bmp.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return bmp
|
||||
}
|
||||
fun toMat(): Mat {
|
||||
|
||||
override fun toMat(): Mat {
|
||||
val mat = Mat(height, width, CvType.CV_32FC1)
|
||||
mat.put(0, 0, probmap)
|
||||
return mat
|
||||
|
||||
@@ -53,8 +53,8 @@ import androidx.core.graphics.scale
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import org.fairscan.app.domain.Point
|
||||
import org.fairscan.app.domain.scaledTo
|
||||
import org.fairscan.imageprocessing.Point
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
import org.fairscan.app.ui.components.CameraPermissionState
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -16,7 +16,7 @@ package org.fairscan.app.ui.screens.camera
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.runtime.Immutable
|
||||
import org.fairscan.app.domain.Quad
|
||||
import org.fairscan.imageprocessing.Quad
|
||||
|
||||
@Immutable
|
||||
data class LiveAnalysisState(
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.fairscan.app.ui.screens.camera
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import androidx.camera.core.ImageProxy
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -30,9 +31,12 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.fairscan.app.AppContainer
|
||||
import org.fairscan.app.domain.detectDocumentQuad
|
||||
import org.fairscan.app.domain.extractDocument
|
||||
import org.fairscan.app.domain.scaledTo
|
||||
import org.fairscan.imageprocessing.Quad
|
||||
import org.fairscan.imageprocessing.detectDocumentQuad
|
||||
import org.fairscan.imageprocessing.extractDocument
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
import org.opencv.android.Utils
|
||||
import org.opencv.core.Mat
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
sealed interface CameraEvent {
|
||||
@@ -143,7 +147,7 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
|
||||
}
|
||||
if (quad != null) {
|
||||
val resizedQuad = quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height)
|
||||
corrected = extractDocument(bitmap, resizedQuad, imageProxy.imageInfo.rotationDegrees)
|
||||
corrected = extractDocumentFromBitmap(bitmap, resizedQuad, imageProxy.imageInfo.rotationDegrees)
|
||||
}
|
||||
}
|
||||
return@withContext corrected
|
||||
@@ -179,3 +183,15 @@ sealed class CaptureState {
|
||||
val processed: Bitmap
|
||||
) : CaptureState()
|
||||
}
|
||||
|
||||
fun extractDocumentFromBitmap(originalBitmap: Bitmap, quad: Quad, rotationDegrees: Int): Bitmap {
|
||||
val inputMat = Mat()
|
||||
Utils.bitmapToMat(originalBitmap, inputMat)
|
||||
return toBitmap(extractDocument(inputMat, quad, rotationDegrees))
|
||||
}
|
||||
|
||||
private fun toBitmap(mat: Mat): Bitmap {
|
||||
val outputBitmap = createBitmap(mat.cols(), mat.rows())
|
||||
Utils.matToBitmap(mat, outputBitmap)
|
||||
return outputBitmap
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.aboutLibrariesAndroid) apply false
|
||||
alias(libs.plugins.license)
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
|
||||
}
|
||||
|
||||
license {
|
||||
|
||||
@@ -14,6 +14,7 @@ datastore = "1.2.0"
|
||||
documentfile = "1.1.0"
|
||||
litert = "1.4.1"
|
||||
opencv = "4.12.0"
|
||||
opencv_java = "4.9.0-0"
|
||||
assertj = "3.27.6"
|
||||
pdfbox = "2.0.27.0"
|
||||
zoomable = "2.9.0"
|
||||
@@ -22,6 +23,7 @@ protobuf = "0.9.5"
|
||||
protobufJavaLite = "4.33.1"
|
||||
kotlinSerialization = "1.9.0"
|
||||
reorderable = "3.0.0"
|
||||
jetbrainsKotlinJvm = "2.2.21"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -54,6 +56,7 @@ litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "
|
||||
litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }
|
||||
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
||||
opencv = { group="org.opencv", name="opencv", version.ref = "opencv" }
|
||||
opencvjava = { group="org.openpnp", name="opencv", version.ref = "opencv_java" }
|
||||
pdfbox = { group = "com.tom-roush", name = "pdfbox-android", version.ref = "pdfbox" }
|
||||
zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zoomable" }
|
||||
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
|
||||
@@ -70,3 +73,4 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
|
||||
license = { id = "com.github.hierynomus.license", version.ref = "license" }
|
||||
aboutLibrariesAndroid = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" }
|
||||
protobuf = { id = "com.google.protobuf", version.ref = "protobuf" }
|
||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
|
||||
|
||||
1
imageprocessing/.gitignore
vendored
Normal file
1
imageprocessing/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
19
imageprocessing/build.gradle.kts
Normal file
19
imageprocessing/build.gradle.kts
Normal file
@@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
id("java-library")
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm)
|
||||
}
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation(libs.opencvjava)
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation(libs.assertj)
|
||||
}
|
||||
@@ -12,15 +12,11 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain
|
||||
package org.fairscan.imageprocessing
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.graphics.createBitmap
|
||||
import org.fairscan.app.domain.ImageSegmentationService.Segmentation
|
||||
import org.fairscan.app.domain.quad.detectDocumentQuadFromProbmap
|
||||
import org.fairscan.app.domain.quad.findQuadFromRightAngles
|
||||
import org.fairscan.app.domain.quad.minAreaRect
|
||||
import org.opencv.android.Utils
|
||||
import org.fairscan.imageprocessing.quad.detectDocumentQuadFromProbmap
|
||||
import org.fairscan.imageprocessing.quad.findQuadFromRightAngles
|
||||
import org.fairscan.imageprocessing.quad.minAreaRect
|
||||
import org.opencv.core.Core
|
||||
import org.opencv.core.CvType
|
||||
import org.opencv.core.Mat
|
||||
@@ -31,7 +27,13 @@ import org.opencv.imgproc.Imgproc
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
fun detectDocumentQuad(mask: Segmentation, isLiveAnalysis: Boolean, minQuadAreaRatio: Double = 0.02): Quad? {
|
||||
interface Mask {
|
||||
val width: Int
|
||||
val height: Int
|
||||
fun toMat(): Mat
|
||||
}
|
||||
|
||||
fun detectDocumentQuad(mask: Mask, isLiveAnalysis: Boolean, minQuadAreaRatio: Double = 0.02): Quad? {
|
||||
val mat = mask.toMat()
|
||||
val (biggest: MatOfPoint2f?, area) = biggestContour(mat)
|
||||
var vertices: List<Point>?
|
||||
@@ -114,7 +116,7 @@ fun refineMask(original: Mat): Mat {
|
||||
return opened
|
||||
}
|
||||
|
||||
fun extractDocument(originalBitmap: Bitmap, quad: Quad, rotationDegrees: Int): Bitmap {
|
||||
fun extractDocument(inputMat: Mat, quad: Quad, rotationDegrees: Int): Mat {
|
||||
val widthTop = norm(quad.topLeft, quad.topRight)
|
||||
val widthBottom = norm(quad.bottomLeft, quad.bottomRight)
|
||||
val targetWidth = (widthTop + widthBottom) / 2
|
||||
@@ -137,8 +139,6 @@ fun extractDocument(originalBitmap: Bitmap, quad: Quad, rotationDegrees: Int): B
|
||||
)
|
||||
val transform = Imgproc.getPerspectiveTransform(srcPoints, dstPoints)
|
||||
|
||||
val inputMat = Mat()
|
||||
Utils.bitmapToMat(originalBitmap, inputMat)
|
||||
val outputMat = Mat()
|
||||
val outputSize = Size(targetWidth.toDouble(), targetHeight.toDouble())
|
||||
Imgproc.warpPerspective(inputMat, outputMat, transform, outputSize)
|
||||
@@ -147,7 +147,7 @@ fun extractDocument(originalBitmap: Bitmap, quad: Quad, rotationDegrees: Int): B
|
||||
val enhanced = enhanceCapturedImage(resized)
|
||||
val rotated = rotate(enhanced, rotationDegrees)
|
||||
|
||||
return toBitmap(rotated)
|
||||
return rotated
|
||||
}
|
||||
|
||||
fun resize(original: Mat, targetMax: Double): Mat {
|
||||
@@ -177,12 +177,6 @@ fun rotate(input: Mat, degrees: Int): Mat {
|
||||
return output
|
||||
}
|
||||
|
||||
private fun toBitmap(mat: Mat): Bitmap {
|
||||
val outputBitmap = createBitmap(mat.cols(), mat.rows())
|
||||
Utils.matToBitmap(mat, outputBitmap)
|
||||
return outputBitmap
|
||||
}
|
||||
|
||||
fun Point.toCv(): org.opencv.core.Point {
|
||||
return org.opencv.core.Point(x, y)
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain
|
||||
package org.fairscan.imageprocessing
|
||||
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.hypot
|
||||
@@ -12,9 +12,8 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain
|
||||
package org.fairscan.imageprocessing
|
||||
|
||||
import android.util.Log
|
||||
import org.opencv.core.Core
|
||||
import org.opencv.core.CvType
|
||||
import org.opencv.core.Mat
|
||||
@@ -25,12 +24,10 @@ import kotlin.math.max
|
||||
|
||||
fun enhanceCapturedImage(img: Mat): Mat {
|
||||
return if (isColoredDocument(img)) {
|
||||
Log.i("PostProcessing", "color document")
|
||||
val result = Mat()
|
||||
Core.convertScaleAbs(img, result, 1.2, 10.0)
|
||||
result
|
||||
} else {
|
||||
Log.i("PostProcessing", "grayscale document")
|
||||
val gray = multiScaleRetinex(img)
|
||||
val contrastedGray = enhanceContrastAuto(gray)
|
||||
val result = Mat()
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain.quad
|
||||
package org.fairscan.imageprocessing.quad
|
||||
|
||||
import org.opencv.core.Mat
|
||||
import org.opencv.core.CvType
|
||||
@@ -12,9 +12,9 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain.quad
|
||||
package org.fairscan.imageprocessing.quad
|
||||
|
||||
import org.fairscan.app.domain.Point
|
||||
import org.fairscan.imageprocessing.Point
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain.quad
|
||||
package org.fairscan.imageprocessing.quad
|
||||
|
||||
import org.fairscan.app.domain.Point
|
||||
import org.fairscan.imageprocessing.Point
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.sqrt
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.fairscan.app.domain
|
||||
package org.fairscan.imageprocessing
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
@@ -21,4 +21,4 @@ dependencyResolutionManagement {
|
||||
|
||||
rootProject.name = "FairScan"
|
||||
include(":app")
|
||||
|
||||
include(":imageprocessing")
|
||||
|
||||
Reference in New Issue
Block a user