michael@0: /* michael@0: * Copyright (c) 2012 The WebM project authors. All Rights Reserved. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license michael@0: * that can be found in the LICENSE file in the root of the source michael@0: * tree. An additional intellectual property rights grant can be found michael@0: * in the file PATENTS. All contributing project authors may michael@0: * be found in the AUTHORS file in the root of the source tree. michael@0: */ michael@0: michael@0: #include "denoising.h" michael@0: michael@0: #include "vp8/common/reconinter.h" michael@0: #include "vpx/vpx_integer.h" michael@0: #include "vpx_mem/vpx_mem.h" michael@0: #include "vp8_rtcd.h" michael@0: michael@0: static const unsigned int NOISE_MOTION_THRESHOLD = 25 * 25; michael@0: /* SSE_DIFF_THRESHOLD is selected as ~95% confidence assuming michael@0: * var(noise) ~= 100. michael@0: */ michael@0: static const unsigned int SSE_DIFF_THRESHOLD = 16 * 16 * 20; michael@0: static const unsigned int SSE_THRESHOLD = 16 * 16 * 40; michael@0: michael@0: /* michael@0: * The filter function was modified to reduce the computational complexity. michael@0: * Step 1: michael@0: * Instead of applying tap coefficients for each pixel, we calculated the michael@0: * pixel adjustments vs. pixel diff value ahead of time. michael@0: * adjustment = filtered_value - current_raw michael@0: * = (filter_coefficient * diff + 128) >> 8 michael@0: * where michael@0: * filter_coefficient = (255 << 8) / (256 + ((absdiff * 330) >> 3)); michael@0: * filter_coefficient += filter_coefficient / michael@0: * (3 + motion_magnitude_adjustment); michael@0: * filter_coefficient is clamped to 0 ~ 255. michael@0: * michael@0: * Step 2: michael@0: * The adjustment vs. diff curve becomes flat very quick when diff increases. michael@0: * This allowed us to use only several levels to approximate the curve without michael@0: * changing the filtering algorithm too much. michael@0: * The adjustments were further corrected by checking the motion magnitude. michael@0: * The levels used are: michael@0: * diff adjustment w/o motion correction adjustment w/ motion correction michael@0: * [-255, -16] -6 -7 michael@0: * [-15, -8] -4 -5 michael@0: * [-7, -4] -3 -4 michael@0: * [-3, 3] diff diff michael@0: * [4, 7] 3 4 michael@0: * [8, 15] 4 5 michael@0: * [16, 255] 6 7 michael@0: */ michael@0: michael@0: int vp8_denoiser_filter_c(YV12_BUFFER_CONFIG *mc_running_avg, michael@0: YV12_BUFFER_CONFIG *running_avg, MACROBLOCK *signal, michael@0: unsigned int motion_magnitude, int y_offset, michael@0: int uv_offset) michael@0: { michael@0: unsigned char *sig = signal->thismb; michael@0: int sig_stride = 16; michael@0: unsigned char *mc_running_avg_y = mc_running_avg->y_buffer + y_offset; michael@0: int mc_avg_y_stride = mc_running_avg->y_stride; michael@0: unsigned char *running_avg_y = running_avg->y_buffer + y_offset; michael@0: int avg_y_stride = running_avg->y_stride; michael@0: int r, c, i; michael@0: int sum_diff = 0; michael@0: int adj_val[3] = {3, 4, 6}; michael@0: michael@0: /* If motion_magnitude is small, making the denoiser more aggressive by michael@0: * increasing the adjustment for each level. */ michael@0: if (motion_magnitude <= MOTION_MAGNITUDE_THRESHOLD) michael@0: { michael@0: for (i = 0; i < 3; i++) michael@0: adj_val[i] += 1; michael@0: } michael@0: michael@0: for (r = 0; r < 16; ++r) michael@0: { michael@0: for (c = 0; c < 16; ++c) michael@0: { michael@0: int diff = 0; michael@0: int adjustment = 0; michael@0: int absdiff = 0; michael@0: michael@0: diff = mc_running_avg_y[c] - sig[c]; michael@0: absdiff = abs(diff); michael@0: michael@0: /* When |diff| < 4, use pixel value from last denoised raw. */ michael@0: if (absdiff <= 3) michael@0: { michael@0: running_avg_y[c] = mc_running_avg_y[c]; michael@0: sum_diff += diff; michael@0: } michael@0: else michael@0: { michael@0: if (absdiff >= 4 && absdiff <= 7) michael@0: adjustment = adj_val[0]; michael@0: else if (absdiff >= 8 && absdiff <= 15) michael@0: adjustment = adj_val[1]; michael@0: else michael@0: adjustment = adj_val[2]; michael@0: michael@0: if (diff > 0) michael@0: { michael@0: if ((sig[c] + adjustment) > 255) michael@0: running_avg_y[c] = 255; michael@0: else michael@0: running_avg_y[c] = sig[c] + adjustment; michael@0: michael@0: sum_diff += adjustment; michael@0: } michael@0: else michael@0: { michael@0: if ((sig[c] - adjustment) < 0) michael@0: running_avg_y[c] = 0; michael@0: else michael@0: running_avg_y[c] = sig[c] - adjustment; michael@0: michael@0: sum_diff -= adjustment; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Update pointers for next iteration. */ michael@0: sig += sig_stride; michael@0: mc_running_avg_y += mc_avg_y_stride; michael@0: running_avg_y += avg_y_stride; michael@0: } michael@0: michael@0: if (abs(sum_diff) > SUM_DIFF_THRESHOLD) michael@0: return COPY_BLOCK; michael@0: michael@0: vp8_copy_mem16x16(running_avg->y_buffer + y_offset, avg_y_stride, michael@0: signal->thismb, sig_stride); michael@0: return FILTER_BLOCK; michael@0: } michael@0: michael@0: int vp8_denoiser_allocate(VP8_DENOISER *denoiser, int width, int height) michael@0: { michael@0: int i; michael@0: assert(denoiser); michael@0: michael@0: for (i = 0; i < MAX_REF_FRAMES; i++) michael@0: { michael@0: denoiser->yv12_running_avg[i].flags = 0; michael@0: michael@0: if (vp8_yv12_alloc_frame_buffer(&(denoiser->yv12_running_avg[i]), width, michael@0: height, VP8BORDERINPIXELS) michael@0: < 0) michael@0: { michael@0: vp8_denoiser_free(denoiser); michael@0: return 1; michael@0: } michael@0: vpx_memset(denoiser->yv12_running_avg[i].buffer_alloc, 0, michael@0: denoiser->yv12_running_avg[i].frame_size); michael@0: michael@0: } michael@0: denoiser->yv12_mc_running_avg.flags = 0; michael@0: michael@0: if (vp8_yv12_alloc_frame_buffer(&(denoiser->yv12_mc_running_avg), width, michael@0: height, VP8BORDERINPIXELS) < 0) michael@0: { michael@0: vp8_denoiser_free(denoiser); michael@0: return 1; michael@0: } michael@0: michael@0: vpx_memset(denoiser->yv12_mc_running_avg.buffer_alloc, 0, michael@0: denoiser->yv12_mc_running_avg.frame_size); michael@0: return 0; michael@0: } michael@0: michael@0: void vp8_denoiser_free(VP8_DENOISER *denoiser) michael@0: { michael@0: int i; michael@0: assert(denoiser); michael@0: michael@0: for (i = 0; i < MAX_REF_FRAMES ; i++) michael@0: { michael@0: vp8_yv12_de_alloc_frame_buffer(&denoiser->yv12_running_avg[i]); michael@0: } michael@0: vp8_yv12_de_alloc_frame_buffer(&denoiser->yv12_mc_running_avg); michael@0: } michael@0: michael@0: michael@0: void vp8_denoiser_denoise_mb(VP8_DENOISER *denoiser, michael@0: MACROBLOCK *x, michael@0: unsigned int best_sse, michael@0: unsigned int zero_mv_sse, michael@0: int recon_yoffset, michael@0: int recon_uvoffset) michael@0: { michael@0: int mv_row; michael@0: int mv_col; michael@0: unsigned int motion_magnitude2; michael@0: michael@0: MV_REFERENCE_FRAME frame = x->best_reference_frame; michael@0: MV_REFERENCE_FRAME zero_frame = x->best_zeromv_reference_frame; michael@0: michael@0: enum vp8_denoiser_decision decision = FILTER_BLOCK; michael@0: michael@0: if (zero_frame) michael@0: { michael@0: YV12_BUFFER_CONFIG *src = &denoiser->yv12_running_avg[frame]; michael@0: YV12_BUFFER_CONFIG *dst = &denoiser->yv12_mc_running_avg; michael@0: YV12_BUFFER_CONFIG saved_pre,saved_dst; michael@0: MB_MODE_INFO saved_mbmi; michael@0: MACROBLOCKD *filter_xd = &x->e_mbd; michael@0: MB_MODE_INFO *mbmi = &filter_xd->mode_info_context->mbmi; michael@0: int sse_diff = zero_mv_sse - best_sse; michael@0: michael@0: saved_mbmi = *mbmi; michael@0: michael@0: /* Use the best MV for the compensation. */ michael@0: mbmi->ref_frame = x->best_reference_frame; michael@0: mbmi->mode = x->best_sse_inter_mode; michael@0: mbmi->mv = x->best_sse_mv; michael@0: mbmi->need_to_clamp_mvs = x->need_to_clamp_best_mvs; michael@0: mv_col = x->best_sse_mv.as_mv.col; michael@0: mv_row = x->best_sse_mv.as_mv.row; michael@0: michael@0: if (frame == INTRA_FRAME || michael@0: ((unsigned int)(mv_row *mv_row + mv_col *mv_col) michael@0: <= NOISE_MOTION_THRESHOLD && michael@0: sse_diff < (int)SSE_DIFF_THRESHOLD)) michael@0: { michael@0: /* michael@0: * Handle intra blocks as referring to last frame with zero motion michael@0: * and let the absolute pixel difference affect the filter factor. michael@0: * Also consider small amount of motion as being random walk due michael@0: * to noise, if it doesn't mean that we get a much bigger error. michael@0: * Note that any changes to the mode info only affects the michael@0: * denoising. michael@0: */ michael@0: mbmi->ref_frame = michael@0: x->best_zeromv_reference_frame; michael@0: michael@0: src = &denoiser->yv12_running_avg[zero_frame]; michael@0: michael@0: mbmi->mode = ZEROMV; michael@0: mbmi->mv.as_int = 0; michael@0: x->best_sse_inter_mode = ZEROMV; michael@0: x->best_sse_mv.as_int = 0; michael@0: best_sse = zero_mv_sse; michael@0: } michael@0: michael@0: saved_pre = filter_xd->pre; michael@0: saved_dst = filter_xd->dst; michael@0: michael@0: /* Compensate the running average. */ michael@0: filter_xd->pre.y_buffer = src->y_buffer + recon_yoffset; michael@0: filter_xd->pre.u_buffer = src->u_buffer + recon_uvoffset; michael@0: filter_xd->pre.v_buffer = src->v_buffer + recon_uvoffset; michael@0: /* Write the compensated running average to the destination buffer. */ michael@0: filter_xd->dst.y_buffer = dst->y_buffer + recon_yoffset; michael@0: filter_xd->dst.u_buffer = dst->u_buffer + recon_uvoffset; michael@0: filter_xd->dst.v_buffer = dst->v_buffer + recon_uvoffset; michael@0: michael@0: if (!x->skip) michael@0: { michael@0: vp8_build_inter_predictors_mb(filter_xd); michael@0: } michael@0: else michael@0: { michael@0: vp8_build_inter16x16_predictors_mb(filter_xd, michael@0: filter_xd->dst.y_buffer, michael@0: filter_xd->dst.u_buffer, michael@0: filter_xd->dst.v_buffer, michael@0: filter_xd->dst.y_stride, michael@0: filter_xd->dst.uv_stride); michael@0: } michael@0: filter_xd->pre = saved_pre; michael@0: filter_xd->dst = saved_dst; michael@0: *mbmi = saved_mbmi; michael@0: michael@0: } michael@0: michael@0: mv_row = x->best_sse_mv.as_mv.row; michael@0: mv_col = x->best_sse_mv.as_mv.col; michael@0: motion_magnitude2 = mv_row * mv_row + mv_col * mv_col; michael@0: if (best_sse > SSE_THRESHOLD || motion_magnitude2 michael@0: > 8 * NOISE_MOTION_THRESHOLD) michael@0: { michael@0: decision = COPY_BLOCK; michael@0: } michael@0: michael@0: if (decision == FILTER_BLOCK) michael@0: { michael@0: /* Filter. */ michael@0: decision = vp8_denoiser_filter(&denoiser->yv12_mc_running_avg, michael@0: &denoiser->yv12_running_avg[INTRA_FRAME], michael@0: x, michael@0: motion_magnitude2, michael@0: recon_yoffset, recon_uvoffset); michael@0: } michael@0: if (decision == COPY_BLOCK) michael@0: { michael@0: /* No filtering of this block; it differs too much from the predictor, michael@0: * or the motion vector magnitude is considered too big. michael@0: */ michael@0: vp8_copy_mem16x16( michael@0: x->thismb, 16, michael@0: denoiser->yv12_running_avg[INTRA_FRAME].y_buffer + recon_yoffset, michael@0: denoiser->yv12_running_avg[INTRA_FRAME].y_stride); michael@0: } michael@0: }