Skip to content

Camera undistort for RVC4 #1363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open

Conversation

asahtik
Copy link
Contributor

@asahtik asahtik commented Jun 23, 2025

Purpose

Implements undistortion on RVC4 (Camera / ImageManip)

Specification

None / not applicable

Dependencies & Potential Impact

None / not applicable

Deployment Plan

None / not applicable

Testing & Validation

None / not applicable

Copy link
Collaborator

@moratom moratom left a comment

Choose a reason for hiding this comment

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

Thanks!

Left some comments/questions.
We should also move the camera_undistort.py example from the RVC2 folder before merging.

Comment on lines 202 to 379
}

// Scale down crop
{
auto cfg = std::make_shared<dai::ImageManipConfig>(*config);
cfg->setOutputSize(600, 400, dai::ImageManipConfig::ResizeMode::CENTER_CROP);
configQueue->send(cfg);
auto outFrame = outputQueue->get<dai::ImgFrame>();
REQUIRE(outFrame != nullptr);
REQUIRE(outFrame->getWidth() == 600);
REQUIRE(outFrame->getHeight() == 400);
}

// Scale down letterbox
{
auto cfg = std::make_shared<dai::ImageManipConfig>(*config);
cfg->setOutputSize(600, 400, dai::ImageManipConfig::ResizeMode::LETTERBOX);
configQueue->send(cfg);
auto outFrame = outputQueue->get<dai::ImgFrame>();
REQUIRE(outFrame != nullptr);
REQUIRE(outFrame->getWidth() == 600);
REQUIRE(outFrame->getHeight() == 400);
}

// Scale down letterbox bg
{
auto cfg = std::make_shared<dai::ImageManipConfig>(*config);
cfg->setOutputSize(600, 400, dai::ImageManipConfig::ResizeMode::LETTERBOX);
cfg->setBackgroundColor(100, 0, 0);
configQueue->send(cfg);
auto outFrame = outputQueue->get<dai::ImgFrame>();
REQUIRE(outFrame != nullptr);
REQUIRE(outFrame->getWidth() == 600);
REQUIRE(outFrame->getHeight() == 400);
}

// Crop
{
auto cfg = std::make_shared<dai::ImageManipConfig>(*config);
cfg->addCrop(100, 200, 600, 400);
configQueue->send(cfg);
auto outFrame = outputQueue->get<dai::ImgFrame>();
REQUIRE(outFrame != nullptr);
REQUIRE(outFrame->getWidth() == 600);
REQUIRE(outFrame->getHeight() == 400);
}

// Affine
{
auto cfg = std::make_shared<dai::ImageManipConfig>(*config);
cfg->addCropRotatedRect(dai::RotatedRect(dai::Point2f(350, 250), dai::Size2f(600, 400), 20));
configQueue->send(cfg);
auto outFrame = outputQueue->get<dai::ImgFrame>();
REQUIRE(outFrame != nullptr);
REQUIRE(outFrame->getWidth() == 600);
REQUIRE(outFrame->getHeight() == 400);
}

// Scale down small
{
auto cfg = std::make_shared<dai::ImageManipConfig>(*config);
cfg->addCrop(100, 100, 199, 199);
cfg->setOutputSize(100, 100);
configQueue->send(cfg);
auto outFrame = outputQueue->get<dai::ImgFrame>();
REQUIRE(outFrame != nullptr);
REQUIRE(outFrame->getWidth() == 100);
REQUIRE(outFrame->getHeight() == 100);
}

p.stop();
}

TEST_CASE("ImageManip NV12") {
runManipTests(dai::ImgFrame::Type::NV12, false);
}

TEST_CASE("ImageManip GRAY8") {
runManipTests(dai::ImgFrame::Type::GRAY8, false);
}

TEST_CASE("ImageManip RGB888i") {
runManipTests(dai::ImgFrame::Type::RGB888i, false);
}

TEST_CASE("ImageManip NV12 undistort no coefficients") {
runManipTests(dai::ImgFrame::Type::NV12, true);
}

TEST_CASE("ImageManip NV12 undistort") {
runManipTests(dai::ImgFrame::Type::NV12, true, {-7.56764030456543, 18.97133445739746, 0.0006435539107769728, -5.642612813971937e-05, 6.156050682067871, -7.587080001831055, 19.094820022583008, 5.732314109802246, 0.0, 0.0, 0.0, 0.0, -0.0009434317471459508, 0.002672438742592931});
}

TEST_CASE("ImageManip GRAY8 undistort") {
runManipTests(dai::ImgFrame::Type::GRAY8, true, {-7.56764030456543, 18.97133445739746, 0.0006435539107769728, -5.642612813971937e-05, 6.156050682067871, -7.587080001831055, 19.094820022583008, 5.732314109802246, 0.0, 0.0, 0.0, 0.0, -0.0009434317471459508, 0.002672438742592931});
}

TEST_CASE("ImageManip RGB888i undistort") {
runManipTests(dai::ImgFrame::Type::RGB888i, true, {-7.56764030456543, 18.97133445739746, 0.0006435539107769728, -5.642612813971937e-05, 6.156050682067871, -7.587080001831055, 19.094820022583008, 5.732314109802246, 0.0, 0.0, 0.0, 0.0, -0.0009434317471459508, 0.002672438742592931});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't actually test if undistortion is working? I think we should add a distorted and an undistorted image and then compare byte by byte.

Comment on lines +920 to +944
#ifdef DEPTHAI_HAVE_OPENCV_SUPPORT
void dai::impl::UndistortOpenCvImpl::undistort(cv::Mat& src, cv::Mat& dst) {
if(dst.size().width == (int)width && dst.size().height == (int)height) {
cv::remap(src, dst, undistortMap1, undistortMap2, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));
} else if(dst.size().width == (int)width / 2 && dst.size().height == (int)height / 2) {
if(undistortMap1Half.empty() || undistortMap2Half.empty()) {
cv::Mat cvCameraMatrix(3, 3, CV_32F, this->cameraMatrix.data());
cvCameraMatrix.at<float>(0, 2) /= 2;
cvCameraMatrix.at<float>(1, 2) /= 2;
cv::Mat newCameraMatrix = cv::getOptimalNewCameraMatrix(cvCameraMatrix, distCoeffs, cv::Size(width / 2, height / 2), 1);
cv::initUndistortRectifyMap(
cvCameraMatrix, distCoeffs, cv::Mat(), newCameraMatrix, cv::Size(width / 2, height / 2), CV_16SC2, undistortMap1Half, undistortMap2Half);
}
cv::remap(src, dst, undistortMap1Half, undistortMap2Half, cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(127, 127));
} else {
throw std::runtime_error(fmt::format("UndistortImpl: Output size does not match the expected size (got {}x{}, expected {}x{} or {}x{})",
dst.size().width,
dst.size().height,
width,
height,
width / 2,
height / 2));
}
}
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should get the camera matrix for initUndistortRectifyMap from the ImageManip itself (and alpha should not be used) to keep the behavior predictable.

So right now however ImageManip node is configured the ImgTransformations and internally as well we should be able to tell what are the target image intrinsics and then use those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants