Skip to content
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

Fix rectifyRoi when used with binning and/or ROI #378

Merged
merged 3 commits into from
Nov 23, 2021

Conversation

mintar
Copy link
Contributor

@mintar mintar commented Mar 4, 2021

The previous implementation used K_ and P_ (the matrices that include binning and ROI). However, the ROI is specified in unbinned and untransformed raw image coordinates (see REP-104), so rectifyRoi has to use K_full_ and P_full_.

@mintar
Copy link
Contributor Author

mintar commented Mar 4, 2021

Here are some side-by-side comparisons of the effect of this PR. Shown in red is the rawRoi (drawn in the raw image before rectification), green is the rectifiedRoi.

Left: without this PR, Right: with this PR

no binning, no ROI (output is identical)

img

binning = 2, no ROI (output is identical)

img_with_binning

no binning, with ROI (output is fixed by this PR)

img_with_roi

with binning, with ROI (output is fixed by this PR)

img_with_binning_and_roi

Code

The following code was used to produce these results:

TEST_F(PinholeTest, rectifiedRoiSize) {
  // --- prepare grid_img
  cv::Mat grid_img(model_.fullResolution(), CV_8UC3, cv::Scalar(0, 0, 0));

  // draw a grid
  const cv::Scalar color = cv::Scalar(255, 255, 255);
  const int thickness = 7;
  const int type = 8;
  for (size_t y = 0; y <= grid_img.rows; y += grid_img.rows / 10) {
    cv::line(grid_img,
             cv::Point(0, y), cv::Point(cam_info_.width, y),
             color, type, thickness);
  }
  for (size_t x = 0; x <= grid_img.cols; x += grid_img.cols / 10) {
    cv::line(grid_img,
             cv::Point(x, 0), cv::Point(x, cam_info_.height),
             color, type, thickness);
  }

  cv::Rect rectified_roi = model_.rectifiedRoi();
  cv::Size reduced_resolution = model_.reducedResolution();
  EXPECT_EQ(0, rectified_roi.x);
  EXPECT_EQ(0, rectified_roi.y);
  EXPECT_EQ(640, rectified_roi.width);
  EXPECT_EQ(480, rectified_roi.height);
  EXPECT_EQ(640, reduced_resolution.width);
  EXPECT_EQ(480, reduced_resolution.height);

  {
    cv::Mat raw_img = grid_img.clone();
    cv::rectangle(raw_img, model_.rawRoi(), cv::Scalar(0, 0, 255), 1);
    raw_img = raw_img(model_.rawRoi());
    cv::resize(raw_img, raw_img, cv::Size(), 1.0 / model_.binningX(), 1.0 / model_.binningY(), cv::INTER_NEAREST);
    cv::Mat rectified_img;
    model_.rectifyImage(raw_img, rectified_img);

    cv::Mat full_rectified_img(model_.fullResolution(), CV_8UC3, cv::Scalar(0, 0, 0));
    cv::resize(rectified_img, rectified_img, cv::Size(), model_.binningX(), model_.binningY(), cv::INTER_NEAREST);
    rectified_img.copyTo(full_rectified_img(model_.rawRoi()));
    cv::rectangle(full_rectified_img, model_.rectifiedRoi(), cv::Scalar(0, 255, 0), 1);
    cv::imwrite("/tmp/img.png", full_rectified_img);
  }

  cam_info_.binning_x = 2;
  cam_info_.binning_y = 2;
  model_.fromCameraInfo(cam_info_);
  rectified_roi = model_.rectifiedRoi();
  reduced_resolution = model_.reducedResolution();
  EXPECT_EQ(0, rectified_roi.x);
  EXPECT_EQ(0, rectified_roi.y);
  EXPECT_EQ(640, rectified_roi.width);
  EXPECT_EQ(480, rectified_roi.height);
  EXPECT_EQ(320, reduced_resolution.width);
  EXPECT_EQ(240, reduced_resolution.height);

  {
    cv::Mat raw_img = grid_img.clone();
    cv::rectangle(raw_img, model_.rawRoi(), cv::Scalar(0, 0, 255), 1);
    raw_img = raw_img(model_.rawRoi());
    cv::resize(raw_img, raw_img, cv::Size(), 1.0 / model_.binningX(), 1.0 / model_.binningY(), cv::INTER_NEAREST);
    cv::Mat rectified_img;
    model_.rectifyImage(raw_img, rectified_img);

    cv::Mat full_rectified_img(model_.fullResolution(), CV_8UC3, cv::Scalar(0, 0, 0));
    cv::resize(rectified_img, rectified_img, cv::Size(), model_.binningX(), model_.binningY(), cv::INTER_NEAREST);
    rectified_img.copyTo(full_rectified_img(model_.rawRoi()));
    cv::rectangle(full_rectified_img, model_.rectifiedRoi(), cv::Scalar(0, 255, 0), 1);
    cv::imwrite("/tmp/img_with_binning.png", full_rectified_img);
  }

  cam_info_.binning_x = 1;
  cam_info_.binning_y = 1;
  cam_info_.roi.x_offset = 100;
  cam_info_.roi.y_offset = 50;
  cam_info_.roi.width = 400;
  cam_info_.roi.height = 300;
  cam_info_.roi.do_rectify = true;
  model_.fromCameraInfo(cam_info_);
  rectified_roi = model_.rectifiedRoi();
  reduced_resolution = model_.reducedResolution();
  EXPECT_EQ(137, rectified_roi.x);
  EXPECT_EQ(82, rectified_roi.y);
  EXPECT_EQ(321, rectified_roi.width);
  EXPECT_EQ(242, rectified_roi.height);
  EXPECT_EQ(321, reduced_resolution.width);
  EXPECT_EQ(242, reduced_resolution.height);

  {
    cv::Mat raw_img = grid_img.clone();
    cv::rectangle(raw_img, model_.rawRoi(), cv::Scalar(0, 0, 255), 1);
    raw_img = raw_img(model_.rawRoi());
    cv::resize(raw_img, raw_img, cv::Size(), 1.0 / model_.binningX(), 1.0 / model_.binningY(), cv::INTER_NEAREST);
    cv::Mat rectified_img;
    model_.rectifyImage(raw_img, rectified_img);

    cv::Mat full_rectified_img(model_.fullResolution(), CV_8UC3, cv::Scalar(0, 0, 0));
    cv::resize(rectified_img, rectified_img, cv::Size(), model_.binningX(), model_.binningY(), cv::INTER_NEAREST);
    rectified_img.copyTo(full_rectified_img(model_.rawRoi()));
    cv::rectangle(full_rectified_img, model_.rectifiedRoi(), cv::Scalar(0, 255, 0), 1);
    cv::imwrite("/tmp/img_with_roi.png", full_rectified_img);
  }

  cam_info_.binning_x = 2;
  cam_info_.binning_y = 2;
  cam_info_.roi.x_offset = 100;
  cam_info_.roi.y_offset = 50;
  cam_info_.roi.width = 400;
  cam_info_.roi.height = 300;
  cam_info_.roi.do_rectify = true;
  model_.fromCameraInfo(cam_info_);
  rectified_roi = model_.rectifiedRoi();
  reduced_resolution = model_.reducedResolution();
  EXPECT_EQ(137, rectified_roi.x);
  EXPECT_EQ(82, rectified_roi.y);
  EXPECT_EQ(321, rectified_roi.width);
  EXPECT_EQ(242, rectified_roi.height);
  EXPECT_EQ(160, reduced_resolution.width);
  EXPECT_EQ(121, reduced_resolution.height);

  {
    cv::Mat raw_img = grid_img.clone();
    cv::rectangle(raw_img, model_.rawRoi(), cv::Scalar(0, 0, 255), 1);
    raw_img = raw_img(model_.rawRoi());
    cv::resize(raw_img, raw_img, cv::Size(), 1.0 / model_.binningX(), 1.0 / model_.binningY(), cv::INTER_NEAREST);
    cv::Mat rectified_img;
    model_.rectifyImage(raw_img, rectified_img);

    cv::Mat full_rectified_img(model_.fullResolution(), CV_8UC3, cv::Scalar(0, 0, 0));
    cv::resize(rectified_img, rectified_img, cv::Size(), model_.binningX(), model_.binningY(), cv::INTER_NEAREST);
    rectified_img.copyTo(full_rectified_img(model_.rawRoi()));
    cv::rectangle(full_rectified_img, model_.rectifiedRoi(), cv::Scalar(0, 255, 0), 1);
    cv::imwrite("/tmp/img_with_binning_and_roi.png", full_rectified_img);
  }
}

