blob: 1d844e61de4c04cc7c42d0fa573e17802a7133b7 [file] [log] [blame]
jackychen8f9902a2015-11-26 02:59:48 -08001/*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
jackychenafaae0d2016-04-12 23:02:55 -070010
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/video_processing/video_denoiser.h"
Yves Gerey3e707812018-11-28 16:47:49 +010012
13#include <stdint.h>
14#include <string.h>
15
16#include "api/video/i420_buffer.h"
Mirko Bonadei65432062017-12-11 09:32:13 +010017#include "third_party/libyuv/include/libyuv/planar_functions.h"
jackychen8f9902a2015-11-26 02:59:48 -080018
jackychene42c0ae2016-04-16 10:44:16 -070019namespace webrtc {
20
jackychen8556c482016-04-20 16:04:31 -070021#if DISPLAY || DISPLAYNEON
jackychenafaae0d2016-04-12 23:02:55 -070022static void ShowRect(const std::unique_ptr<DenoiserFilter>& filter,
23 const std::unique_ptr<uint8_t[]>& d_status,
24 const std::unique_ptr<uint8_t[]>& moving_edge_red,
25 const std::unique_ptr<uint8_t[]>& x_density,
26 const std::unique_ptr<uint8_t[]>& y_density,
Yves Gerey665174f2018-06-19 15:03:05 +020027 const uint8_t* u_src,
28 int stride_u_src,
29 const uint8_t* v_src,
30 int stride_v_src,
31 uint8_t* u_dst,
32 int stride_u_dst,
33 uint8_t* v_dst,
34 int stride_v_dst,
jackychenafaae0d2016-04-12 23:02:55 -070035 int mb_rows_,
nisse18ee17d2016-11-07 01:34:59 -080036 int mb_cols_) {
jackychenafaae0d2016-04-12 23:02:55 -070037 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
38 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
39 int mb_index = mb_row * mb_cols_ + mb_col;
40 const uint8_t* mb_src_u =
nisse18ee17d2016-11-07 01:34:59 -080041 u_src + (mb_row << 3) * stride_u_src + (mb_col << 3);
jackychenafaae0d2016-04-12 23:02:55 -070042 const uint8_t* mb_src_v =
nisse18ee17d2016-11-07 01:34:59 -080043 v_src + (mb_row << 3) * stride_v_src + (mb_col << 3);
44 uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_dst + (mb_col << 3);
45 uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_dst + (mb_col << 3);
jackychenafaae0d2016-04-12 23:02:55 -070046 uint8_t uv_tmp[8 * 8];
47 memset(uv_tmp, 200, 8 * 8);
48 if (d_status[mb_index] == 1) {
49 // Paint to red.
Zhaoliang Maa4205472020-11-27 23:23:38 +080050 libyuv::CopyPlane(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst, 8, 8);
51 libyuv::CopyPlane(uv_tmp, 8, mb_dst_v, stride_v_dst, 8, 8);
jackychenafaae0d2016-04-12 23:02:55 -070052 } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
53 x_density[mb_col] * y_density[mb_row]) {
54 // Paint to blue.
Zhaoliang Maa4205472020-11-27 23:23:38 +080055 libyuv::CopyPlane(uv_tmp, 8, mb_dst_u, stride_u_dst, 8, 8);
56 libyuv::CopyPlane(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst, 8, 8);
jackychenafaae0d2016-04-12 23:02:55 -070057 } else {
Zhaoliang Maa4205472020-11-27 23:23:38 +080058 libyuv::CopyPlane(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst, 8, 8);
59 libyuv::CopyPlane(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst, 8, 8);
jackychenafaae0d2016-04-12 23:02:55 -070060 }
61 }
62 }
63}
64#endif
65
jackychen67e94fb2016-01-11 21:34:07 -080066VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
67 : width_(0),
68 height_(0),
jackychenfa0befe2016-04-01 07:46:58 -070069 filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)),
70 ne_(new NoiseEstimation()) {}
jackychen8f9902a2015-11-26 02:59:48 -080071
Magnus Jedvert7a721e82017-06-14 11:28:08 +020072void VideoDenoiser::DenoiserReset(
73 rtc::scoped_refptr<I420BufferInterface> frame) {
Niels Möller6af2e862016-06-17 09:12:44 +020074 width_ = frame->width();
75 height_ = frame->height();
jackychenafaae0d2016-04-12 23:02:55 -070076 mb_cols_ = width_ >> 4;
77 mb_rows_ = height_ >> 4;
jackychenafaae0d2016-04-12 23:02:55 -070078
79 // Init noise estimator and allocate buffers.
80 ne_->Init(width_, height_, cpu_type_);
81 moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]);
82 mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]);
83 x_density_.reset(new uint8_t[mb_cols_]);
84 y_density_.reset(new uint8_t[mb_rows_]);
85 moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]);
86}
87
88int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) {
89 if (noise_level == 0)
jackychenfa0befe2016-04-01 07:46:58 -070090 return 1;
jackychenafaae0d2016-04-12 23:02:55 -070091 if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) ||
92 (mb_col >= (15 * mb_cols_ >> 4)))
93 return 3;
94 else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) ||
95 (mb_col >= (7 * mb_cols_ >> 3)))
jackychenfa0befe2016-04-01 07:46:58 -070096 return 2;
97 else
jackychenafaae0d2016-04-12 23:02:55 -070098 return 1;
jackychenfa0befe2016-04-01 07:46:58 -070099}
100
jackychenafaae0d2016-04-12 23:02:55 -0700101void VideoDenoiser::ReduceFalseDetection(
102 const std::unique_ptr<uint8_t[]>& d_status,
103 std::unique_ptr<uint8_t[]>* moving_edge_red,
104 int noise_level) {
105 // From up left corner.
106 int mb_col_stop = mb_cols_ - 1;
107 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
108 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
109 if (d_status[mb_row * mb_cols_ + mb_col]) {
110 mb_col_stop = mb_col - 1;
111 break;
112 }
113 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700114 }
115 }
jackychenafaae0d2016-04-12 23:02:55 -0700116 // From bottom left corner.
117 mb_col_stop = mb_cols_ - 1;
118 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
119 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
120 if (d_status[mb_row * mb_cols_ + mb_col]) {
121 mb_col_stop = mb_col - 1;
122 break;
123 }
124 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700125 }
126 }
jackychenafaae0d2016-04-12 23:02:55 -0700127 // From up right corner.
128 mb_col_stop = 0;
129 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
130 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
131 if (d_status[mb_row * mb_cols_ + mb_col]) {
132 mb_col_stop = mb_col + 1;
133 break;
134 }
135 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700136 }
137 }
jackychenafaae0d2016-04-12 23:02:55 -0700138 // From bottom right corner.
139 mb_col_stop = 0;
140 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
141 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
142 if (d_status[mb_row * mb_cols_ + mb_col]) {
143 mb_col_stop = mb_col + 1;
144 break;
145 }
146 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychen8f9902a2015-11-26 02:59:48 -0800147 }
148 }
149}
150
jackychenafaae0d2016-04-12 23:02:55 -0700151bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status,
152 int mb_row,
153 int mb_col) {
154 bool ret = false;
155 int mb_index = mb_row * mb_cols_ + mb_col;
156 if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1)
157 ret = false;
158 else
159 ret = d_status[mb_index + 1] || d_status[mb_index - 1] ||
160 d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_];
161 return ret;
jackychenfa0befe2016-04-01 07:46:58 -0700162}
jackychenfa0befe2016-04-01 07:46:58 -0700163
nisse18ee17d2016-11-07 01:34:59 -0800164void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src,
165 int stride_src,
166 uint8_t* y_dst,
167 int stride_dst) {
jackychenafaae0d2016-04-12 23:02:55 -0700168 // Loop over to copy src block if the block is marked as moving object block
169 // or if the block may cause trailing artifacts.
170 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
171 const int mb_index_base = mb_row * mb_cols_;
nisse18ee17d2016-11-07 01:34:59 -0800172 const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_src;
173 uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_dst;
jackychenafaae0d2016-04-12 23:02:55 -0700174 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
175 const int mb_index = mb_index_base + mb_col;
176 const uint32_t offset_col = mb_col << 4;
177 const uint8_t* mb_src = mb_src_base + offset_col;
178 uint8_t* mb_dst = mb_dst_base + offset_col;
179 // Check if the block is a moving object block or may cause a trailing
180 // artifacts.
181 if (mb_filter_decision_[mb_index] != FILTER_BLOCK ||
182 IsTrailingBlock(moving_edge_, mb_row, mb_col) ||
183 (x_density_[mb_col] * y_density_[mb_row] &&
184 moving_object_[mb_row * mb_cols_ + mb_col])) {
185 // Copy y source.
Zhaoliang Maa4205472020-11-27 23:23:38 +0800186 libyuv::CopyPlane(mb_src, stride_src, mb_dst, stride_dst, 16, 16);
jackychenfa0befe2016-04-01 07:46:58 -0700187 }
188 }
189 }
190}
jackychenfa0befe2016-04-01 07:46:58 -0700191
nisse18ee17d2016-11-07 01:34:59 -0800192void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src,
193 int stride_src,
194 uint8_t* y_dst,
195 int stride_dst) {
196 int height_margin = height_ - (mb_rows_ << 4);
197 if (height_margin > 0) {
198 const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_src;
199 uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_dst;
200 libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
201 width_, height_margin);
jackychen6650d6d2016-04-25 16:53:59 -0700202 }
nisse18ee17d2016-11-07 01:34:59 -0800203 int width_margin = width_ - (mb_cols_ << 4);
204 if (width_margin > 0) {
jackychen6650d6d2016-04-25 16:53:59 -0700205 const uint8_t* margin_y_src = y_src + (mb_cols_ << 4);
206 uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4);
nisse18ee17d2016-11-07 01:34:59 -0800207 libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
208 width_ - (mb_cols_ << 4), mb_rows_ << 4);
jackychen6650d6d2016-04-25 16:53:59 -0700209 }
210}
211
Magnus Jedvert7a721e82017-06-14 11:28:08 +0200212rtc::scoped_refptr<I420BufferInterface> VideoDenoiser::DenoiseFrame(
213 rtc::scoped_refptr<I420BufferInterface> frame,
Niels Möller6af2e862016-06-17 09:12:44 +0200214 bool noise_estimation_enabled) {
jackychenafaae0d2016-04-12 23:02:55 -0700215 // If previous width and height are different from current frame's, need to
216 // reallocate the buffers and no denoising for the current frame.
nisse18ee17d2016-11-07 01:34:59 -0800217 if (!prev_buffer_ || width_ != frame->width() || height_ != frame->height()) {
218 DenoiserReset(frame);
219 prev_buffer_ = frame;
220 return frame;
jackychen8f9902a2015-11-26 02:59:48 -0800221 }
jackychenfa0befe2016-04-01 07:46:58 -0700222
jackychenafaae0d2016-04-12 23:02:55 -0700223 // Set buffer pointers.
Niels Möller6af2e862016-06-17 09:12:44 +0200224 const uint8_t* y_src = frame->DataY();
nisse18ee17d2016-11-07 01:34:59 -0800225 int stride_y_src = frame->StrideY();
226 rtc::scoped_refptr<I420Buffer> dst =
Ilya Nikolaevskiy4c87d832020-09-18 15:18:54 +0200227 buffer_pool_.CreateI420Buffer(width_, height_);
nisse18ee17d2016-11-07 01:34:59 -0800228
229 uint8_t* y_dst = dst->MutableDataY();
230 int stride_y_dst = dst->StrideY();
231
232 const uint8_t* y_dst_prev = prev_buffer_->DataY();
233 int stride_prev = prev_buffer_->StrideY();
234
jackychenafaae0d2016-04-12 23:02:55 -0700235 memset(x_density_.get(), 0, mb_cols_);
236 memset(y_density_.get(), 0, mb_rows_);
237 memset(moving_object_.get(), 1, mb_cols_ * mb_rows_);
jackychenfa0befe2016-04-01 07:46:58 -0700238
jackychenafaae0d2016-04-12 23:02:55 -0700239 uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0;
jackychen9bfa1062016-05-03 11:21:26 -0700240 int thr_var_base = 16 * 16 * 2;
jackychenfa0befe2016-04-01 07:46:58 -0700241 // Loop over blocks to accumulate/extract noise level and update x/y_density
242 // factors for moving object detection.
jackychenafaae0d2016-04-12 23:02:55 -0700243 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
244 const int mb_index_base = mb_row * mb_cols_;
nisse18ee17d2016-11-07 01:34:59 -0800245 const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_y_src;
246 uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_y_dst;
247 const uint8_t* mb_dst_prev_base = y_dst_prev + (mb_row << 4) * stride_prev;
jackychenafaae0d2016-04-12 23:02:55 -0700248 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
249 const int mb_index = mb_index_base + mb_col;
250 const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0);
251 const int pos_factor = PositionCheck(mb_row, mb_col, noise_level);
252 const uint32_t thr_var_adp = thr_var_base * pos_factor;
253 const uint32_t offset_col = mb_col << 4;
254 const uint8_t* mb_src = mb_src_base + offset_col;
255 uint8_t* mb_dst = mb_dst_base + offset_col;
nisse18ee17d2016-11-07 01:34:59 -0800256 const uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col;
jackychenafaae0d2016-04-12 23:02:55 -0700257
258 // TODO(jackychen): Need SSE2/NEON opt.
259 int luma = 0;
260 if (ne_enable) {
261 for (int i = 4; i < 12; ++i) {
262 for (int j = 4; j < 12; ++j) {
nisse18ee17d2016-11-07 01:34:59 -0800263 luma += mb_src[i * stride_y_src + j];
jackychenafaae0d2016-04-12 23:02:55 -0700264 }
jackychenfa0befe2016-04-01 07:46:58 -0700265 }
266 }
267
jackychenafaae0d2016-04-12 23:02:55 -0700268 // Get the filtered block and filter_decision.
269 mb_filter_decision_[mb_index] =
nisse18ee17d2016-11-07 01:34:59 -0800270 filter_->MbDenoise(mb_dst_prev, stride_prev, mb_dst, stride_y_dst,
271 mb_src, stride_y_src, 0, noise_level);
jackychenfa0befe2016-04-01 07:46:58 -0700272
jackychenafaae0d2016-04-12 23:02:55 -0700273 // If filter decision is FILTER_BLOCK, no need to check moving edge.
274 // It is unlikely for a moving edge block to be filtered in current
275 // setting.
276 if (mb_filter_decision_[mb_index] == FILTER_BLOCK) {
277 uint32_t sse_t = 0;
278 if (ne_enable) {
279 // The variance used in noise estimation is based on the src block in
280 // time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
nisse18ee17d2016-11-07 01:34:59 -0800281 uint32_t noise_var = filter_->Variance16x8(
282 mb_dst_prev, stride_y_dst, mb_src, stride_y_src, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700283 ne_->GetNoise(mb_index, noise_var, luma);
jackychenfa0befe2016-04-01 07:46:58 -0700284 }
jackychenafaae0d2016-04-12 23:02:55 -0700285 moving_edge_[mb_index] = 0; // Not a moving edge block.
jackychenfa0befe2016-04-01 07:46:58 -0700286 } else {
287 uint32_t sse_t = 0;
jackychenafaae0d2016-04-12 23:02:55 -0700288 // The variance used in MOD is based on the filtered blocks in time
289 // T (mb_dst) and T-1 (mb_dst_prev).
nisse18ee17d2016-11-07 01:34:59 -0800290 uint32_t noise_var = filter_->Variance16x8(
291 mb_dst_prev, stride_prev, mb_dst, stride_y_dst, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700292 if (noise_var > thr_var_adp) { // Moving edge checking.
293 if (ne_enable) {
294 ne_->ResetConsecLowVar(mb_index);
295 }
296 moving_edge_[mb_index] = 1; // Mark as moving edge block.
297 x_density_[mb_col] += (pos_factor < 3);
298 y_density_[mb_row] += (pos_factor < 3);
jackychenfa0befe2016-04-01 07:46:58 -0700299 } else {
jackychenafaae0d2016-04-12 23:02:55 -0700300 moving_edge_[mb_index] = 0;
301 if (ne_enable) {
302 // The variance used in noise estimation is based on the src block
303 // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
304 uint32_t noise_var = filter_->Variance16x8(
nisse18ee17d2016-11-07 01:34:59 -0800305 mb_dst_prev, stride_prev, mb_src, stride_y_src, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700306 ne_->GetNoise(mb_index, noise_var, luma);
307 }
jackychenfa0befe2016-04-01 07:46:58 -0700308 }
jackychenfa0befe2016-04-01 07:46:58 -0700309 }
jackychenafaae0d2016-04-12 23:02:55 -0700310 } // End of for loop
311 } // End of for loop
312
313 ReduceFalseDetection(moving_edge_, &moving_object_, noise_level);
314
nisse18ee17d2016-11-07 01:34:59 -0800315 CopySrcOnMOB(y_src, stride_y_src, y_dst, stride_y_dst);
jackychenafaae0d2016-04-12 23:02:55 -0700316
jackychen6650d6d2016-04-25 16:53:59 -0700317 // When frame width/height not divisible by 16, copy the margin to
318 // denoised_frame.
319 if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_)
nisse18ee17d2016-11-07 01:34:59 -0800320 CopyLumaOnMargin(y_src, stride_y_src, y_dst, stride_y_dst);
jackychen6650d6d2016-04-25 16:53:59 -0700321
jackychenafaae0d2016-04-12 23:02:55 -0700322 // Copy u/v planes.
Yves Gerey665174f2018-06-19 15:03:05 +0200323 libyuv::CopyPlane(frame->DataU(), frame->StrideU(), dst->MutableDataU(),
324 dst->StrideU(), (width_ + 1) >> 1, (height_ + 1) >> 1);
325 libyuv::CopyPlane(frame->DataV(), frame->StrideV(), dst->MutableDataV(),
326 dst->StrideV(), (width_ + 1) >> 1, (height_ + 1) >> 1);
jackychenafaae0d2016-04-12 23:02:55 -0700327
jackychen8556c482016-04-20 16:04:31 -0700328#if DISPLAY || DISPLAYNEON
jackychenfa0befe2016-04-01 07:46:58 -0700329 // Show rectangular region
nisse18ee17d2016-11-07 01:34:59 -0800330 ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_,
331 frame->DataU(), frame->StrideU(), frame->DataV(), frame->StrideV(),
Yves Gerey665174f2018-06-19 15:03:05 +0200332 dst->MutableDataU(), dst->StrideU(), dst->MutableDataV(),
333 dst->StrideV(), mb_rows_, mb_cols_);
jackychenfa0befe2016-04-01 07:46:58 -0700334#endif
nisse18ee17d2016-11-07 01:34:59 -0800335 prev_buffer_ = dst;
336 return dst;
jackychen8f9902a2015-11-26 02:59:48 -0800337}
338
339} // namespace webrtc