Merge pull request #22959 from feuerste:parallel_mertens
authorMarco Feuerstein <marco.feuerstein@gmail.com>
Wed, 21 Dec 2022 14:10:59 +0000 (15:10 +0100)
committerGitHub <noreply@github.com>
Wed, 21 Dec 2022 14:10:59 +0000 (14:10 +0000)
Parallelize implementation of HDR MergeMertens.

* Parallelize MergeMertens.

* Added performance tests for HDR.

* Ran clang-format.

* Optimizations.

* Fix data path for Windows.

* Remove compiiation warning on Windows.

* Remove clang-format for existing file.

* Addressing reviewer comments.

* Ensure correct summation order.

* Add test for determinism.

* Move result pyramid into sync struct.

* Reuse sync for first loop as well.

* Use OpenCV's threading primitives.

* Remove cout.

modules/photo/perf/perf_hdr.cpp [new file with mode: 0644]
modules/photo/src/merge.cpp
modules/python/test/test_umat.py

diff --git a/modules/photo/perf/perf_hdr.cpp b/modules/photo/perf/perf_hdr.cpp
new file mode 100644 (file)
index 0000000..4001a79
--- /dev/null
@@ -0,0 +1,64 @@
+// This file is part of OpenCV project.
+// It is subject to the license terms in the LICENSE file found in the top-level directory
+// of this distribution and at http://opencv.org/license.html.
+
+#include "perf_precomp.hpp"
+
+namespace opencv_test
+{
+namespace
+{
+struct ExposureSeq
+{
+    std::vector<Mat> images;
+    std::vector<float> times;
+};
+
+ExposureSeq loadExposureSeq(const std::string& list_filename)
+{
+    std::ifstream list_file(list_filename);
+    EXPECT_TRUE(list_file.is_open());
+    string name;
+    float val;
+    const String path(list_filename.substr(0, list_filename.find_last_of("\\/") + 1));
+    ExposureSeq seq;
+    while (list_file >> name >> val)
+    {
+        Mat img = imread(path + name);
+        EXPECT_FALSE(img.empty()) << "Could not load input image " << path + name;
+        seq.images.push_back(img);
+        seq.times.push_back(1 / val);
+    }
+    list_file.close();
+    return seq;
+}
+
+PERF_TEST(HDR, Mertens)
+{
+    const ExposureSeq seq = loadExposureSeq(getDataPath("cv/hdr/exposures/list.txt"));
+    Ptr<MergeMertens> merge = createMergeMertens();
+    Mat result(seq.images.front().size(), seq.images.front().type());
+    TEST_CYCLE() merge->process(seq.images, result);
+    SANITY_CHECK_NOTHING();
+}
+
+PERF_TEST(HDR, Debevec)
+{
+    const ExposureSeq seq = loadExposureSeq(getDataPath("cv/hdr/exposures/list.txt"));
+    Ptr<MergeDebevec> merge = createMergeDebevec();
+    Mat result(seq.images.front().size(), seq.images.front().type());
+    TEST_CYCLE() merge->process(seq.images, result, seq.times);
+    SANITY_CHECK_NOTHING();
+}
+
+PERF_TEST(HDR, Robertson)
+{
+    const ExposureSeq seq = loadExposureSeq(getDataPath("cv/hdr/exposures/list.txt"));
+    Ptr<MergeRobertson> merge = createMergeRobertson();
+    Mat result(seq.images.front().size(), seq.images.front().type());
+    TEST_CYCLE() merge->process(seq.images, result, seq.times);
+    SANITY_CHECK_NOTHING();
+}
+
+} // namespace
+} // namespace opencv_test
index e6a00fedb857229345bf335d7281d43beab10059..b0a36d4f6f49c6fead1a66a6aa5ed4c78c37e0d1 100644 (file)
@@ -172,87 +172,97 @@ public:
 
         std::vector<Mat> weights(images.size());
         Mat weight_sum = Mat::zeros(size, CV_32F);
+        Mutex weight_sum_mutex;
 