mintar added 2 commits April 16, 2021 18:36
The previous implementation used K_ and P_ (the matrices that include
binning and ROI). However, the ROI is specified in unbinned and
untransformed raw image coordinates (see REP-104), so rectifyRoi has to
use K_full_ and P_full_.
Each test runs in its own test fixture, so restoring the original state
at the end of a test is not necessary.
@mintar
Copy link
Contributor Author

mintar commented Apr 19, 2021

@mjcarroll : I've rebased this PR onto the latest noetic branch to fix merge conflicts. It's ready for review now.

@mintar
Copy link
Contributor Author

mintar commented May 4, 2021

@mjcarroll : Could you review this PR?

This fixes a problem with the following sequence:

1. fromCameraInfo is called with ROI A.  | rectified_roi_dirty = true
2. rectifiedRoi is called                | rectified_roi_dirty = false
3. fromCameraInfo is called with ROI B.  | rectified_roi_dirty = true
4. fromCameraInfo is called with ROI B.  | rectified_roi_dirty = false

The bug is that rectified_roi_dirty was incorrectly set to `false` by the last
line. If now rectifiedRoi is called again, the cached rectified_roi for
ROI A will be returned, but it should be recalculated based on ROI B.

This commit fixes that.
@mintar
Copy link
Contributor Author

