Add AWB slider/toggle (#1477)

Also reworks OV9782 defaults. Probably doesn't work on windows. We should hide these sliders probably. 

Co-authored-by: Cameron (3539) <theforgelover@gmail.com>
This commit is contained in:
Matt
2024-10-25 00:27:40 -07:00
committed by GitHub
parent 385059c233
commit aee432127a
21 changed files with 288 additions and 30 deletions

View File

@@ -31,12 +31,15 @@ You can run one of the many built in examples straight from the command line, to
Note that these are case sensitive!
* `-PArchOverride=foobar`: builds for a target system other than your current architecture. Valid overrides are:
* linuxathena
* `-PArchOverride=foobar`: builds for a target system other than your current architecture. [Valid overrides](https://github.com/wpilibsuite/wpilib-tool-plugin/blob/main/src/main/java/edu/wpi/first/tools/NativePlatforms.java) are:
* winx32
* winx64
* winarm64
* macx64
* macarm64
* linuxx64
* linuxarm64
* arm64
* x86-64
* x86
* linuxathena
- `-PtgtIP`: Specifies where `./gradlew deploy` should try to copy the fat JAR to
- `-Pprofile`: enables JVM profiling

View File

@@ -130,6 +130,25 @@ const interactiveCols = computed(() =>
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)"
/>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoWhiteBalance"
class="pt-2"
label="Auto White Balance"
:switch-cols="interactiveCols"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoWhiteBalance: args }, false)"
/>
<pv-slider
v-if="!useCameraSettingsStore().currentPipelineSettings.cameraAutoWhiteBalance"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraWhiteBalanceTemp"
label="White Balance Temperature"
:min="useCameraSettingsStore().minWhiteBalanceTemp"
:max="useCameraSettingsStore().maxWhiteBalanceTemp"
:slider-cols="interactiveCols"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraWhiteBalanceTemp: args }, false)"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.inputImageRotationMode"
label="Orientation"

View File

@@ -77,6 +77,12 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
},
maxExposureRaw(): number {
return this.currentCameraSettings.maxExposureRaw;
},
minWhiteBalanceTemp(): number {
return this.currentCameraSettings.minWhiteBalanceTemp;
},
maxWhiteBalanceTemp(): number {
return this.currentCameraSettings.maxWhiteBalanceTemp;
}
},
actions: {
@@ -116,7 +122,9 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
pipelineNicknames: d.pipelineNicknames,
currentPipelineIndex: d.currentPipelineIndex,
pipelineSettings: d.currentPipelineSettings,
cameraQuirks: d.cameraQuirks
cameraQuirks: d.cameraQuirks,
minWhiteBalanceTemp: d.minWhiteBalanceTemp,
maxWhiteBalanceTemp: d.maxWhiteBalanceTemp
}));
this.cameras = configuredCameras.length > 0 ? configuredCameras : [PlaceholderCameraSettings];
},

View File

