blob: cf262f012616bf1e120e8d95ef2b0e49fb0e7963 [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"
Mirko Bonadei65432062017-12-11 09:32:13 +010012#include "common_video/libyuv/include/webrtc_libyuv.h"
13#include "third_party/libyuv/include/libyuv/planar_functions.h"
jackychen8f9902a2015-11-26 02:59:48 -080014
jackychene42c0ae2016-04-16 10:44:16 -070015namespace webrtc {
16
jackychen8556c482016-04-20 16:04:31 -070017#if DISPLAY || DISPLAYNEON
jackychenafaae0d2016-04-12 23:02:55 -070018static void CopyMem8x8(const uint8_t* src,
19 int src_stride,
20 uint8_t* dst,
21 int dst_stride) {
22 for (int i = 0; i < 8; i++) {
23 memcpy(dst, src, 8);
24 src += src_stride;
25 dst += dst_stride;
26 }
27}
28
29static void ShowRect(const std::unique_ptr<DenoiserFilter>& filter,
30 const std::unique_ptr<uint8_t[]>& d_status,
31 const std::unique_ptr<uint8_t[]>& moving_edge_red,
32 const std::unique_ptr<uint8_t[]>& x_density,
33 const std::unique_ptr<uint8_t[]>& y_density,
Yves Gerey665174f2018-06-19 15:03:05 +020034 const uint8_t* u_src,
35 int stride_u_src,
36 const uint8_t* v_src,
37 int stride_v_src,
38 uint8_t* u_dst,
39 int stride_u_dst,
40 uint8_t* v_dst,
41 int stride_v_dst,
jackychenafaae0d2016-04-12 23:02:55 -070042 int mb_rows_,
nisse18ee17d2016-11-07 01:34:59 -080043 int mb_cols_) {
jackychenafaae0d2016-04-12 23:02:55 -070044 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
45 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
46 int mb_index = mb_row * mb_cols_ + mb_col;
47 const uint8_t* mb_src_u =
nisse18ee17d2016-11-07 01:34:59 -080048 u_src + (mb_row << 3) * stride_u_src + (mb_col << 3);
jackychenafaae0d2016-04-12 23:02:55 -070049 const uint8_t* mb_src_v =
nisse18ee17d2016-11-07 01:34:59 -080050 v_src + (mb_row << 3) * stride_v_src + (mb_col << 3);
51 uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_dst + (mb_col << 3);
52 uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_dst + (mb_col << 3);
jackychenafaae0d2016-04-12 23:02:55 -070053 uint8_t uv_tmp[8 * 8];
54 memset(uv_tmp, 200, 8 * 8);
55 if (d_status[mb_index] == 1) {
56 // Paint to red.
nisse18ee17d2016-11-07 01:34:59 -080057 CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst);
58 CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_dst);
jackychenafaae0d2016-04-12 23:02:55 -070059 } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
60 x_density[mb_col] * y_density[mb_row]) {
61 // Paint to blue.
nisse18ee17d2016-11-07 01:34:59 -080062 CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_dst);
63 CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst);
jackychenafaae0d2016-04-12 23:02:55 -070064 } else {
nisse18ee17d2016-11-07 01:34:59 -080065 CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst);
66 CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst);
jackychenafaae0d2016-04-12 23:02:55 -070067 }
68 }
69 }
70}
71#endif
72
jackychen67e94fb2016-01-11 21:34:07 -080073VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
74 : width_(0),
75 height_(0),
jackychenfa0befe2016-04-01 07:46:58 -070076 filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)),
77 ne_(new NoiseEstimation()) {}
jackychen8f9902a2015-11-26 02:59:48 -080078
Magnus Jedvert7a721e82017-06-14 11:28:08 +020079void VideoDenoiser::DenoiserReset(
80 rtc::scoped_refptr<I420BufferInterface> frame) {
Niels Möller6af2e862016-06-17 09:12:44 +020081 width_ = frame->width();
82 height_ = frame->height();
jackychenafaae0d2016-04-12 23:02:55 -070083 mb_cols_ = width_ >> 4;
84 mb_rows_ = height_ >> 4;
jackychenafaae0d2016-04-12 23:02:55 -070085
86 // Init noise estimator and allocate buffers.
87 ne_->Init(width_, height_, cpu_type_);
88 moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]);
89 mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]);
90 x_density_.reset(new uint8_t[mb_cols_]);
91 y_density_.reset(new uint8_t[mb_rows_]);
92 moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]);
93}
94
95int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) {
96 if (noise_level == 0)
jackychenfa0befe2016-04-01 07:46:58 -070097 return 1;
jackychenafaae0d2016-04-12 23:02:55 -070098 if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) ||
99 (mb_col >= (15 * mb_cols_ >> 4)))
100 return 3;
101 else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) ||
102 (mb_col >= (7 * mb_cols_ >> 3)))
jackychenfa0befe2016-04-01 07:46:58 -0700103 return 2;
104 else
jackychenafaae0d2016-04-12 23:02:55 -0700105 return 1;
jackychenfa0befe2016-04-01 07:46:58 -0700106}
107
jackychenafaae0d2016-04-12 23:02:55 -0700108void VideoDenoiser::ReduceFalseDetection(
109 const std::unique_ptr<uint8_t[]>& d_status,
110 std::unique_ptr<uint8_t[]>* moving_edge_red,
111 int noise_level) {
112 // From up left corner.
113 int mb_col_stop = mb_cols_ - 1;
114 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
115 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
116 if (d_status[mb_row * mb_cols_ + mb_col]) {
117 mb_col_stop = mb_col - 1;
118 break;
119 }
120 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700121 }
122 }
jackychenafaae0d2016-04-12 23:02:55 -0700123 // From bottom left corner.
124 mb_col_stop = mb_cols_ - 1;
125 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
126 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
127 if (d_status[mb_row * mb_cols_ + mb_col]) {
128 mb_col_stop = mb_col - 1;
129 break;
130 }
131 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700132 }
133 }
jackychenafaae0d2016-04-12 23:02:55 -0700134 // From up right corner.
135 mb_col_stop = 0;
136 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
137 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
138 if (d_status[mb_row * mb_cols_ + mb_col]) {
139 mb_col_stop = mb_col + 1;
140 break;
141 }
142 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700143 }
144 }
jackychenafaae0d2016-04-12 23:02:55 -0700145 // From bottom right corner.
146 mb_col_stop = 0;
147 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
148 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
149 if (d_status[mb_row * mb_cols_ + mb_col]) {
150 mb_col_stop = mb_col + 1;
151 break;
152 }
153 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychen8f9902a2015-11-26 02:59:48 -0800154 }
155 }
156}
157
jackychenafaae0d2016-04-12 23:02:55 -0700158bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status,
159 int mb_row,
160 int mb_col) {
161 bool ret = false;
162 int mb_index = mb_row * mb_cols_ + mb_col;
163 if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1)
164 ret = false;
165 else
166 ret = d_status[mb_index + 1] || d_status[mb_index - 1] ||
167 d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_];
168 return ret;
jackychenfa0befe2016-04-01 07:46:58 -0700169}
jackychenfa0befe2016-04-01 07:46:58 -0700170
nisse18ee17d2016-11-07 01:34:59 -0800171void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src,
172 int stride_src,
173 uint8_t* y_dst,
174 int stride_dst) {
jackychenafaae0d2016-04-12 23:02:55 -0700175 // Loop over to copy src block if the block is marked as moving object block
176 // or if the block may cause trailing artifacts.
177 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
178 const int mb_index_base = mb_row * mb_cols_;
nisse18ee17d2016-11-07 01:34:59 -0800179 const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_src;
180 uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_dst;
jackychenafaae0d2016-04-12 23:02:55 -0700181 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
182 const int mb_index = mb_index_base + mb_col;
183 const uint32_t offset_col = mb_col << 4;
184 const uint8_t* mb_src = mb_src_base + offset_col;
185 uint8_t* mb_dst = mb_dst_base + offset_col;
186 // Check if the block is a moving object block or may cause a trailing
187 // artifacts.
188 if (mb_filter_decision_[mb_index] != FILTER_BLOCK ||
189 IsTrailingBlock(moving_edge_, mb_row, mb_col) ||
190 (x_density_[mb_col] * y_density_[mb_row] &&
191 moving_object_[mb_row * mb_cols_ + mb_col])) {
192 // Copy y source.
nisse18ee17d2016-11-07 01:34:59 -0800193 filter_->CopyMem16x16(mb_src, stride_src, mb_dst, stride_dst);
jackychenfa0befe2016-04-01 07:46:58 -0700194 }
195 }
196 }
197}
jackychenfa0befe2016-04-01 07:46:58 -0700198
nisse18ee17d2016-11-07 01:34:59 -0800199void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src,
200 int stride_src,
201 uint8_t* y_dst,
202 int stride_dst) {
203 int height_margin = height_ - (mb_rows_ << 4);
204 if (height_margin > 0) {
205 const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_src;
206 uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_dst;
207 libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
208 width_, height_margin);
jackychen6650d6d2016-04-25 16:53:59 -0700209 }
nisse18ee17d2016-11-07 01:34:59 -0800210 int width_margin = width_ - (mb_cols_ << 4);
211 if (width_margin > 0) {
jackychen6650d6d2016-04-25 16:53:59 -0700212 const uint8_t* margin_y_src = y_src + (mb_cols_ << 4);
213 uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4);
nisse18ee17d2016-11-07 01:34:59 -0800214 libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
215 width_ - (mb_cols_ << 4), mb_rows_ << 4);
jackychen6650d6d2016-04-25 16:53:59 -0700216 }
217}
218
Magnus Jedvert7a721e82017-06-14 11:28:08 +0200219rtc::scoped_refptr<I420BufferInterface> VideoDenoiser::DenoiseFrame(
220 rtc::scoped_refptr<I420BufferInterface> frame,
Niels Möller6af2e862016-06-17 09:12:44 +0200221 bool noise_estimation_enabled) {
jackychenafaae0d2016-04-12 23:02:55 -0700222 // If previous width and height are different from current frame's, need to
223 // reallocate the buffers and no denoising for the current frame.
nisse18ee17d2016-11-07 01:34:59 -0800224 if (!prev_buffer_ || width_ != frame->width() || height_ != frame->height()) {
225 DenoiserReset(frame);
226 prev_buffer_ = frame;
227 return frame;
jackychen8f9902a2015-11-26 02:59:48 -0800228 }
jackychenfa0befe2016-04-01 07:46:58 -0700229
jackychenafaae0d2016-04-12 23:02:55 -0700230 // Set buffer pointers.
Niels Möller6af2e862016-06-17 09:12:44 +0200231 const uint8_t* y_src = frame->DataY();
nisse18ee17d2016-11-07 01:34:59 -0800232 int stride_y_src = frame->StrideY();
233 rtc::scoped_refptr<I420Buffer> dst =
234 buffer_pool_.CreateBuffer(width_, height_);
235
236 uint8_t* y_dst = dst->MutableDataY();
237 int stride_y_dst = dst->StrideY();
238
239 const uint8_t* y_dst_prev = prev_buffer_->DataY();
240 int stride_prev = prev_buffer_->StrideY();
241
jackychenafaae0d2016-04-12 23:02:55 -0700242 memset(x_density_.get(), 0, mb_cols_);
243 memset(y_density_.get(), 0, mb_rows_);
244 memset(moving_object_.get(), 1, mb_cols_ * mb_rows_);
jackychenfa0befe2016-04-01 07:46:58 -0700245
jackychenafaae0d2016-04-12 23:02:55 -0700246 uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0;
jackychen9bfa1062016-05-03 11:21:26 -0700247 int thr_var_base = 16 * 16 * 2;
jackychenfa0befe2016-04-01 07:46:58 -0700248 // Loop over blocks to accumulate/extract noise level and update x/y_density
249 // factors for moving object detection.
jackychenafaae0d2016-04-12 23:02:55 -0700250 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
251 const int mb_index_base = mb_row * mb_cols_;
nisse18ee17d2016-11-07 01:34:59 -0800252 const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_y_src;
253 uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_y_dst;
254 const uint8_t* mb_dst_prev_base = y_dst_prev + (mb_row << 4) * stride_prev;
jackychenafaae0d2016-04-12 23:02:55 -0700255 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
256 const int mb_index = mb_index_base + mb_col;
257 const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0);
258 const int pos_factor = PositionCheck(mb_row, mb_col, noise_level);
259 const uint32_t thr_var_adp = thr_var_base * pos_factor;
260 const uint32_t offset_col = mb_col << 4;
261 const uint8_t* mb_src = mb_src_base + offset_col;
262 uint8_t* mb_dst = mb_dst_base + offset_col;
nisse18ee17d2016-11-07 01:34:59 -0800263 const uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col;
jackychenafaae0d2016-04-12 23:02:55 -0700264
265 // TODO(jackychen): Need SSE2/NEON opt.
266 int luma = 0;
267 if (ne_enable) {
268 for (int i = 4; i < 12; ++i) {
269 for (int j = 4; j < 12; ++j) {
nisse18ee17d2016-11-07 01:34:59 -0800270 luma += mb_src[i * stride_y_src + j];
jackychenafaae0d2016-04-12 23:02:55 -0700271 }
jackychenfa0befe2016-04-01 07:46:58 -0700272 }
273 }
274
jackychenafaae0d2016-04-12 23:02:55 -0700275 // Get the filtered block and filter_decision.
276 mb_filter_decision_[mb_index] =
nisse18ee17d2016-11-07 01:34:59 -0800277 filter_->MbDenoise(mb_dst_prev, stride_prev, mb_dst, stride_y_dst,
278 mb_src, stride_y_src, 0, noise_level);
jackychenfa0befe2016-04-01 07:46:58 -0700279
jackychenafaae0d2016-04-12 23:02:55 -0700280 // If filter decision is FILTER_BLOCK, no need to check moving edge.
281 // It is unlikely for a moving edge block to be filtered in current
282 // setting.
283 if (mb_filter_decision_[mb_index] == FILTER_BLOCK) {
284 uint32_t sse_t = 0;
285 if (ne_enable) {
286 // The variance used in noise estimation is based on the src block in
287 // time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
nisse18ee17d2016-11-07 01:34:59 -0800288 uint32_t noise_var = filter_->Variance16x8(
289 mb_dst_prev, stride_y_dst, mb_src, stride_y_src, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700290 ne_->GetNoise(mb_index, noise_var, luma);
jackychenfa0befe2016-04-01 07:46:58 -0700291 }
jackychenafaae0d2016-04-12 23:02:55 -0700292 moving_edge_[mb_index] = 0; // Not a moving edge block.
jackychenfa0befe2016-04-01 07:46:58 -0700293 } else {
294 uint32_t sse_t = 0;
jackychenafaae0d2016-04-12 23:02:55 -0700295 // The variance used in MOD is based on the filtered blocks in time
296 // T (mb_dst) and T-1 (mb_dst_prev).
nisse18ee17d2016-11-07 01:34:59 -0800297 uint32_t noise_var = filter_->Variance16x8(
298 mb_dst_prev, stride_prev, mb_dst, stride_y_dst, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700299 if (noise_var > thr_var_adp) { // Moving edge checking.
300 if (ne_enable) {
301 ne_->ResetConsecLowVar(mb_index);
302 }
303 moving_edge_[mb_index] = 1; // Mark as moving edge block.
304 x_density_[mb_col] += (pos_factor < 3);
305 y_density_[mb_row] += (pos_factor < 3);
jackychenfa0befe2016-04-01 07:46:58 -0700306 } else {
jackychenafaae0d2016-04-12 23:02:55 -0700307 moving_edge_[mb_index] = 0;
308 if (ne_enable) {
309 // The variance used in noise estimation is based on the src block
310 // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
311 uint32_t noise_var = filter_->Variance16x8(
nisse18ee17d2016-11-07 01:34:59 -0800312 mb_dst_prev, stride_prev, mb_src, stride_y_src, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700313 ne_->GetNoise(mb_index, noise_var, luma);
314 }
jackychenfa0befe2016-04-01 07:46:58 -0700315 }
jackychenfa0befe2016-04-01 07:46:58 -0700316 }
jackychenafaae0d2016-04-12 23:02:55 -0700317 } // End of for loop
318 } // End of for loop
319
320 ReduceFalseDetection(moving_edge_, &moving_object_, noise_level);
321
nisse18ee17d2016-11-07 01:34:59 -0800322 CopySrcOnMOB(y_src, stride_y_src, y_dst, stride_y_dst);
jackychenafaae0d2016-04-12 23:02:55 -0700323
jackychen6650d6d2016-04-25 16:53:59 -0700324 // When frame width/height not divisible by 16, copy the margin to
325 // denoised_frame.
326 if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_)
nisse18ee17d2016-11-07 01:34:59 -0800327 CopyLumaOnMargin(y_src, stride_y_src, y_dst, stride_y_dst);
jackychen6650d6d2016-04-25 16:53:59 -0700328
jackychenafaae0d2016-04-12 23:02:55 -0700329 // Copy u/v planes.
Yves Gerey665174f2018-06-19 15:03:05 +0200330 libyuv::CopyPlane(frame->DataU(), frame->StrideU(), dst->MutableDataU(),
331 dst->StrideU(), (width_ + 1) >> 1, (height_ + 1) >> 1);
332 libyuv::CopyPlane(frame->DataV(), frame->StrideV(), dst->MutableDataV(),
333 dst->StrideV(), (width_ + 1) >> 1, (height_ + 1) >> 1);
jackychenafaae0d2016-04-12 23:02:55 -0700334
jackychen8556c482016-04-20 16:04:31 -0700335#if DISPLAY || DISPLAYNEON
jackychenfa0befe2016-04-01 07:46:58 -0700336 // Show rectangular region
nisse18ee17d2016-11-07 01:34:59 -0800337 ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_,
338 frame->DataU(), frame->StrideU(), frame->DataV(), frame->StrideV(),
Yves Gerey665174f2018-06-19 15:03:05 +0200339 dst->MutableDataU(), dst->StrideU(), dst->MutableDataV(),
340 dst->StrideV(), mb_rows_, mb_cols_);
jackychenfa0befe2016-04-01 07:46:58 -0700341#endif
nisse18ee17d2016-11-07 01:34:59 -0800342 prev_buffer_ = dst;
343 return dst;
jackychen8f9902a2015-11-26 02:59:48 -0800344}
345
346} // namespace webrtc