Capture: fallback to last successful live analysis for quad detection (#12)

This commit is contained in:
pynicolas
2025-07-27 10:20:25 +02:00
committed by GitHub
parent d8697b5cb3
commit 949ca3c3f9
5 changed files with 77 additions and 5 deletions

View File

@@ -68,7 +68,7 @@ fun detectDocumentQuad(mask: Bitmap, minQuadAreaRatio: Double = 0.02): Quad? {
}
val vertices = biggest?.toList()?.map { Point(it.x.toInt(), it.y.toInt()) }
return createQuad(vertices)
return if (vertices?.size == 4) createQuad(vertices) else null
}
/**

View File

@@ -44,10 +44,28 @@ data class Quad(
Line(bottomRight, bottomLeft),
Line(bottomLeft, topLeft))
}
fun rotate90(iterations: Int, imageWidth: Int, imageHeight: Int): Quad {
val rotatedPoints = listOf(
rotate90(topLeft, imageWidth, imageHeight, iterations),
rotate90(topRight, imageWidth, imageHeight, iterations),
rotate90(bottomRight, imageWidth, imageHeight, iterations),
rotate90(bottomLeft, imageWidth, imageHeight, iterations)
)
return createQuad(rotatedPoints)
}
private fun rotate90(p: Point, width: Int, height: Int, iterations: Int): Point {
return when (iterations % 4) {
1 -> Point(height - p.y, p.x) // 90°
2 -> Point(width - p.x, height - p.y) // 180°
3 -> Point(p.y, width - p.x) // 270°
else -> p // 0°
}
}
}
fun createQuad(vertices: List<Point>?): Quad? {
if (vertices == null || vertices.size != 4) return null
fun createQuad(vertices: List<Point>): Quad {
require(vertices.size == 4)
// Centroid of the points
val cx = vertices.map { it.x }.average()

View File

@@ -22,4 +22,5 @@ data class LiveAnalysisState(
val inferenceTime: Long = 0L,
val binaryMask: Bitmap? = null,
val documentQuad: Quad? = null,
val timestamp: Long = System.currentTimeMillis(),
)

View File

@@ -65,6 +65,7 @@ class MainViewModel(
private var _liveAnalysisState = MutableStateFlow(LiveAnalysisState())
val liveAnalysisState: StateFlow<LiveAnalysisState> = _liveAnalysisState.asStateFlow()
private var lastSuccessfulLiveAnalysisState: LiveAnalysisState? = null
private val _screenStack = MutableStateFlow<List<Screen>>(listOf(Screen.Camera))
val currentScreen: StateFlow<Screen> = _screenStack.map { it.last() }
@@ -86,11 +87,15 @@ class MainViewModel(
LiveAnalysisState(
inferenceTime = it.inferenceTime,
binaryMask = binaryMask,
documentQuad = detectDocumentQuad(binaryMask)
documentQuad = detectDocumentQuad(binaryMask),
timestamp = System.currentTimeMillis(),
)
}
.collect {
_liveAnalysisState.value = it
if (it.documentQuad != null) {
lastSuccessfulLiveAnalysisState = it
}
}
}
}
@@ -164,7 +169,22 @@ class MainViewModel(
val segmentation = imageSegmentationService.runSegmentationAndReturn(bitmap, 0)
if (segmentation != null) {
val mask = segmentation.segmentation.toBinaryMask()
val quad = detectDocumentQuad(mask)
var quad = detectDocumentQuad(mask)
if (quad == null) {
val now = System.currentTimeMillis()
lastSuccessfulLiveAnalysisState?.timestamp?.let {
val offset = now - it
Log.i("Quad", "Last successful live analysis was $offset ms ago")
}
val recentLive = lastSuccessfulLiveAnalysisState?.takeIf {
now - it.timestamp <= 1500
}
val rotations = (-imageProxy.imageInfo.rotationDegrees / 90) + 4
quad = recentLive?.documentQuad?.rotate90(rotations, mask.width, mask.height)
if (quad != null) {
Log.i("Quad", "Using quad taken in live analysis; rotations=$rotations")
}
}
if (quad != null) {
val resizedQuad = quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height)
corrected = extractDocument(bitmap, resizedQuad, imageProxy.imageInfo.rotationDegrees)