@@ -77,6 +77,9 @@ export interface PipelineSettings {
hsvSaturation: WebsocketNumberPair | [number, number];
pipelineType: PipelineType;
contourIntersection: number;
cameraAutoWhiteBalance: boolean;
cameraWhiteBalanceTemp: number;
}
export type ConfigurablePipelineSettings = Partial<
Omit<
@@ -137,7 +140,9 @@ export const DefaultPipelineSettings: Omit<
cornerDetectionStrategy: 0,
cornerDetectionAccuracyPercentage: 10,
hsvSaturation: { first: 50, second: 255 },
contourIntersection: 1
contourIntersection: 1,
cameraAutoWhiteBalance: false,
cameraWhiteBalanceTemp: 4000
};
export interface ReflectivePipelineSettings extends PipelineSettings {

View File

@@ -198,6 +198,9 @@ export interface CameraSettings {
minExposureRaw: number;
maxExposureRaw: number;
minWhiteBalanceTemp: number;
maxWhiteBalanceTemp: number;
}
export interface CameraSettingsChangeRequest {
@@ -286,20 +289,25 @@ export const PlaceholderCameraSettings: CameraSettings = {
quirks: {
AWBGain: false,
AdjustableFocus: false,
ArduOV9281: false,
ArduOV2311: false,
ArduOV9782: false,
ArduOV9281Controls: false,
ArduOV2311Controls: false,
ArduOV9782Controls: false,
ArduCamCamera: false,
CompletelyBroken: false,
FPSCap100: false,
Gain: false,
PiCam: false,
StickyFPS: false
StickyFPS: false,
InnoOV9281Controls: false,
LifeCamControls: false,
PsEyeControls: false
}
},
isCSICamera: false,
minExposureRaw: 1,
maxExposureRaw: 100
maxExposureRaw: 100,
minWhiteBalanceTemp: 2000,
maxWhiteBalanceTemp: 10000
};
export enum CalibrationBoardTypes {

View File

@@ -60,6 +60,8 @@ export interface WebsocketCameraSettingsUpdate {
cameraQuirks: QuirkyCamera;
minExposureRaw: number;
maxExposureRaw: number;
minWhiteBalanceTemp: number;
maxWhiteBalanceTemp: number;
}
export interface WebsocketNTUpdate {
connected: boolean;

View File

@@ -191,5 +191,7 @@ public class PhotonConfiguration {
public boolean isCSICamera;
public double minExposureRaw;
public double maxExposureRaw;
public double minWhiteBalanceTemp;
public double maxWhiteBalanceTemp;
}
}

View File

@@ -45,8 +45,57 @@ public class TimedTaskManager {
}
}
private final ScheduledExecutorService timedTaskExecutorPool =
new ScheduledThreadPoolExecutor(2, new CaughtThreadFactory());
private static class CaughtScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
public CaughtScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, threadFactory);
}
public Runnable wrap(Runnable runnable) {
return () -> {
try {
runnable.run();
} catch (Throwable t) {
logger.error("Exception thrown by threadpool: " + t.getMessage(), t);
}
};
}
public <V> Callable<V> wrap(Callable<V> runnable) {
return () -> {
try {
return runnable.call();
} catch (Throwable t) {
logger.error("Exception thrown by threadpool: " + t.getMessage(), t);
return null;
}
};
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
return super.schedule(wrap(callable), delay, unit);
}
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
return super.schedule(wrap(command), delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(
Runnable command, long initialDelay, long period, TimeUnit unit) {
return super.scheduleAtFixedRate(wrap(command), initialDelay, period, unit);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(
Runnable command, long initialDelay, long delay, TimeUnit unit) {
return super.scheduleWithFixedDelay(wrap(command), initialDelay, delay, unit);
}
}
private final CaughtScheduledThreadPoolExecutor timedTaskExecutorPool =
new CaughtScheduledThreadPoolExecutor(2, new CaughtThreadFactory());
private final ConcurrentHashMap<String, Future<?>> activeTasks = new ConcurrentHashMap<>();
public void addTask(String identifier, Runnable runnable, long millisInterval) {

View File

@@ -27,7 +27,7 @@ public enum CameraQuirk {
/** Cap at 100FPS for high-bandwidth cameras */
FPSCap100,
/** Separate red/blue gain controls available */
AWBGain,
AwbRedBlueGain,
/** Will not work with photonvision - Logitec C270 at least */
CompletelyBroken,
/** Has adjustable focus and autofocus switch */

View File

@@ -138,5 +138,21 @@ public class FileVisionSource extends VisionSource {
public double getMaxExposureRaw() {
return 100f;
}
@Override
public void setAutoWhiteBalance(boolean autowb) {}
@Override
public void setWhiteBalanceTemp(double temp) {}
@Override
public double getMaxWhiteBalanceTemp() {
return 2;
}
@Override
public double getMinWhiteBalanceTemp() {
return 1;
}
}
}

View File

@@ -260,4 +260,24 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
public double getMaxExposureRaw() {
return this.maxExposure;
}
@Override
public void setAutoWhiteBalance(boolean autowb) {
throw new RuntimeException("Libcamera doesn't support AWB temp (yet)");
}
@Override
public void setWhiteBalanceTemp(double temp) {
throw new RuntimeException("Libcamera doesn't support AWB temp -- use red/blue gain instead");
}
@Override
public double getMaxWhiteBalanceTemp() {
return 2;
}
@Override
public double getMinWhiteBalanceTemp() {
return 1;
}
}

View File

@@ -97,7 +97,7 @@ public class QuirkyCamera {
-1,
"unicam",
CameraQuirk.Gain,
CameraQuirk.AWBGain); // PiCam (using libpicam GPU Driver on raspberry pi)
CameraQuirk.AwbRedBlueGain); // PiCam (using libcamera GPU Driver on raspberry pi)
@JsonProperty("baseName")
public final String baseName;

