jcelaya / hdrmerge

HDR exposure merging
http://jcelaya.github.io/hdrmerge/
Other
355 stars 78 forks source link

Use 1st value >1‰ of the histogram top for `satThreshold` #134

Closed Floessie closed 6 years ago

Floessie commented 6 years ago

Hi,

This is a proposal (taken from and) for #126, which prevents outliers from defining what's the maximum per channel. Instead, the histogram is searched top to bottom for the first value covering more than one per mil of the brightest image.

Take this as a basis and not a final solution.

Best, Flössie

Beep6581 commented 6 years ago

@Floessie how can we test this?

Floessie commented 6 years ago

@Beep6581 The bin in #126 is still valid and exhibits the problem well. Maybe test this branch against a known stack of yours and see, if there are differences in the mask.

As said, this is only meant as a basis for something cleverer.

Best, Flössie

Beep6581 commented 6 years ago

I confirm that it improves the situation in your Canon EOS 6D image set.

My Sony ILCE-7M2's white levels are not auto-detected correctly in master, forcing me to set them manually, as the theoretical maximum white is higher than the actual white. You can even see the Sony compression artifacts in the screenshot below because of the wrong white level. With your patch, the actual white level is auto-detected correctly.

Unpatched: screenshot_20180501_190555

Patched: screenshot_20180501_185933

heckflosse commented 6 years ago

@Floessie I will try to write the loop using omp.

Floessie commented 6 years ago

@heckflosse This is a no-brainer for you, I'm sure. :+1:

heckflosse commented 6 years ago

@Floessie

Here's the patch

diff --git a/src/Image.hpp b/src/Image.hpp
index dc0fded..972fdeb 100644
--- a/src/Image.hpp
+++ b/src/Image.hpp
@@ -74,6 +74,10 @@ public:
         return brightness > r.brightness;
     }
     void setSaturationThreshold(uint16_t sat);
+    uint16_t getMax() const
+    {
+        return max;
+    }

 private:
     struct ResponseFunction {
diff --git a/src/ImageStack.cpp b/src/ImageStack.cpp
index 9097bb4..fb29d7a 100644
--- a/src/ImageStack.cpp
+++ b/src/ImageStack.cpp
@@ -21,6 +21,7 @@
  */

 #include <algorithm>
+#include <valarray>
 #include "ImageStack.hpp"
 #include "Log.hpp"
 #include "BoxBlur.hpp"
@@ -50,11 +51,13 @@ int ImageStack::addImage(Image && i) {

 void ImageStack::calculateSaturationLevel(const RawParameters & params, bool useCustomWl) {
     // Calculate max value of brightest image and assume it is saturated
-    uint16_t maxPerColor[4] = { 0, 0, 0, 0 };
     Image & brightest = images.front();
+
+    std::vector<std::vector<size_t>> histograms(4, std::vector<size_t>(brightest.getMax() + 1));
+
     #pragma omp parallel
     {
-        uint16_t maxPerColorThr[4] = { 0, 0, 0, 0 };
+        std::vector<std::vector<size_t>> histogramsThr(4, std::vector<size_t>(brightest.getMax() + 1));
         #pragma omp for schedule(dynamic,16) nowait
         for (size_t y = 0; y < height; ++y) {
             // get the color codes from x = 0 to 5, works for bayer and xtrans
@@ -66,23 +69,33 @@ void ImageStack::calculateSaturationLevel(const RawParameters & params, bool use
             for (; x < width - 5; x+=6) {
                 for(size_t j = 0; j < 6; ++j) {
                     uint16_t v = brightest(x + j, y);
-                    if (v > maxPerColorThr[fcrow[j]]) {
-                        maxPerColorThr[fcrow[j]] = v;
-                    }
+                    ++histogramsThr[fcrow[j]][v];
                 }
             }
             // remaining pixels
             for (size_t j = 0; x < width; ++x, ++j) {
-                 uint16_t v = brightest(x, y);
-                if (v > maxPerColorThr[fcrow[j]]) {
-                    maxPerColorThr[fcrow[j]] = v;
-                 }
-             }
+                uint16_t v = brightest(x, y);
+                ++histogramsThr[fcrow[j]][v];
+            }
         }
         #pragma omp critical
         {
-            for(int c = 0; c < 4; ++c) {
-                maxPerColor[c] = std::max(maxPerColorThr[c], maxPerColor[c]);
+            for (int c = 0; c < 4; ++c) {
+                for (int i = 0; i < histograms[c].size(); ++i) {
+                    histograms[c][i] += histogramsThr[c][i];
+                }
+            }
+        }
+    }
+
+    uint16_t maxPerColor[4] = { 0, 0, 0, 0 };
+
+    for (int c = 0; c < 4; ++c) {
+        for (int i = histograms[c].size() - 1; i >= 0; --i) {
+            const size_t v = histograms[c][i];
+            if (v > width * height / 1000) {
+                maxPerColor[c] = i;
+                break;
             }
         }
     }
@@ -93,9 +106,8 @@ void ImageStack::calculateSaturationLevel(const RawParameters & params, bool use
             satThreshold = maxPerColor[c];
         }
     }
-    if(!useCustomWl) // only scale when no custom white level was specified
-        satThreshold *= 0.99;
-    else
+
+    if(useCustomWl)
         Log::debug( "Using custom white level ", params.max );

     for (auto & i : images) {
Floessie commented 6 years ago

@heckflosse Ingo, thanks!

heckflosse commented 6 years ago

:+1: for merge from me

Beep6581 commented 6 years ago

@Floessie @heckflosse I compared d7afdb1 to d2efefd. Both the mask and the HDR DNG (when viewed in RT) look identical. +1 for merge.

Floessie commented 6 years ago

@heckflosse @Beep6581 Thanks for testing! I hope you're not waiting for me to merge, as I don't have that permission.

heckflosse commented 6 years ago

@Floessie Oh, completely forgot that you don't have permission.