Adjust values for ExportQuality
This commit is contained in:
@@ -27,7 +27,7 @@ enum class ExportQuality(
|
|||||||
maxPixels = 2_000_000
|
maxPixels = 2_000_000
|
||||||
),
|
),
|
||||||
HIGH(
|
HIGH(
|
||||||
jpegQuality = 90,
|
jpegQuality = 80,
|
||||||
maxPixels = 5_000_000
|
maxPixels = 4_000_000
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Pierre-Yves Nicolas
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
* 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.evaluation
|
||||||
|
|
||||||
|
import org.fairscan.imageprocessing.detectDocumentQuad
|
||||||
|
import org.fairscan.imageprocessing.extractDocument
|
||||||
|
import org.fairscan.imageprocessing.isColoredDocument
|
||||||
|
import org.fairscan.imageprocessing.scaledTo
|
||||||
|
import org.opencv.core.MatOfInt
|
||||||
|
import org.opencv.imgcodecs.Imgcodecs
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
nu.pattern.OpenCV.loadLocally()
|
||||||
|
ExportQualityEvaluator.runEvaluation()
|
||||||
|
}
|
||||||
|
|
||||||
|
object ExportQualityEvaluator {
|
||||||
|
|
||||||
|
fun runEvaluation() {
|
||||||
|
val root = File("evaluation")
|
||||||
|
val datasetDir = File(root, "dataset")
|
||||||
|
val imageDir = File(datasetDir, "images")
|
||||||
|
val outputDir = File("evaluation/reports/export_quality").apply { mkdirs() }
|
||||||
|
|
||||||
|
val imgFiles = imageDir.listFiles { f -> f.extension.lowercase() == "jpg" }
|
||||||
|
?.toList() ?: listOf<File>()
|
||||||
|
|
||||||
|
val qualities = listOf(60, 75, 80)
|
||||||
|
val maxPixelsList = listOf(1_500_000, 2_000_000, 4_000_000)
|
||||||
|
|
||||||
|
for (imgFile in imgFiles) {
|
||||||
|
val imgName = imgFile.nameWithoutExtension
|
||||||
|
val maskFile = File(datasetDir, "masks/$imgName.png")
|
||||||
|
if (!maskFile.exists()) continue
|
||||||
|
|
||||||
|
val sourceMat = Imgcodecs.imread(imgFile.absolutePath)
|
||||||
|
if (sourceMat.empty()) continue
|
||||||
|
|
||||||
|
val maskMat = Imgcodecs.imread(maskFile.absolutePath, Imgcodecs.IMREAD_UNCHANGED)
|
||||||
|
if (maskMat.empty()) continue
|
||||||
|
|
||||||
|
println("Processing ${imgName}...")
|
||||||
|
|
||||||
|
val mask = MatMask(maskMat)
|
||||||
|
|
||||||
|
val quad = detectDocumentQuad(mask, isLiveAnalysis = false)
|
||||||
|
?.scaledTo(mask.width, mask.height, sourceMat.width(), sourceMat.height())
|
||||||
|
if (quad == null) {
|
||||||
|
println("Failed to detect quad for $imgName")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val isColored = isColoredDocument(sourceMat, mask, quad)
|
||||||
|
|
||||||
|
for (quality in qualities) {
|
||||||
|
|
||||||
|
for (maxPixels in maxPixelsList) {
|
||||||
|
val outputMat =
|
||||||
|
extractDocument(sourceMat, quad, 0, isColored, maxPixels.toLong())
|
||||||
|
|
||||||
|
val outputFile = File(outputDir, "$imgName-$quality-$maxPixels.jpg")
|
||||||
|
val params = MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, quality)
|
||||||
|
if (!Imgcodecs.imwrite(outputFile.absolutePath, outputMat, params)) {
|
||||||
|
throw RuntimeException("Could not write image to ${outputFile.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
params.release()
|
||||||
|
outputMat.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceMat.release()
|
||||||
|
generateHtmlReport(outputDir, imgName, qualities, maxPixelsList, crop = CropParams())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CropParams(
|
||||||
|
val centerX: Double = 0.5,
|
||||||
|
val centerY: Double = 0.5,
|
||||||
|
val size: Double = 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
fun generateHtmlReport(
|
||||||
|
outputDir: File,
|
||||||
|
imgName: String,
|
||||||
|
qualities: List<Int>,
|
||||||
|
maxPixelsList: List<Int>,
|
||||||
|
crop: CropParams
|
||||||
|
) {
|
||||||
|
val htmlFile = File(outputDir, "$imgName.html")
|
||||||
|
|
||||||
|
htmlFile.writeText(
|
||||||
|
"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>$imgName – Export quality comparison</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(${maxPixelsList.size}, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.cell {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.crop {
|
||||||
|
width: 600px;
|
||||||
|
height: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
.crop img {
|
||||||
|
transform-origin: top left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>$imgName</h1>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
Center X <input type="range" id="cx" min="0" max="1" step="0.01" value="${crop.centerX}">
|
||||||
|
Center Y <input type="range" id="cy" min="0" max="1" step="0.01" value="${crop.centerY}">
|
||||||
|
Size <input type="range" id="size" min="0.05" max="0.5" step="0.01" value="${crop.size}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${qualities.joinToString("") { q ->
|
||||||
|
"""
|
||||||
|
<div class="grid">
|
||||||
|
${
|
||||||
|
maxPixelsList.joinToString("") { mp ->
|
||||||
|
val fileName = "$imgName-$q-$mp.jpg"
|
||||||
|
"""
|
||||||
|
<div class="cell">
|
||||||
|
<div>q$q - $mp px - ${File(outputDir, fileName).length() / 1024}kB</div>
|
||||||
|
<div class="crop">
|
||||||
|
<img src="$fileName" data-img>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
}}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const imgs = document.querySelectorAll('[data-img]');
|
||||||
|
const cxInput = document.getElementById('cx');
|
||||||
|
const cyInput = document.getElementById('cy');
|
||||||
|
const sizeInput = document.getElementById('size');
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const cx = parseFloat(cxInput.value);
|
||||||
|
const cy = parseFloat(cyInput.value);
|
||||||
|
const size = parseFloat(sizeInput.value);
|
||||||
|
|
||||||
|
imgs.forEach(img => {
|
||||||
|
const crop = img.parentElement;
|
||||||
|
const cropSize = crop.clientWidth;
|
||||||
|
|
||||||
|
const iw = img.naturalWidth;
|
||||||
|
const ih = img.naturalHeight;
|
||||||
|
|
||||||
|
const minDim = Math.min(iw, ih);
|
||||||
|
|
||||||
|
const cropPixels = size * minDim;
|
||||||
|
|
||||||
|
const scale = cropSize / cropPixels;
|
||||||
|
|
||||||
|
const centerX = cx * iw;
|
||||||
|
const centerY = cy * ih;
|
||||||
|
|
||||||
|
const x0 = centerX - cropPixels / 2;
|
||||||
|
const y0 = centerY - cropPixels / 2;
|
||||||
|
|
||||||
|
img.style.transform =
|
||||||
|
`translate(${'$'}{-x0 * scale}px, ${'$'}{-y0 * scale}px) scale(${'$'}{scale})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
imgs.forEach(img => {
|
||||||
|
if (img.complete) update();
|
||||||
|
else img.onload = update;
|
||||||
|
});
|
||||||
|
|
||||||
|
cxInput.oninput = update;
|
||||||
|
cyInput.oninput = update;
|
||||||
|
sizeInput.oninput = update;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user