Add rotatedrect detections for OD and bump rubik JNI (#2325)

## Description

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

Pursuant to PhotonVision/rubik_jni#21 modify the neuralnetworkresult and related code to use a rotatedrect. This is scaffolding for implementing OBB -- overall this shouldn't change behavior of existing normal object detection models. This PR also bumps the rubik_jni version.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
This commit is contained in:
Sam Freund
2026-02-18 11:01:00 -06:00
committed by GitHub
parent f2e262d59d
commit 95f637f1d6
6 changed files with 48 additions and 14 deletions

View File

@@ -39,7 +39,7 @@ ext {
javalinVersion = "6.7.0"
libcameraDriverVersion = "v2026.0.0"
rknnVersion = "v2026.0.1"
rubikVersion = "v2026.0.1"
rubikVersion = "dev-v2026.0.1-3-g977bb2e"
frcYear = "2026"
mrcalVersion = "v2026.0.0";

View File

@@ -21,7 +21,8 @@ import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Rect2d;
import org.opencv.core.Point;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
@@ -86,21 +87,26 @@ public class Letterbox {
* @return The resized detections
*/
public List<NeuralNetworkPipeResult> resizeDetections(List<NeuralNetworkPipeResult> unscaled) {
var ret = new ArrayList<NeuralNetworkPipeResult>();
var ret = new ArrayList<NeuralNetworkPipeResult>(unscaled.size());
for (var t : unscaled) {
var scale = 1.0 / this.scale;
var boundingBox = t.bbox();
double x = (boundingBox.x - this.dx) * scale;
double y = (boundingBox.y - this.dy) * scale;
double width = boundingBox.width * scale;
double height = boundingBox.height * scale;
double cx = (boundingBox.center.x - this.dx) * scale;
double cy = (boundingBox.center.y - this.dy) * scale;
double width = boundingBox.size.width * scale;
double height = boundingBox.size.height * scale;
Point center = new Point(cx, cy);
Size size = new Size(width, height);
// angle is unchanged from letterbox transformation
ret.add(
new NeuralNetworkPipeResult(
new Rect2d(x, y, width, height), t.classIdx(), t.confidence()));
new RotatedRect(center, size, boundingBox.angle), t.classIdx(), t.confidence()));
}
return ret;
}
}

View File

@@ -65,7 +65,9 @@ public class RubikObjectDetector implements ObjectDetector {
// Create the detector
try {
ptr = RubikJNI.create(model.modelFile.getPath().toString());
ptr =
RubikJNI.create(
model.modelFile.getPath().toString(), model.properties.version().ordinal());
} catch (Exception e) {
logger.error("Failed to create detector from path " + model.modelFile.getPath(), e);
throw new RuntimeException(

View File

@@ -57,6 +57,17 @@ public class Contour implements Releasable {
new Point(box.x, box.y + box.height));
}
public Contour(RotatedRect obb) {
Point[] pts = new Point[4];
for (int i = 0; i < 4; ++i) pts[i] = new Point();
obb.points(pts);
// target: tl tr br bl
// pts array: "The order is bottomLeft, topLeft, topRight, bottomRight."
this.mat = new MatOfPoint(pts[1], pts[2], pts[3], pts[0]);
}
public MatOfPoint2f getMat2f() {
if (mat2f == null) {
mat2f = new MatOfPoint2f(mat.toArray());

View File

@@ -22,6 +22,7 @@ import java.util.List;
import org.photonvision.common.util.numbers.DoubleCouple;
import org.photonvision.vision.frame.FrameStaticProperties;
import org.photonvision.vision.pipe.CVPipe;
import org.photonvision.vision.target.TargetCalculations;
public class FilterObjectDetectionsPipe
extends CVPipe<
@@ -44,13 +45,13 @@ public class FilterObjectDetectionsPipe
var boc = contour.bbox();
// Area filtering
double areaPercentage = boc.area() / params.frameStaticProperties().imageArea * 100.0;
double areaPercentage = boc.size.area() / params.frameStaticProperties().imageArea * 100.0;
double minAreaPercentage = params.area().getFirst();
double maxAreaPercentage = params.area().getSecond();
if (areaPercentage < minAreaPercentage || areaPercentage > maxAreaPercentage) return;
// Aspect ratio filtering; much simpler since always axis-aligned
double aspectRatio = boc.width / boc.height;
// Aspect ratio filtering
double aspectRatio = TargetCalculations.getAspectRatio(boc, params.isLandscape());
if (aspectRatio < params.ratio().getFirst() || aspectRatio > params.ratio().getSecond()) return;
m_filteredContours.add(contour);

View File

@@ -17,6 +17,20 @@
package org.photonvision.vision.pipe.impl;
import org.opencv.core.Point;
import org.opencv.core.Rect2d;
import org.opencv.core.RotatedRect;
import org.opencv.core.Size;
public record NeuralNetworkPipeResult(Rect2d bbox, int classIdx, double confidence) {}
public record NeuralNetworkPipeResult(RotatedRect bbox, int classIdx, double confidence) {
public NeuralNetworkPipeResult(Rect2d rect, int classIdx, double confidence) {
// turn the axis-aligned rect into a RotatedRect with angle 0 degrees
this(
new RotatedRect(
new Point(rect.x + (rect.width) / 2, rect.y + (rect.height) / 2),
new Size(rect.width, rect.height),
0.0),
classIdx,
confidence);
}
}