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

@@ -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 {