/* * Copyright (c) 2015 The WebRTC 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 "modules/video_processing/video_denoiser.h" #include #include #include "api/video/i420_buffer.h" #include "third_party/libyuv/include/libyuv/planar_functions.h" namespace webrtc { #if DISPLAY || DISPLAYNEON static void CopyMem8x8(const uint8_t* src, int src_stride, uint8_t* dst, int dst_stride) { for (int i = 0; i < 8; i++) { memcpy(dst, src, 8); src += src_stride; dst += dst_stride; } } static void ShowRect(const std::unique_ptr& filter, const std::unique_ptr& d_status, const std::unique_ptr& moving_edge_red, const std::unique_ptr& x_density, const std::unique_ptr& y_density, const uint8_t* u_src, int stride_u_src, const uint8_t* v_src, int stride_v_src, uint8_t* u_dst, int stride_u_dst, uint8_t* v_dst, int stride_v_dst, int mb_rows_, int mb_cols_) { for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) { for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) { int mb_index = mb_row * mb_cols_ + mb_col; const uint8_t* mb_src_u = u_src + (mb_row << 3) * stride_u_src + (mb_col << 3); const uint8_t* mb_src_v = v_src + (mb_row << 3) * stride_v_src + (mb_col << 3); uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_dst + (mb_col << 3); uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_dst + (mb_col << 3); uint8_t uv_tmp[8 * 8]; memset(uv_tmp, 200, 8 * 8); if (d_status[mb_index] == 1) { // Paint to red. CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst); CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_dst); } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] && x_density[mb_col] * y_density[mb_row]) { // Paint to blue. CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_dst); CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst); } else { CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst); CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst); } } } } #endif VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection) : width_(0), height_(0), filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)), ne_(new NoiseEstimation()) {} void VideoDenoiser::DenoiserReset( rtc::scoped_refptr frame) { width_ = frame->width(); height_ = frame->height(); mb_cols_ = width_ >> 4; mb_rows_ = height_ >> 4; // Init noise estimator and allocate buffers. ne_->Init(width_, height_, cpu_type_); moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]); mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]); x_density_.reset(new uint8_t[mb_cols_]); y_density_.reset(new uint8_t[mb_rows_]); moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]); } int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) { if (noise_level == 0) return 1; if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) || (mb_col >= (15 * mb_cols_ >> 4))) return 3; else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) || (mb_col >= (7 * mb_cols_ >> 3))) return 2; else return 1; } void VideoDenoiser::ReduceFalseDetection( const std::unique_ptr& d_status, std::unique_ptr* moving_edge_red, int noise_level) { // From up left corner. int mb_col_stop = mb_cols_ - 1; for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) { for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) { if (d_status[mb_row * mb_cols_ + mb_col]) { mb_col_stop = mb_col - 1; break; } (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; } } // From bottom left corner. mb_col_stop = mb_cols_ - 1; for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) { for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) { if (d_status[mb_row * mb_cols_ + mb_col]) { mb_col_stop = mb_col - 1; break; } (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; } } // From up right corner. mb_col_stop = 0; for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) { for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) { if (d_status[mb_row * mb_cols_ + mb_col]) { mb_col_stop = mb_col + 1; break; } (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; } } // From bottom right corner. mb_col_stop = 0; for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) { for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) { if (d_status[mb_row * mb_cols_ + mb_col]) { mb_col_stop = mb_col + 1; break; } (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0; } } } bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr& d_status, int mb_row, int mb_col) { bool ret = false; int mb_index = mb_row * mb_cols_ + mb_col; if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1) ret = false; else ret = d_status[mb_index + 1] || d_status[mb_index - 1] || d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_]; return ret; } void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src, int stride_src, uint8_t* y_dst, int stride_dst) { // Loop over to copy src block if the block is marked as moving object block // or if the block may cause trailing artifacts. for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) { const int mb_index_base = mb_row * mb_cols_; const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_src; uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_dst; for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) { const int mb_index = mb_index_base + mb_col; const uint32_t offset_col = mb_col << 4; const uint8_t* mb_src = mb_src_base + offset_col; uint8_t* mb_dst = mb_dst_base + offset_col; // Check if the block is a moving object block or may cause a trailing // artifacts. if (mb_filter_decision_[mb_index] != FILTER_BLOCK || IsTrailingBlock(moving_edge_, mb_row, mb_col) || (x_density_[mb_col] * y_density_[mb_row] && moving_object_[mb_row * mb_cols_ + mb_col])) { // Copy y source. filter_->CopyMem16x16(mb_src, stride_src, mb_dst, stride_dst); } } } } void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src, int stride_src, uint8_t* y_dst, int stride_dst) { int height_margin = height_ - (mb_rows_ << 4); if (height_margin > 0) { const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_src; uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_dst; libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst, width_, height_margin); } int width_margin = width_ - (mb_cols_ << 4); if (width_margin > 0) { const uint8_t* margin_y_src = y_src + (mb_cols_ << 4); uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4); libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst, width_ - (mb_cols_ << 4), mb_rows_ << 4); } } rtc::scoped_refptr VideoDenoiser::DenoiseFrame( rtc::scoped_refptr frame, bool noise_estimation_enabled) { // If previous width and height are different from current frame's, need to // reallocate the buffers and no denoising for the current frame. if (!prev_buffer_ || width_ != frame->width() || height_ != frame->height()) { DenoiserReset(frame); prev_buffer_ = frame; return frame; } // Set buffer pointers. const uint8_t* y_src = frame->DataY(); int stride_y_src = frame->StrideY(); rtc::scoped_refptr dst = buffer_pool_.CreateI420Buffer(width_, height_); uint8_t* y_dst = dst->MutableDataY(); int stride_y_dst = dst->StrideY(); const uint8_t* y_dst_prev = prev_buffer_->DataY(); int stride_prev = prev_buffer_->StrideY(); memset(x_density_.get(), 0, mb_cols_); memset(y_density_.get(), 0, mb_rows_); memset(moving_object_.get(), 1, mb_cols_ * mb_rows_); uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0; int thr_var_base = 16 * 16 * 2; // Loop over blocks to accumulate/extract noise level and update x/y_density // factors for moving object detection. for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) { const int mb_index_base = mb_row * mb_cols_; const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_y_src; uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_y_dst; const uint8_t* mb_dst_prev_base = y_dst_prev + (mb_row << 4) * stride_prev; for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) { const int mb_index = mb_index_base + mb_col; const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0); const int pos_factor = PositionCheck(mb_row, mb_col, noise_level); const uint32_t thr_var_adp = thr_var_base * pos_factor; const uint32_t offset_col = mb_col << 4; const uint8_t* mb_src = mb_src_base + offset_col; uint8_t* mb_dst = mb_dst_base + offset_col; const uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col; // TODO(jackychen): Need SSE2/NEON opt. int luma = 0; if (ne_enable) { for (int i = 4; i < 12; ++i) { for (int j = 4; j < 12; ++j) { luma += mb_src[i * stride_y_src + j]; } } } // Get the filtered block and filter_decision. mb_filter_decision_[mb_index] = filter_->MbDenoise(mb_dst_prev, stride_prev, mb_dst, stride_y_dst, mb_src, stride_y_src, 0, noise_level); // If filter decision is FILTER_BLOCK, no need to check moving edge. // It is unlikely for a moving edge block to be filtered in current // setting. if (mb_filter_decision_[mb_index] == FILTER_BLOCK) { uint32_t sse_t = 0; if (ne_enable) { // The variance used in noise estimation is based on the src block in // time t (mb_src) and filtered block in time t-1 (mb_dist_prev). uint32_t noise_var = filter_->Variance16x8( mb_dst_prev, stride_y_dst, mb_src, stride_y_src, &sse_t); ne_->GetNoise(mb_index, noise_var, luma); } moving_edge_[mb_index] = 0; // Not a moving edge block. } else { uint32_t sse_t = 0; // The variance used in MOD is based on the filtered blocks in time // T (mb_dst) and T-1 (mb_dst_prev). uint32_t noise_var = filter_->Variance16x8( mb_dst_prev, stride_prev, mb_dst, stride_y_dst, &sse_t); if (noise_var > thr_var_adp) { // Moving edge checking. if (ne_enable) { ne_->ResetConsecLowVar(mb_index); } moving_edge_[mb_index] = 1; // Mark as moving edge block. x_density_[mb_col] += (pos_factor < 3); y_density_[mb_row] += (pos_factor < 3); } else { moving_edge_[mb_index] = 0; if (ne_enable) { // The variance used in noise estimation is based on the src block // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev). uint32_t noise_var = filter_->Variance16x8( mb_dst_prev, stride_prev, mb_src, stride_y_src, &sse_t); ne_->GetNoise(mb_index, noise_var, luma); } } } } // End of for loop } // End of for loop ReduceFalseDetection(moving_edge_, &moving_object_, noise_level); CopySrcOnMOB(y_src, stride_y_src, y_dst, stride_y_dst); // When frame width/height not divisible by 16, copy the margin to // denoised_frame. if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_) CopyLumaOnMargin(y_src, stride_y_src, y_dst, stride_y_dst); // Copy u/v planes. libyuv::CopyPlane(frame->DataU(), frame->StrideU(), dst->MutableDataU(), dst->StrideU(), (width_ + 1) >> 1, (height_ + 1) >> 1); libyuv::CopyPlane(frame->DataV(), frame->StrideV(), dst->MutableDataV(), dst->StrideV(), (width_ + 1) >> 1, (height_ + 1) >> 1); #if DISPLAY || DISPLAYNEON // Show rectangular region ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_, frame->DataU(), frame->StrideU(), frame->DataV(), frame->StrideV(), dst->MutableDataU(), dst->StrideU(), dst->MutableDataV(), dst->StrideV(), mb_rows_, mb_cols_); #endif prev_buffer_ = dst; return dst; } } // namespace webrtc