PiCam Tweaks (#63)

* Update QuirkyCamera to do name-based matching

* Add pi-cam exposure set

* Refactor QuirkyCamera

* Add PiCam quirk test, fix tests for no-name quirky cameras

* Apply Spotless

* Add pinhole model unit test
This commit is contained in:
Banks T
2020-09-08 12:11:29 -04:00
committed by GitHub
parent 7bf92a9db3
commit ddd15d362b
7 changed files with 99 additions and 22 deletions

View File

@@ -39,6 +39,14 @@ public class MathUtils {
return FastMath.atan(FastMath.toRadians(angle.doubleValue() - 90));
}
public static int safeDivide(int quotient, int divisor) {
if (divisor == 0) {
return 0;
} else {
return quotient / divisor;
}
}
public static double roundTo(double value, int to) {
double toMult = Math.pow(10, to);
return (double) Math.round(value * toMult) / toMult;

View File

@@ -20,8 +20,6 @@ package org.photonvision.vision.camera;
public enum CameraQuirk {
/** Camera settable for controllable image gain */
Gain,
/** For cameras that need a bit of encouragement for settings to stick */
DoubleSet,
/** For cameras that are pi cams */
/** For the Raspberry Pi Camera */
PiCam
}

View File

@@ -25,22 +25,37 @@ public class QuirkyCamera {
private static final List<QuirkyCamera> quirkyCameras =
List.of(
new QuirkyCamera(0x2000, 0x1415, "PS3Eye", CameraQuirk.Gain),
new QuirkyCamera(0x72E, 0x45D, "LifeCam VX-5500", CameraQuirk.DoubleSet),
new QuirkyCamera(-1, -1, "PiCam", CameraQuirk.PiCam));
new QuirkyCamera(0x2000, 0x1415, CameraQuirk.Gain), // PS3Eye
new QuirkyCamera(-1, -1, "mmal service 16.1", CameraQuirk.PiCam) // PiCam
);
public static final QuirkyCamera DefaultCamera = new QuirkyCamera(0, 0, "", List.of());
public static final QuirkyCamera DefaultCamera = new QuirkyCamera(0, 0, "");
public final String baseName;
public final int usbVid;
public final int usbPid;
public final String baseName;
public final HashMap<CameraQuirk, Boolean> quirks;
private QuirkyCamera(int usbVid, int usbPid, String baseName, CameraQuirk quirk) {
this(usbVid, usbPid, baseName, List.of(quirk));
/**
* Creates a QuirkyCamera that matches by USB VID/PID
*
* @param usbVid USB VID of camera
* @param usbPid USB PID of camera
* @param quirks Camera quirks
*/
private QuirkyCamera(int usbVid, int usbPid, CameraQuirk... quirks) {
this(usbVid, usbPid, "", quirks);
}
private QuirkyCamera(int usbVid, int usbPid, String baseName, List<CameraQuirk> quirks) {
/**
* Creates a QuirkyCamera that matches by USB VID/PID and name
*
* @param usbVid USB VID of camera
* @param usbPid USB PID of camera
* @param baseName CSCore name of camera
* @param quirks Camera quirks
*/
private QuirkyCamera(int usbVid, int usbPid, String baseName, CameraQuirk... quirks) {
this.usbVid = usbVid;
this.usbPid = usbPid;
this.baseName = baseName;
@@ -58,13 +73,23 @@ public class QuirkyCamera {
return quirks.get(quirk);
}
public static QuirkyCamera getQuirkyCamera(int usbVid, int usbPid) {
return getQuirkyCamera(usbVid, usbPid, "");
}
public static QuirkyCamera getQuirkyCamera(int usbVid, int usbPid, String baseName) {
for (var qc : quirkyCameras) {
if (qc.usbVid == usbVid && qc.usbPid == usbPid) {
boolean hasBaseName = !qc.baseName.equals("");
boolean matchesBaseName = qc.baseName.equals(baseName) || !hasBaseName;
if (qc.usbVid == usbVid && qc.usbPid == usbPid && matchesBaseName) {
return qc;
}
}
return new QuirkyCamera(usbVid, usbPid, baseName, List.of());
return new QuirkyCamera(usbVid, usbPid, baseName);
}
public boolean hasQuirks() {
return quirks.containsValue(true);
}
@Override

View File

@@ -26,6 +26,7 @@ import java.util.*;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.math.MathUtils;
import org.photonvision.vision.frame.FrameProvider;
import org.photonvision.vision.frame.provider.USBFrameProvider;
import org.photonvision.vision.processes.VisionSource;
@@ -45,10 +46,16 @@ public class USBCameraSource implements VisionSource {
logger = new Logger(USBCameraSource.class, config.nickname, LogGroup.Camera);
configuration = config;
camera = new UsbCamera(config.nickname, config.path);
cvSink = CameraServer.getInstance().getVideo(this.camera);
cameraQuirks =
QuirkyCamera.getQuirkyCamera(
camera.getInfo().productId, camera.getInfo().vendorId, config.baseName);
cvSink = CameraServer.getInstance().getVideo(this.camera);
if (cameraQuirks.hasQuirks()) {
logger.info("Quirky camera detected: " + cameraQuirks.baseName);
}
usbCameraSettables = new USBCameraSettables(config);
usbFrameProvider = new USBFrameProvider(cvSink, usbCameraSettables);
}
@@ -74,8 +81,15 @@ public class USBCameraSource implements VisionSource {
@Override
public void setExposure(int exposure) {
try {
camera.setExposureManual(exposure);
camera.setExposureManual(exposure);
if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) {
camera.getProperty("auto_exposure").set(1); // auto exposure off
camera
.getProperty("exposure_time_absolute")
.set(MathUtils.safeDivide(10000, exposure)); // exposure time is a range, 0-10000
} else {
camera.setExposureManual(exposure);
camera.setExposureManual(exposure);
}
} catch (VideoException e) {
logger.error("Failed to set camera exposure!", e);
}

View File

@@ -22,6 +22,7 @@ import edu.wpi.first.wpilibj.geometry.Rotation2d;
import org.apache.commons.math3.fraction.Fraction;
import org.apache.commons.math3.util.FastMath;
import org.opencv.core.Point;
import org.photonvision.common.util.numbers.DoubleCouple;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
/** Represents the properties of a frame. */
@@ -75,8 +76,17 @@ public class FrameStaticProperties {
centerPoint = new Point(centerX, centerY);
// pinhole model calculations
double diagonalView = FastMath.toRadians(this.fov);
Fraction aspectFraction = new Fraction(this.imageWidth, this.imageHeight);
DoubleCouple horizVertViews =
calculateHorizontalVerticalFoV(this.fov, this.imageWidth, this.imageHeight);
horizontalFocalLength = this.imageWidth / (2 * FastMath.tan(horizVertViews.getFirst() / 2));
verticalFocalLength = this.imageHeight / (2 * FastMath.tan(horizVertViews.getSecond() / 2));
}
public static DoubleCouple calculateHorizontalVerticalFoV(
double diagonalFoV, int imageWidth, int imageHeight) {
double diagonalView = FastMath.toRadians(diagonalFoV);
Fraction aspectFraction = new Fraction(imageWidth, imageHeight);
int horizontalRatio = aspectFraction.getNumerator();
int verticalRatio = aspectFraction.getDenominator();
@@ -87,7 +97,6 @@ public class FrameStaticProperties {
double verticalView =
FastMath.atan(FastMath.tan(diagonalView / 2) * (verticalRatio / diagonalAspect)) * 2;
horizontalFocalLength = this.imageWidth / (2 * FastMath.tan(horizontalView / 2));
verticalFocalLength = this.imageHeight / (2 * FastMath.tan(verticalView / 2));
return new DoubleCouple(horizontalView, verticalView);
}
}