add edge detector to rdopt
Change-Id: If40fb06008b0b960a6dd55b40be14ba76587d311
diff --git a/aom/aomcx.h b/aom/aomcx.h
index 9955ed8..9703cf8 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -1080,7 +1080,7 @@
AOM_CTRL_USE_TYPE(AV1E_SET_MTU, unsigned int)
#define AOM_CTRL_AV1E_SET_MTU
-AOM_CTRL_USE_TYPE(AV1E_SET_TIMING_INFO_TYPE, aom_timing_info_type_t)
+AOM_CTRL_USE_TYPE(AV1E_SET_TIMING_INFO_TYPE, int) /* aom_timing_info_type_t */
#define AOM_CTRL_AV1E_SET_TIMING_INFO_TYPE
AOM_CTRL_USE_TYPE(AV1E_SET_ENABLE_DF, unsigned int)
diff --git a/av1/common/txb_common.h b/av1/common/txb_common.h
index 1dda51f..698e95b 100644
--- a/av1/common/txb_common.h
+++ b/av1/common/txb_common.h
@@ -12,6 +12,8 @@
#ifndef AOM_AV1_COMMON_TXB_COMMON_H_
#define AOM_AV1_COMMON_TXB_COMMON_H_
+#include "av1/common/onyxc_int.h"
+
extern const int16_t k_eob_group_start[12];
extern const int16_t k_eob_offset_bits[12];
diff --git a/av1/encoder/rdopt.c b/av1/encoder/rdopt.c
index 8f7526f..c65d6d4 100644
--- a/av1/encoder/rdopt.c
+++ b/av1/encoder/rdopt.c
@@ -30,6 +30,7 @@
#include "av1/common/idct.h"
#include "av1/common/mvref_common.h"
#include "av1/common/obmc.h"
+#include "av1/common/onyxc_int.h"
#include "av1/common/pred_common.h"
#include "av1/common/quant_common.h"
#include "av1/common/reconinter.h"
@@ -12321,3 +12322,76 @@
}
}
}
+
+static INLINE uint8_t get_pix(const uint8_t *src, int stride, int i, int j) {
+ return src[i + stride * j];
+}
+
+// 8-tap Gaussian convolution filter with sigma = 1.3, sums to 128,
+// all co-efficients must be even.
+DECLARE_ALIGNED(16, static const int16_t, gauss_filter[8]) = { 2, 12, 30, 40,
+ 30, 12, 2, 0 };
+
+void gaussian_blur(const uint8_t *src, int src_stride, int w, int h,
+ uint8_t *dst) {
+ ConvolveParams conv_params = get_conv_params(0, 0, 0);
+ InterpFilterParams filter = { .filter_ptr = gauss_filter,
+ .taps = 8,
+ .subpel_shifts = 0,
+ .interp_filter = EIGHTTAP_REGULAR };
+ av1_convolve_2d_sr(src, src_stride, dst, w, w, h, &filter, &filter, 0, 0,
+ &conv_params);
+}
+
+/* Use standard 3x3 Sobel matrix. */
+sobel_xy sobel(const uint8_t *input, int stride, int i, int j) {
+ int16_t s_x = get_pix(input, stride, i - 1, j - 1) -
+ get_pix(input, stride, i + 1, j - 1) +
+ 2 * get_pix(input, stride, i - 1, j) -
+ 2 * get_pix(input, stride, i + 1, j) +
+ get_pix(input, stride, i - 1, j + 1) -
+ get_pix(input, stride, i + 1, j + 1);
+ int16_t s_y = get_pix(input, stride, i - 1, j - 1) +
+ 2 * get_pix(input, stride, i, j - 1) +
+ get_pix(input, stride, i + 1, j - 1) -
+ get_pix(input, stride, i - 1, j + 1) -
+ 2 * get_pix(input, stride, i, j + 1) -
+ get_pix(input, stride, i + 1, j + 1);
+ sobel_xy r = { .x = s_x, .y = s_y };
+ return r;
+}
+
+static uint16_t edge_probability(const uint8_t *input, int w, int h) {
+ // The probability of an edge in the whole image is the same as the highest
+ // probability of an edge for any individual pixel. Use Soebel as the metric
+ // for finding an edge.
+ uint16_t highest = 0;
+ // Ignore the 1 pixel border around the image for the computation.
+ for (int i = 1; i < w - 1; ++i) {
+ for (int j = 1; j < h - 1; ++j) {
+ sobel_xy g = sobel(input, w, i, j);
+ uint16_t magnitude = (uint16_t)sqrt(g.x * g.x + g.y * g.y);
+ highest = AOMMAX(highest, magnitude);
+ }
+ }
+ return highest;
+}
+
+/* Uses most of the Canny edge detection algorithm to find if there are any
+ * edges in the image.
+ */
+uint16_t av1_edge_exists(const uint8_t *src, int src_stride, int w, int h) {
+ if (w < 3 || h < 3) {
+ return 0;
+ }
+ uint8_t *blurred = NULL;
+ blurred = (uint8_t *)aom_memalign(32, sizeof(*blurred) * w * h);
+ gaussian_blur(src, src_stride, w, h, blurred);
+ // Skip the non-maximum suppression step in Canny edge detection. We just
+ // want a probability of an edge existing in the buffer, which is determined
+ // by the strongest edge in it -- we don't need to eliminate the weaker
+ // edges. Use Soebel for the edge detection.
+ uint16_t prob = edge_probability(blurred, w, h);
+ aom_free(blurred);
+ return prob;
+}
diff --git a/av1/encoder/rdopt.h b/av1/encoder/rdopt.h
index 4c11f90..d4df1e8 100644
--- a/av1/encoder/rdopt.h
+++ b/av1/encoder/rdopt.h
@@ -126,6 +126,33 @@
struct macroblock *x, int mi_row, int mi_col, struct RD_STATS *rd_cost,
BLOCK_SIZE bsize, PICK_MODE_CONTEXT *ctx, int64_t best_rd_so_far);
+/** Returns an integer indicating the strength of the edge.
+ * 0 means no edge found, 556 is the strength of a solid black/white edge,
+ * and the number may range higher if the signal is even stronger (e.g., on a
+ * corner).
+ */
+uint16_t av1_edge_exists(const uint8_t *src, int src_stride, int w, int h);
+
+/** Applies a Gaussian blur with sigma = 1.3. Used by av1_edge_exists and
+ * tests.
+ */
+void gaussian_blur(const uint8_t *src, int src_stride, int w, int h,
+ uint8_t *dst);
+
+/* Applies standard 3x3 Sobel matrix. */
+typedef struct y4m_input y4m_input;
+
+/*The function used to perform chroma conversion.*/
+typedef void (*y4m_convert_func)(y4m_input *_y4m, unsigned char *_dst,
+ unsigned char *_src);
+
+typedef struct {
+ int16_t x;
+ int16_t y;
+} sobel_xy;
+
+sobel_xy sobel(const uint8_t *input, int stride, int i, int j);
+
#if CONFIG_COLLECT_INTER_MODE_RD_STATS
void av1_inter_mode_data_init(struct TileDataEnc *tile_data);
void av1_inter_mode_data_fit(TileDataEnc *tile_data, int rdmult);
diff --git a/test/edge_detect_test.cc b/test/edge_detect_test.cc
new file mode 100644
index 0000000..6dd33a9
--- /dev/null
+++ b/test/edge_detect_test.cc
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include "aom_mem/aom_mem.h"
+#include "av1/encoder/rdopt.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+namespace {
+
+class GaussianBlurTest : public ::testing::Test {};
+
+/** Get the (x, y) value from the input; if i or j is outside of the width
+ * or height, the nearest pixel value is returned.
+ */
+static uint8_t get_xy(const uint8_t *data, int w, int h, int i, int j) {
+ return data[AOMMAX(AOMMIN(i, w - 1), 0) + w * AOMMAX(AOMMIN(j, h - 1), 0)];
+}
+
+/** Given the image data, creates a new image with padded values, so an
+ * 8-tap filter can be convolved. The padded value is the same as the closest
+ * value in the image. Returns a pointer to the start of the image in the
+ * padded data. Must be freed with free_for_convolve.
+ */
+uint8_t *pad_8tap_convolve(const uint8_t *data, int w, int h) {
+ // AVX2 optimizations require the width to be a multiple of 8 and the height
+ // a multiple of 2.
+ assert(w % 8 == 0);
+ assert(h % 2 == 0);
+ // For an 8-tap filter, we need to pad with 3 lines on top and on the left,
+ // and 4 lines on the right and bottom, for 7 extra lines.
+ const int pad_w = w + 7;
+ const int pad_h = h + 7;
+ uint8_t *dst = (uint8_t *)aom_memalign(32, pad_w * pad_h);
+ // Fill in the data from the original.
+ for (int i = 0; i < pad_w; ++i) {
+ for (int j = 0; j < pad_h; ++j) {
+ dst[i + j * pad_w] = get_xy(data, w, h, i - 3, j - 3);
+ }
+ }
+ return dst + (w + 7) * 3 + 3;
+}
+
+static void free_pad_8tap(uint8_t *padded, int width) {
+ aom_free(padded - (width + 7) * 3 - 3);
+}
+
+static int stride_8tap(int width) { return width + 7; }
+
+TEST(GaussianBlurTest, UniformBrightness) {
+ // Generate images ranging in size from 8x8 to 32x32, with
+ // varying levels of brightness. In all cases, the algorithm should
+ // produce the same output.
+ // Note that width must increment in values of 8 for the AVX2 code to work.
+ for (int width = 8; width <= 32; width += 8) {
+ // Note that height must be even for the AVX2 code to work.
+ for (int height = 4; height <= 10; height += 2) {
+ for (int brightness = 0; brightness < 255; ++brightness) {
+ uint8_t *orig = (uint8_t *)malloc(width * height);
+ for (int i = 0; i < width * height; ++i) {
+ orig[i] = brightness;
+ }
+ uint8_t *padded = pad_8tap_convolve(orig, width, height);
+ free(orig);
+ uint8_t *output = (uint8_t *)aom_memalign(32, width * height);
+ gaussian_blur(padded, stride_8tap(width), width, height, output);
+ for (int i = 0; i < width * height; ++i) {
+ ASSERT_EQ(brightness, output[i]);
+ }
+ free_pad_8tap(padded, width);
+ aom_free(output);
+ }
+ }
+ }
+}
+
+TEST(GaussianBlurTest, SimpleExample) {
+ // Randomly generated 8x2.
+ const uint8_t luma[16] = { 241, 147, 7, 90, 184, 103, 28, 186,
+ 2, 248, 49, 242, 114, 146, 127, 22 };
+ uint8_t expected[] = { 151, 132, 117, 119, 124, 117, 109, 113,
+ 111, 124, 129, 135, 135, 122, 103, 88 };
+ const int w = 8;
+ const int h = 2;
+ uint8_t *padded = pad_8tap_convolve(luma, w, h);
+ uint8_t *output = (uint8_t *)aom_memalign(32, w * h);
+ gaussian_blur(padded, stride_8tap(w), w, h, output);
+
+ for (int i = 0; i < w * h; ++i) {
+ ASSERT_EQ(expected[i], output[i]);
+ }
+
+ free_pad_8tap(padded, w);
+ aom_free(output);
+}
+
+class EdgeDetectTest : public ::testing::Test {};
+
+TEST(EdgeDetectTest, UniformBrightness) {
+ // Generate images ranging in size from 8x2 to 32x32, with
+ // varying levels of brightness. In all cases, the algorithm should
+ // produce the same output.
+ for (int width = 8; width <= 32; width += 8) {
+ for (int height = 2; height <= 32; height += 2) {
+ for (int brightness = 0; brightness < 255; ++brightness) {
+ uint8_t *orig = (uint8_t *)malloc(width * height);
+ for (int i = 0; i < width * height; ++i) {
+ orig[i] = brightness;
+ }
+ uint8_t *padded = pad_8tap_convolve(orig, width, height);
+ free(orig);
+ ASSERT_EQ(0,
+ av1_edge_exists(padded, stride_8tap(width), width, height));
+ free_pad_8tap(padded, width);
+ }
+ }
+ }
+}
+
+// Generate images ranging in size from 8x2 to 32x32, black on one side
+// and white on the other.
+TEST(EdgeDetectTest, BlackWhite) {
+ for (int width = 8; width <= 32; width += 8) {
+ for (int height = 2; height <= 32; height += 2) {
+ uint8_t *orig = (uint8_t *)malloc(width * height);
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ if (i < width / 2) {
+ orig[i + j * width] = 0;
+ } else {
+ orig[i + j * width] = 255;
+ }
+ }
+ }
+ uint8_t *padded = pad_8tap_convolve(orig, width, height);
+ free(orig);
+ if (height < 3) {
+ ASSERT_EQ(0,
+ av1_edge_exists(padded, stride_8tap(width), width, height));
+ } else {
+ ASSERT_LE(556,
+ av1_edge_exists(padded, stride_8tap(width), width, height));
+ }
+ free_pad_8tap(padded, width);
+ }
+ }
+}
+
+} // namespace
diff --git a/test/test.cmake b/test/test.cmake
index b16ae14..513a0f1 100644
--- a/test/test.cmake
+++ b/test/test.cmake
@@ -179,6 +179,7 @@
"${AOM_ROOT}/test/comp_avg_pred_test.cc"
"${AOM_ROOT}/test/comp_avg_pred_test.h"
"${AOM_ROOT}/test/comp_mask_variance_test.cc"
+ "${AOM_ROOT}/test/edge_detect_test.cc"
"${AOM_ROOT}/test/encodetxb_test.cc"
"${AOM_ROOT}/test/error_block_test.cc"
"${AOM_ROOT}/test/fft_test.cc"