mirror of
https://github.com/opencv/opencv.git
synced 2024-11-25 03:30:34 +08:00
added block-based gain compensator (opencv_stitching), added --preview flag.
This commit is contained in:
parent
dc3fe6e9cf
commit
7881134cf7
@ -52,8 +52,10 @@ Ptr<ExposureCompensator> 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<Point> &corners, const vector<Mat> &
|
||||
}
|
||||
|
||||
|
||||
void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector<Mat> &images,
|
||||
const vector<pair<Mat,uchar> > &masks)
|
||||
void GainCompensator::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());
|
||||
|
||||
@ -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]));
|
||||
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<Point> &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<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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@
|
||||
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,
|
||||
@ -68,14 +68,31 @@ public:
|
||||
};
|
||||
|
||||
|
||||
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__
|
@ -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 <float>]\n"
|
||||
<< "\t[--seam_megapix <float>]\n"
|
||||
<< "\t[--compose_megapix <float>]\n"
|
||||
<< "\t[--match_conf <float>]\n"
|
||||
<< "\t[--ba (ray|focal_ray)]\n"
|
||||
<< "\t[--conf_thresh <float>]\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 <int>]\n"
|
||||
<< "\t[--output <result_img>]\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 <float>\n"
|
||||
" Resolution for image registration step. The default is 0.6.\n"
|
||||
" --seam_megapix <float>\n"
|
||||
" Resolution for seam estimation step. The default is 0.1.\n"
|
||||
" --compose_megapix <float>\n"
|
||||
" Resolution for compositing step. Use -1 for original resolution.\n"
|
||||
" The default is -1.\n"
|
||||
" --match_conf <float>\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 <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
|
||||
vector<string> 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<float>(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<ImageFeatures> features(num_images);
|
||||
SurfFeaturesFinder finder(trygpu);
|
||||
SurfFeaturesFinder finder(try_gpu);
|
||||
Mat full_img, img;
|
||||
|
||||
vector<Mat> images(num_images);
|
||||
@ -333,9 +362,9 @@ int main(int argc, char* argv[])
|
||||
LOGLN("Pairwise matching... ");
|
||||
t = getTickCount();
|
||||
vector<MatchesInfo> 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<MultiBandBlender*>(static_cast<Blender*>(blender));
|
||||
mb->setNumBands(numbands);
|
||||
mb->setNumBands(num_bands);
|
||||
}
|
||||
blender->prepare(corners, sizes);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user