Change to init/reset level of the denoiser from kDenLow to kDenMedium, and the init noise level to kLow. This affects the denoiser level during the initialization stage of the noise estimation. Improves denoising for noisy content during init stage of noise estimation, with little effect for low noise/clean content. Change-Id: I247a17b0f01f646fc2e91a4a070ad69bdb788cae
840 lines
30 KiB
C
840 lines
30 KiB
C
/*
|
|
* Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#include "./vpx_dsp_rtcd.h"
|
|
#include "vpx_dsp/vpx_dsp_common.h"
|
|
#include "vpx_scale/yv12config.h"
|
|
#include "vpx/vpx_integer.h"
|
|
#include "vp9/common/vp9_reconinter.h"
|
|
#include "vp9/encoder/vp9_context_tree.h"
|
|
#include "vp9/encoder/vp9_denoiser.h"
|
|
#include "vp9/encoder/vp9_encoder.h"
|
|
|
|
#ifdef OUTPUT_YUV_DENOISED
|
|
static void make_grayscale(YV12_BUFFER_CONFIG *yuv);
|
|
#endif
|
|
|
|
static int absdiff_thresh(BLOCK_SIZE bs, int increase_denoising) {
|
|
(void)bs;
|
|
return 3 + (increase_denoising ? 1 : 0);
|
|
}
|
|
|
|
static int delta_thresh(BLOCK_SIZE bs, int increase_denoising) {
|
|
(void)bs;
|
|
(void)increase_denoising;
|
|
return 4;
|
|
}
|
|
|
|
static int noise_motion_thresh(BLOCK_SIZE bs, int increase_denoising) {
|
|
(void)bs;
|
|
(void)increase_denoising;
|
|
return 625;
|
|
}
|
|
|
|
static unsigned int sse_thresh(BLOCK_SIZE bs, int increase_denoising) {
|
|
return (1 << num_pels_log2_lookup[bs]) * (increase_denoising ? 80 : 40);
|
|
}
|
|
|
|
static int sse_diff_thresh(BLOCK_SIZE bs, int increase_denoising,
|
|
int motion_magnitude) {
|
|
if (motion_magnitude > noise_motion_thresh(bs, increase_denoising)) {
|
|
if (increase_denoising)
|
|
return (1 << num_pels_log2_lookup[bs]) << 2;
|
|
else
|
|
return 0;
|
|
} else {
|
|
return (1 << num_pels_log2_lookup[bs]) << 4;
|
|
}
|
|
}
|
|
|
|
static int total_adj_weak_thresh(BLOCK_SIZE bs, int increase_denoising) {
|
|
return (1 << num_pels_log2_lookup[bs]) * (increase_denoising ? 3 : 2);
|
|
}
|
|
|
|
// TODO(jackychen): If increase_denoising is enabled in the future,
|
|
// we might need to update the code for calculating 'total_adj' in
|
|
// case the C code is not bit-exact with corresponding sse2 code.
|
|
int vp9_denoiser_filter_c(const uint8_t *sig, int sig_stride,
|
|
const uint8_t *mc_avg, int mc_avg_stride,
|
|
uint8_t *avg, int avg_stride, int increase_denoising,
|
|
BLOCK_SIZE bs, int motion_magnitude) {
|
|
int r, c;
|
|
const uint8_t *sig_start = sig;
|
|
const uint8_t *mc_avg_start = mc_avg;
|
|
uint8_t *avg_start = avg;
|
|
int diff, adj, absdiff, delta;
|
|
int adj_val[] = { 3, 4, 6 };
|
|
int total_adj = 0;
|
|
int shift_inc = 1;
|
|
|
|
// If motion_magnitude is small, making the denoiser more aggressive by
|
|
// increasing the adjustment for each level. Add another increment for
|
|
// blocks that are labeled for increase denoising.
|
|
if (motion_magnitude <= MOTION_MAGNITUDE_THRESHOLD) {
|
|
if (increase_denoising) {
|
|
shift_inc = 2;
|
|
}
|
|
adj_val[0] += shift_inc;
|
|
adj_val[1] += shift_inc;
|
|
adj_val[2] += shift_inc;
|
|
}
|
|
|
|
// First attempt to apply a strong temporal denoising filter.
|
|
for (r = 0; r < (4 << b_height_log2_lookup[bs]); ++r) {
|
|
for (c = 0; c < (4 << b_width_log2_lookup[bs]); ++c) {
|
|
diff = mc_avg[c] - sig[c];
|
|
absdiff = abs(diff);
|
|
|
|
if (absdiff <= absdiff_thresh(bs, increase_denoising)) {
|
|
avg[c] = mc_avg[c];
|
|
total_adj += diff;
|
|
} else {
|
|
switch (absdiff) {
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7: adj = adj_val[0]; break;
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 13:
|
|
case 14:
|
|
case 15: adj = adj_val[1]; break;
|
|
default: adj = adj_val[2];
|
|
}
|
|
if (diff > 0) {
|
|
avg[c] = VPXMIN(UINT8_MAX, sig[c] + adj);
|
|
total_adj += adj;
|
|
} else {
|
|
avg[c] = VPXMAX(0, sig[c] - adj);
|
|
total_adj -= adj;
|
|
}
|
|
}
|
|
}
|
|
sig += sig_stride;
|
|
avg += avg_stride;
|
|
mc_avg += mc_avg_stride;
|
|
}
|
|
|
|
// If the strong filter did not modify the signal too much, we're all set.
|
|
if (abs(total_adj) <= total_adj_strong_thresh(bs, increase_denoising)) {
|
|
return FILTER_BLOCK;
|
|
}
|
|
|
|
// Otherwise, we try to dampen the filter if the delta is not too high.
|
|
delta = ((abs(total_adj) - total_adj_strong_thresh(bs, increase_denoising)) >>
|
|
num_pels_log2_lookup[bs]) +
|
|
1;
|
|
|
|
if (delta >= delta_thresh(bs, increase_denoising)) {
|
|
return COPY_BLOCK;
|
|
}
|
|
|
|
mc_avg = mc_avg_start;
|
|
avg = avg_start;
|
|
sig = sig_start;
|
|
for (r = 0; r < (4 << b_height_log2_lookup[bs]); ++r) {
|
|
for (c = 0; c < (4 << b_width_log2_lookup[bs]); ++c) {
|
|
diff = mc_avg[c] - sig[c];
|
|
adj = abs(diff);
|
|
if (adj > delta) {
|
|
adj = delta;
|
|
}
|
|
if (diff > 0) {
|
|
// Diff positive means we made positive adjustment above
|
|
// (in first try/attempt), so now make negative adjustment to bring
|
|
// denoised signal down.
|
|
avg[c] = VPXMAX(0, avg[c] - adj);
|
|
total_adj -= adj;
|
|
} else {
|
|
// Diff negative means we made negative adjustment above
|
|
// (in first try/attempt), so now make positive adjustment to bring
|
|
// denoised signal up.
|
|
avg[c] = VPXMIN(UINT8_MAX, avg[c] + adj);
|
|
total_adj += adj;
|
|
}
|
|
}
|
|
sig += sig_stride;
|
|
avg += avg_stride;
|
|
mc_avg += mc_avg_stride;
|
|
}
|
|
|
|
// We can use the filter if it has been sufficiently dampened
|
|
if (abs(total_adj) <= total_adj_weak_thresh(bs, increase_denoising)) {
|
|
return FILTER_BLOCK;
|
|
}
|
|
return COPY_BLOCK;
|
|
}
|
|
|
|
static uint8_t *block_start(uint8_t *framebuf, int stride, int mi_row,
|
|
int mi_col) {
|
|
return framebuf + (stride * mi_row << 3) + (mi_col << 3);
|
|
}
|
|
|
|
static VP9_DENOISER_DECISION perform_motion_compensation(
|
|
VP9_COMMON *const cm, VP9_DENOISER *denoiser, MACROBLOCK *mb, BLOCK_SIZE bs,
|
|
int increase_denoising, int mi_row, int mi_col, PICK_MODE_CONTEXT *ctx,
|
|
int motion_magnitude, int is_skin, int *zeromv_filter, int consec_zeromv,
|
|
int num_spatial_layers, int width, int lst_fb_idx, int gld_fb_idx,
|
|
int use_svc, int spatial_layer, int use_gf_temporal_ref) {
|
|
const int sse_diff = (ctx->newmv_sse == UINT_MAX)
|
|
? 0
|
|
: ((int)ctx->zeromv_sse - (int)ctx->newmv_sse);
|
|
int frame;
|
|
int denoise_layer_idx = 0;
|
|
MACROBLOCKD *filter_mbd = &mb->e_mbd;
|
|
MODE_INFO *mi = filter_mbd->mi[0];
|
|
MODE_INFO saved_mi;
|
|
int i;
|
|
struct buf_2d saved_dst[MAX_MB_PLANE];
|
|
struct buf_2d saved_pre[MAX_MB_PLANE];
|
|
const RefBuffer *saved_block_refs[2];
|
|
MV_REFERENCE_FRAME saved_frame;
|
|
|
|
frame = ctx->best_reference_frame;
|
|
|
|
saved_mi = *mi;
|
|
|
|
if (is_skin && (motion_magnitude > 0 || consec_zeromv < 4)) return COPY_BLOCK;
|
|
|
|
// Avoid denoising small blocks. When noise > kDenLow or frame width > 480,
|
|
// denoise 16x16 blocks.
|
|
if (bs == BLOCK_8X8 || bs == BLOCK_8X16 || bs == BLOCK_16X8 ||
|
|
(bs == BLOCK_16X16 && width > 480 &&
|
|
denoiser->denoising_level <= kDenLow))
|
|
return COPY_BLOCK;
|
|
|
|
// If the best reference frame uses inter-prediction and there is enough of a
|
|
// difference in sum-squared-error, use it.
|
|
if (frame != INTRA_FRAME && frame != ALTREF_FRAME &&
|
|
(frame != GOLDEN_FRAME || num_spatial_layers == 1 ||
|
|
use_gf_temporal_ref) &&
|
|
sse_diff > sse_diff_thresh(bs, increase_denoising, motion_magnitude)) {
|
|
mi->ref_frame[0] = ctx->best_reference_frame;
|
|
mi->mode = ctx->best_sse_inter_mode;
|
|
mi->mv[0] = ctx->best_sse_mv;
|
|
} else {
|
|
// Otherwise, use the zero reference frame.
|
|
frame = ctx->best_zeromv_reference_frame;
|
|
ctx->newmv_sse = ctx->zeromv_sse;
|
|
// Bias to last reference.
|
|
if ((num_spatial_layers > 1 && !use_gf_temporal_ref) ||
|
|
frame == ALTREF_FRAME ||
|
|
(frame != LAST_FRAME &&
|
|
((ctx->zeromv_lastref_sse<(5 * ctx->zeromv_sse)>> 2) ||
|
|
denoiser->denoising_level >= kDenHigh))) {
|
|
frame = LAST_FRAME;
|
|
ctx->newmv_sse = ctx->zeromv_lastref_sse;
|
|
}
|
|
mi->ref_frame[0] = frame;
|
|
mi->mode = ZEROMV;
|
|
mi->mv[0].as_int = 0;
|
|
ctx->best_sse_inter_mode = ZEROMV;
|
|
ctx->best_sse_mv.as_int = 0;
|
|
*zeromv_filter = 1;
|
|
if (denoiser->denoising_level > kDenMedium) {
|
|
motion_magnitude = 0;
|
|
}
|
|
}
|
|
|
|
saved_frame = frame;
|
|
// When using SVC, we need to map REF_FRAME to the frame buffer index.
|
|
if (use_svc) {
|
|
if (frame == LAST_FRAME)
|
|
frame = lst_fb_idx + 1;
|
|
else if (frame == GOLDEN_FRAME)
|
|
frame = gld_fb_idx + 1;
|
|
// Shift for the second spatial layer.
|
|
if (num_spatial_layers - spatial_layer == 2)
|
|
frame = frame + denoiser->num_ref_frames;
|
|
denoise_layer_idx = num_spatial_layers - spatial_layer - 1;
|
|
}
|
|
|
|
// Force copy (no denoise, copy source in denoised buffer) if
|
|
// running_avg_y[frame] is NULL.
|
|
if (denoiser->running_avg_y[frame].buffer_alloc == NULL) {
|
|
// Restore everything to its original state
|
|
*mi = saved_mi;
|
|
return COPY_BLOCK;
|
|
}
|
|
|
|
if (ctx->newmv_sse > sse_thresh(bs, increase_denoising)) {
|
|
// Restore everything to its original state
|
|
*mi = saved_mi;
|
|
return COPY_BLOCK;
|
|
}
|
|
if (motion_magnitude > (noise_motion_thresh(bs, increase_denoising) << 3)) {
|
|
// Restore everything to its original state
|
|
*mi = saved_mi;
|
|
return COPY_BLOCK;
|
|
}
|
|
|
|
// We will restore these after motion compensation.
|
|
for (i = 0; i < MAX_MB_PLANE; ++i) {
|
|
saved_pre[i] = filter_mbd->plane[i].pre[0];
|
|
saved_dst[i] = filter_mbd->plane[i].dst;
|
|
}
|
|
saved_block_refs[0] = filter_mbd->block_refs[0];
|
|
|
|
// Set the pointers in the MACROBLOCKD to point to the buffers in the denoiser
|
|
// struct.
|
|
filter_mbd->plane[0].pre[0].buf =
|
|
block_start(denoiser->running_avg_y[frame].y_buffer,
|
|
denoiser->running_avg_y[frame].y_stride, mi_row, mi_col);
|
|
filter_mbd->plane[0].pre[0].stride = denoiser->running_avg_y[frame].y_stride;
|
|
filter_mbd->plane[1].pre[0].buf =
|
|
block_start(denoiser->running_avg_y[frame].u_buffer,
|
|
denoiser->running_avg_y[frame].uv_stride, mi_row, mi_col);
|
|
filter_mbd->plane[1].pre[0].stride = denoiser->running_avg_y[frame].uv_stride;
|
|
filter_mbd->plane[2].pre[0].buf =
|
|
block_start(denoiser->running_avg_y[frame].v_buffer,
|
|
denoiser->running_avg_y[frame].uv_stride, mi_row, mi_col);
|
|
filter_mbd->plane[2].pre[0].stride = denoiser->running_avg_y[frame].uv_stride;
|
|
|
|
filter_mbd->plane[0].dst.buf = block_start(
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].y_buffer,
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].y_stride, mi_row, mi_col);
|
|
filter_mbd->plane[0].dst.stride =
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].y_stride;
|
|
filter_mbd->plane[1].dst.buf = block_start(
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].u_buffer,
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].uv_stride, mi_row, mi_col);
|
|
filter_mbd->plane[1].dst.stride =
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].uv_stride;
|
|
filter_mbd->plane[2].dst.buf = block_start(
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].v_buffer,
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].uv_stride, mi_row, mi_col);
|
|
filter_mbd->plane[2].dst.stride =
|
|
denoiser->mc_running_avg_y[denoise_layer_idx].uv_stride;
|
|
|
|
set_ref_ptrs(cm, filter_mbd, saved_frame, NONE);
|
|
vp9_build_inter_predictors_sby(filter_mbd, mi_row, mi_col, bs);
|
|
|
|
// Restore everything to its original state
|
|
*mi = saved_mi;
|
|
filter_mbd->block_refs[0] = saved_block_refs[0];
|
|
for (i = 0; i < MAX_MB_PLANE; ++i) {
|
|
filter_mbd->plane[i].pre[0] = saved_pre[i];
|
|
filter_mbd->plane[i].dst = saved_dst[i];
|
|
}
|
|
|
|
return FILTER_BLOCK;
|
|
}
|
|
|
|
void vp9_denoiser_denoise(VP9_COMP *cpi, MACROBLOCK *mb, int mi_row, int mi_col,
|
|
BLOCK_SIZE bs, PICK_MODE_CONTEXT *ctx,
|
|
VP9_DENOISER_DECISION *denoiser_decision,
|
|
int use_gf_temporal_ref) {
|
|
int mv_col, mv_row;
|
|
int motion_magnitude = 0;
|
|
int zeromv_filter = 0;
|
|
VP9_DENOISER *denoiser = &cpi->denoiser;
|
|
VP9_DENOISER_DECISION decision = COPY_BLOCK;
|
|
|
|
const int shift =
|
|
cpi->svc.number_spatial_layers - cpi->svc.spatial_layer_id == 2
|
|
? denoiser->num_ref_frames
|
|
: 0;
|
|
YV12_BUFFER_CONFIG avg = denoiser->running_avg_y[INTRA_FRAME + shift];
|
|
const int denoise_layer_index =
|
|
cpi->svc.number_spatial_layers - cpi->svc.spatial_layer_id - 1;
|
|
YV12_BUFFER_CONFIG mc_avg = denoiser->mc_running_avg_y[denoise_layer_index];
|
|
uint8_t *avg_start = block_start(avg.y_buffer, avg.y_stride, mi_row, mi_col);
|
|
|
|
uint8_t *mc_avg_start =
|
|
block_start(mc_avg.y_buffer, mc_avg.y_stride, mi_row, mi_col);
|
|
struct buf_2d src = mb->plane[0].src;
|
|
int is_skin = 0;
|
|
int increase_denoising = 0;
|
|
int consec_zeromv = 0;
|
|
int last_is_reference = cpi->ref_frame_flags & VP9_LAST_FLAG;
|
|
mv_col = ctx->best_sse_mv.as_mv.col;
|
|
mv_row = ctx->best_sse_mv.as_mv.row;
|
|
motion_magnitude = mv_row * mv_row + mv_col * mv_col;
|
|
|
|
if (cpi->use_skin_detection && bs <= BLOCK_32X32 &&
|
|
denoiser->denoising_level < kDenHigh) {
|
|
int motion_level = (motion_magnitude < 16) ? 0 : 1;
|
|
// If motion for current block is small/zero, compute consec_zeromv for
|
|
// skin detection (early exit in skin detection is done for large
|
|
// consec_zeromv when current block has small/zero motion).
|
|
consec_zeromv = 0;
|
|
if (motion_level == 0) {
|
|
VP9_COMMON *const cm = &cpi->common;
|
|
int j, i;
|
|
// Loop through the 8x8 sub-blocks.
|
|
const int bw = num_8x8_blocks_wide_lookup[bs];
|
|
const int bh = num_8x8_blocks_high_lookup[bs];
|
|
const int xmis = VPXMIN(cm->mi_cols - mi_col, bw);
|
|
const int ymis = VPXMIN(cm->mi_rows - mi_row, bh);
|
|
const int block_index = mi_row * cm->mi_cols + mi_col;
|
|
consec_zeromv = 100;
|
|
for (i = 0; i < ymis; i++) {
|
|
for (j = 0; j < xmis; j++) {
|
|
int bl_index = block_index + i * cm->mi_cols + j;
|
|
consec_zeromv = VPXMIN(cpi->consec_zero_mv[bl_index], consec_zeromv);
|
|
// No need to keep checking 8x8 blocks if any of the sub-blocks
|
|
// has small consec_zeromv (since threshold for no_skin based on
|
|
// zero/small motion in skin detection is high, i.e, > 4).
|
|
if (consec_zeromv < 4) {
|
|
i = ymis;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// TODO(marpan): Compute skin detection over sub-blocks.
|
|
is_skin = vp9_compute_skin_block(
|
|
mb->plane[0].src.buf, mb->plane[1].src.buf, mb->plane[2].src.buf,
|
|
mb->plane[0].src.stride, mb->plane[1].src.stride, bs, consec_zeromv,
|
|
motion_level);
|
|
}
|
|
if (!is_skin && denoiser->denoising_level == kDenHigh) increase_denoising = 1;
|
|
|
|
// Copy block if LAST_FRAME is not a reference.
|
|
// Last doesn't always exist when SVC layers are dynamically changed, e.g. top
|
|
// spatial layer doesn't have last reference when it's brought up for the
|
|
// first time on the fly.
|
|
if (last_is_reference && denoiser->denoising_level >= kDenLow &&
|
|
!ctx->sb_skip_denoising)
|
|
decision = perform_motion_compensation(
|
|
&cpi->common, denoiser, mb, bs, increase_denoising, mi_row, mi_col, ctx,
|
|
motion_magnitude, is_skin, &zeromv_filter, consec_zeromv,
|
|
cpi->svc.number_spatial_layers, cpi->Source->y_width, cpi->lst_fb_idx,
|
|
cpi->gld_fb_idx, cpi->use_svc, cpi->svc.spatial_layer_id,
|
|
use_gf_temporal_ref);
|
|
|
|
if (decision == FILTER_BLOCK) {
|
|
decision = vp9_denoiser_filter(src.buf, src.stride, mc_avg_start,
|
|
mc_avg.y_stride, avg_start, avg.y_stride,
|
|
increase_denoising, bs, motion_magnitude);
|
|
}
|
|
|
|
if (decision == FILTER_BLOCK) {
|
|
vpx_convolve_copy(avg_start, avg.y_stride, src.buf, src.stride, NULL, 0, 0,
|
|
0, 0, num_4x4_blocks_wide_lookup[bs] << 2,
|
|
num_4x4_blocks_high_lookup[bs] << 2);
|
|
} else { // COPY_BLOCK
|
|
vpx_convolve_copy(src.buf, src.stride, avg_start, avg.y_stride, NULL, 0, 0,
|
|
0, 0, num_4x4_blocks_wide_lookup[bs] << 2,
|
|
num_4x4_blocks_high_lookup[bs] << 2);
|
|
}
|
|
*denoiser_decision = decision;
|
|
if (decision == FILTER_BLOCK && zeromv_filter == 1)
|
|
*denoiser_decision = FILTER_ZEROMV_BLOCK;
|
|
}
|
|
|
|
static void copy_frame(YV12_BUFFER_CONFIG *const dest,
|
|
const YV12_BUFFER_CONFIG *const src) {
|
|
int r;
|
|
const uint8_t *srcbuf = src->y_buffer;
|
|
uint8_t *destbuf = dest->y_buffer;
|
|
|
|
assert(dest->y_width == src->y_width);
|
|
assert(dest->y_height == src->y_height);
|
|
|
|
for (r = 0; r < dest->y_height; ++r) {
|
|
memcpy(destbuf, srcbuf, dest->y_width);
|
|
destbuf += dest->y_stride;
|
|
srcbuf += src->y_stride;
|
|
}
|
|
}
|
|
|
|
static void swap_frame_buffer(YV12_BUFFER_CONFIG *const dest,
|
|
YV12_BUFFER_CONFIG *const src) {
|
|
uint8_t *tmp_buf = dest->y_buffer;
|
|
assert(dest->y_width == src->y_width);
|
|
assert(dest->y_height == src->y_height);
|
|
dest->y_buffer = src->y_buffer;
|
|
src->y_buffer = tmp_buf;
|
|
}
|
|
|
|
void vp9_denoiser_update_frame_info(
|
|
VP9_DENOISER *denoiser, YV12_BUFFER_CONFIG src, struct SVC *svc,
|
|
FRAME_TYPE frame_type, int refresh_alt_ref_frame, int refresh_golden_frame,
|
|
int refresh_last_frame, int alt_fb_idx, int gld_fb_idx, int lst_fb_idx,
|
|
int resized, int svc_refresh_denoiser_buffers, int second_spatial_layer) {
|
|
const int shift = second_spatial_layer ? denoiser->num_ref_frames : 0;
|
|
// Copy source into denoised reference buffers on KEY_FRAME or
|
|
// if the just encoded frame was resized. For SVC, copy source if the base
|
|
// spatial layer was key frame.
|
|
if (frame_type == KEY_FRAME || resized != 0 || denoiser->reset ||
|
|
svc_refresh_denoiser_buffers) {
|
|
int i;
|
|
// Start at 1 so as not to overwrite the INTRA_FRAME
|
|
for (i = 1; i < denoiser->num_ref_frames; ++i) {
|
|
if (denoiser->running_avg_y[i + shift].buffer_alloc != NULL)
|
|
copy_frame(&denoiser->running_avg_y[i + shift], &src);
|
|
}
|
|
denoiser->reset = 0;
|
|
return;
|
|
}
|
|
|
|
if (svc->temporal_layering_mode == VP9E_TEMPORAL_LAYERING_MODE_BYPASS &&
|
|
svc->use_set_ref_frame_config) {
|
|
int i;
|
|
for (i = 0; i < REF_FRAMES; i++) {
|
|
if (svc->update_buffer_slot[svc->spatial_layer_id] & (1 << i))
|
|
copy_frame(&denoiser->running_avg_y[i + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
} else {
|
|
// If more than one refresh occurs, must copy frame buffer.
|
|
if ((refresh_alt_ref_frame + refresh_golden_frame + refresh_last_frame) >
|
|
1) {
|
|
if (refresh_alt_ref_frame) {
|
|
copy_frame(&denoiser->running_avg_y[alt_fb_idx + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
if (refresh_golden_frame) {
|
|
copy_frame(&denoiser->running_avg_y[gld_fb_idx + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
if (refresh_last_frame) {
|
|
copy_frame(&denoiser->running_avg_y[lst_fb_idx + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
} else {
|
|
if (refresh_alt_ref_frame) {
|
|
swap_frame_buffer(&denoiser->running_avg_y[alt_fb_idx + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
if (refresh_golden_frame) {
|
|
swap_frame_buffer(&denoiser->running_avg_y[gld_fb_idx + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
if (refresh_last_frame) {
|
|
swap_frame_buffer(&denoiser->running_avg_y[lst_fb_idx + 1 + shift],
|
|
&denoiser->running_avg_y[INTRA_FRAME + shift]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void vp9_denoiser_reset_frame_stats(PICK_MODE_CONTEXT *ctx) {
|
|
ctx->zeromv_sse = UINT_MAX;
|
|
ctx->newmv_sse = UINT_MAX;
|
|
ctx->zeromv_lastref_sse = UINT_MAX;
|
|
ctx->best_sse_mv.as_int = 0;
|
|
}
|
|
|
|
void vp9_denoiser_update_frame_stats(MODE_INFO *mi, unsigned int sse,
|
|
PREDICTION_MODE mode,
|
|
PICK_MODE_CONTEXT *ctx) {
|
|
if (mi->mv[0].as_int == 0 && sse < ctx->zeromv_sse) {
|
|
ctx->zeromv_sse = sse;
|
|
ctx->best_zeromv_reference_frame = mi->ref_frame[0];
|
|
if (mi->ref_frame[0] == LAST_FRAME) ctx->zeromv_lastref_sse = sse;
|
|
}
|
|
|
|
if (mi->mv[0].as_int != 0 && sse < ctx->newmv_sse) {
|
|
ctx->newmv_sse = sse;
|
|
ctx->best_sse_inter_mode = mode;
|
|
ctx->best_sse_mv = mi->mv[0];
|
|
ctx->best_reference_frame = mi->ref_frame[0];
|
|
}
|
|
}
|
|
|
|
static int vp9_denoiser_realloc_svc_helper(VP9_COMMON *cm,
|
|
VP9_DENOISER *denoiser, int fb_idx) {
|
|
int fail = 0;
|
|
if (denoiser->running_avg_y[fb_idx].buffer_alloc == NULL) {
|
|
fail =
|
|
vpx_alloc_frame_buffer(&denoiser->running_avg_y[fb_idx], cm->width,
|
|
cm->height, cm->subsampling_x, cm->subsampling_y,
|
|
#if CONFIG_VP9_HIGHBITDEPTH
|
|
cm->use_highbitdepth,
|
|
#endif
|
|
VP9_ENC_BORDER_IN_PIXELS, 0);
|
|
if (fail) {
|
|
vp9_denoiser_free(denoiser);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int vp9_denoiser_realloc_svc(VP9_COMMON *cm, VP9_DENOISER *denoiser,
|
|
struct SVC *svc, int svc_buf_shift,
|
|
int refresh_alt, int refresh_gld, int refresh_lst,
|
|
int alt_fb_idx, int gld_fb_idx, int lst_fb_idx) {
|
|
int fail = 0;
|
|
if (svc->temporal_layering_mode == VP9E_TEMPORAL_LAYERING_MODE_BYPASS &&
|
|
svc->use_set_ref_frame_config) {
|
|
int i;
|
|
for (i = 0; i < REF_FRAMES; i++) {
|
|
if (cm->frame_type == KEY_FRAME ||
|
|
svc->update_buffer_slot[svc->spatial_layer_id] & (1 << i)) {
|
|
fail = vp9_denoiser_realloc_svc_helper(cm, denoiser,
|
|
i + 1 + svc_buf_shift);
|
|
}
|
|
}
|
|
} else {
|
|
if (refresh_alt) {
|
|
// Increase the frame buffer index by 1 to map it to the buffer index in
|
|
// the denoiser.
|
|
fail = vp9_denoiser_realloc_svc_helper(cm, denoiser,
|
|
alt_fb_idx + 1 + svc_buf_shift);
|
|
if (fail) return 1;
|
|
}
|
|
if (refresh_gld) {
|
|
fail = vp9_denoiser_realloc_svc_helper(cm, denoiser,
|
|
gld_fb_idx + 1 + svc_buf_shift);
|
|
if (fail) return 1;
|
|
}
|
|
if (refresh_lst) {
|
|
fail = vp9_denoiser_realloc_svc_helper(cm, denoiser,
|
|
lst_fb_idx + 1 + svc_buf_shift);
|
|
if (fail) return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int vp9_denoiser_alloc(VP9_COMMON *cm, struct SVC *svc, VP9_DENOISER *denoiser,
|
|
int use_svc, int noise_sen, int width, int height,
|
|
int ssx, int ssy,
|
|
#if CONFIG_VP9_HIGHBITDEPTH
|
|
int use_highbitdepth,
|
|
#endif
|
|
int border) {
|
|
int i, layer, fail, init_num_ref_frames;
|
|
const int legacy_byte_alignment = 0;
|
|
int num_layers = 1;
|
|
int scaled_width = width;
|
|
int scaled_height = height;
|
|
if (use_svc) {
|
|
LAYER_CONTEXT *lc = &svc->layer_context[svc->spatial_layer_id *
|
|
svc->number_temporal_layers +
|
|
svc->temporal_layer_id];
|
|
get_layer_resolution(width, height, lc->scaling_factor_num,
|
|
lc->scaling_factor_den, &scaled_width, &scaled_height);
|
|
// For SVC: only denoise at most 2 spatial (highest) layers.
|
|
if (noise_sen >= 2)
|
|
// Denoise from one spatial layer below the top.
|
|
svc->first_layer_denoise = VPXMAX(svc->number_spatial_layers - 2, 0);
|
|
else
|
|
// Only denoise the top spatial layer.
|
|
svc->first_layer_denoise = VPXMAX(svc->number_spatial_layers - 1, 0);
|
|
num_layers = svc->number_spatial_layers - svc->first_layer_denoise;
|
|
}
|
|
assert(denoiser != NULL);
|
|
denoiser->num_ref_frames = use_svc ? SVC_REF_FRAMES : NONSVC_REF_FRAMES;
|
|
init_num_ref_frames = use_svc ? MAX_REF_FRAMES : NONSVC_REF_FRAMES;
|
|
denoiser->num_layers = num_layers;
|
|
CHECK_MEM_ERROR(cm, denoiser->running_avg_y,
|
|
vpx_calloc(denoiser->num_ref_frames * num_layers,
|
|
sizeof(denoiser->running_avg_y[0])));
|
|
CHECK_MEM_ERROR(
|
|
cm, denoiser->mc_running_avg_y,
|
|
vpx_calloc(num_layers, sizeof(denoiser->mc_running_avg_y[0])));
|
|
|
|
for (layer = 0; layer < num_layers; ++layer) {
|
|
const int denoise_width = (layer == 0) ? width : scaled_width;
|
|
const int denoise_height = (layer == 0) ? height : scaled_height;
|
|
for (i = 0; i < init_num_ref_frames; ++i) {
|
|
fail = vpx_alloc_frame_buffer(
|
|
&denoiser->running_avg_y[i + denoiser->num_ref_frames * layer],
|
|
denoise_width, denoise_height, ssx, ssy,
|
|
#if CONFIG_VP9_HIGHBITDEPTH
|
|
use_highbitdepth,
|
|
#endif
|
|
border, legacy_byte_alignment);
|
|
if (fail) {
|
|
vp9_denoiser_free(denoiser);
|
|
return 1;
|
|
}
|
|
#ifdef OUTPUT_YUV_DENOISED
|
|
make_grayscale(&denoiser->running_avg_y[i]);
|
|
#endif
|
|
}
|
|
|
|
fail = vpx_alloc_frame_buffer(&denoiser->mc_running_avg_y[layer],
|
|
denoise_width, denoise_height, ssx, ssy,
|
|
#if CONFIG_VP9_HIGHBITDEPTH
|
|
use_highbitdepth,
|
|
#endif
|
|
border, legacy_byte_alignment);
|
|
if (fail) {
|
|
vp9_denoiser_free(denoiser);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// denoiser->last_source only used for noise_estimation, so only for top
|
|
// layer.
|
|
fail = vpx_alloc_frame_buffer(&denoiser->last_source, width, height, ssx, ssy,
|
|
#if CONFIG_VP9_HIGHBITDEPTH
|
|
use_highbitdepth,
|
|
#endif
|
|
border, legacy_byte_alignment);
|
|
if (fail) {
|
|
vp9_denoiser_free(denoiser);
|
|
return 1;
|
|
}
|
|
#ifdef OUTPUT_YUV_DENOISED
|
|
make_grayscale(&denoiser->running_avg_y[i]);
|
|
#endif
|
|
denoiser->frame_buffer_initialized = 1;
|
|
denoiser->denoising_level = kDenMedium;
|
|
denoiser->prev_denoising_level = kDenMedium;
|
|
denoiser->reset = 0;
|
|
denoiser->current_denoiser_frame = 0;
|
|
return 0;
|
|
}
|
|
|
|
void vp9_denoiser_free(VP9_DENOISER *denoiser) {
|
|
int i;
|
|
if (denoiser == NULL) {
|
|
return;
|
|
}
|
|
denoiser->frame_buffer_initialized = 0;
|
|
for (i = 0; i < denoiser->num_ref_frames * denoiser->num_layers; ++i) {
|
|
vpx_free_frame_buffer(&denoiser->running_avg_y[i]);
|
|
}
|
|
vpx_free(denoiser->running_avg_y);
|
|
denoiser->running_avg_y = NULL;
|
|
|
|
for (i = 0; i < denoiser->num_layers; ++i) {
|
|
vpx_free_frame_buffer(&denoiser->mc_running_avg_y[i]);
|
|
}
|
|
|
|
vpx_free(denoiser->mc_running_avg_y);
|
|
denoiser->mc_running_avg_y = NULL;
|
|
vpx_free_frame_buffer(&denoiser->last_source);
|
|
}
|
|
|
|
static void force_refresh_longterm_ref(VP9_COMP *const cpi) {
|
|
SVC *const svc = &cpi->svc;
|
|
// If long term reference is used, force refresh of that slot, so
|
|
// denoiser buffer for long term reference stays in sync.
|
|
if (svc->use_gf_temporal_ref_current_layer) {
|
|
int index = svc->spatial_layer_id;
|
|
if (svc->number_spatial_layers == 3) index = svc->spatial_layer_id - 1;
|
|
assert(index >= 0);
|
|
cpi->alt_fb_idx = svc->buffer_gf_temporal_ref[index].idx;
|
|
cpi->refresh_alt_ref_frame = 1;
|
|
}
|
|
}
|
|
|
|
void vp9_denoiser_set_noise_level(VP9_COMP *const cpi, int noise_level) {
|
|
VP9_DENOISER *const denoiser = &cpi->denoiser;
|
|
denoiser->denoising_level = noise_level;
|
|
if (denoiser->denoising_level > kDenLowLow &&
|
|
denoiser->prev_denoising_level == kDenLowLow) {
|
|
denoiser->reset = 1;
|
|
force_refresh_longterm_ref(cpi);
|
|
} else {
|
|
denoiser->reset = 0;
|
|
}
|
|
denoiser->prev_denoising_level = denoiser->denoising_level;
|
|
}
|
|
|
|
// Scale/increase the partition threshold
|
|
// for denoiser speed-up.
|
|
int64_t vp9_scale_part_thresh(int64_t threshold, VP9_DENOISER_LEVEL noise_level,
|
|
int content_state, int temporal_layer_id) {
|
|
if ((content_state == kLowSadLowSumdiff) ||
|
|
(content_state == kHighSadLowSumdiff) ||
|
|
(content_state == kLowVarHighSumdiff) || (noise_level == kDenHigh) ||
|
|
(temporal_layer_id != 0)) {
|
|
int64_t scaled_thr =
|
|
(temporal_layer_id < 2) ? (3 * threshold) >> 1 : (7 * threshold) >> 2;
|
|
return scaled_thr;
|
|
} else {
|
|
return (5 * threshold) >> 2;
|
|
}
|
|
}
|
|
|
|
// Scale/increase the ac skip threshold for
|
|
// denoiser speed-up.
|
|
int64_t vp9_scale_acskip_thresh(int64_t threshold,
|
|
VP9_DENOISER_LEVEL noise_level, int abs_sumdiff,
|
|
int temporal_layer_id) {
|
|
if (noise_level >= kDenLow && abs_sumdiff < 5)
|
|
return threshold *=
|
|
(noise_level == kDenLow) ? 2 : (temporal_layer_id == 2) ? 10 : 6;
|
|
else
|
|
return threshold;
|
|
}
|
|
|
|
void vp9_denoiser_reset_on_first_frame(VP9_COMP *const cpi) {
|
|
if (vp9_denoise_svc_non_key(cpi) &&
|
|
cpi->denoiser.current_denoiser_frame == 0) {
|
|
cpi->denoiser.reset = 1;
|
|
force_refresh_longterm_ref(cpi);
|
|
}
|
|
}
|
|
|
|
void vp9_denoiser_update_ref_frame(VP9_COMP *const cpi) {
|
|
VP9_COMMON *const cm = &cpi->common;
|
|
SVC *const svc = &cpi->svc;
|
|
|
|
if (cpi->oxcf.noise_sensitivity > 0 && denoise_svc(cpi) &&
|
|
cpi->denoiser.denoising_level > kDenLowLow) {
|
|
int svc_refresh_denoiser_buffers = 0;
|
|
int denoise_svc_second_layer = 0;
|
|
FRAME_TYPE frame_type = cm->intra_only ? KEY_FRAME : cm->frame_type;
|
|
cpi->denoiser.current_denoiser_frame++;
|
|
if (cpi->use_svc) {
|
|
const int svc_buf_shift =
|
|
svc->number_spatial_layers - svc->spatial_layer_id == 2
|
|
? cpi->denoiser.num_ref_frames
|
|
: 0;
|
|
int layer =
|
|
LAYER_IDS_TO_IDX(svc->spatial_layer_id, svc->temporal_layer_id,
|
|
svc->number_temporal_layers);
|
|
LAYER_CONTEXT *const lc = &svc->layer_context[layer];
|
|
svc_refresh_denoiser_buffers =
|
|
lc->is_key_frame || svc->spatial_layer_sync[svc->spatial_layer_id];
|
|
denoise_svc_second_layer =
|
|
svc->number_spatial_layers - svc->spatial_layer_id == 2 ? 1 : 0;
|
|
// Check if we need to allocate extra buffers in the denoiser
|
|
// for refreshed frames.
|
|
if (vp9_denoiser_realloc_svc(cm, &cpi->denoiser, svc, svc_buf_shift,
|
|
cpi->refresh_alt_ref_frame,
|
|
cpi->refresh_golden_frame,
|
|
cpi->refresh_last_frame, cpi->alt_fb_idx,
|
|
cpi->gld_fb_idx, cpi->lst_fb_idx))
|
|
vpx_internal_error(&cm->error, VPX_CODEC_MEM_ERROR,
|
|
"Failed to re-allocate denoiser for SVC");
|
|
}
|
|
vp9_denoiser_update_frame_info(
|
|
&cpi->denoiser, *cpi->Source, svc, frame_type,
|
|
cpi->refresh_alt_ref_frame, cpi->refresh_golden_frame,
|
|
cpi->refresh_last_frame, cpi->alt_fb_idx, cpi->gld_fb_idx,
|
|
cpi->lst_fb_idx, cpi->resize_pending, svc_refresh_denoiser_buffers,
|
|
denoise_svc_second_layer);
|
|
}
|
|
}
|
|
|
|
#ifdef OUTPUT_YUV_DENOISED
|
|
static void make_grayscale(YV12_BUFFER_CONFIG *yuv) {
|
|
int r, c;
|
|
uint8_t *u = yuv->u_buffer;
|
|
uint8_t *v = yuv->v_buffer;
|
|
|
|
for (r = 0; r < yuv->uv_height; ++r) {
|
|
for (c = 0; c < yuv->uv_width; ++c) {
|
|
u[c] = UINT8_MAX / 2;
|
|
v[c] = UINT8_MAX / 2;
|
|
}
|
|
u += yuv->uv_stride;
|
|
v += yuv->uv_stride;
|
|
}
|
|
}
|
|
#endif
|