computer-vision-project/src/main/kotlin/network/rs485/ben/computervision/CvContext.kt

111 lines
4.3 KiB
Kotlin

package network.rs485.ben.computervision
import io.ktor.utils.io.pool.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.opencv.core.*
import org.opencv.imgcodecs.Imgcodecs
import org.opencv.imgproc.Imgproc
import java.nio.file.Path
data class MatInfo(val rows: Int, val cols: Int, val type: Int) {
companion object {
fun from(mat: Mat) = MatInfo(mat.rows(), mat.cols(), mat.type())
fun from(mat: Mat, type: Int) = MatInfo(mat.rows(), mat.cols(), type)
}
fun create(): Mat = Mat(rows, cols, type)
}
private val histMatInfo = MatInfo(256, 1, CvType.CV_32FC1)
private val predefinedChannels = listOf(16, 24, 64, 128)
private val matPoolsToCreate = listOf(
MatInfo(32, 32, CvType.CV_8UC1) to Runtime.getRuntime().availableProcessors() * 4,
histMatInfo to Runtime.getRuntime().availableProcessors() * 4,
) + predefinedChannels.map {
MatInfo(it, it, CvType.CV_8UC1) to Runtime.getRuntime().availableProcessors() * 2
}
object CvContext {
lateinit var gaussianKernel: Mat
lateinit var histChannelPar: MatOfInt
lateinit var histSizePar: MatOfInt
lateinit var histRangesPar: MatOfFloat
lateinit var matPools: HashMap<MatInfo, MatPool>
fun initialize() {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME)
this.gaussianKernel = Imgproc.getGaussianKernel(GAUSSIAN_KERNEL_SIZE, -1.0)
this.histChannelPar = MatOfInt(0)
this.histSizePar = MatOfInt(HIST_SIZE)
this.histRangesPar = MatOfFloat(0f, 255f)
this.matPools = matPoolsToCreate.associateTo(HashMap()) { (matInfo, capacity) ->
matInfo to MatPool(matInfo, capacity)
}
}
internal inline fun <T> withGrayCalcHist(image: Mat, mask: Mat, block: (Mat) -> T): T = withMat(histMatInfo) { hist ->
Imgproc.calcHist(
/* images = */ listOf(image),
/* channels = */ histChannelPar,
/* mask = */ mask,
/* hist = */ hist,
/* histSize = */ histSizePar,
/* ranges = */ histRangesPar,
)
block(hist)
}
internal inline fun <T> withRotationMatrix(angle: Double, center: Point, block: (Mat) -> T): T =
Imgproc.getRotationMatrix2D(
/* center = */ center,
/* angle = */ Math.toDegrees(angle),
/* scale = */ 1.0,
).letAutoclean(block)
class MatPool(
private val info: MatInfo,
capacity: Int,
) : DefaultPool<Mat>(capacity) {
override fun produceInstance(): Mat = info.create()
}
internal inline fun <T> withMat(matInfo: MatInfo, block: (Mat) -> T): T {
val upperMatInfo = if (matInfo.type == CvType.CV_8UC1) {
predefinedChannels.find { matInfo.rows <= it && matInfo.cols <= it }
?.let { MatInfo(it, it, CvType.CV_8UC1) }
?: matInfo
} else matInfo
return if (matPools.containsKey(upperMatInfo)) {
val pool = matPools[upperMatInfo]!!
val mat = pool.borrow()
autoclean(mat, { pool.recycle(mat) }, block)
} else {
println("Not pooled: $matInfo")
matInfo.create().letAutoclean(block)
}
}
internal fun recycle(image: Mat) = matPools[MatInfo.from(image)]?.recycle(image) ?: image.release()
internal suspend fun readAndConvertImage(imagePath: Path): Pair<Mat, Mat> {
val displayImage = withContext(Dispatchers.IO) {
Imgcodecs.imread(imagePath.toString())
} ?: throw RuntimeException("Could not load image from $imagePath")
println("Loaded image $imagePath as $displayImage")
val matInfo = MatInfo(displayImage.rows(), displayImage.cols(), CvType.CV_8UC1)
val image = matPools.computeIfAbsent(matInfo) {
MatPool(matInfo, Runtime.getRuntime().availableProcessors())
}.borrow()
Imgproc.cvtColor(displayImage, image, Imgproc.COLOR_BGR2GRAY)
return displayImage to image
}
}
internal inline fun <V, T> autoclean(obj: V, release: () -> Unit, block: (V) -> T): T =
runCatching { block(obj) }.also { release() }.getOrThrow()
internal inline fun <T> Mat.letAutoclean(block: (Mat) -> T): T =
autoclean(this, this::release, block)
internal inline fun <T> withTmpMat(block: (tmp: Mat) -> T): T = Mat().letAutoclean(block)