View File

@@ -23,7 +23,12 @@ import org.photonvision.common.configuration.CameraConfiguration;
public class ArduOV9782CameraSettables extends GenericUSBCameraSettables {
public ArduOV9782CameraSettables(CameraConfiguration configuration, UsbCamera camera) {
super(configuration, camera);
whiteBalanceTemperature = 3500;
// Arbitrary, worked well in Chris's basement
lastWhiteBalanceTemp = 5300;
// According to Chris' pi, running an older kernel at least
this.minExposure = 1;
this.maxExposure = 70;
}
@Override
@@ -32,7 +37,7 @@ public class ArduOV9782CameraSettables extends GenericUSBCameraSettables {
softSet("exposure_metering_mode", 0);
softSet("exposure_dynamic_framerate", 0);
softSet("white_balance_automatic", 0);
softSet("white_balance_temperature", whiteBalanceTemperature);
softSet("white_balance_temperature", lastWhiteBalanceTemp);
}
@Override

View File

@@ -46,15 +46,18 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
protected VideoProperty exposureAbsProp = null;
protected VideoProperty autoExposureProp = null;
protected VideoProperty wbTempProp = null;
protected double minExposure = 1;
protected double maxExposure = 80000;
protected double minWhiteBalanceTemp = 1;
protected double maxWhiteBalanceTemp = 4000;
protected int lastWhiteBalanceTemp = 4000;
protected static final int PROP_AUTO_EXPOSURE_ENABLED = 3;
protected static final int PROP_AUTO_EXPOSURE_DISABLED = 1;
protected int whiteBalanceTemperature = 4000;
protected UsbCamera camera;
protected CameraConfiguration configuration;
@@ -73,6 +76,14 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
}
}
protected void setUpWhiteBalanceProperties() {
wbTempProp = findProperty("white_balance_temperature", "WhiteBalance").orElse(null);
if (wbTempProp != null) {
this.minWhiteBalanceTemp = wbTempProp.getMin();
this.maxWhiteBalanceTemp = wbTempProp.getMax();
}
}
protected void setUpExposureProperties() {
// Photonvision needs to be able to control absolute exposure. Make sure we can
// first.
@@ -102,6 +113,54 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
softSet("focus_absolute", 0); // Focus into infinity
}
@Override
public void setWhiteBalanceTemp(double tempNumber) {
if (wbTempProp == null) {
// bail
return;
}
try {
int temp = (int) Math.round(tempNumber);
softSet("white_balance_automatic", 0);
int propVal = (int) MathUtil.clamp(temp, minWhiteBalanceTemp, maxWhiteBalanceTemp);
logger.debug(
"Setting property "
+ wbTempProp.getName()
+ " to "
+ propVal
+ " (user requested "
+ temp
+ " degrees)");
wbTempProp.set(propVal);
this.lastWhiteBalanceTemp = temp;
} catch (VideoException e) {
logger.error("Failed to set camera exposure!", e);
}
}
@Override
public void setAutoWhiteBalance(boolean autoWB) {
logger.debug("Setting auto white balance to " + autoWB);
if (autoWB) {
// Seems to be a rpi-specific property?
softSet("white_balance_automatic", 1);
} else {
softSet("white_balance_automatic", 0);
if (wbTempProp != null) {
wbTempProp.set(this.lastWhiteBalanceTemp);
}
}
}
public void setAutoExposure(boolean cameraAutoExposure) {
logger.debug("Setting auto exposure to " + cameraAutoExposure);
@@ -110,9 +169,6 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
softSet("auto_exposure_bias", 0);
softSet("iso_sensitivity_auto", 0); // Disable auto ISO adjustment
softSet("iso_sensitivity", 0); // Manual ISO adjustment
softSet("white_balance_auto_preset", 2); // Auto white-balance disabled
softSet("white_balance_automatic", 0);
softSet("white_balance_temperature", whiteBalanceTemperature);
autoExposureProp.set(PROP_AUTO_EXPOSURE_DISABLED);
// Most cameras leave exposure time absolute at the last value from their AE
@@ -125,8 +181,6 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
softSet("auto_exposure_bias", 12);
softSet("iso_sensitivity_auto", 1);
softSet("iso_sensitivity", 1); // Manual ISO adjustment by default
softSet("white_balance_auto_preset", 1); // Auto white-balance enabled
softSet("white_balance_automatic", 1);
autoExposureProp.set(PROP_AUTO_EXPOSURE_ENABLED);
}
}
@@ -302,4 +356,14 @@ public class GenericUSBCameraSettables extends VisionSourceSettables {
return Optional.ofNullable(retProp);
}
@Override
public double getMaxWhiteBalanceTemp() {
return maxWhiteBalanceTemp;
}
@Override
public double getMinWhiteBalanceTemp() {
return minWhiteBalanceTemp;
}
}