mintar commented Jun 4, 2021

Could somebody review this PR? It's rather small and has tests to prove it's right.

@mintar
Copy link
Contributor Author

mintar commented Oct 26, 2021

Ping again... could somebody merge this please?

@flynneva
Copy link
Contributor

@wjwwood @clalancette anything you guys could do here to help get this merged? I've been following this PR....painfully over the last 8 months and it looks like a great contribution.

@flynneva
Copy link
Contributor

also maybe @mjcarroll could help out here too?

@mintar mintar mentioned this pull request Nov 5, 2021
@mintar
Copy link
Contributor Author

mintar commented Nov 22, 2021

@mjcarroll Monthly reminder to please merge this...

@mjcarroll mjcarroll merged commit 15ddb06 into ros-perception:noetic Nov 23, 2021
@mintar mintar deleted the fix_rectify_roi branch November 24, 2021 12:25
mintar added a commit to mintar/vision_opencv that referenced this pull request Jan 13, 2022
* Fix rectifyRoi when used with binning and/or ROI

The previous implementation used K_ and P_ (the matrices that include
binning and ROI). However, the ROI is specified in unbinned and
untransformed raw image coordinates (see REP-104), so rectifyRoi has to
use K_full_ and P_full_.

* test: Remove useless restoring of original state

Each test runs in its own test fixture, so restoring the original state
at the end of a test is not necessary.

* Don't overwrite rectified_roi_dirty flag

This fixes a problem with the following sequence:

1. fromCameraInfo is called with ROI A.  | rectified_roi_dirty = true
2. rectifiedRoi is called                | rectified_roi_dirty = false
3. fromCameraInfo is called with ROI B.  | rectified_roi_dirty = true
4. fromCameraInfo is called with ROI B.  | rectified_roi_dirty = false

The bug is that rectified_roi_dirty was incorrectly set to `false` by the last
line. If now rectifiedRoi is called again, the cached rectified_roi for
ROI A will be returned, but it should be recalculated based on ROI B.

This commit fixes that.
mintar added a commit to mintar/vision_opencv that referenced this pull request Jan 26, 2022
* Fix rectifyRoi when used with binning and/or ROI

The previous implementation used K_ and P_ (the matrices that include
binning and ROI). However, the ROI is specified in unbinned and
untransformed raw image coordinates (see REP-104), so rectifyRoi has to
use K_full_ and P_full_.

* test: Remove useless restoring of original state

Each test runs in its own test fixture, so restoring the original state
at the end of a test is not necessary.

* Don't overwrite rectified_roi_dirty flag

This fixes a problem with the following sequence:

1. fromCameraInfo is called with ROI A.  | rectified_roi_dirty = true
2. rectifiedRoi is called                | rectified_roi_dirty = false
3. fromCameraInfo is called with ROI B.  | rectified_roi_dirty = true
4. fromCameraInfo is called with ROI B.  | rectified_roi_dirty = false

The bug is that rectified_roi_dirty was incorrectly set to `false` by the last
line. If now rectifiedRoi is called again, the cached rectified_roi for
ROI A will be returned, but it should be recalculated based on ROI B.

This commit fixes that.
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.

3 participants