| /* |
| * Copyright (c) 2021, Alliance for Open Media. All rights reserved |
| * |
| * This source code is subject to the terms of the BSD 3-Clause Clear License |
| * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear |
| * License was not distributed with this source code in the LICENSE file, you |
| * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. 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 |
| * aomedia.org/license/patent-license/. |
| */ |
| |
| #include "config/aom_dsp_rtcd.h" |
| #include "av1/common/av1_common_int.h" |
| #include "av1/encoder/pickdeband.h" |
| |
| |
| /* CAMBI preprocessing functions */ |
| static void copy_8b_to_10b_buffer(uint16_t *data, int stride, |
| unsigned in_w, unsigned in_h, |
| uint16_t *out_data, int out_stride, |
| unsigned out_w, unsigned out_h) { |
| |
| // if the input and output sizes are the same |
| if (in_w == out_w && in_h == out_h) { |
| for (unsigned i = 0; i < out_h; i++) |
| for (unsigned j = 0; j < out_w; j++) |
| out_data[i * out_stride + j] = data[i * stride + j] << 2; |
| } else { |
| printf("Error in copy_8b_to_10b_buffer: different size\n"); |
| } |
| } |
| |
| static void copy_10b_buffer(uint16_t *data, int stride, |
| unsigned in_w, unsigned in_h, |
| uint16_t *out_data, int out_stride, |
| unsigned out_w, unsigned out_h) { |
| if (in_w == out_w && in_h == out_h) { |
| for (unsigned i = 0; i < out_h; i++) { |
| memcpy(out_data, data, in_w * sizeof(uint16_t)); |
| data += stride; |
| out_data += out_stride; |
| } |
| } else { |
| printf("Error in copy_10b_buffer: different size\n"); |
| } |
| } |
| |
| static void anti_dithering_filter(uint16_t *data, int stride, |
| int width, int height) { |
| |
| for (int i = 0; i < height - 1; i++) { |
| for (int j = 0; j < width - 1; j++) { |
| data[i * stride + j] = (data[i * stride + j] + |
| data[i * stride + j + 1] + |
| data[(i + 1) * stride + j] + |
| data[(i + 1) * stride + j + 1]) >> 2; |
| } |
| |
| // Last column |
| int j = width - 1; |
| data[i * stride + j] = (data[i * stride + j] + |
| data[(i + 1) * stride + j]) >> 1; |
| } |
| |
| // Last row |
| int i = height - 1; |
| for (int j = 0; j < width - 1; j++) { |
| data[i * stride + j] = (data[i * stride + j] + |
| data[i * stride + j + 1]) >> 1; |
| } |
| } |
| |
| void cambi_preprocessing(uint16_t *data, int stride, int in_w, int in_h, |
| int bit_depth, uint16_t *out_data, int out_stride, |
| int out_w, int out_h) { |
| if (bit_depth == 8) { |
| copy_8b_to_10b_buffer(data, stride, in_w, in_h, |
| out_data, out_stride, out_w, out_h); |
| anti_dithering_filter(out_data, out_stride, out_w, out_h); |
| } |
| else { |
| copy_10b_buffer(data, stride, in_w, in_h, out_data, out_stride, |
| out_w, out_h); |
| } |
| } |
| |
| |
| /* CAMBI processing functions */ |
| static inline uint16_t get_pixels_in_window(uint16_t window_length) { |
| uint16_t odd_length = 2 * (window_length >> 1) + 1; |
| return odd_length * odd_length; |
| } |
| |
| static inline uint16_t adjust_cambi_window_size(uint16_t size, |
| unsigned width, |
| unsigned height) { |
| // Adjustment with (input_width + input_height) / (4K_WIDTH + 4K_HEIGHT) |
| return CLAMP(((size * (width + height)) / 375) >> 4, |
| 5, CAMBI_DEFAULT_WINDOW_SIZE); |
| } |
| |
| void set_cambi_window(DebandInfo *const dbi) { |
| dbi->window_size = CAMBI_DEFAULT_WINDOW_SIZE; |
| dbi->window_size = |
| adjust_cambi_window_size(dbi->window_size, dbi->stride, dbi->height); |
| dbi->pixels_in_window = get_pixels_in_window(dbi->window_size); |
| } |
| |
| static void cambi_decimate(uint16_t *data, int stride, |
| unsigned width, unsigned height) { |
| for (unsigned i = 0; i < height; i++) { |
| for (unsigned j = 0; j < width; j++) { |
| data[i * stride + j] = data[(i << 1) * stride + (j << 1)]; |
| } |
| } |
| } |
| |
| /* Spatial mask functions */ |
| static inline bool get_derivative_data(const uint16_t *data, |
| int i, int j, int str) { |
| return (data[i * str + j] == data[(i + 1) * str + j]) && |
| (data[i * str + j] == data[i * str + j + 1]); |
| } |
| |
| /* |
| * This function calculates the horizontal and vertical derivatives of the image |
| * using 2x1 and 1x2 kernels. |
| */ |
| static void get_spatial_mask_for_index(DebandInfo *dbi, uint16_t mask_index, |
| uint16_t filter_size, int width, |
| int height) { |
| uint16_t pad_size = filter_size >> 1; |
| uint16_t *image_data = dbi->frame; |
| uint16_t *mask_data = dbi->mask; |
| int stride = dbi->stride; |
| uint32_t *dp = dbi->buffers.mask_dp; |
| |
| int dp_width = width + 2 * pad_size + 1; |
| int dp_height = 2 * pad_size + 2; |
| memset(dp, 0, dp_width * dp_height * sizeof(uint32_t)); |
| |
| // Initial computation: fill dp except for the last row |
| for (int i = 0; i < pad_size; i++) { |
| int cur_row_start = (i + pad_size + 1) * dp_width; |
| int prev_row_start = cur_row_start - dp_width; |
| int curr_col = pad_size + 1; |
| for (int j = 0; j < width + pad_size; j++, curr_col++) { |
| int value = (i < height-1 && j < width-1 ? get_derivative_data(image_data, i, j, stride) : 0); |
| dp[cur_row_start + curr_col] = |
| value |
| + dp[prev_row_start + curr_col] |
| + dp[cur_row_start + curr_col - 1] |
| - dp[prev_row_start + curr_col - 1]; |
| } |
| } |
| |
| // Start from the last row in the dp matrix |
| int curr_row = dp_height - 1; |
| int prev_row = dp_height - 2; |
| int bottom = 2 * pad_size; |
| for (int i = pad_size; i < height + pad_size; i++) { |
| // First compute the values of dp for curr_row |
| int curr_col = pad_size + 1; |
| for (int j = 0; j < width + pad_size; j++, curr_col++) { |
| int value = (i < height-1 && j < width-1 ? get_derivative_data(image_data, i, j, stride) : 0); |
| dp[curr_row * dp_width + curr_col] = |
| value |
| + dp[prev_row * dp_width + curr_col] |
| + dp[curr_row * dp_width + curr_col - 1] |
| - dp[prev_row * dp_width + curr_col - 1]; |
| } |
| prev_row = curr_row; |
| curr_row = curr_row==(dp_height-1) ? 0 : curr_row+1; |
| bottom = bottom==(dp_height-1) ? 0 : bottom+1; |
| |
| // Then use the values to compute the square sum for the curr computed row. |
| int right = 2*pad_size + 1; |
| int top = curr_row; |
| for (int left = 0; left < width; left++, right++) { |
| int result = |
| dp[bottom * dp_width + right] |
| - dp[bottom * dp_width + left] |
| - dp[top * dp_width + right] |
| + dp[top * dp_width + left]; |
| mask_data[(i - pad_size) * stride + left] = (result > mask_index); |
| } |
| } |
| } |
| |
| void cambi_get_spatial_mask(DebandInfo *dbi, int width, int height) { |
| const uint16_t filter_size = CAMBI_MASK_FILTER_SIZE; |
| uint16_t mask_index = cambi_get_mask_index(width, height, filter_size); |
| get_spatial_mask_for_index(dbi, mask_index, filter_size, width, height); |
| } |
| |
| static inline uint16_t min3(uint16_t a, uint16_t b, uint16_t c) { |
| if (a <= b && a <= c) return a; |
| if (b <= c) return b; |
| return c; |
| } |
| |
| static inline uint16_t mode3(uint16_t a, uint16_t b, uint16_t c) { |
| if (a == b || a == c) return a; |
| if (b == c) return b; |
| return min3(a, b, c); |
| } |
| |
| void cambi_filter_mode(DebandInfo *dbi, int width, int height) { |
| uint16_t *data = dbi->frame; |
| ptrdiff_t stride = dbi->stride; |
| uint16_t *buffer = dbi->buffers.filter_mode_buffer; |
| |
| for (int i = 0; i < height; i++) { |
| int curr_line = i % 3; |
| buffer[curr_line * width + 0] = data[i * stride + 0]; |
| for (int j = 1; j < width - 1; j++) { |
| buffer[curr_line * width + j] = mode3(data[i * stride + j - 1], data[i * stride + j], data[i * stride + j + 1]); |
| } |
| buffer[curr_line * width + width - 1] = data[i * stride + width - 1]; |
| |
| if (i > 1) { |
| for (int j = 0; j < width; j++) { |
| data[(i - 1) * stride + j] = mode3(buffer[0 * width + j], buffer[1 * width + j], buffer[2 * width + j]); |
| } |
| } |
| } |
| } |
| |
| static inline void increment_range(uint16_t *arr, int left, int right) { |
| for (int i = left; i < right; i++) { |
| arr[i]++; |
| } |
| } |
| |
| static inline void decrement_range(uint16_t *arr, int left, int right) { |
| for (int i = left; i < right; i++) { |
| arr[i]--; |
| } |
| } |
| |
| static inline void cambi_histogram_sub_edge(uint16_t *histograms, |
| uint16_t *image, uint16_t *mask, |
| int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| const uint16_t num_diffs) { |
| long int index = (i - pad_size - 1) * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| decrement_range(&histograms[val * width], AOMMAX(j - pad_size, 0), |
| AOMMIN(j + pad_size + 1, width)); |
| } |
| } |
| |
| static inline void cambi_histogram_sub(uint16_t *histograms, const uint16_t *image, |
| const uint16_t *mask, int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| uint16_t num_diffs) { |
| long int index = (i - pad_size - 1) * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| decrement_range(&histograms[val * width], j - pad_size, j + pad_size + 1); |
| } |
| } |
| |
| static inline void cambi_histogram_add_edge(uint16_t *histograms, uint16_t *image, |
| uint16_t *mask, int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| const uint16_t num_diffs) { |
| long int index = (i + pad_size) * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], AOMMAX(j - pad_size, 0), |
| AOMMIN(j + pad_size + 1, width)); |
| } |
| } |
| |
| static inline void cambi_histogram_add(uint16_t *histograms, const uint16_t *image, |
| const uint16_t *mask, int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| uint16_t num_diffs) { |
| long int index = (i + pad_size) * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], j - pad_size, j + pad_size + 1); |
| } |
| } |
| |
| static inline void cambi_histogram_add_edge_first_pass(uint16_t *histograms, |
| uint16_t *image, uint16_t *mask, |
| int i, int j, int width, |
| ptrdiff_t stride, |
| uint16_t pad_size, |
| const uint16_t num_diffs) { |
| long int index = i * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], AOMMAX(j - pad_size, 0), |
| AOMMIN(j + pad_size + 1, width)); |
| } |
| } |
| |
| static inline void cambi_histogram_add_first_pass(uint16_t *histograms, |
| uint16_t *image, uint16_t *mask, |
| int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| const uint16_t num_diffs) { |
| long int index = i * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], j - pad_size, j + pad_size + 1); |
| } |
| } |
| |
| static float c_value_pixel(const uint16_t *histograms, uint16_t value, |
| const int *diff_weights, uint16_t num_diffs, |
| const uint16_t *tvi_thresholds, |
| int histogram_col, int histogram_width) { |
| uint16_t p_0 = histograms[value * histogram_width + histogram_col]; |
| float val; |
| float c_value = (float) 0.0; |
| for (uint16_t d = 0; d < num_diffs; d++) { |
| if (value <= tvi_thresholds[d]) { |
| uint16_t p_1 = histograms[(value + d + 1) |
| * histogram_width + histogram_col]; |
| uint16_t p_2 = histograms[(value - d - 1) |
| * histogram_width + histogram_col]; |
| if (p_1 > p_2) { |
| val = (float)(diff_weights[d] * p_0 * p_1) / (float)(p_1 + p_0); |
| } else { |
| val = (float)(diff_weights[d] * p_0 * p_2) / (float)(p_2 + p_0); |
| } |
| |
| if (val > c_value) { |
| c_value = val; |
| } |
| } |
| } |
| return c_value; |
| } |
| |
| static inline void calculate_c_values_row(float *c_values, uint16_t *histograms, |
| uint16_t *image, const uint16_t *mask, |
| int row, int width, ptrdiff_t stride, |
| const uint16_t num_diffs, |
| const uint16_t *tvi_for_diff, |
| DebandInfo *dbi) { |
| for (int col = 0; col < width; col++) { |
| if (mask[row * stride + col]) { |
| c_values[row * width + col] = c_value_pixel( |
| histograms, image[row * stride + col] + num_diffs, |
| dbi->diffs_weights, num_diffs, tvi_for_diff, col, width); |
| } |
| } |
| } |
| |
| static void calculate_c_values(DebandInfo *dbi, int width, int height) { |
| uint16_t *image = dbi->frame; |
| uint16_t *mask = dbi->mask; |
| ptrdiff_t stride = dbi->stride; |
| float *c_values = dbi->buffers.c_values; |
| uint16_t *histograms = dbi->buffers.c_values_histograms; |
| uint16_t window_size = dbi->window_size; |
| uint16_t num_diffs = dbi->num_diffs; |
| uint16_t *tvi_for_diff = dbi->tvi_for_diff; |
| uint16_t pad_size = window_size >> 1; |
| const uint16_t num_bins = 1024 + 2*num_diffs; |
| |
| memset(c_values, 0.0, sizeof(float) * width * height); |
| |
| // Use a histogram for each pixel in width |
| // histograms[i * width + j] accesses the j'th histogram, i'th value |
| // This is done for cache optimization reasons |
| memset(histograms, 0, width * num_bins * sizeof(uint16_t)); |
| |
| // First pass: first pad_size rows |
| for (int i = 0; i < pad_size; i++) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_add_edge_first_pass(histograms, image, mask, i, j, |
| width, stride, pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_add_first_pass(histograms, image, mask, i, j, width, |
| stride, pad_size, num_diffs); |
| } |
| for (int j = AOMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_add_edge_first_pass(histograms, image, mask, i, j, |
| width, stride, pad_size, num_diffs); |
| } |
| } |
| |
| // Iterate over all rows, unrolled into 3 loops to avoid conditions |
| for (int i = 0; i < pad_size + 1; i++) { |
| if (i + pad_size < height) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_add(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = AOMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| } |
| calculate_c_values_row(c_values, histograms, image, mask, i, width, stride, |
| num_diffs, tvi_for_diff, dbi); |
| } |
| |
| for (int i = pad_size + 1; i < height - pad_size; i++) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_sub(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| cambi_histogram_add(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = AOMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| calculate_c_values_row(c_values, histograms, image, mask, i, width, stride, |
| num_diffs, tvi_for_diff, dbi); |
| } |
| |
| for (int i = height - pad_size; i < height; i++) { |
| if (i - pad_size - 1 >= 0) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_sub(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = AOMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| } |
| calculate_c_values_row(c_values, histograms, image, mask, i, width, stride, |
| num_diffs, tvi_for_diff, dbi); |
| } |
| } |
| |
| static double average_topk_elements(const float *arr, int topk_elements) { |
| double sum = 0; |
| for (int i = 0; i < topk_elements; i++) |
| sum += arr[i]; |
| |
| return (double) sum / topk_elements; |
| } |
| |
| static void quick_select(float *arr, int n, int k) { |
| if (n == k) return; |
| int left = 0; |
| int right = n - 1; |
| while (left < right) { |
| float pivot = arr[k]; |
| int i = left; |
| int j = right; |
| do { |
| while (arr[i] > pivot) { |
| i++; |
| } |
| while (arr[j] < pivot) { |
| j--; |
| } |
| if (i <= j) { |
| SWAP_FLOATS(arr[i], arr[j]); |
| i++; |
| j--; |
| } |
| } while (i <= j); |
| if (j < k) { |
| left = i; |
| } |
| if (k < i) { |
| right = j; |
| } |
| } |
| } |
| |
| static double spatial_pooling(float *c_values, double topk, |
| int width, int height) { |
| int num_elements = height * width; |
| int topk_num_elements = CLAMP((int) (topk * num_elements), 1, num_elements); |
| quick_select(c_values, num_elements, topk_num_elements); |
| return average_topk_elements(c_values, topk_num_elements); |
| } |
| |
| // Inner product weighting scores for each scale |
| static inline double weight_scores_per_scale(const int *scale_weights, |
| const double *scores_per_scale, |
| uint16_t normalization) { |
| double score = 0.0; |
| for (unsigned scale = 0; scale < CAMBI_NUM_SCALES; scale++) { |
| score += scores_per_scale[scale] * scale_weights[scale]; |
| } |
| return score / normalization; |
| } |
| |
| double cambi_score(DebandInfo *dbi, int frame_width, int frame_height) { |
| double scores_per_scale[CAMBI_NUM_SCALES]; |
| int scaled_width = frame_width; |
| int scaled_height = frame_height; |
| |
| for (unsigned scale = 0; scale < CAMBI_NUM_SCALES; scale++) { |
| if (scale > 0) { |
| scaled_width = (scaled_width + 1) >> 1; |
| scaled_height = (scaled_height + 1) >> 1; |
| cambi_decimate(dbi->frame, dbi->stride, scaled_width, scaled_height); |
| cambi_decimate(dbi->mask, dbi->stride, scaled_width, scaled_height); |
| } |
| else { |
| cambi_get_spatial_mask(dbi, scaled_width, scaled_height); |
| } |
| cambi_filter_mode(dbi, scaled_width, scaled_height); |
| calculate_c_values(dbi, scaled_width, scaled_height); |
| scores_per_scale[scale] = spatial_pooling(dbi->buffers.c_values, dbi->topk, |
| scaled_width, scaled_height); |
| } |
| |
| return weight_scores_per_scale(dbi->scale_weights, scores_per_scale, |
| dbi->pixels_in_window); |
| } |
| |
| double avm_compute_cambi(const YV12_BUFFER_CONFIG *frame, DebandInfo *dbi, MACROBLOCKD *xd) { |
| av1_setup_dst_planes(xd->plane, frame, 0, 0, 0, 1, NULL); |
| struct buf_2d pre_buf = xd->plane[0].dst; |
| int src_stride = xd->plane[0].dst.stride; |
| int frame_width = pre_buf.width; |
| int frame_height = pre_buf.height; |
| int bit_depth = xd->bd; |
| uint16_t *src16 = pre_buf.buf; |
| |
| set_cambi_window(dbi); |
| cambi_preprocessing(src16, src_stride, frame_width, frame_height, bit_depth, |
| dbi->frame, dbi->stride, frame_width, frame_height); |
| return cambi_score(dbi, frame_width, frame_height); |
| } |
| |
| /*!\brief Assess banding via CAMBI |
| * |
| * \ingroup in_loop_cdef |
| * |
| * Searches for presence of banding computing CAMBI on refernce and distorted |
| * |
| * \param[in] frame Compressed frame buffer |
| * \param[in] ref Source frame buffer |
| * \param[in,out] cm Pointer to top level common structure |
| * \param[in] xd Pointer to common current coding block structure |
| * |
| * \return Nothing is returned. Instead, presence of banding is storedoptimal CDEF parameters are stored |
| * in the \c dbi structure of type \ref DebandInfo inside \c cm: |
| * \arg \c deband_enable: enabled when banding detected, disabled otherwise |
| * |
| */ |
| void avm_deband_search(const YV12_BUFFER_CONFIG *frame, |
| const YV12_BUFFER_CONFIG *ref, AV1_COMMON *cm, |
| MACROBLOCKD *xd) { |
| DebandInfo *const dbi = &cm->deband_info; |
| double cambi_ref = avm_compute_cambi(ref, dbi, xd); |
| double cambi_enc = avm_compute_cambi(frame, dbi, xd); |
| |
| int bit_depth = xd->bd; |
| double diff_threshold = bit_depth==8 ? CAMBI_DIFF_THRESHOLD_8b |
| : CAMBI_DIFF_THRESHOLD_10b; |
| |
| dbi->deband_enable = (cambi_enc - cambi_ref >= diff_threshold); |
| |
| // double src_threshold = bit_depth==8 ? CAMBI_SOURCE_THRESHOLD_8b |
| // : CAMBI_SOURCE_THRESHOLD_10b; |
| // dbi->deband_enable &= (cambi_ref < src_threshold); |
| |
| printf("CAMBI ref: %f CAMBI enc: %f, enable: %d\n", |
| cambi_ref, cambi_enc, dbi->deband_enable); |
| } |