blob: e51f6285885ddb855f763289831551997a19f64d [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"
13
jackychene42c0ae2016-04-16 10:44:16 -070014namespace webrtc {
15
jackychen8556c482016-04-20 16:04:31 -070016#if DISPLAY || DISPLAYNEON
jackychenafaae0d2016-04-12 23:02:55 -070017static void CopyMem8x8(const uint8_t* src,
18 int src_stride,
19 uint8_t* dst,
20 int dst_stride) {
21 for (int i = 0; i < 8; i++) {
22 memcpy(dst, src, 8);
23 src += src_stride;
24 dst += dst_stride;
25 }
26}
27
28static void ShowRect(const std::unique_ptr<DenoiserFilter>& filter,
29 const std::unique_ptr<uint8_t[]>& d_status,
30 const std::unique_ptr<uint8_t[]>& moving_edge_red,
31 const std::unique_ptr<uint8_t[]>& x_density,
32 const std::unique_ptr<uint8_t[]>& y_density,
33 const uint8_t* u_src,
34 const uint8_t* v_src,
35 uint8_t* u_dst,
36 uint8_t* v_dst,
37 int mb_rows_,
38 int mb_cols_,
39 int stride_u_,
40 int stride_v_) {
41 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
42 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
43 int mb_index = mb_row * mb_cols_ + mb_col;
44 const uint8_t* mb_src_u =
45 u_src + (mb_row << 3) * stride_u_ + (mb_col << 3);
46 const uint8_t* mb_src_v =
47 v_src + (mb_row << 3) * stride_v_ + (mb_col << 3);
48 uint8_t* mb_dst_u = u_dst + (mb_row << 3) * stride_u_ + (mb_col << 3);
49 uint8_t* mb_dst_v = v_dst + (mb_row << 3) * stride_v_ + (mb_col << 3);
50 uint8_t uv_tmp[8 * 8];
51 memset(uv_tmp, 200, 8 * 8);
52 if (d_status[mb_index] == 1) {
53 // Paint to red.
54 CopyMem8x8(mb_src_u, stride_u_, mb_dst_u, stride_u_);
55 CopyMem8x8(uv_tmp, 8, mb_dst_v, stride_v_);
56 } else if (moving_edge_red[mb_row * mb_cols_ + mb_col] &&
57 x_density[mb_col] * y_density[mb_row]) {
58 // Paint to blue.
59 CopyMem8x8(uv_tmp, 8, mb_dst_u, stride_u_);
60 CopyMem8x8(mb_src_v, stride_v_, mb_dst_v, stride_v_);
61 } else {
62 CopyMem8x8(mb_src_u, stride_u_, mb_dst_u, stride_u_);
63 CopyMem8x8(mb_src_v, stride_v_, mb_dst_v, stride_v_);
64 }
65 }
66 }
67}
68#endif
69
jackychen67e94fb2016-01-11 21:34:07 -080070VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
71 : width_(0),
72 height_(0),
jackychenfa0befe2016-04-01 07:46:58 -070073 filter_(DenoiserFilter::Create(runtime_cpu_detection, &cpu_type_)),
74 ne_(new NoiseEstimation()) {}
jackychen8f9902a2015-11-26 02:59:48 -080075
Niels Möller6af2e862016-06-17 09:12:44 +020076void VideoDenoiser::DenoiserReset(
77 const rtc::scoped_refptr<VideoFrameBuffer>& frame,
78 rtc::scoped_refptr<I420Buffer>* denoised_frame,
79 rtc::scoped_refptr<I420Buffer>* denoised_frame_prev) {
80 width_ = frame->width();
81 height_ = frame->height();
jackychenafaae0d2016-04-12 23:02:55 -070082 mb_cols_ = width_ >> 4;
83 mb_rows_ = height_ >> 4;
Niels Möller6af2e862016-06-17 09:12:44 +020084 stride_y_ = frame->StrideY();
85 stride_u_ = frame->StrideU();
86 stride_v_ = frame->StrideV();
jackychenafaae0d2016-04-12 23:02:55 -070087
88 // Allocate an empty buffer for denoised_frame_prev.
Niels Möller6af2e862016-06-17 09:12:44 +020089 *denoised_frame_prev = new rtc::RefCountedObject<I420Buffer>(
90 width_, height_, stride_y_, stride_u_, stride_v_);
jackychenafaae0d2016-04-12 23:02:55 -070091 // Allocate and initialize denoised_frame with key frame.
Niels Möller6af2e862016-06-17 09:12:44 +020092 *denoised_frame = I420Buffer::CopyKeepStride(frame);
jackychenafaae0d2016-04-12 23:02:55 -070093
94 // Init noise estimator and allocate buffers.
95 ne_->Init(width_, height_, cpu_type_);
96 moving_edge_.reset(new uint8_t[mb_cols_ * mb_rows_]);
97 mb_filter_decision_.reset(new DenoiserDecision[mb_cols_ * mb_rows_]);
98 x_density_.reset(new uint8_t[mb_cols_]);
99 y_density_.reset(new uint8_t[mb_rows_]);
100 moving_object_.reset(new uint8_t[mb_cols_ * mb_rows_]);
101}
102
103int VideoDenoiser::PositionCheck(int mb_row, int mb_col, int noise_level) {
104 if (noise_level == 0)
jackychenfa0befe2016-04-01 07:46:58 -0700105 return 1;
jackychenafaae0d2016-04-12 23:02:55 -0700106 if ((mb_row <= (mb_rows_ >> 4)) || (mb_col <= (mb_cols_ >> 4)) ||
107 (mb_col >= (15 * mb_cols_ >> 4)))
108 return 3;
109 else if ((mb_row <= (mb_rows_ >> 3)) || (mb_col <= (mb_cols_ >> 3)) ||
110 (mb_col >= (7 * mb_cols_ >> 3)))
jackychenfa0befe2016-04-01 07:46:58 -0700111 return 2;
112 else
jackychenafaae0d2016-04-12 23:02:55 -0700113 return 1;
jackychenfa0befe2016-04-01 07:46:58 -0700114}
115
jackychenafaae0d2016-04-12 23:02:55 -0700116void VideoDenoiser::ReduceFalseDetection(
117 const std::unique_ptr<uint8_t[]>& d_status,
118 std::unique_ptr<uint8_t[]>* moving_edge_red,
119 int noise_level) {
120 // From up left corner.
121 int mb_col_stop = mb_cols_ - 1;
122 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
123 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
124 if (d_status[mb_row * mb_cols_ + mb_col]) {
125 mb_col_stop = mb_col - 1;
126 break;
127 }
128 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700129 }
130 }
jackychenafaae0d2016-04-12 23:02:55 -0700131 // From bottom left corner.
132 mb_col_stop = mb_cols_ - 1;
133 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
134 for (int mb_col = 0; mb_col <= mb_col_stop; ++mb_col) {
135 if (d_status[mb_row * mb_cols_ + mb_col]) {
136 mb_col_stop = mb_col - 1;
137 break;
138 }
139 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700140 }
141 }
jackychenafaae0d2016-04-12 23:02:55 -0700142 // From up right corner.
143 mb_col_stop = 0;
144 for (int mb_row = 0; mb_row <= mb_rows_ - 1; ++mb_row) {
145 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
146 if (d_status[mb_row * mb_cols_ + mb_col]) {
147 mb_col_stop = mb_col + 1;
148 break;
149 }
150 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychenfa0befe2016-04-01 07:46:58 -0700151 }
152 }
jackychenafaae0d2016-04-12 23:02:55 -0700153 // From bottom right corner.
154 mb_col_stop = 0;
155 for (int mb_row = mb_rows_ - 1; mb_row >= 0; --mb_row) {
156 for (int mb_col = mb_cols_ - 1; mb_col >= mb_col_stop; --mb_col) {
157 if (d_status[mb_row * mb_cols_ + mb_col]) {
158 mb_col_stop = mb_col + 1;
159 break;
160 }
161 (*moving_edge_red)[mb_row * mb_cols_ + mb_col] = 0;
jackychen8f9902a2015-11-26 02:59:48 -0800162 }
163 }
164}
165
jackychenafaae0d2016-04-12 23:02:55 -0700166bool VideoDenoiser::IsTrailingBlock(const std::unique_ptr<uint8_t[]>& d_status,
167 int mb_row,
168 int mb_col) {
169 bool ret = false;
170 int mb_index = mb_row * mb_cols_ + mb_col;
171 if (!mb_row || !mb_col || mb_row == mb_rows_ - 1 || mb_col == mb_cols_ - 1)
172 ret = false;
173 else
174 ret = d_status[mb_index + 1] || d_status[mb_index - 1] ||
175 d_status[mb_index + mb_cols_] || d_status[mb_index - mb_cols_];
176 return ret;
jackychenfa0befe2016-04-01 07:46:58 -0700177}
jackychenfa0befe2016-04-01 07:46:58 -0700178
jackychenafaae0d2016-04-12 23:02:55 -0700179void VideoDenoiser::CopySrcOnMOB(const uint8_t* y_src, uint8_t* y_dst) {
180 // Loop over to copy src block if the block is marked as moving object block
181 // or if the block may cause trailing artifacts.
182 for (int mb_row = 0; mb_row < mb_rows_; ++mb_row) {
183 const int mb_index_base = mb_row * mb_cols_;
184 const int offset_base = (mb_row << 4) * stride_y_;
185 const uint8_t* mb_src_base = y_src + offset_base;
186 uint8_t* mb_dst_base = y_dst + offset_base;
187 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
188 const int mb_index = mb_index_base + mb_col;
189 const uint32_t offset_col = mb_col << 4;
190 const uint8_t* mb_src = mb_src_base + offset_col;
191 uint8_t* mb_dst = mb_dst_base + offset_col;
192 // Check if the block is a moving object block or may cause a trailing
193 // artifacts.
194 if (mb_filter_decision_[mb_index] != FILTER_BLOCK ||
195 IsTrailingBlock(moving_edge_, mb_row, mb_col) ||
196 (x_density_[mb_col] * y_density_[mb_row] &&
197 moving_object_[mb_row * mb_cols_ + mb_col])) {
198 // Copy y source.
199 filter_->CopyMem16x16(mb_src, stride_y_, mb_dst, stride_y_);
jackychenfa0befe2016-04-01 07:46:58 -0700200 }
201 }
202 }
203}
jackychenfa0befe2016-04-01 07:46:58 -0700204
jackychen6650d6d2016-04-25 16:53:59 -0700205void VideoDenoiser::CopyLumaOnMargin(const uint8_t* y_src, uint8_t* y_dst) {
206 if ((mb_rows_ << 4) != height_) {
207 const uint8_t* margin_y_src = y_src + (mb_rows_ << 4) * stride_y_;
208 uint8_t* margin_y_dst = y_dst + (mb_rows_ << 4) * stride_y_;
209 memcpy(margin_y_dst, margin_y_src, (height_ - (mb_rows_ << 4)) * stride_y_);
210 }
211 if ((mb_cols_ << 4) != width_) {
212 const uint8_t* margin_y_src = y_src + (mb_cols_ << 4);
213 uint8_t* margin_y_dst = y_dst + (mb_cols_ << 4);
214 for (int i = 0; i < height_; ++i) {
215 for (int j = mb_cols_ << 4; j < width_; ++j) {
216 margin_y_dst[i * stride_y_ + j] = margin_y_src[i * stride_y_ + j];
217 }
218 }
219 }
220}
221
Niels Möller6af2e862016-06-17 09:12:44 +0200222void VideoDenoiser::DenoiseFrame(
223 const rtc::scoped_refptr<VideoFrameBuffer>& frame,
224 rtc::scoped_refptr<I420Buffer>* denoised_frame,
225 rtc::scoped_refptr<I420Buffer>* denoised_frame_prev,
226 bool noise_estimation_enabled) {
jackychenafaae0d2016-04-12 23:02:55 -0700227 // If previous width and height are different from current frame's, need to
228 // reallocate the buffers and no denoising for the current frame.
Niels Möller6af2e862016-06-17 09:12:44 +0200229 if (width_ != frame->width() || height_ != frame->height()) {
jackychenafaae0d2016-04-12 23:02:55 -0700230 DenoiserReset(frame, denoised_frame, denoised_frame_prev);
jackychen8f9902a2015-11-26 02:59:48 -0800231 return;
232 }
jackychenfa0befe2016-04-01 07:46:58 -0700233
jackychenafaae0d2016-04-12 23:02:55 -0700234 // Set buffer pointers.
Niels Möller6af2e862016-06-17 09:12:44 +0200235 const uint8_t* y_src = frame->DataY();
236 const uint8_t* u_src = frame->DataU();
237 const uint8_t* v_src = frame->DataV();
238 uint8_t* y_dst = (*denoised_frame)->MutableDataY();
239 uint8_t* u_dst = (*denoised_frame)->MutableDataU();
240 uint8_t* v_dst = (*denoised_frame)->MutableDataV();
241 uint8_t* y_dst_prev = (*denoised_frame_prev)->MutableDataY();
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_;
252 const int offset_base = (mb_row << 4) * stride_y_;
253 const uint8_t* mb_src_base = y_src + offset_base;
254 uint8_t* mb_dst_base = y_dst + offset_base;
255 uint8_t* mb_dst_prev_base = y_dst_prev + offset_base;
256 for (int mb_col = 0; mb_col < mb_cols_; ++mb_col) {
257 const int mb_index = mb_index_base + mb_col;
258 const bool ne_enable = (mb_index % NOISE_SUBSAMPLE_INTERVAL == 0);
259 const int pos_factor = PositionCheck(mb_row, mb_col, noise_level);
260 const uint32_t thr_var_adp = thr_var_base * pos_factor;
261 const uint32_t offset_col = mb_col << 4;
262 const uint8_t* mb_src = mb_src_base + offset_col;
263 uint8_t* mb_dst = mb_dst_base + offset_col;
264 uint8_t* mb_dst_prev = mb_dst_prev_base + offset_col;
265
266 // TODO(jackychen): Need SSE2/NEON opt.
267 int luma = 0;
268 if (ne_enable) {
269 for (int i = 4; i < 12; ++i) {
270 for (int j = 4; j < 12; ++j) {
271 luma += mb_src[i * stride_y_ + j];
272 }
jackychenfa0befe2016-04-01 07:46:58 -0700273 }
274 }
275
jackychenafaae0d2016-04-12 23:02:55 -0700276 // Get the filtered block and filter_decision.
277 mb_filter_decision_[mb_index] =
278 filter_->MbDenoise(mb_dst_prev, stride_y_, mb_dst, stride_y_, mb_src,
279 stride_y_, 0, noise_level);
jackychenfa0befe2016-04-01 07:46:58 -0700280
jackychenafaae0d2016-04-12 23:02:55 -0700281 // If filter decision is FILTER_BLOCK, no need to check moving edge.
282 // It is unlikely for a moving edge block to be filtered in current
283 // setting.
284 if (mb_filter_decision_[mb_index] == FILTER_BLOCK) {
285 uint32_t sse_t = 0;
286 if (ne_enable) {
287 // The variance used in noise estimation is based on the src block in
288 // time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
289 uint32_t noise_var = filter_->Variance16x8(mb_dst_prev, stride_y_,
290 mb_src, stride_y_, &sse_t);
291 ne_->GetNoise(mb_index, noise_var, luma);
jackychenfa0befe2016-04-01 07:46:58 -0700292 }
jackychenafaae0d2016-04-12 23:02:55 -0700293 moving_edge_[mb_index] = 0; // Not a moving edge block.
jackychenfa0befe2016-04-01 07:46:58 -0700294 } else {
295 uint32_t sse_t = 0;
jackychenafaae0d2016-04-12 23:02:55 -0700296 // The variance used in MOD is based on the filtered blocks in time
297 // T (mb_dst) and T-1 (mb_dst_prev).
298 uint32_t noise_var = filter_->Variance16x8(mb_dst_prev, stride_y_,
299 mb_dst, stride_y_, &sse_t);
300 if (noise_var > thr_var_adp) { // Moving edge checking.
301 if (ne_enable) {
302 ne_->ResetConsecLowVar(mb_index);
303 }
304 moving_edge_[mb_index] = 1; // Mark as moving edge block.
305 x_density_[mb_col] += (pos_factor < 3);
306 y_density_[mb_row] += (pos_factor < 3);
jackychenfa0befe2016-04-01 07:46:58 -0700307 } else {
jackychenafaae0d2016-04-12 23:02:55 -0700308 moving_edge_[mb_index] = 0;
309 if (ne_enable) {
310 // The variance used in noise estimation is based on the src block
311 // in time t (mb_src) and filtered block in time t-1 (mb_dist_prev).
312 uint32_t noise_var = filter_->Variance16x8(
313 mb_dst_prev, stride_y_, mb_src, stride_y_, &sse_t);
314 ne_->GetNoise(mb_index, noise_var, luma);
315 }
jackychenfa0befe2016-04-01 07:46:58 -0700316 }
jackychenfa0befe2016-04-01 07:46:58 -0700317 }
jackychenafaae0d2016-04-12 23:02:55 -0700318 } // End of for loop
319 } // End of for loop
320
321 ReduceFalseDetection(moving_edge_, &moving_object_, noise_level);
322
323 CopySrcOnMOB(y_src, y_dst);
324
jackychen6650d6d2016-04-25 16:53:59 -0700325 // When frame width/height not divisible by 16, copy the margin to
326 // denoised_frame.
327 if ((mb_rows_ << 4) != height_ || (mb_cols_ << 4) != width_)
328 CopyLumaOnMargin(y_src, y_dst);
329
jackychenafaae0d2016-04-12 23:02:55 -0700330 // TODO(jackychen): Need SSE2/NEON opt.
331 // Copy u/v planes.
332 memcpy(u_dst, u_src, (height_ >> 1) * stride_u_);
333 memcpy(v_dst, v_src, (height_ >> 1) * stride_v_);
334
jackychen8556c482016-04-20 16:04:31 -0700335#if DISPLAY || DISPLAYNEON
jackychenfa0befe2016-04-01 07:46:58 -0700336 // Show rectangular region
jackychenafaae0d2016-04-12 23:02:55 -0700337 ShowRect(filter_, moving_edge_, moving_object_, x_density_, y_density_, u_src,
338 v_src, u_dst, v_dst, mb_rows_, mb_cols_, stride_u_, stride_v_);
jackychenfa0befe2016-04-01 07:46:58 -0700339#endif
jackychen8f9902a2015-11-26 02:59:48 -0800340}
341
342} // namespace webrtc