From: Alexey Spizhevoy Date: Sat, 28 May 2011 12:18:49 +0000 (+0000) Subject: added block-based gain compensator (opencv_stitching), added --preview flag. X-Git-Tag: accepted/2.0/20130307.220821~3019 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7881134cf7e2b42dd098b07d552f02845a13eb81;p=profile%2Fivi%2Fopencv.git added block-based gain compensator (opencv_stitching), added --preview flag. --- diff --git a/modules/stitching/exposure_compensate.cpp b/modules/stitching/exposure_compensate.cpp index 4a923f4..02ef8e6 100644 --- a/modules/stitching/exposure_compensate.cpp +++ b/modules/stitching/exposure_compensate.cpp @@ -52,8 +52,10 @@ Ptr ExposureCompensator::createDefault(int type) { if (type == NO) return new NoExposureCompensator(); - if (type == OVERLAP) - return new OverlapExposureCompensator(); + if (type == GAIN) + return new GainCompensator(); + if (type == GAIN_BLOCKS) + return new BlocksGainCompensator(); CV_Error(CV_StsBadArg, "unsupported exposure compensation method"); return NULL; } @@ -69,8 +71,8 @@ void ExposureCompensator::feed(const vector &corners, const vector & } -void OverlapExposureCompensator::feed(const vector &corners, const vector &images, - const vector > &masks) +void GainCompensator::feed(const vector &corners, const vector &images, + const vector > &masks) { CV_Assert(corners.size() == images.size() && images.size() == masks.size()); @@ -96,7 +98,7 @@ void OverlapExposureCompensator::feed(const vector &corners, const vector submask2 = masks[j].first(Rect(roi.tl() - corners[j], roi.br() - corners[j])); intersect = (submask1 == masks[i].second) & (submask2 == masks[j].second); - N(i, j) = N(j, i) = countNonZero(intersect); + N(i, j) = N(j, i) = max(1, countNonZero(intersect)); double Isum1 = 0, Isum2 = 0; for (int y = 0; y < roi.height; ++y) @@ -112,8 +114,8 @@ void OverlapExposureCompensator::feed(const vector &corners, const vector } } } - I(i, j) = Isum1 / max(N(i, j), 1); - I(j, i) = Isum2 / max(N(i, j), 1); + I(i, j) = Isum1 / N(i, j); + I(j, i) = Isum2 / N(i, j); } } } @@ -135,11 +137,103 @@ void OverlapExposureCompensator::feed(const vector &corners, const vector } } - solve(A, b, gains); + solve(A, b, gains_); } -void OverlapExposureCompensator::apply(int index, Point /*corner*/, Mat &image, const Mat &/*mask*/) +void GainCompensator::apply(int index, Point /*corner*/, Mat &image, const Mat &/*mask*/) { - image *= gains(index, 0); + image *= gains_(index, 0); } + + +vector GainCompensator::gains() const +{ + vector gains_vec(gains_.rows); + for (int i = 0; i < gains_.rows; ++i) + gains_vec[i] = gains_(i, 0); + return gains_vec; +} + + +void BlocksGainCompensator::feed(const vector &corners, const vector &images, + const vector > &masks) +{ + CV_Assert(corners.size() == images.size() && images.size() == masks.size()); + + const int num_images = static_cast(images.size()); + + vector bl_per_imgs(num_images); + vector block_corners; + vector block_images; + vector > block_masks; + + // Construct blocks for gain compensator + for (int img_idx = 0; img_idx < num_images; ++img_idx) + { + Size bl_per_img((images[img_idx].cols + bl_width_ - 1) / bl_width_, + (images[img_idx].rows + bl_height_ - 1) / bl_height_); + int bl_width = (images[img_idx].cols + bl_per_img.width - 1) / bl_per_img.width; + int bl_height = (images[img_idx].rows + bl_per_img.height - 1) / bl_per_img.height; + bl_per_imgs[img_idx] = bl_per_img; + for (int by = 0; by < bl_per_img.height; ++by) + { + for (int bx = 0; bx < bl_per_img.width; ++bx) + { + Point bl_tl(bx * bl_width, by * bl_height); + Point bl_br(min(bl_tl.x + bl_width, images[img_idx].cols), + min(bl_tl.y + bl_height, images[img_idx].rows)); + + block_corners.push_back(corners[img_idx] + bl_tl); + block_images.push_back(images[img_idx](Rect(bl_tl, bl_br))); + block_masks.push_back(make_pair(masks[img_idx].first(Rect(bl_tl, bl_br)), + masks[img_idx].second)); + } + } + } + + GainCompensator compensator; + compensator.feed(block_corners, block_images, block_masks); + vector gains = compensator.gains(); + gain_maps_.resize(num_images); + + int bl_idx = 0; + for (int img_idx = 0; img_idx < num_images; ++img_idx) + { + Size bl_per_img = bl_per_imgs[img_idx]; + gain_maps_[img_idx].create(bl_per_img); + + for (int by = 0; by < bl_per_img.height; ++by) + for (int bx = 0; bx < bl_per_img.width; ++bx, ++bl_idx) + gain_maps_[img_idx](by, bx) = static_cast(gains[bl_idx]); + + Mat_ ker(1, 3); + ker(0,0) = 0.25; ker(0,1) = 0.5; ker(0,2) = 0.25; + sepFilter2D(gain_maps_[img_idx], gain_maps_[img_idx], CV_32F, ker, ker); + sepFilter2D(gain_maps_[img_idx], gain_maps_[img_idx], CV_32F, ker, ker); + } +} + + +void BlocksGainCompensator::apply(int index, Point /*corner*/, Mat &image, const Mat &/*mask*/) +{ + CV_Assert(image.type() == CV_8UC3); + + Mat_ gain_map; + if (gain_maps_[index].size() == image.size()) + gain_map = gain_maps_[index]; + else + resize(gain_maps_[index], gain_map, image.size(), 0, 0, INTER_LINEAR); + + for (int y = 0; y < image.rows; ++y) + { + const float* gain_row = gain_map.ptr(y); + Point3_* row = image.ptr >(y); + for (int x = 0; x < image.cols; ++x) + { + row[x].x = saturate_cast(row[x].x * gain_row[x]); + row[x].y = saturate_cast(row[x].y * gain_row[x]); + row[x].z = saturate_cast(row[x].z * gain_row[x]); + } + } +} \ No newline at end of file diff --git a/modules/stitching/exposure_compensate.hpp b/modules/stitching/exposure_compensate.hpp index a4d3cea..2c39851 100644 --- a/modules/stitching/exposure_compensate.hpp +++ b/modules/stitching/exposure_compensate.hpp @@ -48,7 +48,7 @@ class ExposureCompensator { public: - enum { NO, OVERLAP, SEGMENT }; + enum { NO, GAIN, GAIN_BLOCKS }; static cv::Ptr createDefault(int type); void feed(const std::vector &corners, const std::vector &images, @@ -68,14 +68,31 @@ public: }; -class OverlapExposureCompensator : public ExposureCompensator +class GainCompensator : public ExposureCompensator { public: void feed(const std::vector &corners, const std::vector &images, const std::vector > &masks); void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); + std::vector gains() const; - cv::Mat_ gains; +private: + cv::Mat_ gains_; +}; + + +class BlocksGainCompensator : public ExposureCompensator +{ +public: + BlocksGainCompensator(int bl_width = 32, int bl_height = 32) + : bl_width_(bl_width), bl_height_(bl_height) {} + void feed(const std::vector &corners, const std::vector &images, + const std::vector > &masks); + void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); + +private: + int bl_width_, bl_height_; + std::vector > gain_maps_; }; #endif // __OPENCV_EXPOSURE_COMPENSATE_HPP__ \ No newline at end of file diff --git a/modules/stitching/main.cpp b/modules/stitching/main.cpp index bba10a6..df78f87 100644 --- a/modules/stitching/main.cpp +++ b/modules/stitching/main.cpp @@ -41,10 +41,12 @@ //M*/ // We follow to methods described in these two papers: -// - Heung-Yeung Shum and Richard Szeliski. -// Construction of panoramic mosaics with global and local alignment. 2000. -// - Matthew Brown and David G. Lowe. -// Automatic Panoramic Image Stitching using Invariant Features. 2007. +// 1) Construction of panoramic mosaics with global and local alignment. +// Heung-Yeung Shum and Richard Szeliski. 2000. +// 2) Eliminating Ghosting and Exposure Artifacts in Image Mosaics. +// Matthew Uyttendaele, Ashley Eden and Richard Szeliski. 2001. +// 3) Automatic Panoramic Image Stitching using Invariant Features. +// Matthew Brown and David G. Lowe. 2007. #include "precomp.hpp" #include "util.hpp" @@ -59,45 +61,63 @@ using namespace cv; void printUsage() { - cout << "Rotation model images stitcher.\n\n"; - cout << "Usage: opencv_stitching img1 img2 [...imgN]\n" - << "\t[--trygpu (yes|no)]\n" - << "\t[--work_megapix ]\n" - << "\t[--seam_megapix ]\n" - << "\t[--compose_megapix ]\n" - << "\t[--match_conf ]\n" - << "\t[--ba (ray|focal_ray)]\n" - << "\t[--conf_thresh ]\n" - << "\t[--wavecorrect (no|yes)]\n" - << "\t[--warp (plane|cylindrical|spherical)]\n" - << "\t[--exposcomp (no|overlap)]\n" - << "\t[--seam (no|voronoi|gc_color|gc_colorgrad)]\n" - << "\t[--blend (no|feather|multiband)]\n" - << "\t[--numbands ]\n" - << "\t[--output ]\n\n"; - cout << "--match_conf\n" - << "\tGood values are in [0.2, 0.8] range usually.\n\n"; - cout << "HINT:\n" - << "\tTry bigger values for --work_megapix if something is wrong.\n\n"; + cout << + "Rotation model images stitcher.\n\n" + "opencv_stitching img1 img2 [...imgN] [flags]\n\n" + "Flags:\n" + " --preview\n" + " Run stitching in the preview mode. Works faster than usual mode,\n" + " but output image will have lower resolution.\n" + " --try_gpu (yes|no)\n" + " Try to use GPU. The default value is 'no'. All default values\n" + " are for CPU mode.\n" + " --work_megapix \n" + " Resolution for image registration step. The default is 0.6.\n" + " --seam_megapix \n" + " Resolution for seam estimation step. The default is 0.1.\n" + " --compose_megapix \n" + " Resolution for compositing step. Use -1 for original resolution.\n" + " The default is -1.\n" + " --match_conf \n" + " Confidence for feature matching step. The default is 0.6.\n" + " --ba (ray|focal_ray)\n" + " Bundle adjustment cost function. The default is 'focal_ray'.\n" + " --conf_thresh \n" + " Threshold for two images are from the same panorama confidence.\n" + " The default is 'focal_ray'.\n" + " --wave_correct (no|yes)\n" + " Perform wave effect correction. The default is 'yes'.\n" + " --warp (plane|cylindrical|spherical)\n" + " Warp surface type. The default is 'spherical'.\n" + " --expos_comp (no|gain|gain_blocks)\n" + " Exposure compensation method. The default is 'gain'.\n" + " --seam (no|voronoi|gc_color|gc_colorgrad)\n" + " Seam estimation method. The default is 'gc_color'.\n" + " --blend (no|feather|multiband)\n" + " Blending method. The default is 'multiband'.\n" + " --num_bands \n" + " Number of bands for multi-band blending method. The default is 5.\n" + " --output \n"; } // Default command line args vector img_names; -bool trygpu = false; +bool preview = false; +bool try_gpu = false; double work_megapix = 0.6; double seam_megapix = 0.1; -double compose_megapix = 1; +double compose_megapix = -1; int ba_space = BundleAdjuster::FOCAL_RAY_SPACE; float conf_thresh = 1.f; bool wave_correct = true; int warp_type = Warper::SPHERICAL; -int expos_comp_type = ExposureCompensator::OVERLAP; +int expos_comp_type = ExposureCompensator::GAIN; bool user_match_conf = false; float match_conf = 0.6f; int seam_find_type = SeamFinder::GC_COLOR; int blend_type = Blender::MULTI_BAND; -int numbands = 5; +int num_bands = 5; string result_name = "result.png"; int parseCmdArgs(int argc, char** argv) @@ -107,18 +127,21 @@ int parseCmdArgs(int argc, char** argv) printUsage(); return -1; } - for (int i = 1; i < argc; ++i) { - if (string(argv[i]) == "--trygpu") + if (string(argv[i]) == "--preview") + { + preview = true; + } + else if (string(argv[i]) == "--try_gpu") { if (string(argv[i + 1]) == "no") - trygpu = false; + try_gpu = false; else if (string(argv[i + 1]) == "yes") - trygpu = true; + try_gpu = true; else { - cout << "Bad --trygpu flag value\n"; + cout << "Bad --try_gpu flag value\n"; return -1; } i++; @@ -167,7 +190,7 @@ int parseCmdArgs(int argc, char** argv) conf_thresh = static_cast(atof(argv[i + 1])); i++; } - else if (string(argv[i]) == "--wavecorrect") + else if (string(argv[i]) == "--wave_correct") { if (string(argv[i + 1]) == "no") wave_correct = false; @@ -175,7 +198,7 @@ int parseCmdArgs(int argc, char** argv) wave_correct = true; else { - cout << "Bad --wavecorrect flag value\n"; + cout << "Bad --wave_correct flag value\n"; return -1; } i++; @@ -195,12 +218,14 @@ int parseCmdArgs(int argc, char** argv) } i++; } - else if (string(argv[i]) == "--exposcomp") + else if (string(argv[i]) == "--expos_comp") { if (string(argv[i + 1]) == "no") expos_comp_type = ExposureCompensator::NO; - else if (string(argv[i + 1]) == "overlap") - expos_comp_type = ExposureCompensator::OVERLAP; + else if (string(argv[i + 1]) == "gain") + expos_comp_type = ExposureCompensator::GAIN; + else if (string(argv[i + 1]) == "gain_blocks") + expos_comp_type = ExposureCompensator::GAIN_BLOCKS; else { cout << "Bad exposure compensation method\n"; @@ -240,9 +265,9 @@ int parseCmdArgs(int argc, char** argv) } i++; } - else if (string(argv[i]) == "--numbands") + else if (string(argv[i]) == "--num_bands") { - numbands = atoi(argv[i + 1]); + num_bands = atoi(argv[i + 1]); i++; } else if (string(argv[i]) == "--output") @@ -253,6 +278,10 @@ int parseCmdArgs(int argc, char** argv) else img_names.push_back(argv[i]); } + if (preview) + { + compose_megapix = work_megapix; + } return 0; } @@ -281,7 +310,7 @@ int main(int argc, char* argv[]) int64 t = getTickCount(); vector features(num_images); - SurfFeaturesFinder finder(trygpu); + SurfFeaturesFinder finder(try_gpu); Mat full_img, img; vector images(num_images); @@ -333,9 +362,9 @@ int main(int argc, char* argv[]) LOGLN("Pairwise matching... "); t = getTickCount(); vector pairwise_matches; - BestOf2NearestMatcher matcher(trygpu); + BestOf2NearestMatcher matcher(try_gpu); if (user_match_conf) - matcher = BestOf2NearestMatcher(trygpu, match_conf); + matcher = BestOf2NearestMatcher(try_gpu, match_conf); matcher(features, pairwise_matches); LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); @@ -533,7 +562,7 @@ int main(int argc, char* argv[]) if (blend_type == Blender::MULTI_BAND) { MultiBandBlender* mb = dynamic_cast(static_cast(blender)); - mb->setNumBands(numbands); + mb->setNumBands(num_bands); } blender->prepare(corners, sizes); }