Created
May 13, 2013 19:45
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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