blob: 8c645f21cb7e834e353a8f219281d0f3acb44ad3 [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
jackychen8f9902a2015-11-26 02:59:48 -080011#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
12#include "webrtc/modules/video_processing/video_denoiser.h"
nisse18ee17d2016-11-07 01:34:59 -080013#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,
nisse18ee17d2016-11-07 01:34:59 -080034 const uint8_t* u_src, int stride_u_src,
35 const uint8_t* v_src, int stride_v_src,
36 uint8_t* u_dst, int stride_u_dst,
37 uint8_t* v_dst, int stride_v_dst,
jackychenafaae0d2016-04-12 23:02:55 -070038 int mb_rows_,
nisse18ee17d2016-11-07 01:34:59 -080039 int mb_cols_) {
jackychenafaae0d2016-04-12 23:02:55 -070040 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
41 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
42 int mb_index = mb_row * mb_cols_ + mb_col;
43 const uint8_t* mb_src_u =
nisse18ee17d2016-11-07 01:34:59 -080044 u_src + (mb_row << 3) * stride_u_src + (mb_col << 3);
jackychenafaae0d2016-04-12 23:02:55 -070045 const uint8_t* mb_src_v =
nisse18ee17d2016-11-07 01:34:59 -080046 v_src + (mb_row << 3) * stride_v_src + (mb_col << 3);
47 uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_dst + (mb_col << 3);
48 uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_dst + (mb_col << 3);
jackychenafaae0d2016-04-12 23:02:55 -070049 uint8_t uv_tmp[8 * 8];
50 memset(uv_tmp, 200, 8 * 8);
51 if (d_status[mb_index] == 1) {
52 // Paint to red.
nisse18ee17d2016-11-07 01:34:59 -080053 CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst);
54 CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_dst);
jackychenafaae0d2016-04-12 23:02:55 -070055 } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
56 x_density[mb_col] * y_density[mb_row]) {
57 // Paint to blue.
nisse18ee17d2016-11-07 01:34:59 -080058 CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_dst);
59 CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst);
jackychenafaae0d2016-04-12 23:02:55 -070060 } else {
nisse18ee17d2016-11-07 01:34:59 -080061 CopyMem8x8(mb_src_u, stride_u_src, mb_dst_u, stride_u_dst);
62 CopyMem8x8(mb_src_v, stride_v_src, mb_dst_v, stride_v_dst);
jackychenafaae0d2016-04-12 23:02:55 -070063 }
64 }
65 }
66}
67#endif
68
jackychen67e94fb2016-01-11 21:34:07 -080069VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
70 : width_(0),
71 height_(0),
jackychenfa0befe2016-04-01 07:46:58 -070072 filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)),
73 ne_(new NoiseEstimation()) {}
jackychen8f9902a2015-11-26 02:59:48 -080074
nisse18ee17d2016-11-07 01:34:59 -080075void VideoDenoiser::DenoiserReset(rtc::scoped_refptr<VideoFrameBuffer> frame) {
Niels Möller6af2e862016-06-17 09:12:44 +020076 width_ = frame->width();
77 height_ = frame->height();
jackychenafaae0d2016-04-12 23:02:55 -070078 mb_cols_ = width_ >> 4;
79 mb_rows_ = height_ >> 4;
jackychenafaae0d2016-04-12 23:02:55 -070080
81 // Init noise estimator and allocate buffers.
82 ne_->Init(width_, height_, cpu_type_);
83 moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]);
84 mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]);
85 x_density_.reset(new uint8_t[mb_cols_]);
86 y_density_.reset(new uint8_t[mb_rows_]);
87 moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]);
88}
89
90int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) {
91 if (noise_level == 0)
jackychenfa0befe2016-04-01 07:46:58 -070092 return 1;
jackychenafaae0d2016-04-12 23:02:55 -070093 if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) ||
94 (mb_col >= (15 * mb_cols_ >> 4)))
95 return 3;
96 else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) ||
97 (mb_col >= (7 * mb_cols_ >> 3)))
jackychenfa0befe2016-04-01 07:46:58 -070098 return 2;
99 else
jackychenafaae0d2016-04-12 23:02:55 -0700100 return 1;
jackychenfa0befe2016-04-01 07:46:58 -0700101}
102
jackychenafaae0d2016-04-12 23:02:55 -0700103void VideoDenoiser::ReduceFalseDetection(
104 const std::unique_ptr<uint8_t[]>& d_status,
105 std::unique_ptr<uint8_t[]>* moving_edge_red,
106 int noise_level) {
107 // From up left corner.
108 int mb_col_stop = mb_cols_ - 1;
109 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
110 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
111 if (d_status[mb_row * mb_cols_ + mb_col]) {
112 mb_col_stop = mb_col - 1;
113 break;
114 }
115 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700116 }
117 }
jackychenafaae0d2016-04-12 23:02:55 -0700118 // From bottom left corner.
119 mb_col_stop = mb_cols_ - 1;
120 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
121 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
122 if (d_status[mb_row * mb_cols_ + mb_col]) {
123 mb_col_stop = mb_col - 1;
124 break;
125 }
126 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700127 }
128 }
jackychenafaae0d2016-04-12 23:02:55 -0700129 // From up right corner.
130 mb_col_stop = 0;
131 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
132 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
133 if (d_status[mb_row * mb_cols_ + mb_col]) {
134 mb_col_stop = mb_col + 1;
135 break;
136 }
137 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700138 }
139 }
jackychenafaae0d2016-04-12 23:02:55 -0700140 // From bottom right corner.
141 mb_col_stop = 0;
142 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
143 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
144 if (d_status[mb_row * mb_cols_ + mb_col]) {
145 mb_col_stop = mb_col + 1;
146 break;
147 }
148 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychen8f9902a2015-11-26 02:59:48 -0800149 }
150 }
151}
152
jackychenafaae0d2016-04-12 23:02:55 -0700153bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status,
154 int mb_row,
155 int mb_col) {
156 bool ret = false;
157 int mb_index = mb_row * mb_cols_ + mb_col;
158 if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1)
159 ret = false;
160 else
161 ret = d_status[mb_index + 1] || d_status[mb_index - 1] ||
162 d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_];
163 return ret;
jackychenfa0befe2016-04-01 07:46:58 -0700164}
jackychenfa0befe2016-04-01 07:46:58 -0700165
nisse18ee17d2016-11-07 01:34:59 -0800166void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src,
167 int stride_src,
168 uint8_t* y_dst,
169 int stride_dst) {
jackychenafaae0d2016-04-12 23:02:55 -0700170 // Loop over to copy src block if the block is marked as moving object block
171 // or if the block may cause trailing artifacts.
172 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
173 const int mb_index_base = mb_row * mb_cols_;
nisse18ee17d2016-11-07 01:34:59 -0800174 const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_src;
175 uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_dst;
jackychenafaae0d2016-04-12 23:02:55 -0700176 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
177 const int mb_index = mb_index_base + mb_col;
178 const uint32_t offset_col = mb_col << 4;
179 const uint8_t* mb_src = mb_src_base + offset_col;
180 uint8_t* mb_dst = mb_dst_base + offset_col;
181 // Check if the block is a moving object block or may cause a trailing
182 // artifacts.
183 if (mb_filter_decision_[mb_index] != FILTER_BLOCK ||
184 IsTrailingBlock(moving_edge_, mb_row, mb_col) ||
185 (x_density_[mb_col] * y_density_[mb_row] &&
186 moving_object_[mb_row * mb_cols_ + mb_col])) {
187 // Copy y source.
nisse18ee17d2016-11-07 01:34:59 -0800188 filter_->CopyMem16x16(mb_src, stride_src, mb_dst, stride_dst);
jackychenfa0befe2016-04-01 07:46:58 -0700189 }
190 }
191 }
192}
jackychenfa0befe2016-04-01 07:46:58 -0700193
nisse18ee17d2016-11-07 01:34:59 -0800194void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src,
195 int stride_src,
196 uint8_t* y_dst,
197 int stride_dst) {
198 int height_margin = height_ - (mb_rows_ << 4);
199 if (height_margin > 0) {
200 const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_src;
201 uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_dst;
202 libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
203 width_, height_margin);
jackychen6650d6d2016-04-25 16:53:59 -0700204 }
nisse18ee17d2016-11-07 01:34:59 -0800205 int width_margin = width_ - (mb_cols_ << 4);
206 if (width_margin > 0) {
jackychen6650d6d2016-04-25 16:53:59 -0700207 const uint8_t* margin_y_src = y_src + (mb_cols_ << 4);
208 uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4);
nisse18ee17d2016-11-07 01:34:59 -0800209 libyuv::CopyPlane(margin_y_src, stride_src, margin_y_dst, stride_dst,
210 width_ - (mb_cols_ << 4), mb_rows_ << 4);
jackychen6650d6d2016-04-25 16:53:59 -0700211 }
212}
213
nisse18ee17d2016-11-07 01:34:59 -0800214rtc::scoped_refptr<VideoFrameBuffer> VideoDenoiser::DenoiseFrame(
215 rtc::scoped_refptr<VideoFrameBuffer> frame,
Niels Möller6af2e862016-06-17 09:12:44 +0200216 bool noise_estimation_enabled) {
jackychenafaae0d2016-04-12 23:02:55 -0700217 // If previous width and height are different from current frame's, need to
218 // reallocate the buffers and no denoising for the current frame.
nisse18ee17d2016-11-07 01:34:59 -0800219 if (!prev_buffer_ || width_ != frame->width() || height_ != frame->height()) {
220 DenoiserReset(frame);
221 prev_buffer_ = frame;
222 return frame;
jackychen8f9902a2015-11-26 02:59:48 -0800223 }
jackychenfa0befe2016-04-01 07:46:58 -0700224
jackychenafaae0d2016-04-12 23:02:55 -0700225 // Set buffer pointers.
Niels Möller6af2e862016-06-17 09:12:44 +0200226 const uint8_t* y_src = frame->DataY();
nisse18ee17d2016-11-07 01:34:59 -0800227 int stride_y_src = frame->StrideY();
228 rtc::scoped_refptr<I420Buffer> dst =
229 buffer_pool_.CreateBuffer(width_, height_);
230
231 uint8_t* y_dst = dst->MutableDataY();
232 int stride_y_dst = dst->StrideY();
233
234 const uint8_t* y_dst_prev = prev_buffer_->DataY();
235 int stride_prev = prev_buffer_->StrideY();
236
jackychenafaae0d2016-04-12 23:02:55 -0700237 memset(x_density_.get(), 0, mb_cols_);
238 memset(y_density_.get(), 0, mb_rows_);
239 memset(moving_object_.get(), 1, mb_cols_ * mb_rows_);
jackychenfa0befe2016-04-01 07:46:58 -0700240
jackychenafaae0d2016-04-12 23:02:55 -0700241 uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0;
jackychen9bfa1062016-05-03 11:21:26 -0700242 int thr_var_base = 16 * 16 * 2;
jackychenfa0befe2016-04-01 07:46:58 -0700243 // Loop over blocks to accumulate/extract noise level and update x/y_density
244 // factors for moving object detection.
jackychenafaae0d2016-04-12 23:02:55 -0700245 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
246 const int mb_index_base = mb_row * mb_cols_;
nisse18ee17d2016-11-07 01:34:59 -0800247 const uint8_t* mb_src_base = y_src + (mb_row << 4) * stride_y_src;
248 uint8_t* mb_dst_base = y_dst + (mb_row << 4) * stride_y_dst;
249 const uint8_t* mb_dst_prev_base = y_dst_prev + (mb_row << 4) * stride_prev;
jackychenafaae0d2016-04-12 23:02:55 -0700250 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
251 const int mb_index = mb_index_base + mb_col;
252 const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0);
253 const int pos_factor = PositionCheck(mb_row, mb_col, noise_level);
254 const uint32_t thr_var_adp = thr_var_base * pos_factor;
255 const uint32_t offset_col = mb_col << 4;
256 const uint8_t* mb_src = mb_src_base + offset_col;
257 uint8_t* mb_dst = mb_dst_base + offset_col;
nisse18ee17d2016-11-07 01:34:59 -0800258 const uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col;
jackychenafaae0d2016-04-12 23:02:55 -0700259
260 // TODO(jackychen): Need SSE2/NEON opt.
261 int luma = 0;
262 if (ne_enable) {
263 for (int i = 4; i < 12; ++i) {
264 for (int j = 4; j < 12; ++j) {
nisse18ee17d2016-11-07 01:34:59 -0800265 luma += mb_src[i * stride_y_src + j];
jackychenafaae0d2016-04-12 23:02:55 -0700266 }
jackychenfa0befe2016-04-01 07:46:58 -0700267 }
268 }
269
jackychenafaae0d2016-04-12 23:02:55 -0700270 // Get the filtered block and filter_decision.
271 mb_filter_decision_[mb_index] =
nisse18ee17d2016-11-07 01:34:59 -0800272 filter_->MbDenoise(mb_dst_prev, stride_prev, mb_dst, stride_y_dst,
273 mb_src, stride_y_src, 0, noise_level);
jackychenfa0befe2016-04-01 07:46:58 -0700274
jackychenafaae0d2016-04-12 23:02:55 -0700275 // If filter decision is FILTER_BLOCK, no need to check moving edge.
276 // It is unlikely for a moving edge block to be filtered in current
277 // setting.
278 if (mb_filter_decision_[mb_index] == FILTER_BLOCK) {
279 uint32_t sse_t = 0;
280 if (ne_enable) {
281 // The variance used in noise estimation is based on the src block in
282 // time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
nisse18ee17d2016-11-07 01:34:59 -0800283 uint32_t noise_var = filter_->Variance16x8(
284 mb_dst_prev, stride_y_dst, mb_src, stride_y_src, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700285 ne_->GetNoise(mb_index, noise_var, luma);
jackychenfa0befe2016-04-01 07:46:58 -0700286 }
jackychenafaae0d2016-04-12 23:02:55 -0700287 moving_edge_[mb_index] = 0; // Not a moving edge block.
jackychenfa0befe2016-04-01 07:46:58 -0700288 } else {
289 uint32_t sse_t = 0;
jackychenafaae0d2016-04-12 23:02:55 -0700290 // The variance used in MOD is based on the filtered blocks in time
291 // T (mb_dst) and T-1 (mb_dst_prev).
nisse18ee17d2016-11-07 01:34:59 -0800292 uint32_t noise_var = filter_->Variance16x8(
293 mb_dst_prev, stride_prev, mb_dst, stride_y_dst, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700294 if (noise_var > thr_var_adp) { // Moving edge checking.
295 if (ne_enable) {
296 ne_->ResetConsecLowVar(mb_index);
297 }
298 moving_edge_[mb_index] = 1; // Mark as moving edge block.
299 x_density_[mb_col] += (pos_factor < 3);
300 y_density_[mb_row] += (pos_factor < 3);
jackychenfa0befe2016-04-01 07:46:58 -0700301 } else {
jackychenafaae0d2016-04-12 23:02:55 -0700302 moving_edge_[mb_index] = 0;
303 if (ne_enable) {
304 // The variance used in noise estimation is based on the src block
305 // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
306 uint32_t noise_var = filter_->Variance16x8(
nisse18ee17d2016-11-07 01:34:59 -0800307 mb_dst_prev, stride_prev, mb_src, stride_y_src, &sse_t);
jackychenafaae0d2016-04-12 23:02:55 -0700308 ne_->GetNoise(mb_index, noise_var, luma);
309 }
jackychenfa0befe2016-04-01 07:46:58 -0700310 }
jackychenfa0befe2016-04-01 07:46:58 -0700311 }
jackychenafaae0d2016-04-12 23:02:55 -0700312 } // End of for loop
313 } // End of for loop
314
315 ReduceFalseDetection(moving_edge_, &moving_object_, noise_level);
316
nisse18ee17d2016-11-07 01:34:59 -0800317 CopySrcOnMOB(y_src, stride_y_src, y_dst, stride_y_dst);
jackychenafaae0d2016-04-12 23:02:55 -0700318
jackychen6650d6d2016-04-25 16:53:59 -0700319 // When frame width/height not divisible by 16, copy the margin to
320 // denoised_frame.
321 if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_)
nisse18ee17d2016-11-07 01:34:59 -0800322 CopyLumaOnMargin(y_src, stride_y_src, y_dst, stride_y_dst);
jackychen6650d6d2016-04-25 16:53:59 -0700323
jackychenafaae0d2016-04-12 23:02:55 -0700324 // Copy u/v planes.
nisse18ee17d2016-11-07 01:34:59 -0800325 libyuv::CopyPlane(frame->DataU(), frame->StrideU(),
326 dst->MutableDataU(), dst->StrideU(),
327 (width_ + 1) >> 1, (height_ + 1) >> 1);
328 libyuv::CopyPlane(frame->DataV(), frame->StrideV(),
329 dst->MutableDataV(), dst->StrideV(),
330 (width_ + 1) >> 1, (height_ + 1) >> 1);
jackychenafaae0d2016-04-12 23:02:55 -0700331
jackychen8556c482016-04-20 16:04:31 -0700332#if DISPLAY || DISPLAYNEON
jackychenfa0befe2016-04-01 07:46:58 -0700333 // Show rectangular region
nisse18ee17d2016-11-07 01:34:59 -0800334 ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_,
335 frame->DataU(), frame->StrideU(), frame->DataV(), frame->StrideV(),
336 dst->MutableDataU(), dst->StrideU(),
337 dst->MutableDataV(), dst->StrideV(),
338 mb_rows_, mb_cols_);
jackychenfa0befe2016-04-01 07:46:58 -0700339#endif
nisse18ee17d2016-11-07 01:34:59 -0800340 prev_buffer_ = dst;
341 return dst;
jackychen8f9902a2015-11-26 02:59:48 -0800342}
343
344} // namespace webrtc