Skip to content

Instantly share code, notes, and snippets.

@rladstaetter
Created May 13, 2013 19:45
Show Gist options
  • Select an option

  • Save rladstaetter/5570916 to your computer and use it in GitHub Desktop.

Select an option

Save rladstaetter/5570916 to your computer and use it in GitHub Desktop.
Scala Application using JavaFX and OpenCV showing some examples for simple 2D filtering
package net.ladstatt.apps
import java.io.ByteArrayInputStream
import java.io.File
import scala.collection.mutable.ArrayBuffer
import scala.util.Failure
import scala.util.Success
import scala.util.Try
import org.opencv.core.CvType
import org.opencv.core.Mat
import org.opencv.core.MatOfByte
import org.opencv.highgui.Highgui
import org.opencv.imgproc.Imgproc
import javafx.application.Application
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue
import javafx.collections.ListChangeListener
import javafx.event.Event
import javafx.event.EventHandler
import javafx.geometry.Pos
import javafx.scene.Scene
import javafx.scene.control.ChoiceBox
import javafx.scene.control.ListCell
import javafx.scene.control.ListView
import javafx.scene.control.TextField
import javafx.scene.image.Image
import javafx.scene.image.ImageView
import javafx.scene.layout.GridPane
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox
import javafx.stage.Stage
import javafx.util.Callback
/**
* For a discussion of the concepts of this application see http://ladstatt.blogspot.com/
*/
trait Utils {
val runOnMac =
{
System.getProperty("os.name").toLowerCase match {
case "mac os x" => true
case _ => false
}
}
/**
* function to measure execution time of first function, optionally executing a display function,
* returning the time in milliseconds
*/
def time[A](a: => A, display: Long => Unit = s => ()): A = {
val now = System.nanoTime
val result = a
val micros = (System.nanoTime - now) / 1000
display(micros)
result
}
}
trait OpenCVUtils extends Utils {
def loadNativeLibs() = {
val nativeLibName = if (runOnMac) "/opt/local/share/OpenCV/java/libopencv_java244.dylib" else "c:/openCV/build/java/x64/opencv_java244.dll"
System.load(new File(nativeLibName).getAbsolutePath())
}
def filter2D(kernel: Mat)(input: Mat): Mat = {
val out = new Mat
Imgproc.filter2D(input, out, -1, kernel)
out
}
def toImage(mat: Mat): Try[Image] =
try {
val memory = new MatOfByte
Highgui.imencode(".png", mat, memory)
Success(new Image(new ByteArrayInputStream(memory.toArray())))
} catch {
case e: Throwable => Failure(e)
}
}
trait JfxUtils {
def mkChangeListener[T](onChangeAction: (ObservableValue[_ <: T], T, T) => Unit): ChangeListener[T] = {
new ChangeListener[T]() {
override def changed(observable: ObservableValue[_ <: T], oldValue: T, newValue: T) = {
onChangeAction(observable, oldValue, newValue)
}
}
}
def mkListChangeListener[E](onChangedAction: ListChangeListener.Change[_ <: E] => Unit) = new ListChangeListener[E] {
def onChanged(changeItem: ListChangeListener.Change[_ <: E]): Unit = {
onChangedAction(changeItem)
}
}
def mkCellFactoryCallback[T](listCellGenerator: ListView[T] => ListCell[T]) = new Callback[ListView[T], ListCell[T]]() {
override def call(list: ListView[T]): ListCell[T] = listCellGenerator(list)
}
def mkEventHandler[E <: Event](f: E => Unit) = new EventHandler[E] { def handle(e: E) = f(e) }
}
/**
* a variable sized gridpane, constrained by size
*/
class KernelInputArray(size: Int, kernelData: ArrayBuffer[Float], applyKernel: => Mat => Unit) extends GridPane with JfxUtils with OpenCVUtils {
def mkKernel = {
val kernel = new Mat(size, size, CvType.CV_32FC1)
kernel.put(0, 0, kernelData.toArray)
kernel
}
for {
row <- 0 until size
col <- 0 until size
} yield {
val textField = {
val tf = new TextField
tf.setPrefWidth(50)
tf.setText(kernelData(row * size + col).toString)
tf.textProperty().addListener(
mkChangeListener[String](
(obVal, oldVal, newVal) => {
try {
kernelData(row * size + col) = tf.getText.toFloat
applyKernel(mkKernel)
} catch {
case e => // no float, ignore
}
}))
tf
}
GridPane.setRowIndex(textField, row)
GridPane.setColumnIndex(textField, col)
getChildren.add(textField)
}
applyKernel(mkKernel)
}
object OpenCVFilter2D {
def main(args: Array[String]): Unit = {
Application.launch(classOf[OpenCVFilter2D], args: _*)
}
}
class OpenCVFilter2D extends Application with JfxUtils with OpenCVUtils {
override def init(): Unit = loadNativeLibs // important to have this statement on the "right" thread
def readImage(file: File): Mat = Highgui.imread(file.getAbsolutePath()) // , 0)
/**
* a map of predefined kernels which can serve as starting points for your experiments
*/
def kernels: Map[String, (Int, ArrayBuffer[Float], Float, Float)] = Map(
"unit" -> (3, ArrayBuffer[Float](
0, 0, 0,
0, 1, 0,
0, 0, 0), 1, 0),
"blur" -> (3, ArrayBuffer[Float](
0, 0.2f, 0,
0.2f, 0.2f, 0.2f,
0, 0.2f, 0), 1, 0),
"moreblur" -> (5, ArrayBuffer[Float](
0, 0, 1, 0, 0,
0, 1, 1, 1, 0,
1, 1, 1, 1, 1,
0, 1, 1, 1, 0,
0, 0, 1, 0, 0), 1f / 13, 0),
"motionblur" -> (9, ArrayBuffer[Float](
1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1), 1f / 9, 0),
"horizontaledges" -> (5, ArrayBuffer[Float](
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
-1, -1, 2, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0), 1, 0),
"verticaledges" -> (5, ArrayBuffer[Float](
0, 0, -1, 0, 0,
0, 0, -1, 0, 0,
0, 0, 4, 0, 0,
0, 0, -1, 0, 0,
0, 0, -1, 0, 0), 1, 0),
"diagonaledges" -> (5, ArrayBuffer[Float](
-1, 0, 0, 0, 0,
0, -2, 0, 0, 0,
0, 0, 6, 0, 0,
0, 0, 0, -2, 0,
0, 0, 0, 0, -1), 1, 0),
"alledges" -> (3, ArrayBuffer[Float](
-1, -1, -1,
-1, 8, -1,
-1, -1, -1), 1, 0),
"sharpen" -> (3, ArrayBuffer[Float](
-1, -1, -1,
-1, 9, -1,
-1, -1, -1), 1, 0),
"subtlesharpen" -> (5, ArrayBuffer[Float](
-1, -1, -1, -1, -1,
-1, 2, 2, 2, -1,
-1, 2, 8, 2, -1,
-1, 2, 2, 2, -1,
-1, -1, -1, -1, -1), 1f / 8, 0),
"excesssharpen" -> (3, ArrayBuffer[Float](
1, 1, 1,
1, -7, 1,
1, 1, 1), 1, 0),
"emboss" -> (3, ArrayBuffer[Float](
-1, -1, 0,
-1, 0, 1,
0, 1, 1), 1f, 0.1f),
"emboss2" -> (5, ArrayBuffer[Float](-1, -1, -1, -1, 0,
-1, -1, -1, 0, 1,
-1, -1, 0, 1, 1,
-1, 0, 1, 1, 1,
0, 1, 1, 1, 1), 1, 0))
def mkKernelInputArray(initialKernelName: String, mutateFn: => Mat => Unit): KernelInputArray = {
val (size, kernelData, factor, bias) = kernels(initialKernelName)
val initalKernel = kernelData.map(_ * factor + bias)
new KernelInputArray(size, initalKernel, mutateFn)
}
override def start(stage: Stage): Unit = {
stage.setTitle("2D Image Filters with OpenCV and JavaFX")
val canvas = new HBox
canvas.setAlignment(Pos.CENTER)
val choiceBox = new ChoiceBox[String]
choiceBox.getItems.addAll(kernels.keySet.toSeq.sortWith(_ < _): _*)
choiceBox.setValue("unit")
val input = readImage(new File("src/main/resources/turbine.png"))
toImage(input) match {
case Failure(e) => println(e.getMessage())
case Success(inputImage) => {
def mutateOutputImage(outputView: ImageView)(kernel: Mat): Unit = {
toImage(filter2D(kernel)(input)) match {
case Failure(e) =>
case Success(mutatedImage) => outputView.setImage(mutatedImage)
}
}
val originalView = new ImageView(inputImage)
val outputView = new ImageView(inputImage) // show input image initially
choiceBox.valueProperty().addListener(mkChangeListener[String](
(obVal, oldVal, newVal) => {
val cell = mkKernelInputArray(newVal, mutateOutputImage(outputView))
canvas.getChildren().clear()
val kernelAndChoiceBox = new VBox
kernelAndChoiceBox.setAlignment(Pos.CENTER)
kernelAndChoiceBox.getChildren.addAll(choiceBox, cell)
canvas.getChildren().addAll(originalView, kernelAndChoiceBox, outputView)
}))
val kernelAndChoiceBox = new VBox
kernelAndChoiceBox.setAlignment(Pos.CENTER)
kernelAndChoiceBox.getChildren.addAll(choiceBox, mkKernelInputArray("unit", mutateOutputImage(outputView)))
canvas.getChildren().addAll(originalView, kernelAndChoiceBox, outputView)
}
}
val scene = new Scene(canvas)
stage.setScene(scene)
stage.show
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment