Skip to content

Commit

Permalink
Bugfix check pano size (#121)
Browse files Browse the repository at this point in the history
* check pano size and exit early

* add test for pano too large error
  • Loading branch information
krupkat committed Feb 10, 2024
1 parent 0048d1d commit 2178134
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 20 deletions.
56 changes: 56 additions & 0 deletions tests/stitcher_pipeline_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>

#ifdef XPANO_WITH_EXIV2
#include <exiv2/exiv2.hpp>
#endif
Expand All @@ -23,6 +24,7 @@

#include "tests/utils.h"
#include "xpano/algorithm/options.h"
#include "xpano/algorithm/stitcher.h"
#include "xpano/constants.h"

using Catch::Matchers::Equals;
Expand Down Expand Up @@ -143,6 +145,60 @@ TEST_CASE("Stitcher pipeline defaults [extra results]") {
CHECK(stitch_result1.cameras->cameras.size() == 3);
}

const std::vector<std::filesystem::path> kInputsFirstPano = {
"data/image01.jpg", "data/image02.jpg", "data/image03.jpg",
"data/image04.jpg", "data/image05.jpg"};

TEST_CASE("Pano too large") {
xpano::pipeline::StitcherPipeline<kReturnFuture> stitcher;

auto loading_task = stitcher.RunLoading(kInputsFirstPano, {}, {});
auto stitch_data = loading_task.future.get();
auto progress = loading_task.progress->Report();
CHECK(progress.tasks_done == progress.num_tasks);

CHECK(stitch_data.images.size() == 5);
REQUIRE(stitch_data.panos.size() == 1);
REQUIRE_THAT(stitch_data.panos[0].ids, Equals<int>({0, 1, 2, 3, 4}));

const float eps = 0.02;

// stitch for the 1st time
auto proj_options = xpano::algorithm::StitchUserOptions{
.projection = {.type = xpano::algorithm::ProjectionType::kPerspective}};
auto stitching_task0 = stitcher.RunStitching(
stitch_data, {.pano_id = 0, .stitch_algorithm = proj_options});

auto stitch_result0 = stitching_task0.future.get();
progress = stitching_task0.progress->Report();
CHECK(progress.tasks_done == progress.num_tasks);

REQUIRE(stitch_result0.pano.has_value());
CHECK_THAT(stitch_result0.pano->rows, WithinRel(1737, eps));
CHECK_THAT(stitch_result0.pano->cols, WithinRel(4303, eps));

REQUIRE(stitch_result0.cameras.has_value());
CHECK(stitch_result0.cameras->cameras.size() == 5);

// rotate and stitch again
auto rot_data = std::array{0.86f, -0.31f, -0.42f, 0.02f, 0.82f,
-0.57f, 0.52f, 0.48f, 0.71f};
auto rotation_matrix = cv::Mat(3, 3, CV_32F, rot_data.data());

stitch_data.panos[0].cameras =
xpano::algorithm::Rotate(*stitch_result0.cameras, rotation_matrix);
auto stitching_task1 = stitcher.RunStitching(
stitch_data, {.pano_id = 0, .stitch_algorithm = proj_options});

auto stitch_result1 = stitching_task1.future.get();
progress = stitching_task1.progress->Report();

REQUIRE(progress.tasks_done != progress.num_tasks);
REQUIRE_FALSE(stitch_result1.pano.has_value());
REQUIRE(stitch_result1.status ==
xpano::algorithm::stitcher::Status::kErrPanoTooLarge);
}

TEST_CASE("Stitcher pipeline single pano matching") {
xpano::pipeline::StitcherPipeline<kReturnFuture> stitcher;
auto loading_task = stitcher.RunLoading(
Expand Down
3 changes: 3 additions & 0 deletions xpano/algorithm/algorithm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ int StitchTasksCount(int num_images) {
1 + // match features
1 + // estimate homography
1 + // bundle adjustment
1 + // compute pano size
1 + // prepare seams
1 + // find seams
num_images + // compose
Expand All @@ -320,6 +321,8 @@ std::string ToString(stitcher::Status& status) {
return "ERR_HOMOGRAPHY_EST_FAIL";
case stitcher::Status::kErrCameraParamsAdjustFail:
return "ERR_CAMERA_PARAMS_ADJUST_FAIL";
case stitcher::Status::kErrPanoTooLarge:
return "ERR_PANO_TOO_LARGE\nReset the project through the edit menu.";
default:
return "ERR_UNKNOWN";
}
Expand Down
1 change: 1 addition & 0 deletions xpano/algorithm/progress.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class ProgressType {
kStitchMatchFeatures,
kStitchEstimateHomography,
kStitchBundleAdjustment,
kStitchComputeRoi,
kStitchSeamsPrepare,
kStitchSeamsFind,
kStitchCompose,
Expand Down
50 changes: 31 additions & 19 deletions xpano/algorithm/stitcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

#include <spdlog/spdlog.h>

#include "xpano/constants.h"
#include "xpano/utils/opencv.h"

namespace xpano::algorithm::stitcher {
Expand Down Expand Up @@ -255,22 +256,6 @@ Status Stitcher::EstimateSeams(std::vector<cv::UMat> *seams) {

// NOLINTNEXTLINE(readability-function-cognitive-complexity):
Status Stitcher::ComposePanorama(cv::OutputArray pano) {
spdlog::info("Estimating seams... ");
NextTask(ProgressType::kStitchSeamsPrepare);

std::vector<cv::UMat> masks_warped;
if (auto status = EstimateSeams(&masks_warped); status != Status::kSuccess) {
return status;
}

seam_est_imgs_.clear();

if (Cancelled()) {
return Status::kCancelled;
}
spdlog::info("Compositing...");
auto compositing_total_timer = Timer();

cv::UMat img_warped;
cv::UMat dilated_mask;
cv::UMat seam_mask;
Expand All @@ -285,6 +270,8 @@ Status Stitcher::ComposePanorama(cv::OutputArray pano) {

cv::Ptr<cv::detail::RotationWarper> warper;
{
spdlog::info("Calculating pano size... ");
NextTask(ProgressType::kStitchComputeRoi);
auto compute_roi_timer = Timer();

// Update warped image scale
Expand All @@ -300,12 +287,37 @@ Status Stitcher::ComposePanorama(cv::OutputArray pano) {
corners[i] = roi.tl();
sizes[i] = roi.size();
}
compute_roi_timer.Report(" compute roi time");
compute_roi_timer.Report(" compute pano size time");
}

auto dst_roi = cv::detail::resultRoi(corners, sizes);
blender_->prepare(dst_roi);

if (dst_roi.width >= kMaxPanoSize || dst_roi.height >= kMaxPanoSize) {
spdlog::error("Panorama is too large to compute: {}x{}, max size is {}",
dst_roi.width, dst_roi.height, kMaxPanoSize);
return Status::kErrPanoTooLarge;
}

std::vector<cv::UMat> masks_warped;
{
spdlog::info("Estimating seams... ");
NextTask(ProgressType::kStitchSeamsPrepare);

if (auto status = EstimateSeams(&masks_warped);
status != Status::kSuccess) {
return status;
}

seam_est_imgs_.clear();

if (Cancelled()) {
return Status::kCancelled;
}
}

spdlog::info("Compositing...");
auto compositing_total_timer = Timer();

blender_->prepare(dst_roi);
for (size_t img_idx = 0; img_idx < imgs_.size(); ++img_idx) {
NextTask(ProgressType::kStitchCompose);
if (auto non_zero = cv::countNonZero(masks_warped[img_idx]);
Expand Down
3 changes: 2 additions & 1 deletion xpano/algorithm/stitcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ enum class Status {
kCancelled,
kErrNeedMoreImgs,
kErrHomographyEstFail,
kErrCameraParamsAdjustFail
kErrCameraParamsAdjustFail,
kErrPanoTooLarge
};

struct WarpHelper {
Expand Down
2 changes: 2 additions & 0 deletions xpano/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,6 @@ constexpr int kCancelAnimationFrameDuration = 128;
const std::string kGithubIssuesLink = "https://github.com/krupkat/xpano/issues";
const std::string kAuthorEmail = "tomas@krupkat.cz";

const int kMaxPanoSize = 16384;

} // namespace xpano
2 changes: 2 additions & 0 deletions xpano/gui/panels/sidebar.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ std::string ProgressLabel(pipeline::ProgressType type) {
return "Estimating homography";
case pipeline::ProgressType::kStitchBundleAdjustment:
return "Bundle adjustment";
case pipeline::ProgressType::kStitchComputeRoi:
return "Computing pano size";
case pipeline::ProgressType::kStitchSeamsPrepare:
return "Preparing seams";
case pipeline::ProgressType::kStitchSeamsFind:
Expand Down

0 comments on commit 2178134

Please sign in to comment.