121 lines
4.5 KiB
Kotlin
121 lines
4.5 KiB
Kotlin
package network.rs485.ben.computervision
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.launch
|
|
import org.opencv.core.*
|
|
import org.opencv.imgproc.Imgproc
|
|
import java.nio.file.Path
|
|
import kotlin.math.roundToInt
|
|
|
|
internal const val DISTANCE_DEVIATION: Double = 1.0
|
|
internal const val DISTANCE_STEP = 0.5
|
|
internal const val DILATION_SIZE = 1.0
|
|
|
|
fun interface DeviationFunction {
|
|
fun calculate(level: Int, value: Double): Double
|
|
}
|
|
|
|
data class DeviationParameters(val deviationMin: Int, val deviationFunc: DeviationFunction, val maximumDeviation: Double) {
|
|
val deviationRange = deviationMin until HIST_SIZE
|
|
}
|
|
|
|
class Golden(
|
|
val goldenName: String,
|
|
val goldenColor: Scalar,
|
|
entity: Entity,
|
|
val image: Mat,
|
|
val displayImage: Mat,
|
|
val windowManager: IWindowManager,
|
|
val deviationParameters: DeviationParameters,
|
|
) : IdentifiableEntity(entity, ImageDimensionProvider(image)) {
|
|
companion object {
|
|
suspend fun createGolden(
|
|
parentScope: CoroutineScope,
|
|
scanner: EntityScanner,
|
|
goldenName: String,
|
|
goldenPath: Path,
|
|
goldenColor: Scalar,
|
|
deviationParameters: DeviationParameters,
|
|
): Golden {
|
|
val windowMgr = WINDOW_MANAGER_FACTORY.createWindowManager(goldenName)
|
|
val (displayImage, image) = CvContext.readAndConvertImage(goldenPath)
|
|
parentScope.launch {
|
|
windowMgr.display(displayImage)
|
|
}
|
|
windowMgr.setDimension(256, 256)
|
|
val entity =
|
|
scanner.scanImage(image, displayImage, windowMgr)
|
|
.takeIf { it.size == 1 }
|
|
?.get(0)
|
|
?: throw RuntimeException("Cannot find $goldenName in golden image")
|
|
val golden = Golden(
|
|
goldenName = goldenName,
|
|
goldenColor = goldenColor,
|
|
entity = entity,
|
|
image = image,
|
|
displayImage = displayImage,
|
|
windowManager = windowMgr,
|
|
deviationParameters = deviationParameters,
|
|
)
|
|
golden.distanceMat.copyToColor(displayImage)
|
|
parentScope.launch {
|
|
golden.updateImage()
|
|
}
|
|
return golden
|
|
}
|
|
}
|
|
|
|
val orientation: Line = run {
|
|
val midX = minMaxDistance.maxLoc.x.roundToInt()
|
|
val midY = minMaxDistance.maxLoc.y.roundToInt()
|
|
val midRounded = Pixel(midX, midY)
|
|
if (minMaxDistance.maxLoc != midRounded.vec2D()) println("${minMaxDistance.maxLoc} != ${midRounded.vec2D()}")
|
|
transformedLines.filter { it.first.y >= midY }
|
|
.flatMap { (first, second) -> listOf(first, second) }
|
|
.map { startPixel ->
|
|
val endPixel = entityMask.searchGreatest(
|
|
center = midRounded,
|
|
direction = (midRounded - startPixel).vec2D().normalize(),
|
|
searchRange = (0..(cols + rows)) // use distance step?
|
|
) ?: throw RuntimeException(
|
|
"Could not find any border for $goldenName center $midRounded in $entityMask:\n" +
|
|
entityMask.dump()
|
|
)
|
|
startPixel to endPixel
|
|
}
|
|
.maxByOrNull { it.vec2D().magnitude }
|
|
?: throw IllegalStateException("Could not find orientation for $goldenName")
|
|
}
|
|
|
|
val orientationRadius = orientation.vec2D().magnitude / 2.0
|
|
|
|
val entityMaskDilated: Mat = Mat(rows, cols, entityMask.type()).also { dest ->
|
|
val dilationKernel = Imgproc.getStructuringElement(
|
|
/* shape = */ Imgproc.MORPH_ELLIPSE,
|
|
/* ksize = */ Size(2 * DILATION_SIZE + 1, 2 * DILATION_SIZE + 1),
|
|
)
|
|
Imgproc.dilate(entityMask, dest, dilationKernel)
|
|
}
|
|
|
|
internal inline fun <T> withCentrumMask(identity: IdentifiableEntity, block: (Mat) -> T): T =
|
|
CvContext.withMat(MatInfo(identity.rows, identity.cols, CvType.CV_8UC1)) { maskMat ->
|
|
Core.inRange(
|
|
/* src = */ identity.distanceMat,
|
|
/* lowerb = */ Scalar(minMaxDistance.maxVal.minus(DISTANCE_DEVIATION)),
|
|
/* upperb = */ Scalar(minMaxDistance.maxVal.plus(DISTANCE_DEVIATION)),
|
|
/* dst = */ maskMat,
|
|
)
|
|
block(maskMat)
|
|
}
|
|
|
|
suspend fun updateImage() = windowManager.updateImage(displayImage)
|
|
|
|
override fun release() {
|
|
super.release()
|
|
entityMaskDilated.release()
|
|
windowManager.dispose()
|
|
}
|
|
|
|
override fun toString(): String = "Golden($goldenName)"
|
|
}
|