Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,14 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) {
py::arg("distortionModel"),
py::arg("distortionCoefficients"),
DOC(dai, ImgTransformation, ImgTransformation, 5))
.def(py::init<size_t, size_t, std::array<std::array<float, 3>, 3>, CameraModel, std::vector<float>, Extrinsics>(),
.def(py::init<size_t, size_t, std::array<std::array<float, 3>, 3>, CameraModel, std::vector<float>, Extrinsics, std::string>(),
py::arg("width"),
py::arg("height"),
py::arg("sourceIntrinsicMatrix"),
py::arg("distortionModel"),
py::arg("distortionCoefficients"),
py::arg("extrinsics"),
py::arg("deviceId") = "",
DOC(dai, ImgTransformation, ImgTransformation, 6))

.def("getTransformationMatrix", &ImgTransformation::getTransformationMatrix, DOC(dai, ImgTransformation, getTransformationMatrix))
Expand All @@ -165,6 +166,7 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) {
.def("getDistortionModel", &ImgTransformation::getDistortionModel, DOC(dai, ImgTransformation, getDistortionModel))
.def("getDistortionCoefficients", &ImgTransformation::getDistortionCoefficients, DOC(dai, ImgTransformation, getDistortionCoefficients))
.def("getExtrinsics", &ImgTransformation::getExtrinsics, DOC(dai, ImgTransformation, getExtrinsics))
.def("getDeviceId", &ImgTransformation::getDeviceId)
.def("getSrcCrops", &ImgTransformation::getSrcCrops, DOC(dai, ImgTransformation, getSrcCrops))
.def("getSrcMaskPt", &ImgTransformation::getSrcMaskPt, py::arg("x"), py::arg("y"), DOC(dai, ImgTransformation, getSrcMaskPt))
.def("getDstMaskPt", &ImgTransformation::getDstMaskPt, py::arg("x"), py::arg("y"), DOC(dai, ImgTransformation, getDstMaskPt))
Expand All @@ -179,6 +181,7 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) {
py::arg("coefficients"),
DOC(dai, ImgTransformation, setDistortionCoefficients))
.def("setExtrinsics", &ImgTransformation::setExtrinsics, py::arg("extrinsics"), DOC(dai, ImgTransformation, setExtrinsics))
.def("setDeviceId", &ImgTransformation::setDeviceId, py::arg("deviceId"))
.def("setSize", &ImgTransformation::setSize, py::arg("width"), py::arg("height"), DOC(dai, ImgTransformation, setSize))
.def("setSourceSize", &ImgTransformation::setSourceSize, py::arg("width"), py::arg("height"), DOC(dai, ImgTransformation, setSourceSize))
.def("addTransformation", &ImgTransformation::addTransformation, py::arg("matrix"), DOC(dai, ImgTransformation, addTransformation))
Expand Down
18 changes: 15 additions & 3 deletions include/depthai/common/ImgTransformations.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <array>
#include <string>
#include <vector>

#include "depthai/common/CameraModel.hpp"
#include "depthai/common/Extrinsics.hpp"
Expand All @@ -24,6 +26,7 @@ struct ImgTransformation {
CameraModel distortionModel = CameraModel::Perspective;
std::vector<float> distortionCoefficients;
Extrinsics extrinsics = {};
std::string deviceId;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the context of multi device pipelines, how would deviceId be used? Looking at the current code, it throws any time the deviceId is different. Is there a reason you decided against deviceId meaning the target device? Is there a reason why we need source device ID at all?


size_t srcWidth = 0;
size_t srcHeight = 0;
Expand Down Expand Up @@ -67,11 +70,13 @@ struct ImgTransformation {
std::array<std::array<float, 3>, 3> sourceIntrinsicMatrix,
CameraModel distortionModel,
std::vector<float> distortionCoefficients,
Extrinsics extrinsics)
Extrinsics extrinsics,
std::string deviceId = {})
: sourceIntrinsicMatrix(sourceIntrinsicMatrix),
distortionModel(distortionModel),
distortionCoefficients(std::move(distortionCoefficients)),
extrinsics(std::move(extrinsics)),
deviceId(std::move(deviceId)),
srcWidth(width),
srcHeight(height),
width(width),
Expand Down Expand Up @@ -149,10 +154,15 @@ struct ImgTransformation {
* @return Extrinsics
*/
Extrinsics getExtrinsics() const;
/**
* Retrieve the device id of the source sensor.
* @return Device id string, or empty if not known.
*/
const std::string& getDeviceId() const;

/**
* Two transformations are equal if the transformation matrices, intrinsic matrices, distortion models,
* distortion coefficients, extrinsics, and sizes are all equal.
* distortion coefficients, extrinsics, device ids, and sizes are all equal.
* @param other Transformation to compare with
* @return True if the transformations are equal, false otherwise
*/
Expand Down Expand Up @@ -251,6 +261,7 @@ struct ImgTransformation {
ImgTransformation& setSize(size_t width, size_t height);
ImgTransformation& setSourceSize(size_t width, size_t height);
ImgTransformation& setExtrinsics(const Extrinsics& extrinsics);
ImgTransformation& setDeviceId(const std::string& deviceId);
ImgTransformation& setIntrinsicMatrix(const std::array<std::array<float, 3>, 3>& intrinsicMatrix);
ImgTransformation& setDistortionModel(CameraModel model);
ImgTransformation& setDistortionCoefficients(const std::vector<float>& coefficients);
Expand Down Expand Up @@ -402,7 +413,8 @@ struct ImgTransformation {
srcHeight,
width,
height,
srcCrops);
srcCrops,
deviceId);
};

} // namespace dai
1 change: 1 addition & 0 deletions protos/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ message ImgTransformation {
uint32 height = 8;
Extrinsics extrinsics = 9;
repeated RotatedRect srcCrops = 10;
string deviceId = 11;
}

