Adjust altref filter strength based on noise level

Uses a simple noise estimator for altref filtering to
modulate the filtering strength.

lowres 30 frames, q mode: -0.05%, not much change in speed

Experiments to be continued with boosts and other parameters.

STATS_CHANGED

Change-Id: Iad5ab781470149ef429dde20e38f46327e934000
diff --git a/av1/encoder/temporal_filter.c b/av1/encoder/temporal_filter.c
index 374ea23..cab4b73 100644
--- a/av1/encoder/temporal_filter.c
+++ b/av1/encoder/temporal_filter.c
@@ -34,6 +34,9 @@
 #include "aom_ports/aom_timer.h"
 #include "aom_scale/aom_scale.h"
 
+#define EDGE_THRESHOLD 50
+#define SQRT_PI_BY_2 1.25331413732
+
 static void temporal_filter_predictors_mb_c(
     MACROBLOCKD *xd, uint8_t *y_mb_ptr, uint8_t *u_mb_ptr, uint8_t *v_mb_ptr,
     int stride, int uv_block_width, int uv_block_height, int mv_row, int mv_col,
@@ -493,6 +496,83 @@
   for (i = 0; i < num_planes; i++) mbd->plane[i].pre[0].buf = input_buffer[i];
 }
 
+// This is an adaptation of the mehtod in the following paper:
+// Shen-Chuan Tai, Shih-Ming Yang, "A fast method for image noise
+// estimation using Laplacian operator and adaptive edge detection,"
+// Proc. 3rd International Symposium on Communications, Control and
+// Signal Processing, 2008, St Julians, Malta.
+//
+// Return noise estimate, or -1.0 if there was a failure
+static double estimate_noise(const uint8_t *src, int width, int height,
+                             int stride, int edge_thresh) {
+  int64_t sum = 0;
+  int64_t num = 0;
+  for (int i = 1; i < height - 1; ++i) {
+    for (int j = 1; j < width - 1; ++j) {
+      const int k = i * stride + j;
+      // Sobel gradients
+      const int Gx = (src[k - stride - 1] - src[k - stride + 1]) +
+                     (src[k + stride - 1] - src[k + stride + 1]) +
+                     2 * (src[k - 1] - src[k + 1]);
+      const int Gy = (src[k - stride - 1] - src[k + stride - 1]) +
+                     (src[k - stride + 1] - src[k + stride + 1]) +
+                     2 * (src[k - stride] - src[k + stride]);
+      const int Ga = abs(Gx) + abs(Gy);
+      if (Ga < edge_thresh) {  // Smooth pixels
+        // Find Laplacian
+        const int v =
+            4 * src[k] -
+            2 * (src[k - 1] + src[k + 1] + src[k - stride] + src[k + stride]) +
+            (src[k - stride - 1] + src[k - stride + 1] + src[k + stride - 1] +
+             src[k + stride + 1]);
+        sum += abs(v);
+        ++num;
+      }
+    }
+  }
+  // If very few smooth pels, return -1 since the estimate is unreliable
+  if (num < 16) return -1.0;
+
+  const double sigma = (double)sum / (6 * num) * SQRT_PI_BY_2;
+  return sigma;
+}
+
+// Return noise estimate, or -1.0 if there was a failure
+static double highbd_estimate_noise(const uint8_t *src8, int width, int height,
+                                    int stride, int bd, int edge_thresh) {
+  uint16_t *src = CONVERT_TO_SHORTPTR(src8);
+  int64_t sum = 0;
+  int64_t num = 0;
+  for (int i = 1; i < height - 1; ++i) {
+    for (int j = 1; j < width - 1; ++j) {
+      const int k = i * stride + j;
+      // Sobel gradients
+      const int Gx = (src[k - stride - 1] - src[k - stride + 1]) +
+                     (src[k + stride - 1] - src[k + stride + 1]) +
+                     2 * (src[k - 1] - src[k + 1]);
+      const int Gy = (src[k - stride - 1] - src[k + stride - 1]) +
+                     (src[k - stride + 1] - src[k + stride + 1]) +
+                     2 * (src[k - stride] - src[k + stride]);
+      const int Ga = ROUND_POWER_OF_TWO(abs(Gx) + abs(Gy), bd - 8);
+      if (Ga < edge_thresh) {  // Smooth pixels
+        // Find Laplacian
+        const int v =
+            4 * src[k] -
+            2 * (src[k - 1] + src[k + 1] + src[k - stride] + src[k + stride]) +
+            (src[k - stride - 1] + src[k - stride + 1] + src[k + stride - 1] +
+             src[k + stride + 1]);
+        sum += ROUND_POWER_OF_TWO(abs(v), bd - 8);
+        ++num;
+      }
+    }
+  }
+  // If very few smooth pels, return -1 since the estimate is unreliable
+  if (num < 16) return -1.0;
+
+  const double sigma = (double)sum / (6 * num) * SQRT_PI_BY_2;
+  return sigma;
+}
+
 // Apply buffer limits and context specific adjustments to arnr filter.
 static void adjust_arnr_filter(AV1_COMP *cpi, int distance, int group_boost,
                                int *arnr_frames, int *arnr_strength) {
@@ -523,10 +603,38 @@
   else
     q = ((int)av1_convert_qindex_to_q(cpi->rc.avg_frame_qindex[KEY_FRAME],
                                       cpi->common.seq_params.bit_depth));
-  if (q > 16) {
-    strength = oxcf->arnr_strength;
+  MACROBLOCKD *mbd = &cpi->td.mb.e_mbd;
+  struct lookahead_entry *buf = av1_lookahead_peek(cpi->lookahead, distance);
+  double noiselevel;
+  if (mbd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH) {
+    noiselevel = highbd_estimate_noise(
+        buf->img.y_buffer, buf->img.y_crop_width, buf->img.y_crop_height,
+        buf->img.y_stride, mbd->bd, EDGE_THRESHOLD);
   } else {
-    strength = oxcf->arnr_strength - ((16 - q) / 2);
+    noiselevel = estimate_noise(buf->img.y_buffer, buf->img.y_crop_width,
+                                buf->img.y_crop_height, buf->img.y_stride,
+                                EDGE_THRESHOLD);
+  }
+  int adj_strength = oxcf->arnr_strength;
+  if (noiselevel > 0) {
+    // Get 4 integer adjustment levels in [-2, 1]
+    int noiselevel_adj;
+    if (noiselevel < 0.75)
+      noiselevel_adj = -2;
+    else if (noiselevel < 1.75)
+      noiselevel_adj = -1;
+    else if (noiselevel < 4.0)
+      noiselevel_adj = 0;
+    else
+      noiselevel_adj = 1;
+    adj_strength += noiselevel_adj;
+  }
+  // printf("[noise level: %g, strength = %d]\n", noiselevel, adj_strength);
+
+  if (q > 16) {
+    strength = adj_strength;
+  } else {
+    strength = adj_strength - ((16 - q) / 2);
     if (strength < 0) strength = 0;
   }
 
@@ -557,14 +665,16 @@
   const GF_GROUP *const gf_group = &cpi->twopass.gf_group;
 
   // Apply context specific adjustments to the arnr filter parameters.
-  adjust_arnr_filter(cpi, distance, rc->gfu_boost, &frames_to_blur, &strength);
-  // TODO(weitinglin): Currently, we enforce the filtering strength on
-  //                   extra ARFs' to be zeros. We should investigate in which
-  //                   case it is more beneficial to use non-zero strength
-  //                   filtering.
   if (gf_group->update_type[gf_group->index] == INTNL_ARF_UPDATE) {
+    // TODO(weitinglin): Currently, we enforce the filtering strength on
+    //                   extra ARFs' to be zeros. We should investigate in which
+    //                   case it is more beneficial to use non-zero strength
+    //                   filtering.
     strength = 0;
     frames_to_blur = 1;
+  } else {
+    adjust_arnr_filter(cpi, distance, rc->gfu_boost, &frames_to_blur,
+                       &strength);
   }
 
   int which_arf = gf_group->arf_update_idx[gf_group->index];