While almost all of my JavaFX friends are somewhere in SF attending a small, unknown Java conference I have refined the code of the Color Extractor application (formerly known as HSV Adjuster). ;-)
Here is a screencast showing the application in action:
Here is a screenshot:
In short, the application now combines the input signal with the HSV mask you can create using the three sliders. The application writes this information into the alpha channel of the input image stream, resulting in pictures like above.
The neat thing is that the image stream is taken from your webcam, and thus it is an interactive way to explore the effects of different settings.
This solves also the greatest shortcoming of the HSV Adjuster application, which didn't yet combine the alpha information with the input image but only showed the alpha channel in black and white. The latter has its own aesthetic appeal, but I think the color extractor application better shows the original intend I had.
The neat thing is that the image stream is taken from your webcam, and thus it is an interactive way to explore the effects of different settings.
This solves also the greatest shortcoming of the HSV Adjuster application, which didn't yet combine the alpha information with the input image but only showed the alpha channel in black and white. The latter has its own aesthetic appeal, but I think the color extractor application better shows the original intend I had.
Implementation Notes:
The application is written in Scala, the GUI Frontend was done in JavaFX and the image processing works with OpenCV by using its Java bindings. OpenCV can split each color channel (RGB) and combine it with alpha channel information (see the alphaBlend method in the source code below).
The conversion of OpenCV Mat data to images which can be displayed by ImageView components is done by the toImage function - in contrast to earlier versions of my Webcam API layer I'm using the approach discussed here - this optimization speeds up the application considerably.
The conversion of OpenCV Mat data to images which can be displayed by ImageView components is done by the toImage function - in contrast to earlier versions of my Webcam API layer I'm using the approach discussed here - this optimization speeds up the application considerably.
Source code of the application is available here. Below you can find the source of the main application for fast reference.
Guys, I wish you a nice time in SF and hope we'll see stunning new work for the JVM platform.
Guys, I wish you a nice time in SF and hope we'll see stunning new work for the JVM platform.
This file contains 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 javafx.scene._ | |
import org.opencv.core._ | |
import org.opencv.imgproc.Imgproc | |
import javafx.application.{Platform, Application} | |
import javafx.scene.control._ | |
import javafx.scene.image.ImageView | |
import javafx.scene.layout.BorderPane | |
import javafx.stage.Stage | |
import java.util.ResourceBundle | |
import javafx.fxml.{FXML, Initializable} | |
import java.net.URL | |
import javafx.beans.property.SimpleObjectProperty | |
import net.ladstatt.jfx._ | |
import org.controlsfx.control.{HsvSlider, RangeSlider} | |
object ColorExtractor { | |
def main(args: Array[String]): Unit = { | |
Application.launch(classOf[ColorExtractor], args: _*) | |
} | |
} | |
class ColorExtractor extends Application with JfxUtils with Initializable with OpenCVUtils { | |
val lowerBoundProperty = new SimpleObjectProperty[Scalar](new Scalar(0, 0, 0)) | |
def setLowerBound(lb: Scalar) = lowerBoundProperty.set(lb) | |
def getLowerBound() = lowerBoundProperty.get | |
val upperBoundProperty = new SimpleObjectProperty[Scalar](new Scalar(255, 255, 255)) | |
def setUpperBound(lb: Scalar) = upperBoundProperty.set(lb) | |
def getUpperBound() = upperBoundProperty.get | |
override def init(): Unit = loadNativeLibs // important to have this statement on the "right" thread | |
def colorSpace(conversionMethod: Int = Imgproc.COLOR_BGR2GRAY)(input: Mat): Mat = { | |
val colorTransformed = new Mat | |
Imgproc.cvtColor(input, colorTransformed, conversionMethod) | |
colorTransformed | |
} | |
def restrain(input: Mat): Mat = { | |
val dest = new Mat | |
val lb = getLowerBound() | |
val ub = getUpperBound() | |
Core.inRange(input, lb, ub, dest) | |
dest | |
} | |
def alphaBlend(src: Mat, alpha: Mat): Mat = { | |
val channels = new java.util.ArrayList[Mat]() | |
Core.split(src, channels) | |
channels.add(alpha) | |
val merged = new Mat | |
Core.merge(channels, merged) | |
merged | |
} | |
@FXML var textFieldLbHue: TextField = _ | |
@FXML var textFieldLbSaturation: TextField = _ | |
@FXML var textFieldLbValue: TextField = _ | |
@FXML var textFieldUbHue: TextField = _ | |
@FXML var textFieldUbSaturation: TextField = _ | |
@FXML var textFieldUbValue: TextField = _ | |
@FXML var hueSlider: HsvSlider = _ | |
@FXML var saturationSlider: HsvSlider = _ | |
@FXML var valueSlider: HsvSlider = _ | |
@FXML var viewPort: ImageView = _ | |
override def start(stage: Stage): Unit = { | |
stage.setTitle("Color Extractor") | |
val imageService = new WebcamService | |
imageService.setOnSucceeded( | |
mkEventHandler( | |
event => { | |
val imageAsMat = event.getSource.getValue.asInstanceOf[Mat] | |
val alphaChannel = restrain(colorSpace(Imgproc.COLOR_BGR2HSV)(imageAsMat)) | |
viewPort.imageProperty.set(toImage(alphaBlend(imageAsMat, alphaChannel))) | |
imageService.restart | |
} | |
)) | |
imageService.start | |
val scene = new Scene(mk[BorderPane](mkFxmlLoader("/colorextractor.fxml", this))) | |
stage.setScene(scene) | |
stage.show | |
} | |
def initialize(url: URL, resourceBundle: ResourceBundle): Unit = { | |
def initRangeSlider(pos: Int, slider: RangeSlider, lowerTextField: TextField, upperTextField: TextField): Unit = { | |
slider.lowValueProperty().addListener(mkChangeListener[Number]( | |
(obVal, oldVal, newVal) => { | |
val mutableBounds = getLowerBound.`val` | |
mutableBounds(pos) = newVal.doubleValue() | |
setLowerBound(new Scalar(mutableBounds)) | |
lowerTextField.setText("%.2f".format(newVal.doubleValue)) | |
} | |
)) | |
slider.highValueProperty().addListener(mkChangeListener[Number]( | |
(obVal, oldVal, newVal) => { | |
val mutableBounds = getUpperBound.`val` | |
mutableBounds(pos) = newVal.doubleValue() | |
setUpperBound(new Scalar(mutableBounds)) | |
upperTextField.setText("%.2f".format(newVal.doubleValue)) | |
} | |
)) | |
} | |
def setRangeSlider(slider: RangeSlider)(range: (Double, Double)) { | |
slider.setLowValue(range._1) | |
slider.setHighValue(range._2) | |
} | |
def setHueSlider = setRangeSlider(hueSlider) _ | |
def setSaturationSlider = setRangeSlider(saturationSlider) _ | |
def setValueSlider = setRangeSlider(valueSlider) _ | |
initRangeSlider(0, hueSlider, textFieldLbHue, textFieldUbHue) | |
initRangeSlider(1, saturationSlider, textFieldLbSaturation, textFieldUbSaturation) | |
initRangeSlider(2, valueSlider, textFieldLbValue, textFieldUbValue) | |
setHueSlider((0.0, 179.0)) | |
setSaturationSlider((0.0, 255.0)) | |
setValueSlider((0.0, 255.0)) | |
} | |
} | |
I too am learning OpenCV with Scala and ScalaFX.
ReplyDeleteDo you have any tips? I'm having trouble, given ScalaFX's very limited documentation and examples that are all over the board.
You can follow the OpenJFX mailing list or @JavaFXpert on twitter for staying up to date with JavaFX development, as well as @fxexperience which gives you a weekly update about JavaFX related news.
DeleteAs far as Scala and JavaFX is concerned: you have to keep in mind that Scala + JavaFX is not the same as ScalaFX. ScalaFX is more like a DSL for creating UIs with the underlying JavaFX technology.
The source code for this article (and most of the other posts as well) is plain Scala.
I think you can get best results if you stay in a JVM Language where you are comfortable with and get proficient with JavaFX. After this it is easier to combine JavaFX with lets say Scala or Clojure for example. As for learning Scala, at the time of writing this comment there is a free online course on Coursera, which will definitely help you get to speed with Scala.