message TransformationMatrix {
Expand Down
29 changes: 29 additions & 0 deletions src/pipeline/datatype/ImgTransformations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,25 @@
#include "pipeline/utilities/Alignment/AlignmentUtilities.hpp"
namespace dai {

namespace {

constexpr float ROUND_UP_EPS = 1e-3f;

bool differentKnownDeviceIds(const ImgTransformation& lhs, const ImgTransformation& rhs) {
const auto& lhsDeviceId = lhs.getDeviceId();
Comment on lines +24 to +25

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lhs and rhs arent general parameter names. From my understanding we will be using deviceId for multi-device cases so this naming really does not fit here

const auto& rhsDeviceId = rhs.getDeviceId();
return !lhsDeviceId.empty() && !rhsDeviceId.empty() && lhsDeviceId != rhsDeviceId;
}

void validateSameKnownDevice(const ImgTransformation& lhs, const ImgTransformation& rhs, const std::string& operation) {
if(differentKnownDeviceIds(lhs, rhs)) {
throw std::runtime_error(
fmt::format("Cannot {} ImgTransformations from different devices: '{}' and '{}'.", operation, lhs.getDeviceId(), rhs.getDeviceId()));
}
}

} // namespace

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets remove the codex generated anonymous namespace :)


// Function to check if a point is inside a rotated rectangle
inline bool isPointInRotatedRectangle(const dai::Point2f& p, const dai::RotatedRect& rect) {
auto theta = -rect.angle * (float)M_PI / 180.0f;
Expand Down Expand Up @@ -50,6 +67,7 @@ inline bool RRinRR(const dai::RotatedRect& in, const dai::RotatedRect& out) {
}

dai::Point2f interSourceFrameTransform(dai::Point2f sourcePt, const ImgTransformation& from, const ImgTransformation& to) {
validateSameKnownDevice(from, to, "remap between");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expand the function to also validate the toCameraSockets like it is done in the below lines. Also if deviceId means target device to where it is pointing to, then we should make it a member funciton of Extrnisics

if(from.isEqualTransformation(to)) {
return sourcePt;
}
Expand All @@ -67,6 +85,7 @@ dai::Point2f interSourceFrameTransform(dai::Point2f sourcePt, const ImgTransform
}

dai::RotatedRect interSourceFrameTransform(const dai::RotatedRect& sourceRect, const ImgTransformation& from, const ImgTransformation& to) {
validateSameKnownDevice(from, to, "remap between");
if(from.isEqualTransformation(to)) {
return sourceRect;
}
Expand Down Expand Up @@ -119,6 +138,7 @@ bool ImgTransformation::isEqualTransformation(const ImgTransformation& other) co
auto thisExtrinsics = getExtrinsics();
auto otherExtrinsics = other.getExtrinsics();
if(!thisExtrinsics.isEqualExtrinsics(otherExtrinsics)) return false;
if(getDeviceId() != other.getDeviceId()) return false;

if(getSize() != other.getSize()) return false;
if(getSourceSize() != other.getSourceSize()) return false;
Expand Down Expand Up @@ -239,6 +259,9 @@ std::vector<float> ImgTransformation::getDistortionCoefficients() const {
Extrinsics ImgTransformation::getExtrinsics() const {
return extrinsics;
}
const std::string& ImgTransformation::getDeviceId() const {
return deviceId;
}
std::vector<dai::RotatedRect> ImgTransformation::getSrcCrops() const {
return srcCrops;
}
Expand Down Expand Up @@ -351,6 +374,10 @@ ImgTransformation& ImgTransformation::setExtrinsics(const Extrinsics& extrinsics
this->extrinsics = extrinsics;
return *this;
}
ImgTransformation& ImgTransformation::setDeviceId(const std::string& deviceId) {
this->deviceId = deviceId;
return *this;
}
ImgTransformation& ImgTransformation::setDistortionModel(CameraModel model) {
distortionModel = model;
return *this;
Expand Down Expand Up @@ -528,10 +555,12 @@ std::array<float, 3> ImgTransformation::getTranslationVectorTo(const ImgTransfor
std::array<std::array<float, 4>, 4> ImgTransformation::getExtrinsicsTransformationMatrixTo(const ImgTransformation& to,
const bool useSpecTranslation,
const LengthUnit sourceUnit) const {
validateSameKnownDevice(*this, to, "get extrinsics transformation between");
return this->extrinsics.getExtrinsicsTransformationTo(to.getExtrinsics(), useSpecTranslation, sourceUnit);
}

bool ImgTransformation::isAlignedTo(const ImgTransformation& to) const {
if(differentKnownDeviceIds(*this, to)) return false;
if(width != to.width || height != to.height) return false;
if(this->distortionModel != to.distortionModel) return false;
auto approxEqual = [](float a, float b, float absTol = ROUND_UP_EPS, float relTol = 2 * ROUND_UP_EPS) {
Expand Down
13 changes: 7 additions & 6 deletions src/pipeline/node/Rectification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,15 @@ std::vector<std::vector<float> > applyRectificationMatrix(const dai::Extrinsics&
return dai::matrix::matrix3x3ToVectorMatrix(dai::matrix::matMul(inputRotationMatrix, arrayR1Inv));
}

dai::ImgTransformation createRectifiedImgTransformation(const dai::Extrinsics& extrinsics,
const cv::Mat& cvIntrinsicMatrix,
uint32_t outputWidth,
uint32_t outputHeight) {
dai::ImgTransformation createRectifiedImgTransformation(
const dai::Extrinsics& extrinsics, const cv::Mat& cvIntrinsicMatrix, uint32_t outputWidth, uint32_t outputHeight, const std::string& deviceId) {
dai::ImgTransformation outputImgTransformation;
// Rectified frame is treated as a brand new camera, so source size is set to output size
outputImgTransformation.setSourceSize(outputWidth, outputHeight);
outputImgTransformation.setSize(outputWidth, outputHeight);
outputImgTransformation.setIntrinsicMatrix(dai::matrix::cvMatToMatrix3x3(cvIntrinsicMatrix));
outputImgTransformation.setExtrinsics(extrinsics);
outputImgTransformation.setDeviceId(deviceId);
outputImgTransformation.setDistortionCoefficients({});
return outputImgTransformation;
}
Expand Down Expand Up @@ -230,8 +229,10 @@ void Rectification::run() {
cv_rectificationMap2X,
cv_rectificationMap2Y);

output1ImgTransformation = createRectifiedImgTransformation(output1Extrinsics, cv_targetCameraMatrix1, output1FrameWidth, output1FrameHeight);
output2ImgTransformation = createRectifiedImgTransformation(output2Extrinsics, cv_targetCameraMatrix2, output2FrameWidth, output2FrameHeight);
output1ImgTransformation = createRectifiedImgTransformation(
output1Extrinsics, cv_targetCameraMatrix1, output1FrameWidth, output1FrameHeight, input1ImgTransformation.getDeviceId());
output2ImgTransformation = createRectifiedImgTransformation(
output2Extrinsics, cv_targetCameraMatrix2, output2FrameWidth, output2FrameHeight, input2ImgTransformation.getDeviceId());

logger->debug("R = {}", matToString(cv_R));
logger->debug("T = {}", matToString(cv_T));
Expand Down
2 changes: 2 additions & 0 deletions src/utility/ProtoSerialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void serializeImgTransformation(proto::common::ImgTransformation* imgTransformat
}

imgTransformation->set_distortionmodel(static_cast<proto::common::CameraModel>(transformation.getDistortionModel()));
imgTransformation->set_deviceid(transformation.getDeviceId());
proto::common::FloatArray* distortionCoefficients = imgTransformation->mutable_distortioncoefficients();
for(const auto& value : transformation.getDistortionCoefficients()) {
distortionCoefficients->add_values(value);
Expand Down Expand Up @@ -185,6 +186,7 @@ ImgTransformation deserializeImgTransformation(const proto::common::ImgTransform
static_cast<CameraModel>(imgTransformation.distortionmodel()),
distortionCoefficients,
extrinsics);
transformation.setDeviceId(imgTransformation.deviceid());
if(transformation.isValid()) {
transformation.addTransformation(transformationMatrix);
if(!srcCrops.empty()) {
Expand Down
3 changes: 3 additions & 0 deletions tests/src/ondevice_tests/img_transformation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ const std::filesystem::path& getTransformationTestDataFolder() {
// -----------------------------------------------------------------------------
TEST_CASE("ImgTransformation in ImgFrame") {
dai::Pipeline pipeline;
const auto expectedDeviceId = pipeline.getDefaultDevice()->getDeviceId();
auto cam = pipeline.create<dai::node::Camera>()->build();
auto camOut = cam->requestOutput({600, 400}, dai::ImgFrame::Type::NV12);
auto q = camOut->createOutputQueue();
Expand All @@ -411,6 +412,8 @@ TEST_CASE("ImgTransformation in ImgFrame") {
REQUIRE(!isIdentity(frame->transformation.getMatrixInv()));
REQUIRE(!isIdentity(frame->transformation.getSourceIntrinsicMatrix()));
REQUIRE(!isIdentity(frame->transformation.getSourceIntrinsicMatrixInv()));
REQUIRE_FALSE(expectedDeviceId.empty());
REQUIRE(frame->transformation.getDeviceId() == expectedDeviceId);
}

// -----------------------------------------------------------------------------
Expand Down
36 changes: 36 additions & 0 deletions tests/src/onhost_tests/image_transformations_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "depthai/common/Extrinsics.hpp"
#include "depthai/common/ImgTransformations.hpp"
#include "depthai/utility/ImageManipImpl.hpp"
#include "depthai/utility/Serialization.hpp"
#define CATCH_CONFIG_MAIN

#include <catch2/catch_all.hpp>
Expand Down Expand Up @@ -231,6 +232,41 @@ TEST_CASE("identityTransformation") {
REQUIRE_THAT(back.height, Catch::Matchers::WithinAbs(r.height, 1e-6));
}

TEST_CASE("ImgTransformation deviceId metadata") {
dai::ImgTransformation source(640, 480);
REQUIRE(source.getDeviceId().empty());

source.setDeviceId("mxid-a");
REQUIRE(source.getDeviceId() == "mxid-a");

dai::ImgTransformation sameDevice(640, 480);
sameDevice.setDeviceId("mxid-a");
REQUIRE(source.isEqualTransformation(sameDevice));
REQUIRE(source.isAlignedTo(sameDevice));

dai::ImgTransformation otherDevice(640, 480);
otherDevice.setDeviceId("mxid-b");
REQUIRE_FALSE(source.isEqualTransformation(otherDevice));
REQUIRE_FALSE(source.isAlignedTo(otherDevice));

dai::Point2f point{10.0f, 20.0f};
REQUIRE_THROWS(source.remapPointTo(otherDevice, point));

dai::ImgTransformation replayTransformation(640, 480);
REQUIRE_FALSE(source.isEqualTransformation(replayTransformation));
REQUIRE(source.isAlignedTo(replayTransformation));

const auto remappedReplayPoint = replayTransformation.remapPointTo(source, point);
REQUIRE_THAT(remappedReplayPoint.x, Catch::Matchers::WithinAbs(point.x, 1e-6));
REQUIRE_THAT(remappedReplayPoint.y, Catch::Matchers::WithinAbs(point.y, 1e-6));

const auto serialized = dai::utility::serialize(source);
dai::ImgTransformation deserialized;
dai::utility::deserialize(serialized, deserialized);
REQUIRE(deserialized.getDeviceId() == "mxid-a");
REQUIRE(deserialized.isEqualTransformation(source));
}

// -----------------------------------------------------------------------------
// invalidTransformations
// Purpose:
Expand Down