Invertable hue (#428)

* Add UI-side changes for invertable hue slider

* Add hue inverted range

* Add new slider backgrounds to threshold tab

* Run spotless

* Updated libpicam.so to artifact built from commit c458bab87740 in that repo on gerth2's pi.

* undo the java-side hack since isVCSMSupported is fixed

* Hook up hue inversion frontend to backend and UI tweaks

* Remove unused .flipped class

* Fix hueInverted name in Vue.js store

Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
Co-authored-by: Matt <matthew.morley.ca@gmail.com>
Co-authored-by: Chris Gerth <chrisgerth010592@gmail.com>
This commit is contained in:
Banks T
2022-02-21 22:41:51 -05:00
committed by GitHub
parent d779fe23f0
commit 5144819ce2
11 changed files with 133 additions and 23 deletions

View File

@@ -18,10 +18,10 @@
:mandatory="true"
>
<v-radio
v-for="(name,index) in list"
v-for="(radioName,index) in list"
:key="index"
color="#ffd843"
:label="name"
:label="radioName"
:value="index"
:disabled="disabled"
/>

View File

@@ -19,7 +19,9 @@
hide-details
class="align-center"
dark
color="accent"
:color="inverted ? 'rgba(255, 255, 255, 0.2)' : 'accent'"
:track-color="inverted ? 'accent' : undefined"
thumb-color="accent"
:step="step"
@input="handleInput"
@mousedown="$emit('rollback', localValue)"
@@ -76,7 +78,7 @@ export default {
TooltippedLabel,
},
// eslint-disable-next-line vue/require-prop-types
props: ["name", "min", "max", "value", "step", "tooltip", "disabled"],
props: ["name", "min", "max", "value", "step", "tooltip", "disabled", "inverted"],
data() {
return {
prependFocused: false,

View File

@@ -6,8 +6,10 @@ function initColorPicker() {
canvas = document.createElement('canvas');
image = document.querySelector('#normal-stream');
canvas.width = image.width;
canvas.height = image.height;
if (image !== null) {
canvas.width = image.width;
canvas.height = image.height;
}
}
//Called on click of the image,

View File

@@ -64,6 +64,7 @@ export default new Vuex.Store({
hsvHue: [0, 15],
hsvSaturation: [0, 15],
hsvValue: [0, 25],
hueInverted: false,
contourArea: [0, 12],
contourRatio: [0, 12],
contourFullness: [0, 12],

View File

@@ -1,16 +1,21 @@
<template>
<div>
<div :style="{'--averageHue': averageHue}">
<CVrangeSlider
id="hue-slider"
v-model="hsvHue"
:class="hueInverted ? 'inverted-slider' : 'normal-slider'"
name="Hue"
tooltip="Describes color"
:min="0"
:max="180"
:inverted="hueInverted"
@input="handlePipelineData('hsvHue')"
@rollback="e => rollback('hue',e)"
/>
<CVrangeSlider
id="sat-slider"
v-model="hsvSaturation"
class="normal-slider"
name="Saturation"
tooltip="Describes colorfulness; the smaller this value the 'whiter' the color becomes"
:min="0"
@@ -19,7 +24,9 @@
@rollback="e => rollback('saturation',e)"
/>
<CVrangeSlider
id="value-slider"
v-model="hsvValue"
class="normal-slider"
name="Value"
tooltip="Describes lightness; the smaller this value the 'blacker' the color becomes"
:min="0"
@@ -27,6 +34,13 @@
@input="handlePipelineData('hsvValue')"
@rollback="e => rollback('value',e)"
/>
<CVSwitch
v-model="hueInverted"
name="Invert hue"
tooltip="Selects the hue range outside of the hue slider bounds instead of inside"
@input="handlePipelineData('hueInverted')"
@rollback="e => rollback('hueInverted',e)"
/>
<template v-if="currentPipelineType() === 3">
<CVSwitch
v-model="erode"
@@ -133,9 +147,26 @@ export default {
this.$store.commit("mutatePipeline", {"hsvHue": val})
}
},
averageHue: {
get() {
const arr = this.$store.getters.currentPipelineSettings.hsvHue;
if (Array.isArray(arr)) {
return (arr[0] + arr[1])
}
return (arr.first + arr.second);
},
},
hueInverted: {
get() {
return this.$store.getters.currentPipelineSettings.hueInverted;
},
set(val) {
this.$store.commit("mutatePipeline", {"hueInverted": val});
}
},
hsvSaturation: {
get() {
return this.$store.getters.currentPipelineSettings.hsvSaturation
return this.$store.getters.currentPipelineSettings.hsvSaturation;
},
set(val) {
this.$store.commit("mutatePipeline", {"hsvSaturation": val})
@@ -143,15 +174,15 @@ export default {
},
hsvValue: {
get() {
return this.$store.getters.currentPipelineSettings.hsvValue
return this.$store.getters.currentPipelineSettings.hsvValue;
},
set(val) {
this.$store.commit("mutatePipeline", {"hsvValue": val})
this.$store.commit("mutatePipeline", {"hsvValue": val});
}
},
erode: {
get() {
return this.$store.getters.currentPipelineSettings.erode
return this.$store.getters.currentPipelineSettings.erode;
},
set(val) {
this.$store.commit("mutatePipeline", {"erode": val});
@@ -159,7 +190,7 @@ export default {
},
dilate: {
get() {
return this.$store.getters.currentPipelineSettings.dilate
return this.$store.getters.currentPipelineSettings.dilate;
},
set(val) {
this.$store.commit("mutatePipeline", {"dilate": val});
@@ -233,3 +264,31 @@ export default {
}
</script>
<style lang="css" scoped>
#hue-slider >>> .v-slider {
background: linear-gradient( to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100% );
border-radius: 10px;
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
}
#sat-slider >>> .v-slider {
background: linear-gradient( to right, #fff 0%, hsl(var(--averageHue), 100%, 50%) 100% );
border-radius: 10px;
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
}
#value-slider >>> .v-slider {
background: linear-gradient( to right, #000 0%, hsl(var(--averageHue), 100%, 50%) 100% );
border-radius: 10px;
box-shadow: 0px 0px 5px #333, inset 0px 0px 3px #333;
}
>>> .v-slider__thumb {
outline: black solid thin;
}
.normal-slider >>> .v-slider__track-fill {
outline: black solid thin;
}
.inverted-slider >>> .v-slider__track-background {
outline: black solid thin;
}
</style>

View File

@@ -86,7 +86,7 @@ public class PicamJNI {
public static boolean isSupported() {
return libraryLoaded
&& !isVCSMSupported()
&& isVCSMSupported()
&& getSensorModel() != SensorModel.Disconnected
&& Platform.isRaspberryPi()
&& (Platform.currentPiVersion == PiVersion.PI_3
@@ -134,6 +134,8 @@ public class PicamJNI {
public static native void setThresholds(
double hL, double sL, double vL, double hU, double sU, double vU);
public static native void setInvertHue(boolean shouldInvert);
public static native boolean setExposure(int exposure);
public static native boolean setBrightness(int brightness);

View File

@@ -28,19 +28,56 @@ public class HSVPipe extends CVPipe<Mat, Mat, HSVPipe.HSVParams> {
@Override
protected Mat process(Mat in) {
var outputMat = new Mat();
in.copyTo(outputMat);
Imgproc.cvtColor(outputMat, outputMat, Imgproc.COLOR_BGR2HSV, 3);
Core.inRange(outputMat, params.getHsvLower(), params.getHsvUpper(), outputMat);
// We can save a copy here by sending the output of cvtcolor to outputMat directly
// rather than copying. Free performance!
Imgproc.cvtColor(in, outputMat, Imgproc.COLOR_BGR2HSV, 3);
if (params.getHueInverted()) {
// In Java code we do this by taking an image thresholded
// from [0, minHue] and ORing it with [maxHue, 180]
// we want hue from the end of the slider to max hue
Scalar firstLower = params.getHsvLower().clone();
Scalar firstUpper = params.getHsvUpper().clone();
firstLower.val[0] = params.getHsvUpper().val[0];
;
firstUpper.val[0] = 180;
var lowerThresholdMat = new Mat();
Core.inRange(outputMat, firstLower, firstUpper, lowerThresholdMat);
// We want hue from 0 to the start of the slider
var secondLower = params.getHsvLower().clone();
var secondUpper = params.getHsvUpper().clone();
secondLower.val[0] = 0;
secondUpper.val[0] = params.getHsvLower().val[0];
// Now that the output mat's been used by the first inRange, it's fine to mutate it
Core.inRange(outputMat, secondLower, secondUpper, outputMat);
// Now OR the two images together to make a mat that combines the lower and upper bounds
// outputMat holds the second half of the range
Core.bitwise_or(lowerThresholdMat, outputMat, outputMat);
lowerThresholdMat.release();
} else {
Core.inRange(outputMat, params.getHsvLower(), params.getHsvUpper(), outputMat);
}
return outputMat;
}
public static class HSVParams {
private final Scalar m_hsvLower;
private final Scalar m_hsvUpper;
private final boolean m_hueInverted;
public HSVParams(IntegerCouple hue, IntegerCouple saturation, IntegerCouple value) {
public HSVParams(
IntegerCouple hue, IntegerCouple saturation, IntegerCouple value, boolean hueInverted) {
m_hsvLower = new Scalar(hue.getFirst(), saturation.getFirst(), value.getFirst());
m_hsvUpper = new Scalar(hue.getSecond(), saturation.getSecond(), value.getSecond());
this.m_hueInverted = hueInverted;
}
public Scalar getHsvLower() {
@@ -50,5 +87,9 @@ public class HSVPipe extends CVPipe<Mat, Mat, HSVPipe.HSVParams> {
public Scalar getHsvUpper() {
return m_hsvUpper;
}
public boolean getHueInverted() {
return m_hueInverted;
}
}
}

View File

@@ -38,6 +38,7 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
public IntegerCouple hsvHue = new IntegerCouple(50, 180);
public IntegerCouple hsvSaturation = new IntegerCouple(50, 255);
public IntegerCouple hsvValue = new IntegerCouple(50, 255);
public boolean hueInverted = false;
public boolean outputShouldDraw = true;
public boolean outputShowMultipleTargets = false;
@@ -109,6 +110,7 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
&& Objects.equals(hsvHue, that.hsvHue)
&& Objects.equals(hsvSaturation, that.hsvSaturation)
&& Objects.equals(hsvValue, that.hsvValue)
&& Objects.equals(hueInverted, that.hueInverted)
&& Objects.equals(contourArea, that.contourArea)
&& Objects.equals(contourRatio, that.contourRatio)
&& Objects.equals(contourFullness, that.contourFullness)
@@ -132,6 +134,7 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
hsvHue,
hsvSaturation,
hsvValue,
hueInverted,
outputShouldDraw,
outputShowMultipleTargets,
contourArea,

View File

@@ -85,12 +85,14 @@ public class ColoredShapePipeline
settings.hsvHue.getSecond() / 180d,
settings.hsvSaturation.getSecond() / 255d,
settings.hsvValue.getSecond() / 255d);
PicamJNI.setInvertHue(settings.hueInverted);
PicamJNI.setRotation(settings.inputImageRotationMode.value);
PicamJNI.setShouldCopyColor(settings.inputShouldShow);
} else {
var hsvParams =
new HSVPipe.HSVParams(settings.hsvHue, settings.hsvSaturation, settings.hsvValue);
new HSVPipe.HSVParams(
settings.hsvHue, settings.hsvSaturation, settings.hsvValue, settings.hueInverted);
hsvPipe.setParams(hsvParams);
}
@@ -99,10 +101,6 @@ public class ColoredShapePipeline
// TODO: add kernel size to pipeline settings
erodeDilatePipe.setParams(erodeDilateParams);
HSVPipe.HSVParams hsvParams =
new HSVPipe.HSVParams(settings.hsvHue, settings.hsvSaturation, settings.hsvValue);
hsvPipe.setParams(hsvParams);
SpeckleRejectPipe.SpeckleRejectParams speckleRejectParams =
new SpeckleRejectPipe.SpeckleRejectParams(settings.contourSpecklePercentage);
speckleRejectPipe.setParams(speckleRejectParams);

View File

@@ -77,12 +77,14 @@ public class ReflectivePipeline extends CVPipeline<CVPipelineResult, ReflectiveP
settings.hsvHue.getSecond() / 180d,
settings.hsvSaturation.getSecond() / 255d,
settings.hsvValue.getSecond() / 255d);
PicamJNI.setInvertHue(settings.hueInverted);
PicamJNI.setRotation(settings.inputImageRotationMode.value);
PicamJNI.setShouldCopyColor(settings.inputShouldShow);
} else {
var hsvParams =
new HSVPipe.HSVParams(settings.hsvHue, settings.hsvSaturation, settings.hsvValue);
new HSVPipe.HSVParams(
settings.hsvHue, settings.hsvSaturation, settings.hsvValue, settings.hueInverted);
hsvPipe.setParams(hsvParams);
}