-        for(size_t i = 0; i < images.size(); i++) {
-            Mat img, gray, contrast, saturation, wellexp;
-            std::vector<Mat> splitted(channels);
-
-            images[i].convertTo(img, CV_32F, 1.0f/255.0f);
-            if(channels == 3) {
-                cvtColor(img, gray, COLOR_RGB2GRAY);
-            } else {
-                img.copyTo(gray);
-            }
-            split(img, splitted);
+        parallel_for_(Range(0, static_cast<int>(images.size())), [&](const Range& range) {
+            for(int i = range.start; i < range.end; i++) {
+                Mat& img = images[i];
+                Mat gray, contrast, saturation, wellexp;
+                std::vector<Mat> splitted(channels);
 
-            Laplacian(gray, contrast, CV_32F);
-            contrast = abs(contrast);
+                img.convertTo(img, CV_32F, 1.0f/255.0f);
+                if(channels == 3) {
+                    cvtColor(img, gray, COLOR_RGB2GRAY);
+                } else {
+                    img.copyTo(gray);
+                }
+                split(img, splitted);
 
-            Mat mean = Mat::zeros(size, CV_32F);
-            for(int c = 0; c < channels; c++) {
-                mean += splitted[c];
-            }
-            mean /= channels;
+                Laplacian(gray, contrast, CV_32F);
+                contrast = abs(contrast);
 
-            saturation = Mat::zeros(size, CV_32F);
-            for(int c = 0; c < channels;  c++) {
-                Mat deviation = splitted[c] - mean;
-                pow(deviation, 2.0f, deviation);
-                saturation += deviation;
-            }
-            sqrt(saturation, saturation);
+                Mat mean = Mat::zeros(size, CV_32F);
+                for(int c = 0; c < channels; c++) {
+                    mean += splitted[c];
+                }
+                mean /= channels;
 
-            wellexp = Mat::ones(size, CV_32F);
-            for(int c = 0; c < channels; c++) {
-                Mat expo = splitted[c] - 0.5f;
-                pow(expo, 2.0f, expo);
-                expo = -expo / 0.08f;
-                exp(expo, expo);
-                wellexp = wellexp.mul(expo);
-            }
+                saturation = Mat::zeros(size, CV_32F);
+                for(int c = 0; c < channels;  c++) {
+                    Mat deviation = splitted[c] - mean;
+                    pow(deviation, 2.0f, deviation);
+                    saturation += deviation;
+                }
+                sqrt(saturation, saturation);
+
+                wellexp = Mat::ones(size, CV_32F);
+                for(int c = 0; c < channels; c++) {
+                    Mat expo = splitted[c] - 0.5f;
+                    pow(expo, 2.0f, expo);
+                    expo = -expo / 0.08f;
+                    exp(expo, expo);
+                    wellexp = wellexp.mul(expo);
+                }
 
-            pow(contrast, wcon, contrast);
-            pow(saturation, wsat, saturation);
-            pow(wellexp, wexp, wellexp);
+                pow(contrast, wcon, contrast);
+                pow(saturation, wsat, saturation);
+                pow(wellexp, wexp, wellexp);
+
+                weights[i] = contrast;
+                if(channels == 3) {
+                    weights[i] = weights[i].mul(saturation);
+                }
+                weights[i] = weights[i].mul(wellexp) + 1e-12f;
 
-            weights[i] = contrast;
-            if(channels == 3) {
-                weights[i] = weights[i].mul(saturation);
+                AutoLock lock(weight_sum_mutex);
+                weight_sum += weights[i];
             }
-            weights[i] = weights[i].mul(wellexp) + 1e-12f;
-            weight_sum += weights[i];
-        }
+        });
+
         int maxlevel = static_cast<int>(logf(static_cast<float>(min(size.width, size.height))) / logf(2.0f));
         std::vector<Mat> res_pyr(maxlevel + 1);
+        std::vector<Mutex> res_pyr_mutexes(maxlevel + 1);
 
-        for(size_t i = 0; i < images.size(); i++) {
-            weights[i] /= weight_sum;
-            Mat img;
-            images[i].convertTo(img, CV_32F, 1.0f/255.0f);
-
-            std::vector<Mat> img_pyr, weight_pyr;
-            buildPyramid(img, img_pyr, maxlevel);
-            buildPyramid(weights[i], weight_pyr, maxlevel);
-
-            for(int lvl = 0; lvl < maxlevel; lvl++) {
-                Mat up;
-                pyrUp(img_pyr[lvl + 1], up, img_pyr[lvl].size());
-                img_pyr[lvl] -= up;
-            }
-            for(int lvl = 0; lvl <= maxlevel; lvl++) {
-                std::vector<Mat> splitted(channels);
-                split(img_pyr[lvl], splitted);
-                for(int c = 0; c < channels; c++) {
-                    splitted[c] = splitted[c].mul(weight_pyr[lvl]);
+        parallel_for_(Range(0, static_cast<int>(images.size())), [&](const Range& range) {
+            for(int i = range.start; i < range.end; i++) {
+                weights[i] /= weight_sum;
+
+                std::vector<Mat> img_pyr, weight_pyr;
+                buildPyramid(images[i], img_pyr, maxlevel);
+                buildPyramid(weights[i], weight_pyr, maxlevel);
+
+                for(int lvl = 0; lvl < maxlevel; lvl++) {
+                    Mat up;
+                    pyrUp(img_pyr[lvl + 1], up, img_pyr[lvl].size());
+                    img_pyr[lvl] -= up;
                 }
-                merge(splitted, img_pyr[lvl]);
-                if(res_pyr[lvl].empty()) {
-                    res_pyr[lvl] = img_pyr[lvl];
-                } else {
-                    res_pyr[lvl] += img_pyr[lvl];
+                for(int lvl = 0; lvl <= maxlevel; lvl++) {
+                    std::vector<Mat> splitted(channels);
+                    split(img_pyr[lvl], splitted);
+                    for(int c = 0; c < channels; c++) {
+                        splitted[c] = splitted[c].mul(weight_pyr[lvl]);
+                    }
+                    merge(splitted, img_pyr[lvl]);
+
+                    AutoLock lock(res_pyr_mutexes[lvl]);
+                    if(res_pyr[lvl].empty()) {
+                        res_pyr[lvl] = img_pyr[lvl];
+                    } else {
+                        res_pyr[lvl] += img_pyr[lvl];
+                    }
                 }
             }
-        }
+        });
         for(int lvl = maxlevel; lvl > 0; lvl--) {
             Mat up;
             pyrUp(res_pyr[lvl], up, res_pyr[lvl - 1].size());
index 0081bec5464f73d0cbb43c5872d86ff7418e1556..d8a9a10283ea95b306a6441bea6f8aeb65a49c3f 100644 (file)
@@ -107,12 +107,19 @@ class UMat(NewOpenCVTests):
 
         images, _ = load_exposure_seq(os.path.join(test_data_path, 'exposures'))
 
+        # As we want to test mat vs. umat here, we temporarily set only one worker-thread to achieve
+        # deterministic summations inside mertens' parallelized process.
+        num_threads = cv.getNumThreads()
+        cv.setNumThreads(1)
+
         merge = cv.createMergeMertens()
         mat_result = merge.process(images)
 
         umat_images = [cv.UMat(img) for img in images]
         umat_result = merge.process(umat_images)
 
+        cv.setNumThreads(num_threads)
+
         self.assertTrue(np.allclose(umat_result.get(), mat_result))