summaryrefslogtreecommitdiffstats
path: root/libhb
diff options
context:
space:
mode:
authorBradley Sepos <[email protected]>2018-01-09 04:30:28 -0500
committerBradley Sepos <[email protected]>2018-01-09 04:53:56 -0500
commit57465d4ab681d5ac5be582ce13806b301fd0c69a (patch)
tree281bbc73e261694f2d3b1f2e19480ad594f6d302 /libhb
parentfaab5c403f0ed727030f39e0a2db5e312ed9f50f (diff)
libhb: Add CSM prefilter to NLMeans.
CSM is a Conservative Smoothing filter with Median-like tendencies. Conservative Smoothing is a basic noise reduction method that ensures a given pixel is within the values of those around it. A value higher than all the others is clamped to the maximum value in the neighborhood. Likewise, a value lower than all the others is clamped to the minimum value in the neighborhood. Basically, pixel values that seem to "fit in" are left alone, and extreme values are brought in line. CSM takes this a step further. A pixel not affected by the previous part of the algorithm is subjected to additional thresholding. If the pixel value is closer to the minimum or maximum neighborhood value than the median, it is clamped to the half-way point. Finally, a pixel still not affected is subjected to a third level of thresholding, clamping to the half-way point between the median and the previous half-way point. Any other pixel value is deemed close enough to its peers and left alone. In effect, this creates a "soft" median-like filter, where relatively similar values are left alone and increasingly disparate values are nudged closer together. Practically, CSM is the best prefilter to date for improving weight decisions with sources containing a type or amount of noise proving difficult for NLMeans to uniformly dampen or completely remove on its own. Additionally, it does not significantly alter the strength metric in most cases, so it can simply be enabled wherever desired. From what I can tell in my limited testing, the algorithm respects proper detail and edges well enough that it seems to be safe with nearly any source. Perhaps it should be the default if I ever get around to creating NLMeans 2. Unlike the mean and median prefilters where a larger neighborhood increases the strength of the prefilter, a larger CSM neighborhood merely takes more pixels into account, theoretically decreasing the strength of the filter. In practice, the provided 3x3 and 5x5 neighborhoods typically do not produce significantly differing results. Basic usage: Add y-prefilter=16 to your desired parameters and NLMeans will use CSM for weighting decisions. Use y-prefilter=2064 if you want to see the output of the prefilter itself—the visual effect is mild. Adjust these values to 32 and 2080 for a 5x5 neighborhood; 3x3 works well in all cases I've tried.
Diffstat (limited to 'libhb')
-rw-r--r--libhb/nlmeans.c111
1 files changed, 108 insertions, 3 deletions
diff --git a/libhb/nlmeans.c b/libhb/nlmeans.c
index 458a76ab5..dd1fe5b3e 100644
--- a/libhb/nlmeans.c
+++ b/libhb/nlmeans.c
@@ -72,8 +72,8 @@
#define NLMEANS_PREFILTER_MODE_MEAN5X5 2
#define NLMEANS_PREFILTER_MODE_MEDIAN3X3 4
#define NLMEANS_PREFILTER_MODE_MEDIAN5X5 8
-#define NLMEANS_PREFILTER_MODE_RESERVED16 16 // Reserved
-#define NLMEANS_PREFILTER_MODE_RESERVED32 32 // Reserved
+#define NLMEANS_PREFILTER_MODE_CSM3X3 16
+#define NLMEANS_PREFILTER_MODE_CSM5X5 32
#define NLMEANS_PREFILTER_MODE_RESERVED64 64 // Reserved
#define NLMEANS_PREFILTER_MODE_RESERVED128 128 // Reserved
#define NLMEANS_PREFILTER_MODE_REDUCE25 256
@@ -385,6 +385,99 @@ static void nlmeans_filter_median(const uint8_t *src,
}
+static void nlmeans_filter_csm(const uint8_t *src,
+ uint8_t *dst,
+ const int w,
+ const int h,
+ const int border,
+ const int size)
+{
+ // CSM filter
+ const int bw = w + 2 * border;
+ const int offset_min = -((size - 1) /2);
+ const int offset_max = (size + 1) /2;
+ uint8_t min, max,
+ min2, max2,
+ min3, max3,
+ median,
+ pixel;
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ for (int k = offset_min; k < offset_max; k++)
+ {
+ for (int j = offset_min; j < offset_max; j++)
+ {
+ if (k == 0 && j == 0)
+ {
+ // Ignore origin
+ goto end;
+ }
+ pixel = *(src + bw*(y+j) + (x+k));
+ if (k == offset_min && j == offset_min)
+ {
+ // Start calculating neighborhood thresholds
+ min = pixel;
+ max = min;
+ goto end;
+ }
+ if (pixel < min)
+ {
+ min = pixel;
+ }
+ if (pixel > max)
+ {
+ max = pixel;
+ }
+ }
+ end:
+ continue;
+ }
+
+ // Final neighborhood thresholds
+ // min = minimum neighbor pixel value
+ // max = maximum neighbor pixel value
+
+ // Median
+ median = (min + max) / 2;
+
+ // Additional thresholds for median-like filtering
+ min2 = (min + median) / 2;
+ max2 = (max + median) / 2;
+ min3 = (min2 + median) / 2;
+ max3 = (max2 + median) / 2;
+
+ // Clamp to thresholds
+ pixel = *(src + bw*(y) + (x));
+ if (pixel < min)
+ {
+ *(dst + bw*y + x) = min;
+ }
+ else if (pixel > max)
+ {
+ *(dst + bw*y + x) = max;
+ }
+ else if (pixel < min2)
+ {
+ *(dst + bw*y + x) = min2;
+ }
+ else if (pixel > max2)
+ {
+ *(dst + bw*y + x) = max2;
+ }
+ else if (pixel < min3)
+ {
+ *(dst + bw*y + x) = min3;
+ }
+ else if (pixel > max3)
+ {
+ *(dst + bw*y + x) = max3;
+ }
+ }
+ }
+}
+
static void nlmeans_filter_edgeboost(const uint8_t *src,
uint8_t *dst,
const int w,
@@ -496,7 +589,9 @@ static void nlmeans_prefilter(BorderedPlane *src,
if (filter_type & NLMEANS_PREFILTER_MODE_MEAN3X3 ||
filter_type & NLMEANS_PREFILTER_MODE_MEAN5X5 ||
filter_type & NLMEANS_PREFILTER_MODE_MEDIAN3X3 ||
- filter_type & NLMEANS_PREFILTER_MODE_MEDIAN5X5)
+ filter_type & NLMEANS_PREFILTER_MODE_MEDIAN5X5 ||
+ filter_type & NLMEANS_PREFILTER_MODE_CSM3X3 ||
+ filter_type & NLMEANS_PREFILTER_MODE_CSM5X5)
{
// Source image
@@ -537,6 +632,16 @@ static void nlmeans_prefilter(BorderedPlane *src,
// Mean 3x3
nlmeans_filter_mean(image, image_pre, w, h, border, 3);
}
+ else if (filter_type & NLMEANS_PREFILTER_MODE_CSM3X3)
+ {
+ // CSM 3x3
+ nlmeans_filter_csm(image, image_pre, w, h, border, 3);
+ }
+ else if (filter_type & NLMEANS_PREFILTER_MODE_CSM5X5)
+ {
+ // CSM 5x5
+ nlmeans_filter_csm(image, image_pre, w, h, border, 5);
+ }
// Restore edges
if (filter_type & NLMEANS_PREFILTER_MODE_EDGEBOOST)