added block-based gain compensator (opencv_stitching), added --preview flag.

This commit is contained in:
Alexey Spizhevoy 2011-05-28 12:18:49 +00:00
parent dc3fe6e9cf
commit 7881134cf7
3 changed files with 197 additions and 57 deletions

View File

@ -52,8 +52,10 @@ Ptr<ExposureCompensator> ExposureCompensator::createDefault(int type)
{ {
if (type == NO) if (type == NO)
return new NoExposureCompensator(); return new NoExposureCompensator();
if (type == OVERLAP) if (type == GAIN)
return new OverlapExposureCompensator(); return new GainCompensator();
if (type == GAIN_BLOCKS)
return new BlocksGainCompensator();
CV_Error(CV_StsBadArg, "unsupported exposure compensation method"); CV_Error(CV_StsBadArg, "unsupported exposure compensation method");
return NULL; return NULL;
} }
@ -69,8 +71,8 @@ void ExposureCompensator::feed(const vector<Point> &corners, const vector<Mat> &
} }
void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector<Mat> &images, void GainCompensator::feed(const vector<Point> &corners, const vector<Mat> &images,
const vector<pair<Mat,uchar> > &masks) const vector<pair<Mat,uchar> > &masks)
{ {
CV_Assert(corners.size() == images.size() && images.size() == masks.size()); CV_Assert(corners.size() == images.size() && images.size() == masks.size());
@ -96,7 +98,7 @@ void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector
submask2 = masks[j].first(Rect(roi.tl() - corners[j], roi.br() - corners[j])); submask2 = masks[j].first(Rect(roi.tl() - corners[j], roi.br() - corners[j]));
intersect = (submask1 == masks[i].second) & (submask2 == masks[j].second); 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; double Isum1 = 0, Isum2 = 0;
for (int y = 0; y < roi.height; ++y) for (int y = 0; y < roi.height; ++y)
@ -112,8 +114,8 @@ void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector
} }
} }
} }
I(i, j) = Isum1 / max(N(i, j), 1); I(i, j) = Isum1 / N(i, j);
I(j, i) = Isum2 / max(N(i, j), 1); I(j, i) = Isum2 / N(i, j);
} }
} }
} }
@ -135,11 +137,103 @@ void OverlapExposureCompensator::feed(const vector<Point> &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<double> GainCompensator::gains() const
{
vector<double> 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<Point> &corners, const vector<Mat> &images,
const vector<pair<Mat,uchar> > &masks)
{
CV_Assert(corners.size() == images.size() && images.size() == masks.size());
const int num_images = static_cast<int>(images.size());
vector<Size> bl_per_imgs(num_images);
vector<Point> block_corners;
vector<Mat> block_images;
vector<pair<Mat,uchar> > 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<double> 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<float>(gains[bl_idx]);
Mat_<float> 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_<float> 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<float>(y);
Point3_<uchar>* row = image.ptr<Point3_<uchar> >(y);
for (int x = 0; x < image.cols; ++x)
{
row[x].x = saturate_cast<uchar>(row[x].x * gain_row[x]);
row[x].y = saturate_cast<uchar>(row[x].y * gain_row[x]);
row[x].z = saturate_cast<uchar>(row[x].z * gain_row[x]);
}
}
}

View File

@ -48,7 +48,7 @@
class ExposureCompensator class ExposureCompensator
{ {
public: public:
enum { NO, OVERLAP, SEGMENT }; enum { NO, GAIN, GAIN_BLOCKS };
static cv::Ptr<ExposureCompensator> createDefault(int type); static cv::Ptr<ExposureCompensator> createDefault(int type);
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images, void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images,
@ -68,14 +68,31 @@ public:
}; };
class OverlapExposureCompensator : public ExposureCompensator class GainCompensator : public ExposureCompensator
{ {
public: public:
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images, void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images,
const std::vector<std::pair<cv::Mat,uchar> > &masks); const std::vector<std::pair<cv::Mat,uchar> > &masks);
void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); 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__ #endif // __OPENCV_EXPOSURE_COMPENSATE_HPP__

View File

