blob: 61a11fe83b5df56086173f5b1a811d274a4f0b2a [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/scaler.h"
12#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
13#include "webrtc/modules/video_processing/video_denoiser.h"
14
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,
34 const uint8_t* u_src,
35 const uint8_t* v_src,
36 uint8_t* u_dst,
37 uint8_t* v_dst,
38 int mb_rows_,
39 int mb_cols_,
40 int stride_u_,
41 int stride_v_) {
42 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
43 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
44 int mb_index = mb_row * mb_cols_ + mb_col;
45 const uint8_t* mb_src_u =
46 u_src + (mb_row << 3) * stride_u_ + (mb_col << 3);
47 const uint8_t* mb_src_v =
48 v_src + (mb_row << 3) * stride_v_ + (mb_col << 3);
49 uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_ + (mb_col << 3);
50 uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_ + (mb_col << 3);
51 uint8_t uv_tmp[8 * 8];
52 memset(uv_tmp, 200, 8 * 8);
53 if (d_status[mb_index] == 1) {
54 // Paint to red.
55 CopyMem8x8(mb_src_u, stride_u_, mb_dst_u, stride_u_);
56 CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_);
57 } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
58 x_density[mb_col] * y_density[mb_row]) {
59 // Paint to blue.
60 CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_);
61 CopyMem8x8(mb_src_v, stride_v_, mb_dst_v, stride_v_);
62 } else {
63 CopyMem8x8(mb_src_u, stride_u_, mb_dst_u, stride_u_);
64 CopyMem8x8(mb_src_v, stride_v_, mb_dst_v, stride_v_);
65 }
66 }
67 }
68}
69#endif
70
jackychen67e94fb2016-01-11 21:34:07 -080071VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
72 : width_(0),
73 height_(0),
jackychenfa0befe2016-04-01 07:46:58 -070074 filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)),
75 ne_(new NoiseEstimation()) {}
jackychen8f9902a2015-11-26 02:59:48 -080076
jackychenafaae0d2016-04-12 23:02:55 -070077void VideoDenoiser::DenoiserReset(const VideoFrame& frame,
78 VideoFrame* denoised_frame,
79 VideoFrame* denoised_frame_prev) {
80 width_ = frame.width();
81 height_ = frame.height();
82 mb_cols_ = width_ >> 4;
83 mb_rows_ = height_ >> 4;
nissea0591b52016-04-29 02:09:28 -070084 stride_y_ = frame.video_frame_buffer()->StrideY();
85 stride_u_ = frame.video_frame_buffer()->StrideU();
86 stride_v_ = frame.video_frame_buffer()->StrideV();
jackychenafaae0d2016-04-12 23:02:55 -070087
88 // Allocate an empty buffer for denoised_frame_prev.
89 denoised_frame_prev->CreateEmptyFrame(width_, height_, stride_y_, stride_u_,
90 stride_v_);
91 // Allocate and initialize denoised_frame with key frame.
nissea0591b52016-04-29 02:09:28 -070092 denoised_frame->CreateFrame(
93 frame.video_frame_buffer()->DataY(),
94 frame.video_frame_buffer()->DataU(),
95 frame.video_frame_buffer()->DataV(),
96 width_, height_, stride_y_, stride_u_, stride_v_, kVideoRotation_0);
jackychenafaae0d2016-04-12 23:02:55 -070097 // Set time parameters to the output frame.
98 denoised_frame->set_timestamp(frame.timestamp());
99 denoised_frame->set_render_time_ms(frame.render_time_ms());
100
101 // Init noise estimator and allocate buffers.
102 ne_->Init(width_, height_, cpu_type_);
103 moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]);
104 mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]);
105 x_density_.reset(new uint8_t[mb_cols_]);
106 y_density_.reset(new uint8_t[mb_rows_]);
107 moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]);
108}
109
110int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) {
111 if (noise_level == 0)
jackychenfa0befe2016-04-01 07:46:58 -0700112 return 1;
jackychenafaae0d2016-04-12 23:02:55 -0700113 if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) ||
114 (mb_col >= (15 * mb_cols_ >> 4)))
115 return 3;
116 else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) ||
117 (mb_col >= (7 * mb_cols_ >> 3)))
jackychenfa0befe2016-04-01 07:46:58 -0700118 return 2;
119 else
jackychenafaae0d2016-04-12 23:02:55 -0700120 return 1;
jackychenfa0befe2016-04-01 07:46:58 -0700121}
122
jackychenafaae0d2016-04-12 23:02:55 -0700123void VideoDenoiser::ReduceFalseDetection(
124 const std::unique_ptr<uint8_t[]>& d_status,
125 std::unique_ptr<uint8_t[]>* moving_edge_red,
126 int noise_level) {
127 // From up left corner.
128 int mb_col_stop = mb_cols_ - 1;
129 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
130 for (int mb_col = 0; 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 left corner.
139 mb_col_stop = mb_cols_ - 1;
140 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
141 for (int mb_col = 0; 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;
jackychenfa0befe2016-04-01 07:46:58 -0700147 }
148 }
jackychenafaae0d2016-04-12 23:02:55 -0700149 // From up right corner.
150 mb_col_stop = 0;
151 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
152 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
153 if (d_status[mb_row * mb_cols_ + mb_col]) {
154 mb_col_stop = mb_col + 1;
155 break;
156 }
157 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700158 }
159 }
jackychenafaae0d2016-04-12 23:02:55 -0700160 // From bottom right corner.
161 mb_col_stop = 0;
162 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
163 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
164 if (d_status[mb_row * mb_cols_ + mb_col]) {
165 mb_col_stop = mb_col + 1;
166 break;
167 }
168 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychen8f9902a2015-11-26 02:59:48 -0800169 }
170 }
171}
172
jackychenafaae0d2016-04-12 23:02:55 -0700173bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status,
174 int mb_row,
175 int mb_col) {
176 bool ret = false;
177 int mb_index = mb_row * mb_cols_ + mb_col;
178 if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1)
179 ret = false;
180 else
181 ret = d_status[mb_index + 1] || d_status[mb_index - 1] ||
182 d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_];
183 return ret;
jackychenfa0befe2016-04-01 07:46:58 -0700184}
jackychenfa0befe2016-04-01 07:46:58 -0700185
jackychenafaae0d2016-04-12 23:02:55 -0700186void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src, uint8_t* y_dst) {
187 // Loop over to copy src block if the block is marked as moving object block
188 // or if the block may cause trailing artifacts.
189 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
190 const int mb_index_base = mb_row * mb_cols_;
191 const int offset_base = (mb_row << 4) * stride_y_;
192 const uint8_t* mb_src_base = y_src + offset_base;
193 uint8_t* mb_dst_base = y_dst + offset_base;
194 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
195 const int mb_index = mb_index_base + mb_col;
196 const uint32_t offset_col = mb_col << 4;
197 const uint8_t* mb_src = mb_src_base + offset_col;
198 uint8_t* mb_dst = mb_dst_base + offset_col;
199 // Check if the block is a moving object block or may cause a trailing
200 // artifacts.
201 if (mb_filter_decision_[mb_index] != FILTER_BLOCK ||
202 IsTrailingBlock(moving_edge_, mb_row, mb_col) ||
203 (x_density_[mb_col] * y_density_[mb_row] &&
204 moving_object_[mb_row * mb_cols_ + mb_col])) {
205 // Copy y source.
206 filter_->CopyMem16x16(mb_src, stride_y_, mb_dst, stride_y_);
jackychenfa0befe2016-04-01 07:46:58 -0700207 }
208 }
209 }
210}
jackychenfa0befe2016-04-01 07:46:58 -0700211
jackychen6650d6d2016-04-25 16:53:59 -0700212void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src, uint8_t* y_dst) {
213 if ((mb_rows_ << 4) != height_) {
214 const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_y_;
215 uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_y_;
216 memcpy(margin_y_dst, margin_y_src, (height_ - (mb_rows_ << 4)) * stride_y_);
217 }
218 if ((mb_cols_ << 4) != width_) {
219 const uint8_t* margin_y_src = y_src + (mb_cols_ << 4);
220 uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4);
221 for (int i = 0; i < height_; ++i) {
222 for (int j = mb_cols_ << 4; j < width_; ++j) {
223 margin_y_dst[i * stride_y_ + j] = margin_y_src[i * stride_y_ + j];
224 }
225 }
226 }
227}
228
jackychen8f9902a2015-11-26 02:59:48 -0800229void VideoDenoiser::DenoiseFrame(const VideoFrame& frame,
jackychenfa0befe2016-04-01 07:46:58 -0700230 VideoFrame* denoised_frame,
231 VideoFrame* denoised_frame_prev,
jackychenafaae0d2016-04-12 23:02:55 -0700232 bool noise_estimation_enabled) {
233 // If previous width and height are different from current frame's, need to
234 // reallocate the buffers and no denoising for the current frame.
jackychen8f9902a2015-11-26 02:59:48 -0800235 if (width_ != frame.width() || height_ != frame.height()) {
jackychenafaae0d2016-04-12 23:02:55 -0700236 DenoiserReset(frame, denoised_frame, denoised_frame_prev);
jackychen8f9902a2015-11-26 02:59:48 -0800237 return;
238 }
jackychenfa0befe2016-04-01 07:46:58 -0700239
jackychenafaae0d2016-04-12 23:02:55 -0700240 // Set buffer pointers.
nissea0591b52016-04-29 02:09:28 -0700241 const uint8_t* y_src = frame.video_frame_buffer()->DataY();
242 const uint8_t* u_src = frame.video_frame_buffer()->DataU();
243 const uint8_t* v_src = frame.video_frame_buffer()->DataV();
244 uint8_t* y_dst = denoised_frame->video_frame_buffer()->MutableDataY();
245 uint8_t* u_dst = denoised_frame->video_frame_buffer()->MutableDataU();
246 uint8_t* v_dst = denoised_frame->video_frame_buffer()->MutableDataV();
247 uint8_t* y_dst_prev =
248 denoised_frame_prev->video_frame_buffer()->MutableDataY();
jackychenafaae0d2016-04-12 23:02:55 -0700249 memset(x_density_.get(), 0, mb_cols_);
250 memset(y_density_.get(), 0, mb_rows_);
251 memset(moving_object_.get(), 1, mb_cols_ * mb_rows_);
jackychenfa0befe2016-04-01 07:46:58 -0700252
jackychenafaae0d2016-04-12 23:02:55 -0700253 uint8_t noise_level = noise_estimation_enabled ? ne_->GetNoiseLevel() : 0;
254 int thr_var_base = 16 * 16 * 5;
jackychenfa0befe2016-04-01 07:46:58 -0700255 // Loop over blocks to accumulate/extract noise level and update x/y_density
256 // factors for moving object detection.
jackychenafaae0d2016-04-12 23:02:55 -0700257 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
258 const int mb_index_base = mb_row * mb_cols_;
259 const int offset_base = (mb_row << 4) * stride_y_;
260 const uint8_t* mb_src_base = y_src + offset_base;
261 uint8_t* mb_dst_base = y_dst + offset_base;
262 uint8_t* mb_dst_prev_base = y_dst_prev + offset_base;
263 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
264 const int mb_index = mb_index_base + mb_col;
265 const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0);
266 const int pos_factor = PositionCheck(mb_row, mb_col, noise_level);
267 const uint32_t thr_var_adp = thr_var_base * pos_factor;
268 const uint32_t offset_col = mb_col << 4;
269 const uint8_t* mb_src = mb_src_base + offset_col;
270 uint8_t* mb_dst = mb_dst_base + offset_col;
271 uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col;
272
273 // TODO(jackychen): Need SSE2/NEON opt.
274 int luma = 0;
275 if (ne_enable) {
276 for (int i = 4; i < 12; ++i) {
277 for (int j = 4; j < 12; ++j) {
278 luma += mb_src[i * stride_y_ + j];
279 }
jackychenfa0befe2016-04-01 07:46:58 -0700280 }
281 }
282
jackychenafaae0d2016-04-12 23:02:55 -0700283 // Get the filtered block and filter_decision.
284 mb_filter_decision_[mb_index] =
285 filter_->MbDenoise(mb_dst_prev, stride_y_, mb_dst, stride_y_, mb_src,
286 stride_y_, 0, noise_level);
jackychenfa0befe2016-04-01 07:46:58 -0700287
jackychenafaae0d2016-04-12 23:02:55 -0700288 // If filter decision is FILTER_BLOCK, no need to check moving edge.
289 // It is unlikely for a moving edge block to be filtered in current
290 // setting.
291 if (mb_filter_decision_[mb_index] == FILTER_BLOCK) {
292 uint32_t sse_t = 0;
293 if (ne_enable) {
294 // The variance used in noise estimation is based on the src block in
295 // time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
296 uint32_t noise_var = filter_->Variance16x8(mb_dst_prev, stride_y_,
297 mb_src, stride_y_, &sse_t);
298 ne_->GetNoise(mb_index, noise_var, luma);
jackychenfa0befe2016-04-01 07:46:58 -0700299 }
jackychenafaae0d2016-04-12 23:02:55 -0700300 moving_edge_[mb_index] = 0; // Not a moving edge block.
jackychenfa0befe2016-04-01 07:46:58 -0700301 } else {
302 uint32_t sse_t = 0;
jackychenafaae0d2016-04-12 23:02:55 -0700303 // The variance used in MOD is based on the filtered blocks in time
304 // T (mb_dst) and T-1 (mb_dst_prev).
305 uint32_t noise_var = filter_->Variance16x8(mb_dst_prev, stride_y_,
306 mb_dst, stride_y_, &sse_t);
307 if (noise_var > thr_var_adp) { // Moving edge checking.
308 if (ne_enable) {
309 ne_->ResetConsecLowVar(mb_index);
310 }
311 moving_edge_[mb_index] = 1; // Mark as moving edge block.
312 x_density_[mb_col] += (pos_factor < 3);
313 y_density_[mb_row] += (pos_factor < 3);
jackychenfa0befe2016-04-01 07:46:58 -0700314 } else {
jackychenafaae0d2016-04-12 23:02:55 -0700315 moving_edge_[mb_index] = 0;
316 if (ne_enable) {
317 // The variance used in noise estimation is based on the src block
318 // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
319 uint32_t noise_var = filter_->Variance16x8(
320 mb_dst_prev, stride_y_, mb_src, stride_y_, &sse_t);
321 ne_->GetNoise(mb_index, noise_var, luma);
322 }
jackychenfa0befe2016-04-01 07:46:58 -0700323 }
jackychenfa0befe2016-04-01 07:46:58 -0700324 }
jackychenafaae0d2016-04-12 23:02:55 -0700325 } // End of for loop
326 } // End of for loop
327
328 ReduceFalseDetection(moving_edge_, &moving_object_, noise_level);
329
330 CopySrcOnMOB(y_src, y_dst);
331
jackychen6650d6d2016-04-25 16:53:59 -0700332 // When frame width/height not divisible by 16, copy the margin to
333 // denoised_frame.
334 if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_)
335 CopyLumaOnMargin(y_src, y_dst);
336
jackychenafaae0d2016-04-12 23:02:55 -0700337 // TODO(jackychen): Need SSE2/NEON opt.
338 // Copy u/v planes.
339 memcpy(u_dst, u_src, (height_ >> 1) * stride_u_);
340 memcpy(v_dst, v_src, (height_ >> 1) * stride_v_);
341
342 // Set time parameters to the output frame.
343 denoised_frame->set_timestamp(frame.timestamp());
344 denoised_frame->set_render_time_ms(frame.render_time_ms());
jackychenfa0befe2016-04-01 07:46:58 -0700345
jackychen8556c482016-04-20 16:04:31 -0700346#if DISPLAY || DISPLAYNEON
jackychenfa0befe2016-04-01 07:46:58 -0700347 // Show rectangular region
jackychenafaae0d2016-04-12 23:02:55 -0700348 ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_, u_src,
349 v_src, u_dst, v_dst, mb_rows_, mb_cols_, stride_u_, stride_v_);
jackychenfa0befe2016-04-01 07:46:58 -0700350#endif
jackychen8f9902a2015-11-26 02:59:48 -0800351}
352
353} // namespace webrtc