Skip to content

Commit

Permalink
Move to using Absolute Exposure Range (PhotonVision#1352)
Browse files Browse the repository at this point in the history
Uses logic in
PhotonVision/photon-libcamera-gl-driver#16 to
push the ov9281 down to its true minimum exposure.

Updates UI to list the exposure settings in ~~microseconds.~~ Native
units - not everyone works in microseconds.

Does its darndest to actually try to set the exposure in
~~microseconds.~~ Native Units. To do this...

Lifecam is funky when doing this - [cscore limits the exposure settings
to certain quantized
values](https://github.com/wpilibsuite/allwpilib/blob/main/cscore/src/main/native/linux/UsbCameraImpl.cpp#L129).
Add a new camera quirk to allow that.

~~Updated camera quirks to re-evaluate every camera load (rather than
recalling from settings - this shouldn't be necessary)~~ This should be
rolled back, needed for arducam type selection.

Updated camera quirk matching logic to make PID/VID optional, and
basename optional (and only match trailing characters). This enables
mirroring CSCore's logic for identifying lifecams by name.

Updated the USBCamera to primarily use cscore's exposed property names.

Since camera manufacturers use a potpourri of names for the same
thing....

For nice-to-have settings: new soft-set logic to try all possible names,
but gracefully pass if the property isn't there.
For required settings: Search a list for the first setting that's
supported, fail if none are supported.

More logging of camera properties to help debug.

Note: most of this work is because cscore doesn't directly expose a
massaged exposure-setting-absolute API (and, given what we've seen,
probably _shouldn't_, this struggle is not for the faint of heart).

---------

Co-authored-by: Matt <[email protected]>
  • Loading branch information
gerth2 and mcm001 authored Aug 17, 2024
1 parent dbe566c commit f1d1d32
Show file tree
Hide file tree
Showing 40 changed files with 1,275 additions and 646 deletions.
14 changes: 8 additions & 6 deletions photon-client/src/components/cameras/CameraCalibrationCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -389,15 +389,17 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<v-row v-if="isCalibrating">
<v-col cols="12" class="pt-0">
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposure"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
label="Exposure"
tooltip="Directly controls how much light is allowed to fall onto the sensor, which affects apparent brightness"
:min="0"
:max="100"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="8"
:step="0.1"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposure: args }, false)"
:step="1"
@input="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
Expand Down
30 changes: 15 additions & 15 deletions photon-client/src/components/cameras/CameraSettingsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,32 @@ const tempSettingsStruct = ref<CameraSettingsChangeRequest>({
const arducamSelectWrapper = computed<number>({
get: () => {
if (tempSettingsStruct.value.quirksToChange.ArduOV9281) return 1;
else if (tempSettingsStruct.value.quirksToChange.ArduOV2311) return 2;
else if (tempSettingsStruct.value.quirksToChange.ArduOV9782) return 3;
if (tempSettingsStruct.value.quirksToChange.ArduOV9281Controls) return 1;
else if (tempSettingsStruct.value.quirksToChange.ArduOV2311Controls) return 2;
else if (tempSettingsStruct.value.quirksToChange.ArduOV9782Controls) return 3;
else return 0;
},
set: (v) => {
switch (v) {
case 1:
tempSettingsStruct.value.quirksToChange.ArduOV9281 = true;
tempSettingsStruct.value.quirksToChange.ArduOV2311 = false;
tempSettingsStruct.value.quirksToChange.ArduOV9782 = false;
tempSettingsStruct.value.quirksToChange.ArduOV9281Controls = true;
tempSettingsStruct.value.quirksToChange.ArduOV2311Controls = false;
tempSettingsStruct.value.quirksToChange.ArduOV9782Controls = false;
break;
case 2:
tempSettingsStruct.value.quirksToChange.ArduOV9281 = false;
tempSettingsStruct.value.quirksToChange.ArduOV2311 = true;
tempSettingsStruct.value.quirksToChange.ArduOV9782 = false;
tempSettingsStruct.value.quirksToChange.ArduOV9281Controls = false;
tempSettingsStruct.value.quirksToChange.ArduOV2311Controls = true;
tempSettingsStruct.value.quirksToChange.ArduOV9782Controls = false;
break;
case 3:
tempSettingsStruct.value.quirksToChange.ArduOV9281 = false;
tempSettingsStruct.value.quirksToChange.ArduOV2311 = false;
tempSettingsStruct.value.quirksToChange.ArduOV9782 = true;
tempSettingsStruct.value.quirksToChange.ArduOV9281Controls = false;
tempSettingsStruct.value.quirksToChange.ArduOV2311Controls = false;
tempSettingsStruct.value.quirksToChange.ArduOV9782Controls = true;
break;
default:
tempSettingsStruct.value.quirksToChange.ArduOV9281 = false;
tempSettingsStruct.value.quirksToChange.ArduOV2311 = false;
tempSettingsStruct.value.quirksToChange.ArduOV9782 = false;
tempSettingsStruct.value.quirksToChange.ArduOV9281Controls = false;
tempSettingsStruct.value.quirksToChange.ArduOV2311Controls = false;
tempSettingsStruct.value.quirksToChange.ArduOV9782Controls = false;
break;
}
}
Expand Down
12 changes: 6 additions & 6 deletions photon-client/src/components/dashboard/tabs/InputTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ const interactiveCols = computed(() =>
<template>
<div>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposure"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
label="Exposure"
tooltip="Directly controls how much light is allowed to fall onto the sensor, which affects apparent brightness"
:min="0"
:max="100"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="interactiveCols"
:step="0.1"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposure: args }, false)"
:step="1"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
Expand Down
8 changes: 8 additions & 0 deletions photon-client/src/stores/settings/CameraSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
},
isCSICamera(): boolean {
return this.currentCameraSettings.isCSICamera;
},
minExposureRaw(): number {
return this.currentCameraSettings.minExposureRaw;
},
maxExposureRaw(): number {
return this.currentCameraSettings.maxExposureRaw;
}
},
actions: {
Expand Down Expand Up @@ -102,6 +108,8 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
})),
completeCalibrations: d.calibrations,
isCSICamera: d.isCSICamera,
minExposureRaw: d.minExposureRaw,
maxExposureRaw: d.maxExposureRaw,
pipelineNicknames: d.pipelineNicknames,
currentPipelineIndex: d.currentPipelineIndex,
pipelineSettings: d.currentPipelineSettings,
Expand Down
16 changes: 9 additions & 7 deletions photon-client/src/types/PipelineTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export interface PipelineSettings {
hueInverted: boolean;
outputShowMultipleTargets: boolean;
contourSortMode: number;
cameraExposure: number;
cameraExposureRaw: number;
cameraMinExposureRaw: number;
cameraMaxExposureRaw: number;
offsetSinglePoint: { x: number; y: number };
cameraBrightness: number;
offsetDualPointAArea: number;
Expand Down Expand Up @@ -97,7 +99,7 @@ export type ConfigurablePipelineSettings = Partial<
// Omitted settings are changed for all pipeline types
export const DefaultPipelineSettings: Omit<
PipelineSettings,
"cameraGain" | "targetModel" | "ledMode" | "outputShowMultipleTargets" | "cameraExposure" | "pipelineType"
"cameraGain" | "targetModel" | "ledMode" | "outputShowMultipleTargets" | "cameraExposureRaw" | "pipelineType"
> = {
offsetRobotOffsetMode: RobotOffsetPointMode.None,
streamingFrameDivisor: 0,
Expand Down Expand Up @@ -151,7 +153,7 @@ export const DefaultReflectivePipelineSettings: ReflectivePipelineSettings = {
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
cameraExposure: 6,
cameraExposureRaw: 6,
pipelineType: PipelineType.Reflective,

contourFilterRangeY: 2,
Expand Down Expand Up @@ -182,7 +184,7 @@ export const DefaultColoredShapePipelineSettings: ColoredShapePipelineSettings =
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
cameraExposure: 20,
cameraExposureRaw: 20,
pipelineType: PipelineType.ColoredShape,

erode: false,
Expand Down Expand Up @@ -222,7 +224,7 @@ export const DefaultAprilTagPipelineSettings: AprilTagPipelineSettings = {
targetModel: TargetModel.AprilTag6p5in_36h11,
ledMode: false,
outputShowMultipleTargets: true,
cameraExposure: 20,
cameraExposureRaw: 20,
pipelineType: PipelineType.AprilTag,

hammingDist: 0,
Expand Down Expand Up @@ -264,7 +266,7 @@ export const DefaultArucoPipelineSettings: ArucoPipelineSettings = {
cameraGain: 75,
outputShowMultipleTargets: true,
targetModel: TargetModel.AprilTag6p5in_36h11,
cameraExposure: -1,
cameraExposureRaw: -1,
cameraAutoExposure: true,
ledMode: false,
pipelineType: PipelineType.Aruco,
Expand Down Expand Up @@ -299,7 +301,7 @@ export const DefaultObjectDetectionPipelineSettings: ObjectDetectionPipelineSett
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
cameraExposure: 6,
cameraExposureRaw: 6,
confidence: 0.9,
nms: 0.45,
box_thresh: 0.25
Expand Down
18 changes: 13 additions & 5 deletions photon-client/src/types/SettingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,18 @@ export interface CameraCalibrationResult {
export enum ValidQuirks {
AWBGain = "AWBGain",
AdjustableFocus = "AdjustableFocus",
ArduOV9281 = "ArduOV9281",
ArduOV2311 = "ArduOV2311",
ArduOV9782 = "ArduOV9782",
InnoOV9281Controls = "InnoOV9281Controls",
ArduOV9281Controls = "ArduOV9281Controls",
ArduOV2311Controls = "ArduOV2311Controls",
ArduOV9782Controls = "ArduOV9782Controls",
ArduCamCamera = "ArduCamCamera",
CompletelyBroken = "CompletelyBroken",
FPSCap100 = "FPSCap100",
Gain = "Gain",
PiCam = "PiCam",
StickyFPS = "StickyFPS"
StickyFPS = "StickyFPS",
LifeCamControls = "LifeCamControls",
PsEyeControls = "PsEyeControls"
}

export interface QuirkyCamera {
Expand Down Expand Up @@ -190,6 +193,9 @@ export interface CameraSettings {

cameraQuirks: QuirkyCamera;
isCSICamera: boolean;

minExposureRaw: number;
maxExposureRaw: number;
}

export interface CameraSettingsChangeRequest {
Expand Down Expand Up @@ -289,7 +295,9 @@ export const PlaceholderCameraSettings: CameraSettings = {
StickyFPS: false
}
},
isCSICamera: false
isCSICamera: false,
minExposureRaw: 1,
maxExposureRaw: 100
};

export enum CalibrationBoardTypes {
Expand Down
2 changes: 2 additions & 0 deletions photon-client/src/types/WebsocketDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface WebsocketCameraSettingsUpdate {
pipelineNicknames: string[];
videoFormatList: WebsocketVideoFormat;
cameraQuirks: QuirkyCamera;
minExposureRaw: number;
maxExposureRaw: number;
}
export interface WebsocketNTUpdate {
connected: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,5 +178,7 @@ public static class UICameraConfiguration {
public boolean isFovConfigurable = true;
public QuirkyCamera cameraQuirks;
public boolean isCSICamera;
public double minExposureRaw;
public double maxExposureRaw;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ public static long nanosToMicros(long nanos) {
return nanos / 1000;
}

/**
* Constrain a value to only take on certain values. Pick the next-highest allowed value in the
* array if in-between.
*
* @param value value to quantize
* @param allowableSteps sorted array of the allowed values
* @return quantized value
*/
public static int quantize(int value, int[] allowableSteps) {
for (int step : allowableSteps) {
if (value <= step) {
return step;
}
}
return allowableSteps[allowableSteps.length - 1];
}

public static double map(
double value, double in_min, double in_max, double out_min, double out_max) {
return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
public enum CameraQuirk {
/** Camera settable for controllable image gain */
Gain,
/** For the Raspberry Pi Camera */
PiCam,
/** Only certain discrete exposure settings work */
LifeCamControls,
/** Auto-Exposure property uses 1/0, rather than 3/1 */
PsEyeControls,
/** Cap at 100FPS for high-bandwidth cameras */
FPSCap100,
/** Separate red/blue gain controls available */
Expand All @@ -35,14 +37,16 @@ public enum CameraQuirk {
/** Camera is an arducam. This means it shares VID/PID with other arducams (ew) */
ArduCamCamera,
/**
* Camera is an arducam ov9281 which has a funky exposure issue where it is defined in v4l as
* Camera is an arducam USB ov9281 which has a funky exposure issue where it is defined in v4l as
* 1-5000 instead of 1-75
*/
ArduOV9281,
ArduOV9281Controls,
/** Dummy quirk to tell OV2311 from OV9281 */
ArduOV2311,
/*
* Camera is an arducam ov9782 which has specific exposure ranges and needs a specific white balance issue
ArduOV2311Controls,
ArduOV9782Controls,
/**
* Camera is innomaker USB OV9281 which also has incorrect v4l exposure times Real range is more
* like 0-500
*/
ArduOV9782
InnoOV9281Controls,
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ public boolean isVendorCamera() {
return false;
}

@Override
public void remakeSettables() {
// Nothing to do, settables for this type of VisionSource should never be remade.
return;
}

@Override
public boolean hasLEDs() {
return false; // Assume USB cameras do not have photonvision-controlled LEDs
}

private static class FileSourceSettables extends VisionSourceSettables {
private final VideoMode videoMode;

Expand All @@ -93,7 +104,7 @@ private static class FileSourceSettables extends VisionSourceSettables {
}

@Override
public void setExposure(double exposure) {}
public void setExposureRaw(double exposureRaw) {}

public void setAutoExposure(boolean cameraAutoExposure) {}

Expand All @@ -117,5 +128,15 @@ protected void setVideoModeInternal(VideoMode videoMode) {
public HashMap<Integer, VideoMode> getAllVideoModes() {
return videoModes;
}

@Override
public double getMinExposureRaw() {
return 1f;
}

@Override
public double getMaxExposureRaw() {
return 100f;
}
}
}
Loading

0 comments on commit f1d1d32

Please sign in to comment.