@ -41,10 +41,12 @@
//M*/ //M*/
// We follow to methods described in these two papers: // We follow to methods described in these two papers:
// - Heung-Yeung Shum and Richard Szeliski. // 1) Construction of panoramic mosaics with global and local alignment.
// Construction of panoramic mosaics with global and local alignment. 2000. // Heung-Yeung Shum and Richard Szeliski. 2000.
// - Matthew Brown and David G. Lowe. // 2) Eliminating Ghosting and Exposure Artifacts in Image Mosaics.
// Automatic Panoramic Image Stitching using Invariant Features. 2007. // 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 "precomp.hpp"
#include "util.hpp" #include "util.hpp"
@ -59,45 +61,63 @@ using namespace cv;
void printUsage() void printUsage()
{ {
cout << "Rotation model images stitcher.\n\n"; cout <<
cout << "Usage: opencv_stitching img1 img2 [...imgN]\n" "Rotation model images stitcher.\n\n"
<< "\t[--trygpu (yes|no)]\n" "opencv_stitching img1 img2 [...imgN] [flags]\n\n"
<< "\t[--work_megapix <float>]\n" "Flags:\n"
<< "\t[--seam_megapix <float>]\n" " --preview\n"
<< "\t[--compose_megapix <float>]\n" " Run stitching in the preview mode. Works faster than usual mode,\n"
<< "\t[--match_conf <float>]\n" " but output image will have lower resolution.\n"
<< "\t[--ba (ray|focal_ray)]\n" " --try_gpu (yes|no)\n"
<< "\t[--conf_thresh <float>]\n" " Try to use GPU. The default value is 'no'. All default values\n"
<< "\t[--wavecorrect (no|yes)]\n" " are for CPU mode.\n"
<< "\t[--warp (plane|cylindrical|spherical)]\n" " --work_megapix <float>\n"
<< "\t[--exposcomp (no|overlap)]\n" " Resolution for image registration step. The default is 0.6.\n"
<< "\t[--seam (no|voronoi|gc_color|gc_colorgrad)]\n" " --seam_megapix <float>\n"
<< "\t[--blend (no|feather|multiband)]\n" " Resolution for seam estimation step. The default is 0.1.\n"
<< "\t[--numbands <int>]\n" " --compose_megapix <float>\n"
<< "\t[--output <result_img>]\n\n"; " Resolution for compositing step. Use -1 for original resolution.\n"
cout << "--match_conf\n" " The default is -1.\n"
<< "\tGood values are in [0.2, 0.8] range usually.\n\n"; " --match_conf <float>\n"
cout << "HINT:\n" " Confidence for feature matching step. The default is 0.6.\n"
<< "\tTry bigger values for --work_megapix if something is wrong.\n\n"; " --ba (ray|focal_ray)\n"
" Bundle adjustment cost function. The default is 'focal_ray'.\n"
" --conf_thresh <float>\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 <int>\n"
" Number of bands for multi-band blending method. The default is 5.\n"
" --output <result_img>\n";
} }
// Default command line args // Default command line args
vector<string> img_names; vector<string> img_names;
bool trygpu = false; bool preview = false;
bool try_gpu = false;
double work_megapix = 0.6; double work_megapix = 0.6;
double seam_megapix = 0.1; double seam_megapix = 0.1;
double compose_megapix = 1; double compose_megapix = -1;
int ba_space = BundleAdjuster::FOCAL_RAY_SPACE; int ba_space = BundleAdjuster::FOCAL_RAY_SPACE;
float conf_thresh = 1.f; float conf_thresh = 1.f;
bool wave_correct = true; bool wave_correct = true;
int warp_type = Warper::SPHERICAL; int warp_type = Warper::SPHERICAL;
int expos_comp_type = ExposureCompensator::OVERLAP; int expos_comp_type = ExposureCompensator::GAIN;
bool user_match_conf = false; bool user_match_conf = false;
float match_conf = 0.6f; float match_conf = 0.6f;
int seam_find_type = SeamFinder::GC_COLOR; int seam_find_type = SeamFinder::GC_COLOR;
int blend_type = Blender::MULTI_BAND; int blend_type = Blender::MULTI_BAND;
int numbands = 5; int num_bands = 5;
string result_name = "result.png"; string result_name = "result.png";
int parseCmdArgs(int argc, char** argv) int parseCmdArgs(int argc, char** argv)
@ -107,18 +127,21 @@ int parseCmdArgs(int argc, char** argv)
printUsage(); printUsage();
return -1; return -1;
} }
for (int i = 1; i < argc; ++i) 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") if (string(argv[i + 1]) == "no")
trygpu = false; try_gpu = false;
else if (string(argv[i + 1]) == "yes") else if (string(argv[i + 1]) == "yes")
trygpu = true; try_gpu = true;
else else
{ {
cout << "Bad --trygpu flag value\n"; cout << "Bad --try_gpu flag value\n";
return -1; return -1;
} }
i++; i++;
@ -167,7 +190,7 @@ int parseCmdArgs(int argc, char** argv)
conf_thresh = static_cast<float>(atof(argv[i + 1])); conf_thresh = static_cast<float>(atof(argv[i + 1]));
i++; i++;
} }
else if (string(argv[i]) == "--wavecorrect") else if (string(argv[i]) == "--wave_correct")
{ {
if (string(argv[i + 1]) == "no") if (string(argv[i + 1]) == "no")
wave_correct = false; wave_correct = false;
@ -175,7 +198,7 @@ int parseCmdArgs(int argc, char** argv)
wave_correct = true; wave_correct = true;
else else
{ {
cout << "Bad --wavecorrect flag value\n"; cout << "Bad --wave_correct flag value\n";
return -1; return -1;
} }
i++; i++;
@ -195,12 +218,14 @@ int parseCmdArgs(int argc, char** argv)
} }
i++; i++;
} }
else if (string(argv[i]) == "--exposcomp") else if (string(argv[i]) == "--expos_comp")
{ {
if (string(argv[i + 1]) == "no") if (string(argv[i + 1]) == "no")
expos_comp_type = ExposureCompensator::NO; expos_comp_type = ExposureCompensator::NO;
else if (string(argv[i + 1]) == "overlap") else if (string(argv[i + 1]) == "gain")
expos_comp_type = ExposureCompensator::OVERLAP; expos_comp_type = ExposureCompensator::GAIN;
else if (string(argv[i + 1]) == "gain_blocks")
expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
else else
{ {
cout << "Bad exposure compensation method\n"; cout << "Bad exposure compensation method\n";
@ -240,9 +265,9 @@ int parseCmdArgs(int argc, char** argv)
} }
i++; 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++; i++;
} }
else if (string(argv[i]) == "--output") else if (string(argv[i]) == "--output")
@ -253,6 +278,10 @@ int parseCmdArgs(int argc, char** argv)
else else
img_names.push_back(argv[i]); img_names.push_back(argv[i]);
} }
if (preview)
{
compose_megapix = work_megapix;
}
return 0; return 0;
} }
@ -281,7 +310,7 @@ int main(int argc, char* argv[])
int64 t = getTickCount(); int64 t = getTickCount();
vector<ImageFeatures> features(num_images); vector<ImageFeatures> features(num_images);
SurfFeaturesFinder finder(trygpu); SurfFeaturesFinder finder(try_gpu);
Mat full_img, img; Mat full_img, img;
vector<Mat> images(num_images); vector<Mat> images(num_images);
@ -333,9 +362,9 @@ int main(int argc, char* argv[])
LOGLN("Pairwise matching... "); LOGLN("Pairwise matching... ");
t = getTickCount(); t = getTickCount();
vector<MatchesInfo> pairwise_matches; vector<MatchesInfo> pairwise_matches;
BestOf2NearestMatcher matcher(trygpu); BestOf2NearestMatcher matcher(try_gpu);
if (user_match_conf) if (user_match_conf)
matcher = BestOf2NearestMatcher(trygpu, match_conf); matcher = BestOf2NearestMatcher(try_gpu, match_conf);
matcher(features, pairwise_matches); matcher(features, pairwise_matches);
LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
@ -533,7 +562,7 @@ int main(int argc, char* argv[])
if (blend_type == Blender::MULTI_BAND) if (blend_type == Blender::MULTI_BAND)
{ {
MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender)); MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender));
mb->setNumBands(numbands); mb->setNumBands(num_bands);
} }
blender->prepare(corners, sizes); blender->prepare(corners, sizes);
} }