{\r
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;\r
}\r
}\r
\r
\r
-void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector<Mat> &images, \r
- const vector<pair<Mat,uchar> > &masks)\r
+void GainCompensator::feed(const vector<Point> &corners, const vector<Mat> &images, \r
+ const vector<pair<Mat,uchar> > &masks)\r
{\r
CV_Assert(corners.size() == images.size() && images.size() == masks.size());\r
\r
submask2 = masks[j].first(Rect(roi.tl() - corners[j], roi.br() - corners[j]));\r
intersect = (submask1 == masks[i].second) & (submask2 == masks[j].second);\r
\r
- N(i, j) = N(j, i) = countNonZero(intersect);\r
+ N(i, j) = N(j, i) = max(1, countNonZero(intersect));\r
\r
double Isum1 = 0, Isum2 = 0;\r
for (int y = 0; y < roi.height; ++y)\r
}\r
}\r
}\r
- I(i, j) = Isum1 / max(N(i, j), 1);\r
- I(j, i) = Isum2 / max(N(i, j), 1);\r
+ I(i, j) = Isum1 / N(i, j);\r
+ I(j, i) = Isum2 / N(i, j);\r
}\r
}\r
}\r
}\r
}\r
\r
- solve(A, b, gains);\r
+ solve(A, b, gains_);\r
}\r
\r
\r
-void OverlapExposureCompensator::apply(int index, Point /*corner*/, Mat &image, const Mat &/*mask*/)\r
+void GainCompensator::apply(int index, Point /*corner*/, Mat &image, const Mat &/*mask*/)\r
{\r
- image *= gains(index, 0);\r
+ image *= gains_(index, 0);\r
}\r
+\r
+\r
+vector<double> GainCompensator::gains() const\r
+{\r
+ vector<double> gains_vec(gains_.rows);\r
+ for (int i = 0; i < gains_.rows; ++i)\r
+ gains_vec[i] = gains_(i, 0);\r
+ return gains_vec;\r
+}\r
+\r
+\r
+void BlocksGainCompensator::feed(const vector<Point> &corners, const vector<Mat> &images, \r
+ const vector<pair<Mat,uchar> > &masks)\r
+{\r
+ CV_Assert(corners.size() == images.size() && images.size() == masks.size());\r
+\r
+ const int num_images = static_cast<int>(images.size());\r
+\r
+ vector<Size> bl_per_imgs(num_images);\r
+ vector<Point> block_corners;\r
+ vector<Mat> block_images;\r
+ vector<pair<Mat,uchar> > block_masks;\r
+\r
+ // Construct blocks for gain compensator\r
+ for (int img_idx = 0; img_idx < num_images; ++img_idx)\r
+ {\r
+ Size bl_per_img((images[img_idx].cols + bl_width_ - 1) / bl_width_,\r
+ (images[img_idx].rows + bl_height_ - 1) / bl_height_);\r
+ int bl_width = (images[img_idx].cols + bl_per_img.width - 1) / bl_per_img.width;\r
+ int bl_height = (images[img_idx].rows + bl_per_img.height - 1) / bl_per_img.height;\r
+ bl_per_imgs[img_idx] = bl_per_img;\r
+ for (int by = 0; by < bl_per_img.height; ++by)\r
+ {\r
+ for (int bx = 0; bx < bl_per_img.width; ++bx)\r
+ {\r
+ Point bl_tl(bx * bl_width, by * bl_height);\r
+ Point bl_br(min(bl_tl.x + bl_width, images[img_idx].cols),\r
+ min(bl_tl.y + bl_height, images[img_idx].rows));\r
+\r
+ block_corners.push_back(corners[img_idx] + bl_tl);\r
+ block_images.push_back(images[img_idx](Rect(bl_tl, bl_br)));\r
+ block_masks.push_back(make_pair(masks[img_idx].first(Rect(bl_tl, bl_br)), \r
+ masks[img_idx].second));\r
+ }\r
+ }\r
+ }\r
+\r
+ GainCompensator compensator;\r
+ compensator.feed(block_corners, block_images, block_masks);\r
+ vector<double> gains = compensator.gains();\r
+ gain_maps_.resize(num_images);\r
+\r
+ int bl_idx = 0;\r
+ for (int img_idx = 0; img_idx < num_images; ++img_idx)\r
+ {\r
+ Size bl_per_img = bl_per_imgs[img_idx];\r
+ gain_maps_[img_idx].create(bl_per_img);\r
+\r
+ for (int by = 0; by < bl_per_img.height; ++by)\r
+ for (int bx = 0; bx < bl_per_img.width; ++bx, ++bl_idx)\r
+ gain_maps_[img_idx](by, bx) = static_cast<float>(gains[bl_idx]);\r
+ \r
+ Mat_<float> ker(1, 3); \r
+ ker(0,0) = 0.25; ker(0,1) = 0.5; ker(0,2) = 0.25;\r
+ sepFilter2D(gain_maps_[img_idx], gain_maps_[img_idx], CV_32F, ker, ker);\r
+ sepFilter2D(gain_maps_[img_idx], gain_maps_[img_idx], CV_32F, ker, ker);\r
+ }\r
+}\r
+\r
+\r
+void BlocksGainCompensator::apply(int index, Point /*corner*/, Mat &image, const Mat &/*mask*/)\r
+{\r
+ CV_Assert(image.type() == CV_8UC3);\r
+\r
+ Mat_<float> gain_map;\r
+ if (gain_maps_[index].size() == image.size())\r
+ gain_map = gain_maps_[index];\r
+ else\r
+ resize(gain_maps_[index], gain_map, image.size(), 0, 0, INTER_LINEAR);\r
+\r
+ for (int y = 0; y < image.rows; ++y)\r
+ {\r
+ const float* gain_row = gain_map.ptr<float>(y);\r
+ Point3_<uchar>* row = image.ptr<Point3_<uchar> >(y);\r
+ for (int x = 0; x < image.cols; ++x)\r
+ {\r
+ row[x].x = saturate_cast<uchar>(row[x].x * gain_row[x]);\r
+ row[x].y = saturate_cast<uchar>(row[x].y * gain_row[x]);\r
+ row[x].z = saturate_cast<uchar>(row[x].z * gain_row[x]);\r
+ }\r
+ }\r
+}
\ No newline at end of file
class ExposureCompensator
{
public:
- enum { NO, OVERLAP, SEGMENT };
+ enum { NO, GAIN, GAIN_BLOCKS };
static cv::Ptr<ExposureCompensator> createDefault(int type);
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images,
};
-class OverlapExposureCompensator : public ExposureCompensator
+class GainCompensator : public ExposureCompensator
{
public:
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images,
const std::vector<std::pair<cv::Mat,uchar> > &masks);
void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask);
+ std::vector<double> gains() const;
- cv::Mat_<double> gains;
+private:
+ cv::Mat_<double> 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<cv::Point> &corners, const std::vector<cv::Mat> &images,
+ const std::vector<std::pair<cv::Mat,uchar> > &masks);
+ void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask);
+
+private:
+ int bl_width_, bl_height_;
+ std::vector<cv::Mat_<float> > gain_maps_;
};
#endif // __OPENCV_EXPOSURE_COMPENSATE_HPP__
\ No newline at end of file
//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"\r
#include "util.hpp"\r
\r
void printUsage()\r
{\r
- cout << "Rotation model images stitcher.\n\n";\r
- cout << "Usage: opencv_stitching img1 img2 [...imgN]\n" \r
- << "\t[--trygpu (yes|no)]\n"\r
- << "\t[--work_megapix <float>]\n"\r
- << "\t[--seam_megapix <float>]\n"\r
- << "\t[--compose_megapix <float>]\n"\r
- << "\t[--match_conf <float>]\n"\r
- << "\t[--ba (ray|focal_ray)]\n"\r
- << "\t[--conf_thresh <float>]\n"\r
- << "\t[--wavecorrect (no|yes)]\n"\r
- << "\t[--warp (plane|cylindrical|spherical)]\n" \r
- << "\t[--exposcomp (no|overlap)]\n"\r
- << "\t[--seam (no|voronoi|gc_color|gc_colorgrad)]\n" \r
- << "\t[--blend (no|feather|multiband)]\n"\r
- << "\t[--numbands <int>]\n"\r
- << "\t[--output <result_img>]\n\n";\r
- cout << "--match_conf\n"\r
- << "\tGood values are in [0.2, 0.8] range usually.\n\n";\r
- cout << "HINT:\n" \r
- << "\tTry bigger values for --work_megapix if something is wrong.\n\n";\r
+ cout << \r
+ "Rotation model images stitcher.\n\n"\r
+ "opencv_stitching img1 img2 [...imgN] [flags]\n\n" \r
+ "Flags:\n"\r
+ " --preview\n"\r
+ " Run stitching in the preview mode. Works faster than usual mode,\n"\r
+ " but output image will have lower resolution.\n"\r
+ " --try_gpu (yes|no)\n"\r
+ " Try to use GPU. The default value is 'no'. All default values\n"\r
+ " are for CPU mode.\n"\r
+ " --work_megapix <float>\n"\r
+ " Resolution for image registration step. The default is 0.6.\n"\r
+ " --seam_megapix <float>\n"\r
+ " Resolution for seam estimation step. The default is 0.1.\n"\r
+ " --compose_megapix <float>\n"\r
+ " Resolution for compositing step. Use -1 for original resolution.\n"\r
+ " The default is -1.\n"\r
+ " --match_conf <float>\n"\r
+ " Confidence for feature matching step. The default is 0.6.\n"\r
+ " --ba (ray|focal_ray)\n"\r
+ " Bundle adjustment cost function. The default is 'focal_ray'.\n"\r
+ " --conf_thresh <float>\n"\r
+ " Threshold for two images are from the same panorama confidence.\n"\r
+ " The default is 'focal_ray'.\n"\r
+ " --wave_correct (no|yes)\n"\r
+ " Perform wave effect correction. The default is 'yes'.\n"\r
+ " --warp (plane|cylindrical|spherical)\n" \r
+ " Warp surface type. The default is 'spherical'.\n"\r
+ " --expos_comp (no|gain|gain_blocks)\n"\r
+ " Exposure compensation method. The default is 'gain'.\n"\r
+ " --seam (no|voronoi|gc_color|gc_colorgrad)\n" \r
+ " Seam estimation method. The default is 'gc_color'.\n"\r
+ " --blend (no|feather|multiband)\n"\r
+ " Blending method. The default is 'multiband'.\n"\r
+ " --num_bands <int>\n"\r
+ " Number of bands for multi-band blending method. The default is 5.\n"\r
+ " --output <result_img>\n";\r
}\r
\r
\r
// Default command line args\r
vector<string> img_names;\r
-bool trygpu = false;\r
+bool preview = false;\r
+bool try_gpu = false;\r
double work_megapix = 0.6;\r
double seam_megapix = 0.1;\r
-double compose_megapix = 1;\r
+double compose_megapix = -1;\r
int ba_space = BundleAdjuster::FOCAL_RAY_SPACE;\r
float conf_thresh = 1.f;\r
bool wave_correct = true;\r
int warp_type = Warper::SPHERICAL;\r
-int expos_comp_type = ExposureCompensator::OVERLAP;\r
+int expos_comp_type = ExposureCompensator::GAIN;\r
bool user_match_conf = false;\r
float match_conf = 0.6f;\r
int seam_find_type = SeamFinder::GC_COLOR;\r
int blend_type = Blender::MULTI_BAND;\r
-int numbands = 5;\r
+int num_bands = 5;\r
string result_name = "result.png";\r
\r
int parseCmdArgs(int argc, char** argv)\r
printUsage();\r
return -1;\r
}\r
-\r
for (int i = 1; i < argc; ++i)\r
{\r
- if (string(argv[i]) == "--trygpu")\r
+ if (string(argv[i]) == "--preview")\r
+ {\r
+ preview = true;\r
+ }\r
+ else if (string(argv[i]) == "--try_gpu")\r
{\r
if (string(argv[i + 1]) == "no")\r
- trygpu = false;\r
+ try_gpu = false;\r
else if (string(argv[i + 1]) == "yes")\r
- trygpu = true;\r
+ try_gpu = true;\r
else\r
{\r
- cout << "Bad --trygpu flag value\n";\r
+ cout << "Bad --try_gpu flag value\n";\r
return -1;\r
}\r
i++;\r
conf_thresh = static_cast<float>(atof(argv[i + 1]));\r
i++;\r
}\r
- else if (string(argv[i]) == "--wavecorrect")\r
+ else if (string(argv[i]) == "--wave_correct")\r
{\r
if (string(argv[i + 1]) == "no")\r
wave_correct = false;\r
wave_correct = true;\r
else\r
{\r
- cout << "Bad --wavecorrect flag value\n";\r
+ cout << "Bad --wave_correct flag value\n";\r
return -1;\r
}\r
i++;\r
}\r
i++;\r
}\r
- else if (string(argv[i]) == "--exposcomp")\r
+ else if (string(argv[i]) == "--expos_comp")\r
{\r
if (string(argv[i + 1]) == "no")\r
expos_comp_type = ExposureCompensator::NO;\r
- else if (string(argv[i + 1]) == "overlap")\r
- expos_comp_type = ExposureCompensator::OVERLAP;\r
+ else if (string(argv[i + 1]) == "gain")\r
+ expos_comp_type = ExposureCompensator::GAIN;\r
+ else if (string(argv[i + 1]) == "gain_blocks")\r
+ expos_comp_type = ExposureCompensator::GAIN_BLOCKS;\r
else\r
{\r
cout << "Bad exposure compensation method\n";\r
}\r
i++;\r
}\r
- else if (string(argv[i]) == "--numbands")\r
+ else if (string(argv[i]) == "--num_bands")\r
{\r
- numbands = atoi(argv[i + 1]);\r
+ num_bands = atoi(argv[i + 1]);\r
i++;\r
}\r
else if (string(argv[i]) == "--output")\r
else\r
img_names.push_back(argv[i]);\r
}\r
+ if (preview)\r
+ {\r
+ compose_megapix = work_megapix;\r
+ }\r
return 0;\r
}\r
\r
int64 t = getTickCount();\r
\r
vector<ImageFeatures> features(num_images);\r
- SurfFeaturesFinder finder(trygpu);\r
+ SurfFeaturesFinder finder(try_gpu);\r
Mat full_img, img;\r
\r
vector<Mat> images(num_images);\r
LOGLN("Pairwise matching... ");\r
t = getTickCount();\r
vector<MatchesInfo> pairwise_matches;\r
- BestOf2NearestMatcher matcher(trygpu);\r
+ BestOf2NearestMatcher matcher(try_gpu);\r
if (user_match_conf)\r
- matcher = BestOf2NearestMatcher(trygpu, match_conf);\r
+ matcher = BestOf2NearestMatcher(try_gpu, match_conf);\r
matcher(features, pairwise_matches);\r
LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");\r
\r
if (blend_type == Blender::MULTI_BAND)\r
{\r
MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender));\r
- mb->setNumBands(numbands);\r
+ mb->setNumBands(num_bands);\r
}\r
blender->prepare(corners, sizes);\r
}\r