mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-01 02:41:42 +00:00
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:
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user