The application I'm describing in this blog post can help you determining HSV values for objects you show to your webcam.
Here is a video:
Here is a screenshot:
Here is a video:
Here is a screenshot:
![]() |
screenshot of the application |
Here is complete the source, lookup apps/hsvadjuster/ application.
Maybe you take the time to read a little about HSV in the wikipedia.
Maybe you take the time to read a little about HSV in the wikipedia.
This application uses b103 of the early access release of JDK8 and additionally the controlsFX library written by the fxexperience team. The widget I'm using is called RangeSlider.
This time I've used fxml, if you want to use widgets like the RangeSlider don't forget to import them in the header instructions.
This post was very heavily inspired by a blog post on object detection using color separation for C++. Thanks for sharing. There you can find how to use the application to find proper lower and upper bounds for your light conditions and target colors.
For reference, I've created a gist to quickly browse through the key parts of the code:
This time I've used fxml, if you want to use widgets like the RangeSlider don't forget to import them in the header instructions.
This post was very heavily inspired by a blog post on object detection using color separation for C++. Thanks for sharing. There you can find how to use the application to find proper lower and upper bounds for your light conditions and target colors.
For reference, I've created a gist to quickly browse through the key parts of the code:
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 scala.Some | |
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 HsvAdjuster { | |
def main(args: Array[String]): Unit = { | |
Application.launch(classOf[HsvAdjuster], args: _*) | |
} | |
} | |
class HsvAdjuster extends Application with JfxUtils with Initializable with OpenCVUtils { | |
// width and height of input image | |
val (webCamWidth, webCamHeight) = (1280, 720) | |
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 | |
} | |
val colorRanges: Map[String, (Double, Double)] = Map("ALLHue" ->((0.0, 179.0)), | |
"ALLSaturation" ->((0.0, 255.0)), | |
"ALLValue" ->((0.0, 255.0)), | |
"orange" ->((165.0, 179.0)), | |
"yellow" ->((16.0, 28.0)), | |
"green" ->((92.0, 103.0)), | |
"blue" ->((103.0, 115.0)), | |
"white" ->((130.0, 160.0)), | |
"red" ->((160.0, 179.0))) | |
@FXML var labelLbHue: Label = _ | |
@FXML var labelLbSaturation: Label = _ | |
@FXML var labelLbValue: Label = _ | |
@FXML var labelUbHue: Label = _ | |
@FXML var labelUbSaturation: Label = _ | |
@FXML var labelUbValue: Label = _ | |
@FXML var hueSlider: HsvSlider = _ | |
@FXML var saturationSlider: HsvSlider = _ | |
@FXML var valueSlider: HsvSlider = _ | |
@FXML var viewPort: ImageView = _ | |
@FXML var orangePresetButton: Button = _ | |
@FXML var yellowPresetButton: Button = _ | |
@FXML var greenPresetButton: Button = _ | |
@FXML var bluePresetButton: Button = _ | |
@FXML var whitePresetButton: Button = _ | |
@FXML var redPresetButton: Button = _ | |
def initPresetButton(button: Button, range: (Double, Double)) { | |
button.setOnAction(mkEventHandler(e => { | |
hueSlider.setLowValue(range._1) | |
hueSlider.setHighValue(range._2) | |
})) | |
} | |
def toGray = colorSpace(Imgproc.COLOR_BGR2GRAY) _ | |
def toHsv = colorSpace(Imgproc.COLOR_BGR2HSV) _ | |
override def start(stage: Stage): Unit = { | |
stage.setTitle("HsvAdjuster") | |
val imageService = new WebcamService | |
imageService.setOnSucceeded( | |
mkEventHandler( | |
event => { | |
val grabbedMat = restrain(colorSpace(Imgproc.COLOR_BGR2HSV)(event.getSource.getValue.asInstanceOf[Mat])) | |
Platform.runLater( | |
new Runnable() { | |
def run = { | |
viewPort.imageProperty.set(toImage(grabbedMat)) | |
imageService.restart | |
} | |
} | |
) | |
} | |
)) | |
imageService.start | |
val scene = new Scene(mk[BorderPane](mkFxmlLoader("/hsvadjuster.fxml", this))) | |
stage.setScene(scene) | |
stage.show | |
} | |
def initialize(url: URL, resourceBundle: ResourceBundle): Unit = { | |
def initRangeSlider(pos: Int, slider: RangeSlider, lowerLabel: Label, upperLabel: Label): Unit = { | |
slider.lowValueProperty().addListener(mkChangeListener[Number]( | |
(obVal, oldVal, newVal) => { | |
val mutableBounds = getLowerBound.`val` | |
mutableBounds(pos) = newVal.doubleValue() | |
setLowerBound(new Scalar(mutableBounds)) | |
lowerLabel.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)) | |
upperLabel.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, labelLbHue, labelUbHue) | |
initRangeSlider(1, saturationSlider, labelLbSaturation, labelUbSaturation) | |
initRangeSlider(2, valueSlider, labelLbValue, labelUbValue) | |
setHueSlider(colorRanges("ALLHue")) | |
setSaturationSlider(colorRanges("ALLSaturation")) | |
setValueSlider(colorRanges("ALLValue")) | |
initPresetButton(orangePresetButton, colorRanges("orange")) | |
initPresetButton(yellowPresetButton, colorRanges("yellow")) | |
initPresetButton(greenPresetButton, colorRanges("green")) | |
initPresetButton(bluePresetButton, colorRanges("blue")) | |
initPresetButton(whitePresetButton, colorRanges("white")) | |
initPresetButton(redPresetButton, colorRanges("red")) | |
} | |
} | |