View File

@@ -147,6 +147,7 @@ public class USBCameraSource extends VisionSource {
}
settables.setUpExposureProperties();
settables.setUpWhiteBalanceProperties();
return settables;
}

View File

@@ -24,7 +24,7 @@ import org.photonvision.vision.opencv.CVMat;
import org.photonvision.vision.processes.VisionSourceSettables;
public class USBFrameProvider extends CpuImageProcessor {
private static final Logger logger = new Logger(USBFrameProvider.class, LogGroup.Camera);
private final Logger logger;
private final CvSink cvSink;
@@ -33,6 +33,8 @@ public class USBFrameProvider extends CpuImageProcessor {
@SuppressWarnings("SpellCheckingInspection")
public USBFrameProvider(CvSink sink, VisionSourceSettables visionSettables) {
logger = new Logger(USBFrameProvider.class, sink.getName(), LogGroup.Camera);
cvSink = sink;
cvSink.setEnabled(true);
this.settables = visionSettables;

View File

@@ -58,6 +58,9 @@ public class CVPipelineSettings implements Cloneable {
public boolean inputShouldShow = false;
public boolean outputShouldShow = true;
public boolean cameraAutoWhiteBalance = false;
public double cameraWhiteBalanceTemp = 4000;
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -71,6 +74,7 @@ public class CVPipelineSettings implements Cloneable {
&& Double.compare(that.cameraGain, cameraGain) == 0
&& Double.compare(that.cameraRedGain, cameraRedGain) == 0
&& Double.compare(that.cameraBlueGain, cameraBlueGain) == 0
&& Double.compare(that.cameraWhiteBalanceTemp, cameraWhiteBalanceTemp) == 0
&& cameraVideoModeIndex == that.cameraVideoModeIndex
&& ledMode == that.ledMode
&& pipelineType == that.pipelineType
@@ -95,6 +99,7 @@ public class CVPipelineSettings implements Cloneable {
cameraGain,
cameraRedGain,
cameraBlueGain,
cameraWhiteBalanceTemp,
cameraVideoModeIndex,
streamingFrameDivisor,
ledMode,

View File

@@ -112,7 +112,7 @@ public class VisionModule {
if (it.cameraGain == -1) it.cameraGain = 75; // Sane default
});
}
if (cameraQuirks.hasQuirk(CameraQuirk.AWBGain)) {
if (cameraQuirks.hasQuirk(CameraQuirk.AwbRedBlueGain)) {
pipelineManager.userPipelineSettings.forEach(
it -> {
if (it.cameraRedGain == -1) it.cameraRedGain = 11; // Sane defaults
@@ -364,7 +364,7 @@ public class VisionModule {
if (!cameraQuirks.hasQuirk(CameraQuirk.Gain)) {
settings.cameraGain = -1;
}
if (!cameraQuirks.hasQuirk(CameraQuirk.AWBGain)) {
if (!cameraQuirks.hasQuirk(CameraQuirk.AwbRedBlueGain)) {
settings.cameraRedGain = -1;
settings.cameraBlueGain = -1;
}
@@ -442,7 +442,7 @@ public class VisionModule {
pipelineSettings.cameraGain = -1;
}
if (cameraQuirks.hasQuirk(CameraQuirk.AWBGain)) {
if (cameraQuirks.hasQuirk(CameraQuirk.AwbRedBlueGain)) {
// If the AWB gains are disabled for some reason, re-enable it
if (pipelineSettings.cameraRedGain == -1) pipelineSettings.cameraRedGain = 11;
if (pipelineSettings.cameraBlueGain == -1) pipelineSettings.cameraBlueGain = 20;
@@ -451,6 +451,10 @@ public class VisionModule {
} else {
pipelineSettings.cameraRedGain = -1;
pipelineSettings.cameraBlueGain = -1;
// All other cameras (than picams) should support AWB temp
visionSource.getSettables().setWhiteBalanceTemp(pipelineSettings.cameraWhiteBalanceTemp);
visionSource.getSettables().setAutoWhiteBalance(pipelineSettings.cameraAutoWhiteBalance);
}
setVisionLEDs(pipelineSettings.ledMode);
@@ -525,8 +529,10 @@ public class VisionModule {
ret.currentPipelineIndex = pipelineManager.getRequestedIndex();
ret.pipelineNicknames = pipelineManager.getPipelineNicknames();
ret.cameraQuirks = visionSource.getSettables().getConfiguration().cameraQuirks;
ret.maxExposureRaw = visionSource.getSettables().getMaxExposureRaw();
ret.minExposureRaw = visionSource.getSettables().getMinExposureRaw();
ret.maxExposureRaw = visionSource.getSettables().getMaxExposureRaw();
ret.minWhiteBalanceTemp = visionSource.getSettables().getMinWhiteBalanceTemp();
ret.maxWhiteBalanceTemp = visionSource.getSettables().getMaxWhiteBalanceTemp();
// TODO refactor into helper method
var temp = new HashMap<Integer, HashMap<String, Object>>();

View File

@@ -50,6 +50,10 @@ public abstract class VisionSourceSettables {
public abstract void setAutoExposure(boolean cameraAutoExposure);
public abstract void setWhiteBalanceTemp(double temp);
public abstract void setAutoWhiteBalance(boolean autowb);
public abstract void setBrightness(int brightness);
public abstract void setGain(int gain);
@@ -123,4 +127,8 @@ public abstract class VisionSourceSettables {
public FrameStaticProperties getFrameStaticProperties() {
return frameStaticProperties;
}
public abstract double getMinWhiteBalanceTemp();
public abstract double getMaxWhiteBalanceTemp();
}

View File

@@ -72,5 +72,24 @@ public class MockUsbCameraSource extends USBCameraSource {
@Override
public void setUpExposureProperties() {}
@Override
protected void setUpWhiteBalanceProperties() {}
@Override
public void setWhiteBalanceTemp(double tempNumber) {}
@Override
public void setAutoWhiteBalance(boolean autoWB) {}
@Override
public double getMinWhiteBalanceTemp() {
return 1;
}
@Override
public double getMaxWhiteBalanceTemp() {
return 2;
}
}
}

View File

@@ -123,6 +123,22 @@ public class VisionModuleManagerTest {
public double getMaxExposureRaw() {
return 1234;
}
@Override
public void setAutoWhiteBalance(boolean autowb) {}
@Override
public void setWhiteBalanceTemp(double temp) {}
@Override
public double getMaxWhiteBalanceTemp() {
return 1;
}
@Override
public double getMinWhiteBalanceTemp() {
return 2;
}
}
private static class TestDataConsumer implements CVPipelineResultConsumer {