New quad algorithm: identify edges from contour orientation (#130)

Goal: improve precision of automatic document cropping by switching:
- from Douglas-Peucker algorithm (OpenCV's approxPolyDP) + a heuristic for documents missing a corner
- to an algorithm that looks for edges

* New quad algorithm: identify edges from contour orientation
* Performance optimization: reduce number of calls to trigonometric functions
* Performance: use a single threshold for live analysis
* Fix orientation of debug mask and compute it only if required
* Exclude quads that go out of the frame
This commit is contained in:
Pierre-Yves Nicolas
2026-03-07 12:09:41 +01:00
committed by GitHub
parent cf196576fe
commit 343495dafe
14 changed files with 488 additions and 316 deletions

View File

@@ -18,6 +18,7 @@ import org.fairscan.imageprocessing.detectDocumentQuad
import org.fairscan.imageprocessing.extractDocument
import org.fairscan.imageprocessing.isColoredDocument
import org.fairscan.imageprocessing.scaledTo
import org.fairscan.imageprocessing.toImageSize
import org.opencv.imgcodecs.Imgcodecs
import java.io.File
@@ -57,7 +58,7 @@ object ColorDetectionEvaluator {
val mask = MatMask(maskMat)
val quad = detectDocumentQuad(mask, isLiveAnalysis = false)
val quad = detectDocumentQuad(mask, mat.size().toImageSize(), isLiveAnalysis = false)
?.scaledTo(mask.width, mask.height, mat.width(), mat.height())
if (quad == null) continue

View File

@@ -19,6 +19,7 @@ import org.fairscan.imageprocessing.detectDocumentQuad
import org.fairscan.imageprocessing.extractDocument
import org.fairscan.imageprocessing.isColoredDocument
import org.fairscan.imageprocessing.scaledTo
import org.fairscan.imageprocessing.toImageSize
import org.opencv.core.Mat
import org.opencv.imgcodecs.Imgcodecs
import java.io.File
@@ -68,7 +69,8 @@ object DatasetEvaluator {
val mask = MatMask(maskMat)
val quad = detectDocumentQuad(mask, isLiveAnalysis = false)
val originalSize = inputMat.size().toImageSize()
val quad = detectDocumentQuad(mask, originalSize, isLiveAnalysis = false)
?.scaledTo(mask.width, mask.height, inputMat.width(), inputMat.height())
val corrected: Mat? = if (quad != null) {

View File

@@ -18,6 +18,7 @@ import org.fairscan.imageprocessing.detectDocumentQuad
import org.fairscan.imageprocessing.extractDocument
import org.fairscan.imageprocessing.isColoredDocument
import org.fairscan.imageprocessing.scaledTo
import org.fairscan.imageprocessing.toImageSize
import org.opencv.core.MatOfInt
import org.opencv.imgcodecs.Imgcodecs
import java.io.File
@@ -56,7 +57,8 @@ object ExportQualityEvaluator {
val mask = MatMask(maskMat)
val quad = detectDocumentQuad(mask, isLiveAnalysis = false)
val originalSize = sourceMat.size().toImageSize()
val quad = detectDocumentQuad(mask, originalSize, isLiveAnalysis = false)
?.scaledTo(mask.width, mask.height, sourceMat.width(), sourceMat.height())
if (quad == null) {
println("Failed to detect quad for $imgName")

View File

@@ -18,6 +18,7 @@ import nu.pattern.OpenCV
import org.fairscan.imageprocessing.detectDocumentQuad
import org.fairscan.imageprocessing.scaledTo
import org.fairscan.imageprocessing.toCv
import org.fairscan.imageprocessing.toImageSize
import org.opencv.core.Core
import org.opencv.core.Mat
import org.opencv.core.Scalar
@@ -63,7 +64,8 @@ object QuadDetectionEvaluator {
val mask = MatMask(maskMat)
val quad = detectDocumentQuad(mask, isLiveAnalysis = false)
val originalSize = inputMat.size().toImageSize()
val quad = detectDocumentQuad(mask, originalSize, isLiveAnalysis = false)
?.scaledTo(mask.width, mask.height, inputMat.width(), inputMat.height())
val inputOut = File(outputDir, "${e.name}_input.jpg")