mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-28 02:11:40 +00:00
Compare commits
3 Commits
v2026.1.1-
...
v2026.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a0fad54c | ||
|
|
e73420d62a | ||
|
|
12f74423d9 |
@@ -108,7 +108,7 @@ When taking in a result from a `PhotonCamera`, PhotonPoseEstimator offers nine p
|
||||
flat on the floor. This computation takes place on the RoboRIO, and should not take more than 2ms.
|
||||
This also requires addHeadingData to be called every frame so heading data is up to date.
|
||||
|
||||
Calling one of the `estimate<strategy>Pose()` methods on your `PhotonPoseEstimator` will return an `Optional<EstimatedRobotPose>`, which includes a `Pose3d` of the latest estimated pose (using the selected strategy) along with a `double` of the timestamp when the robot pose was estimated. The recommended way to use the estimatePose methods is to
|
||||
Calling one of the `estimate<strategy>Pose()` methods on your `PhotonPoseEstimator` will return an `Optional<EstimatedRobotPose>`, which will be empty if there are no detected tags, not enough detected tags (for multi-tag strategies), missing data (typically heading data), or if the internal solvers failed (this is a rare scenario). `EstimatedRobotPose` includes a `Pose3d` of the latest estimated pose (using the selected strategy) along with a `double` of the timestamp when the robot pose was estimated. The recommended way to use the estimatePose methods is to
|
||||
1. do estimation with one of MultiTag methods, check if the result is empty, then
|
||||
2. fallback to single tag estimation using a method like `estimateLowestAmbiguityPose`.
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
## Coprocessor with regulator
|
||||
|
||||
1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins or using a locking USB C cable. In any case we recommend hot gluing the connector.
|
||||
1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins using either of the two methods listed below or using a locking USB C cable.
|
||||
* Method 1: Soldering to GPIO Header Pins
|
||||
* Using 20 AWG or preferably 18 AWG wires, solder two wires from the regulator to the power header pins on the coprocessor and cover with heat-shrink tubing.
|
||||
* Method 2: Using a Wire-to-Board Connector
|
||||
* Using a wire-to-board connector with 20 AWG or preferably 18 AWG wires, connect two wires from the regulator to the power header pins on the coprocessor. To prevent the connector from becoming unseated, we recommend applying hot glue to the connector.
|
||||
|
||||
2. Run an ethernet cable from your coprocessor to your network switch / radio.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||
|
||||
public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelineSettings>
|
||||
implements Releasable {
|
||||
static final int MAX_MULTI_TARGET_RESULTS = 10;
|
||||
static final int MAX_MULTI_TARGET_RESULTS = 50;
|
||||
|
||||
protected S settings;
|
||||
protected FrameStaticProperties frameStaticProperties;
|
||||
|
||||
@@ -271,6 +271,9 @@ class PhotonCameraSim:
|
||||
camRt = RotTrlTransform3d.makeRelativeTo(cameraPose)
|
||||
|
||||
for tgt in targets:
|
||||
if len(detectableTgts) >= 50:
|
||||
break
|
||||
|
||||
# pose isn't visible, skip to next
|
||||
if not self.canSeeTargetPose(cameraPose, tgt):
|
||||
continue
|
||||
|
||||
@@ -78,6 +78,35 @@ def test_VisibilityCupidShuffle() -> None:
|
||||
assert camera.getLatestResult().hasTargets()
|
||||
|
||||
|
||||
def test_bunchaTargets() -> None:
|
||||
visionSysSim = VisionSystemSim("Test")
|
||||
camera = PhotonCamera("camera")
|
||||
cameraSim = PhotonCameraSim(camera)
|
||||
visionSysSim.addCamera(cameraSim, Transform3d())
|
||||
|
||||
cameraSim.prop.setCalibrationFromFOV(640, 480, fovDiag=Rotation2d.fromDegrees(80.0))
|
||||
|
||||
for i in range(100):
|
||||
targetPose = Pose3d(
|
||||
Translation3d(15.98 + i * 0.1, 0.0, 2.0), Rotation3d(0, 0, math.pi)
|
||||
)
|
||||
visionSysSim.addVisionTargets(
|
||||
[
|
||||
VisionTargetSim(
|
||||
targetPose,
|
||||
TargetModel.createPlanar(width=1.0, height=1.0),
|
||||
4774 + i,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
robotPose = Pose2d(Translation2d(2.0, 0.0), Rotation2d.fromDegrees(5.0))
|
||||
|
||||
visionSysSim.update(robotPose)
|
||||
|
||||
assert len(camera.getLatestResult().getTargets()) == 50
|
||||
|
||||
|
||||
def test_NotVisibleVert1() -> None:
|
||||
targetPose = Pose3d(Translation3d(15.98, 0.0, 2.0), Rotation3d(0, 0, math.pi))
|
||||
|
||||
|
||||
@@ -686,7 +686,7 @@ public class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets or heading data.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimatePnpDistanceTrigSolvePose(
|
||||
PhotonPipelineResult cameraResult) {
|
||||
@@ -758,7 +758,8 @@ public class PhotonPoseEstimator {
|
||||
* @param headingScaleFactor If headingFree is false, this weights the cost of changing our robot
|
||||
* heading estimate against the tag corner reprojection error cont.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets or heading data, or if the
|
||||
* solver fails to solve the problem.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateConstrainedSolvepnpPose(
|
||||
PhotonPipelineResult cameraResult,
|
||||
@@ -770,6 +771,18 @@ public class PhotonPoseEstimator {
|
||||
if (!shouldEstimate(cameraResult)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// Need heading if heading fixed
|
||||
if (!headingFree) {
|
||||
if (headingBuffer.getSample(cameraResult.getTimestampSeconds()).isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
// If heading fixed, force rotation component
|
||||
seedPose =
|
||||
new Pose3d(
|
||||
seedPose.getTranslation(),
|
||||
new Rotation3d(headingBuffer.getSample(cameraResult.getTimestampSeconds()).get()));
|
||||
}
|
||||
}
|
||||
var pnpResult =
|
||||
VisionEstimation.estimateRobotPoseConstrainedSolvepnp(
|
||||
cameraMatrix,
|
||||
@@ -799,7 +812,8 @@ public class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets, no multi-tag results, or
|
||||
* multi-tag is disabled in the web UI.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateCoprocMultiTagPose(
|
||||
PhotonPipelineResult cameraResult) {
|
||||
@@ -829,7 +843,8 @@ public class PhotonPoseEstimator {
|
||||
* @param cameraMatrix Camera intrinsics from camera calibration data
|
||||
* @param distCoeffs Distortion coefficients from camera calibration data.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's less than 2 targets visible or
|
||||
* SolvePnP fails.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateRioMultiTagPose(
|
||||
PhotonPipelineResult cameraResult, Matrix<N3, N3> cameraMatrix, Matrix<N8, N1> distCoeffs) {
|
||||
@@ -861,7 +876,7 @@ public class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateLowestAmbiguityPose(
|
||||
PhotonPipelineResult cameraResult) {
|
||||
@@ -911,7 +926,7 @@ public class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateClosestToCameraHeightPose(
|
||||
PhotonPipelineResult cameraResult) {
|
||||
@@ -989,7 +1004,7 @@ public class PhotonPoseEstimator {
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @param referencePose reference pose to check vector magnitude difference against.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateClosestToReferencePose(
|
||||
PhotonPipelineResult cameraResult, Pose3d referencePose) {
|
||||
@@ -1062,7 +1077,7 @@ public class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An {@link EstimatedRobotPose} with an estimated pose, timestamp, and targets used to
|
||||
* create the estimate.
|
||||
* create the estimate, or an empty optional if there's no targets.
|
||||
*/
|
||||
public Optional<EstimatedRobotPose> estimateAverageBestTargetsPose(
|
||||
PhotonPipelineResult cameraResult) {
|
||||
|
||||
@@ -442,6 +442,10 @@ public class PhotonCameraSim implements AutoCloseable {
|
||||
Mat.zeros(videoFrameSize, CvType.CV_8UC1).assignTo(videoSimFrameRaw);
|
||||
|
||||
for (var tgt : targets) {
|
||||
if (detectableTgts.size() >= 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
// pose isn't visible, skip to next
|
||||
if (!canSeeTargetPose(cameraPose, tgt)) continue;
|
||||
|
||||
|
||||
@@ -634,13 +634,18 @@ PhotonPoseEstimator::EstimateConstrainedSolvepnpPose(
|
||||
if (!ShouldEstimate(cameraResult)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
// Need heading if heading fixed
|
||||
if (!headingFree) {
|
||||
seedPose = frc::Pose3d{
|
||||
seedPose.Translation(),
|
||||
frc::Rotation3d{
|
||||
headingBuffer.Sample(cameraResult.GetTimestamp()).value()}};
|
||||
if (!headingBuffer.Sample(cameraResult.GetTimestamp())) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
// If heading fixed, force rotation component
|
||||
seedPose = frc::Pose3d{
|
||||
seedPose.Translation(),
|
||||
frc::Rotation3d{
|
||||
headingBuffer.Sample(cameraResult.GetTimestamp()).value()}};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<photon::PhotonTrackedTarget> targets{
|
||||
cameraResult.GetTargets().begin(), cameraResult.GetTargets().end()};
|
||||
|
||||
|
||||
@@ -131,6 +131,10 @@ PhotonPipelineResult PhotonCameraSim::Process(
|
||||
blankFrame.assignTo(videoSimFrameRaw);
|
||||
|
||||
for (const auto& tgt : targets) {
|
||||
if (detectableTgts.size() >= 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!CanSeeTargetPose(cameraPose, tgt)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's no targets.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateLowestAmbiguityPose(
|
||||
PhotonPipelineResult cameraResult);
|
||||
@@ -306,7 +306,7 @@ class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's no targets.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateClosestToCameraHeightPose(
|
||||
PhotonPipelineResult cameraResult);
|
||||
@@ -319,7 +319,7 @@ class PhotonPoseEstimator {
|
||||
* @param referencePose reference pose to check vector magnitude difference
|
||||
* against.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's no targets.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateClosestToReferencePose(
|
||||
PhotonPipelineResult cameraResult, frc::Pose3d referencePose);
|
||||
@@ -331,7 +331,8 @@ class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate or std::nullopt if there's no targets,
|
||||
* no multi-tag results, or multi-tag is disabled in the web UI.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateCoprocMultiTagPose(
|
||||
PhotonPipelineResult cameraResult);
|
||||
@@ -345,7 +346,8 @@ class PhotonPoseEstimator {
|
||||
* @param cameraMatrix Camera intrinsics from camera calibration data.
|
||||
* @param distCoeffs Distortion coefficients from camera calibration data.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's less than 2
|
||||
* targets visible or SolvePnP fails.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateRioMultiTagPose(
|
||||
PhotonPipelineResult cameraResult, PhotonCamera::CameraMatrix camMat,
|
||||
@@ -363,7 +365,8 @@ class PhotonPoseEstimator {
|
||||
*
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's no targets
|
||||
* or heading data.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimatePnpDistanceTrigSolvePose(
|
||||
PhotonPipelineResult cameraResult);
|
||||
@@ -372,7 +375,7 @@ class PhotonPoseEstimator {
|
||||
* Return the average of the best target poses using ambiguity as weight.
|
||||
* @param cameraResult A pipeline result from the camera.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's no targets.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateAverageBestTargetsPose(
|
||||
PhotonPipelineResult cameraResult);
|
||||
@@ -401,7 +404,8 @@ class PhotonPoseEstimator {
|
||||
* changing our robot heading estimate against the tag corner reprojection
|
||||
* error cost.
|
||||
* @return An EstimatedRobotPose with an estimated pose, timestamp, and
|
||||
* targets used to create the estimate.
|
||||
* targets used to create the estimate, or std::nullopt if there's no targets
|
||||
* or heading data, or if the solver fails to solve the problem.
|
||||
*/
|
||||
std::optional<EstimatedRobotPose> EstimateConstrainedSolvepnpPose(
|
||||
photon::PhotonPipelineResult cameraResult,
|
||||
|
||||
@@ -179,6 +179,28 @@ class VisionSystemSimTest {
|
||||
assertTrue(result.hasTargets());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBunchaTargets() {
|
||||
var visionSysSim = new VisionSystemSim("Test");
|
||||
var camera = new PhotonCamera(inst, "camera");
|
||||
var cameraSim = new PhotonCameraSim(camera);
|
||||
visionSysSim.addCamera(cameraSim, new Transform3d());
|
||||
cameraSim.prop.setCalibration(640, 480, Rotation2d.fromDegrees(80));
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final var targetPose =
|
||||
new Pose3d(new Translation3d(15.98 + i * 0.1, 0, 1), new Rotation3d(0, 0, Math.PI));
|
||||
visionSysSim.addVisionTargets(new VisionTargetSim(targetPose, new TargetModel(0.5, 0.5), i));
|
||||
}
|
||||
|
||||
var robotPose = new Pose2d(new Translation2d(5, 0), Rotation2d.fromDegrees(5));
|
||||
visionSysSim.update(robotPose);
|
||||
|
||||
var res = waitForSequenceNumber(camera, 1);
|
||||
|
||||
assertEquals(50, res.getTargets().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotVisibleVert1() {
|
||||
final var targetPose =
|
||||
|
||||
@@ -117,6 +117,29 @@ TEST_F(VisionSystemSimTest, TestVisibilityCupidShuffle) {
|
||||
ASSERT_TRUE(camera.GetLatestResult().HasTargets());
|
||||
}
|
||||
|
||||
TEST_F(VisionSystemSimTest, TestBunchaTargets) {
|
||||
photon::VisionSystemSim visionSysSim{"Test"};
|
||||
photon::PhotonCamera camera{"camera"};
|
||||
photon::PhotonCameraSim cameraSim{&camera};
|
||||
visionSysSim.AddCamera(&cameraSim, frc::Transform3d{});
|
||||
cameraSim.prop.SetCalibration(640, 480, frc::Rotation2d{80_deg});
|
||||
|
||||
std::vector<photon::VisionTargetSim> targets;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
targets.emplace_back(
|
||||
frc::Pose3d{
|
||||
frc::Translation3d{15.98_m + i * 0.1_m, 0_m, 1_m},
|
||||
frc::Rotation3d{0_rad, 0_rad, units::radian_t{std::numbers::pi}}},
|
||||
photon::TargetModel{0.5_m, 0.5_m}, i);
|
||||
}
|
||||
visionSysSim.AddVisionTargets(targets);
|
||||
|
||||
frc::Pose2d robotPose{frc::Translation2d{5_m, 0_m}, frc::Rotation2d{5_deg}};
|
||||
visionSysSim.Update(robotPose);
|
||||
|
||||
ASSERT_EQ(camera.GetLatestResult().targets.size(), 50u);
|
||||
}
|
||||
|
||||
TEST_F(VisionSystemSimTest, TestNotVisibleVert1) {
|
||||
frc::Pose3d targetPose{
|
||||
frc::Translation3d{15.98_m, 0_m, 1_m},
|
||||
|
||||
Reference in New Issue
Block a user