Files
PhotonVision/docs/source/docs/simulation/simulation-java.md

309 lines
12 KiB
Markdown
Raw Normal View History

2024-09-14 23:10:02 -05:00
# Simulation Support in PhotonLib in Java
2024-06-30 16:10:12 -04:00
## What Is Simulated?
2024-06-30 16:10:12 -04:00
Simulation is a powerful tool for validating robot code without access to a physical robot. Read more about [simulation in WPILib](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html).
2024-06-30 16:10:12 -04:00
2024-09-14 23:10:02 -05:00
In Java, PhotonLib can simulate cameras on the field and generate target data approximating what would be seen in reality. This simulation attempts to include the following:
2024-06-30 16:10:12 -04:00
- Camera Properties
- Field of Vision
- Lens distortion
- Image noise
- Framerate
- Latency
2024-06-30 16:10:12 -04:00
- Target Data
- Detected / minimum-area-rectangle corners
- Center yaw/pitch
- Contour image area percentage
- Fiducial ID
- Fiducial ambiguity
- Fiducial solvePNP transform estimation
2024-06-30 16:10:12 -04:00
- Camera Raw/Processed Streams (grayscale)
:::{note}
Simulation does NOT include the following:
2024-06-30 16:10:12 -04:00
- Full physical camera/world simulation (targets are automatically thresholded)
- Image Thresholding Process (camera gain, brightness, etc)
- Pipeline switching
- Snapshots
:::
2024-06-30 16:10:12 -04:00
This scope was chosen to balance fidelity of the simulation with the ease of setup, in a way that would best benefit most teams.
```{image} diagrams/SimArchitecture.drawio.svg
:alt: A diagram comparing the architecture of a real PhotonVision process to a simulated
: one.
```
2024-06-30 16:10:12 -04:00
## Drivetrain Simulation Prerequisite
2024-06-30 16:10:12 -04:00
A prerequisite for simulating vision frames is knowing where the camera is on the field-- to utilize PhotonVision simulation, you'll need to supply the simulated robot pose periodically. This requires drivetrain simulation for your robot project if you want to generate camera frames as your robot moves around the field.
References for using PhotonVision simulation with drivetrain simulation can be found in the [PhotonLib Java Examples](https://github.com/PhotonVision/photonvision/blob/2a6fa1b6ac81f239c59d724da5339f608897c510/photonlib-java-examples/README.md) for both a differential drivetrain and a swerve drive.
2024-06-30 16:10:12 -04:00
:::{important}
The simulated drivetrain pose must be separate from the drivetrain estimated pose if a pose estimator is utilized.
:::
2024-06-30 16:10:12 -04:00
## Vision System Simulation
2024-06-30 16:10:12 -04:00
A `VisionSystemSim` represents the simulated world for one or more cameras, and contains the vision targets they can see. It is constructed with a unique label:
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// A vision system sim labelled as "main" in NetworkTables
VisionSystemSim visionSim = new VisionSystemSim("main");
```
2024-06-30 16:10:12 -04:00
PhotonLib will use this label to put a `Field2d` widget on NetworkTables at `/VisionSystemSim-[label]/Sim Field`. This label does not need to match any camera name or pipeline name in PhotonVision.
2024-06-30 16:10:12 -04:00
Vision targets require a `TargetModel`, which describes the shape of the target. For AprilTags, PhotonLib provides `TargetModel.kAprilTag16h5` for the tags used in 2023, and `TargetModel.kAprilTag36h11` for the tags used starting in 2024. For other target shapes, convenience constructors exist for spheres, cuboids, and planar rectangles. For example, a planar rectangle can be created with:
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// A 0.5 x 0.25 meter rectangular target
TargetModel targetModel = new TargetModel(0.5, 0.25);
```
2024-06-30 16:10:12 -04:00
These `TargetModel` are paired with a target pose to create a `VisionTargetSim`. A `VisionTargetSim` is added to the `VisionSystemSim` to become visible to all of its cameras.
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// The pose of where the target is on the field.
// Its rotation determines where "forward" or the target x-axis points.
// Let's say this target is flat against the far wall center, facing the blue driver stations.
Pose3d targetPose = new Pose3d(16, 4, 2, new Rotation3d(0, 0, Math.PI));
// The given target model at the given pose
VisionTargetSim visionTarget = new VisionTargetSim(targetPose, targetModel);
// Add this vision target to the vision system simulation to make it visible
visionSim.addVisionTargets(visionTarget);
```
2024-06-30 16:10:12 -04:00
:::{note}
The pose of a `VisionTargetSim` object can be updated to simulate moving targets. Note, however, that this will break latency simulation for that target.
:::
2024-06-30 16:10:12 -04:00
To use simulated object detection, you must provide an objDetClassId (zero-indexed class ID) and confidence value. When you set objDetConf to -1, the simulation computes confidence based on the area of the target in the camera's field of view. To simulate a object detection model with one class (fuel, index 0) and specify confidence, you'd write:
```{eval-rst}
.. tab-set-code::
.. code-block:: java
// arbitrary position on field
final var targetPose = new Pose3d(new Translation3d(2, 0, 0), new Rotation3d());
// Class id, zero-indexed
final int classId = 0;
// Confidence, between 0 and 1.
final float conf = 0.67f;
// 6 inch diameter ball
final TargetModel ballModel = new TargetModel(Units.inchesToMeters(6));
final var ballTargetSim = new VisionTargetSim(targetPose, ballModel, classId, conf);
// Add this vision target to the vision system simulation to make it visible
visionSim.addVisionTargets(visionTarget);
```
For convenience, an `AprilTagFieldLayout` can also be added to automatically create a target for each of its AprilTags.
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// The layout of AprilTags which we want to add to the vision system
AprilTagFieldLayout tagLayout = AprilTagFieldLayout.loadFromResource(AprilTagFields.kDefaultField.m_resourceFile);
2024-06-30 16:10:12 -04:00
visionSim.addAprilTags(tagLayout);
```
2024-06-30 16:10:12 -04:00
:::{note}
The poses of the AprilTags from this layout depend on its current alliance origin (e.g. blue or red). If this origin is changed later, the targets will have to be cleared from the `VisionSystemSim` and re-added.
:::
2024-06-30 16:10:12 -04:00
## Camera Simulation
2024-06-30 16:10:12 -04:00
Now that we have a simulation world with vision targets, we can add simulated cameras to view it.
Before adding a simulated camera, we need to define its properties. This is done with the `SimCameraProperties` class:
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// The simulated camera properties
SimCameraProperties cameraProp = new SimCameraProperties();
```
2024-06-30 16:10:12 -04:00
By default, this will create a 960 x 720 resolution camera with a 90 degree diagonal FOV(field-of-view) and no noise, distortion, or latency. If we want to change these properties, we can do so:
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// A 640 x 480 camera with a 100 degree diagonal FOV.
cameraProp.setCalibration(640, 480, Rotation2d.fromDegrees(100));
// Approximate detection noise with average and standard deviation error in pixels.
cameraProp.setCalibError(0.25, 0.08);
// Set the camera image capture framerate (Note: this is limited by robot loop rate).
cameraProp.setFPS(20);
// The average and standard deviation in milliseconds of image data latency.
cameraProp.setAvgLatencyMs(35);
cameraProp.setLatencyStdDevMs(5);
```
2024-06-30 16:10:12 -04:00
These properties are used in a `PhotonCameraSim`, which handles generating captured frames of the field from the simulated camera's perspective, and calculating the target data which is sent to the `PhotonCamera` being simulated.
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// The PhotonCamera used in the real robot code.
PhotonCamera camera = new PhotonCamera("cameraName");
// The simulation of this camera. Its values used in real robot code will be updated.
PhotonCameraSim cameraSim = new PhotonCameraSim(camera, cameraProp);
```
2024-06-30 16:10:12 -04:00
The `PhotonCameraSim` can now be added to the `VisionSystemSim`. We have to define a robot-to-camera transform, which describes where the camera is relative to the robot pose (this can be measured in CAD or by hand).
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// Our camera is mounted 0.1 meters forward and 0.5 meters up from the robot pose,
// (Robot pose is considered the center of rotation at the floor level, or Z = 0)
Translation3d robotToCameraTrl = new Translation3d(0.1, 0, 0.5);
// and pitched 15 degrees up.
Rotation3d robotToCameraRot = new Rotation3d(0, Math.toRadians(-15), 0);
Transform3d robotToCamera = new Transform3d(robotToCameraTrl, robotToCameraRot);
// Add this camera to the vision system simulation with the given robot-to-camera transform.
visionSim.addCamera(cameraSim, robotToCamera);
```
2024-06-30 16:10:12 -04:00
:::{important}
You may add multiple cameras to one `VisionSystemSim`, but not one camera to multiple `VisionSystemSim`. All targets in the `VisionSystemSim` will be visible to all its cameras.
:::
2024-06-30 16:10:12 -04:00
If the camera is mounted on a mobile mechanism (like a turret) this transform can be updated in a periodic loop.
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// The turret the camera is mounted on is rotated 5 degrees
Rotation3d turretRotation = new Rotation3d(0, 0, Math.toRadians(5));
robotToCamera = new Transform3d(
robotToCameraTrl.rotateBy(turretRotation),
robotToCameraRot.rotateBy(turretRotation));
visionSim.adjustCamera(cameraSim, robotToCamera);
```
2024-06-30 16:10:12 -04:00
## Low-Resource Vision Simulation with Photonvision
By default, PhotonCameraSim renders two simulated camera streams using OpenCV:
- Raw stream - The unprocessed camera view
- Processed stream - The camera view with vision processing overlays
These streams are nice if you want to actually view the simulated images, but they can be computationally expensive. This may cause lag and reduced simulation performance on lower-powered computers.
Lightweight Configuration
The following configuration disables both streams while still allowing tag detection and pose simulation to work. It's not perfect, but it's much better performance-wise than the default configuration.
.. code-block:: java
// lightweight config version
// var cameraProperties = new SimCameraProperties();
// cameraSim = new PhotonCameraSim(camera, cameraProperties, aprilTagLayout);
// cameraSim.enableRawStream(false); // disables raw image stream
// cameraSim.enableProcessedStream(false); // disables processed image stream
**Use Case**
This configuration is ideal for Chromebooks or low-spec machines where rendering the simulated camera images causes lag, but vision data is still desired for testing.
**What Still Works**
- AprilTag detection
- Pose estimation
- NetworkTables data publishing
- Robot positioning and targeting
**What's Disabled**
- Visual camera stream rendering
- Real-time visual debugging of camera output
## Updating The Simulation World
2024-06-30 16:10:12 -04:00
To update the `VisionSystemSim`, we simply have to pass in the simulated robot pose periodically (in `simulationPeriodic()`).
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// Update with the simulated drivetrain pose. This should be called every loop in simulation.
visionSim.update(robotPoseMeters);
```
2024-06-30 16:10:12 -04:00
Targets and cameras can be added and removed, and camera properties can be changed at any time.
## Visualizing Results
2024-06-30 16:10:12 -04:00
Each `VisionSystemSim` has its own built-in `Field2d` for displaying object poses in the simulation world such as the robot, simulated cameras, and actual/measured target poses.
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// Get the built-in Field2d used by this VisionSystemSim
visionSim.getDebugField();
```
2024-06-30 16:10:12 -04:00
:::{figure} images/SimExampleField.png
_A_ `VisionSystemSim`_'s internal_ `Field2d` _customized with target images and colors_
:::
2024-06-30 16:10:12 -04:00
A `PhotonCameraSim` can also draw and publish generated camera frames to a MJPEG stream similar to an actual PhotonVision process.
2024-06-30 16:10:12 -04:00
```{eval-rst}
2024-06-30 16:10:12 -04:00
.. tab-set-code::
.. code-block:: java
2024-06-30 16:10:12 -04:00
// Enable the raw and processed streams. These are enabled by default.
cameraSim.enableRawStream(true);
cameraSim.enableProcessedStream(true);
// Enable drawing a wireframe visualization of the field to the camera streams.
// This is extremely resource-intensive and is disabled by default.
cameraSim.enableDrawWireframe(true);
```
2024-06-30 16:10:12 -04:00
These streams follow the port order mentioned in {ref}`docs/quick-start/networking:Camera Stream Ports`. For example, a single simulated camera will have its raw stream at `localhost:1181` and processed stream at `localhost:1182`, which can also be found in the CameraServer tab of Shuffleboard like a normal camera stream.
2024-06-30 16:10:12 -04:00
:::{figure} images/SimExampleFrame.png
_A frame from the processed stream of a simulated camera viewing some 2023 AprilTags with the field wireframe enabled_
:::