added block-based gain compensator (opencv_stitching), added --preview flag.
authorAlexey Spizhevoy <no@email>
Sat, 28 May 2011 12:18:49 +0000 (12:18 +0000)
committerAlexey Spizhevoy <no@email>
Sat, 28 May 2011 12:18:49 +0000 (12:18 +0000)
modules/stitching/exposure_compensate.cpp
modules/stitching/exposure_compensate.hpp
modules/stitching/main.cpp

index 4a923f4..02ef8e6 100644 (file)
@@ -52,8 +52,10 @@ Ptr<ExposureCompensator> ExposureCompensator::createDefault(int type)
 {\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
@@ -69,8 +71,8 @@ void ExposureCompensator::feed(const vector<Point> &corners, const vector<Mat> &
 }\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
@@ -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]));\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
@@ -112,8 +114,8 @@ void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector
                         }\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
@@ -135,11 +137,103 @@ void OverlapExposureCompensator::feed(const vector<Point> &corners, const vector
         }\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
index a4d3cea..2c39851 100644 (file)
@@ -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__
\ No newline at end of file
index bba10a6..df78f87 100644 (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
@@ -59,45 +61,63 @@ using namespace cv;
 \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
@@ -107,18 +127,21 @@ int parseCmdArgs(int argc, char** argv)
         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
@@ -167,7 +190,7 @@ int parseCmdArgs(int argc, char** argv)
             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
@@ -175,7 +198,7 @@ int parseCmdArgs(int argc, char** argv)
                 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
@@ -195,12 +218,14 @@ int parseCmdArgs(int argc, char** argv)
             }\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
@@ -240,9 +265,9 @@ int parseCmdArgs(int argc, char** argv)
             }\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
@@ -253,6 +278,10 @@ int parseCmdArgs(int argc, char** argv)
         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
@@ -281,7 +310,7 @@ int main(int argc, char* argv[])
     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
@@ -333,9 +362,9 @@ int main(int argc, char* argv[])
     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
@@ -533,7 +562,7 @@ int main(int argc, char* argv[])
             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