summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/video_processing/video_denoiser.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/libwebrtc/modules/video_processing/video_denoiser.cc
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/video_processing/video_denoiser.cc')
-rw-r--r--third_party/libwebrtc/modules/video_processing/video_denoiser.cc339
1 files changed, 339 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_processing/video_denoiser.cc b/third_party/libwebrtc/modules/video_processing/video_denoiser.cc
new file mode 100644
index 0000000000..7d5368b934
--- /dev/null
+++ b/third_party/libwebrtc/modules/video_processing/video_denoiser.cc
@@ -0,0 +1,339 @@
+/*
+ * 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 <stdint.h>
+#include <string.h>
+
+#include "api/video/i420_buffer.h"
+#include "libyuv/include/libyuv/planar_functions.h"
+
+namespace webrtc {
+
+#if DISPLAY || DISPLAYNEON
+static void ShowRect(const std::unique_ptr<DenoiserFilter>& filter,
+ const std::unique_ptr<uint8_t[]>& d_status,
+ const std::unique_ptr<uint8_t[]>& moving_edge_red,
+ const std::unique_ptr<uint8_t[]>& x_density,
+ const std::unique_ptr<uint8_t[]>& 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.
+ libyuv::CopyPlane(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst, 8, 8);
+ libyuv::CopyPlane(uv_tmp, 8, mb_dst_v, stride_v_dst, 8, 8);
+ } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
+ x_density[mb_col] * y_density[mb_row]) {
+ // Paint to blue.
+ libyuv::CopyPlane(uv_tmp, 8, mb_dst_u, stride_u_dst, 8, 8);
+ libyuv::CopyPlane(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst, 8, 8);
+ } else {
+ libyuv::CopyPlane(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst, 8, 8);
+ libyuv::CopyPlane(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst, 8, 8);
+ }
+ }
+ }
+}
+#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<I420BufferInterface> 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<uint8_t[]>& d_status,
+ std::unique_ptr<uint8_t[]>* 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<uint8_t[]>& 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.
+ libyuv::CopyPlane(mb_src, stride_src, mb_dst, stride_dst, 16, 16);
+ }
+ }
+ }
+}
+
+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<I420BufferInterface> VideoDenoiser::DenoiseFrame(
+ rtc::scoped_refptr<I420BufferInterface> 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<I420Buffer> 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