Add unit test for stand-alone denoiser and fixed some bugs.
The unit test will run the pure C denoiser and SSE2/NEON denoiser (based on the CPU detection) and compare the denoised frames to ensure the bit exact. TBR=tommi@webrtc.org BUG=webrtc:5255 Review URL: https://codereview.webrtc.org/1492053003 Cr-Commit-Position: refs/heads/master@{#11216}
This commit is contained in:
@ -95,7 +95,7 @@ uint32_t VPMFramePreprocessor::GetDecimatedHeight() const {
|
||||
}
|
||||
|
||||
void VPMFramePreprocessor::EnableDenosing(bool enable) {
|
||||
denoiser_.reset(new VideoDenoiser());
|
||||
denoiser_.reset(new VideoDenoiser(true));
|
||||
}
|
||||
|
||||
const VideoFrame* VPMFramePreprocessor::PreprocessFrame(
|
||||
|
||||
156
webrtc/modules/video_processing/test/denoiser_test.cc
Normal file
156
webrtc/modules/video_processing/test/denoiser_test.cc
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
|
||||
#include "webrtc/modules/video_processing/include/video_processing.h"
|
||||
#include "webrtc/modules/video_processing/test/video_processing_unittest.h"
|
||||
#include "webrtc/modules/video_processing/video_denoiser.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST_F(VideoProcessingTest, CopyMem) {
|
||||
rtc::scoped_ptr<DenoiserFilter> df_c(DenoiserFilter::Create(false));
|
||||
rtc::scoped_ptr<DenoiserFilter> df_sse_neon(DenoiserFilter::Create(true));
|
||||
uint8_t src[16 * 16], dst[16 * 16];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
src[i * 16 + j] = i * 16 + j;
|
||||
}
|
||||
}
|
||||
|
||||
memset(dst, 0, 8 * 8);
|
||||
df_c->CopyMem8x8(src, 8, dst, 8);
|
||||
EXPECT_EQ(0, memcmp(src, dst, 8 * 8));
|
||||
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_c->CopyMem16x16(src, 16, dst, 16);
|
||||
EXPECT_EQ(0, memcmp(src, dst, 16 * 16));
|
||||
|
||||
memset(dst, 0, 8 * 8);
|
||||
df_sse_neon->CopyMem16x16(src, 8, dst, 8);
|
||||
EXPECT_EQ(0, memcmp(src, dst, 8 * 8));
|
||||
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_sse_neon->CopyMem16x16(src, 16, dst, 16);
|
||||
EXPECT_EQ(0, memcmp(src, dst, 16 * 16));
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessingTest, Variance) {
|
||||
rtc::scoped_ptr<DenoiserFilter> df_c(DenoiserFilter::Create(false));
|
||||
rtc::scoped_ptr<DenoiserFilter> df_sse_neon(DenoiserFilter::Create(true));
|
||||
uint8_t src[16 * 16], dst[16 * 16];
|
||||
uint32_t sum = 0, sse = 0, var;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
src[i * 16 + j] = i * 16 + j;
|
||||
}
|
||||
}
|
||||
// Compute the 16x8 variance of the 16x16 block.
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
sum += (i * 32 + j);
|
||||
sse += (i * 32 + j) * (i * 32 + j);
|
||||
}
|
||||
}
|
||||
var = sse - ((sum * sum) >> 7);
|
||||
memset(dst, 0, 16 * 16);
|
||||
EXPECT_EQ(var, df_c->Variance16x8(src, 16, dst, 16, &sse));
|
||||
EXPECT_EQ(var, df_sse_neon->Variance16x8(src, 16, dst, 16, &sse));
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessingTest, MbDenoise) {
|
||||
rtc::scoped_ptr<DenoiserFilter> df_c(DenoiserFilter::Create(false));
|
||||
rtc::scoped_ptr<DenoiserFilter> df_sse_neon(DenoiserFilter::Create(true));
|
||||
uint8_t running_src[16 * 16], src[16 * 16], dst[16 * 16], dst_ref[16 * 16];
|
||||
|
||||
// Test case: |diff| <= |3 + shift_inc1|
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
running_src[i * 16 + j] = i * 11 + j;
|
||||
src[i * 16 + j] = i * 11 + j + 2;
|
||||
dst_ref[i * 16 + j] = running_src[i * 16 + j];
|
||||
}
|
||||
}
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_c->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(0, memcmp(dst, dst_ref, 16 * 16));
|
||||
|
||||
// Test case: |diff| >= |4 + shift_inc1|
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
running_src[i * 16 + j] = i * 11 + j;
|
||||
src[i * 16 + j] = i * 11 + j + 5;
|
||||
dst_ref[i * 16 + j] = src[i * 16 + j] - 2;
|
||||
}
|
||||
}
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_c->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(0, memcmp(dst, dst_ref, 16 * 16));
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_sse_neon->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(0, memcmp(dst, dst_ref, 16 * 16));
|
||||
|
||||
// Test case: |diff| >= 8
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
running_src[i * 16 + j] = i * 11 + j;
|
||||
src[i * 16 + j] = i * 11 + j + 8;
|
||||
dst_ref[i * 16 + j] = src[i * 16 + j] - 6;
|
||||
}
|
||||
}
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_c->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(0, memcmp(dst, dst_ref, 16 * 16));
|
||||
memset(dst, 0, 16 * 16);
|
||||
df_sse_neon->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(0, memcmp(dst, dst_ref, 16 * 16));
|
||||
|
||||
// Test case: |diff| > 15
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
running_src[i * 16 + j] = i * 11 + j;
|
||||
src[i * 16 + j] = i * 11 + j + 16;
|
||||
}
|
||||
}
|
||||
memset(dst, 0, 16 * 16);
|
||||
DenoiserDecision decision =
|
||||
df_c->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(COPY_BLOCK, decision);
|
||||
decision = df_sse_neon->MbDenoise(running_src, 16, dst, 16, src, 16, 0, 1);
|
||||
EXPECT_EQ(COPY_BLOCK, decision);
|
||||
}
|
||||
|
||||
TEST_F(VideoProcessingTest, Denoiser) {
|
||||
// Create pure C denoiser.
|
||||
VideoDenoiser denoiser_c(false);
|
||||
// Create SSE or NEON denoiser.
|
||||
VideoDenoiser denoiser_sse_neon(true);
|
||||
VideoFrame denoised_frame_c;
|
||||
VideoFrame denoised_frame_sse_neon;
|
||||
|
||||
rtc::scoped_ptr<uint8_t[]> video_buffer(new uint8_t[frame_length_]);
|
||||
while (fread(video_buffer.get(), 1, frame_length_, source_file_) ==
|
||||
frame_length_) {
|
||||
// Using ConvertToI420 to add stride to the image.
|
||||
EXPECT_EQ(0, ConvertToI420(kI420, video_buffer.get(), 0, 0, width_, height_,
|
||||
0, kVideoRotation_0, &video_frame_));
|
||||
|
||||
denoiser_c.DenoiseFrame(video_frame_, &denoised_frame_c);
|
||||
denoiser_sse_neon.DenoiseFrame(video_frame_, &denoised_frame_sse_neon);
|
||||
|
||||
// Denoising results should be the same for C and SSE/NEON denoiser.
|
||||
ASSERT_EQ(true, denoised_frame_c.EqualsFrame(denoised_frame_sse_neon));
|
||||
}
|
||||
ASSERT_NE(0, feof(source_file_)) << "Error reading source file";
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -20,26 +20,30 @@ const int kMotionMagnitudeThreshold = 8 * 3;
|
||||
const int kSumDiffThreshold = 16 * 16 * 2;
|
||||
const int kSumDiffThresholdHigh = 600;
|
||||
|
||||
DenoiserFilter* DenoiserFilter::Create() {
|
||||
DenoiserFilter* DenoiserFilter::Create(bool runtime_cpu_detection) {
|
||||
DenoiserFilter* filter = NULL;
|
||||
|
||||
if (runtime_cpu_detection) {
|
||||
// If we know the minimum architecture at compile time, avoid CPU detection.
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
// x86 CPU detection required.
|
||||
if (WebRtc_GetCPUInfo(kSSE2)) {
|
||||
filter = new DenoiserFilterSSE2();
|
||||
} else {
|
||||
filter = new DenoiserFilterC();
|
||||
}
|
||||
// x86 CPU detection required.
|
||||
if (WebRtc_GetCPUInfo(kSSE2)) {
|
||||
filter = new DenoiserFilterSSE2();
|
||||
} else {
|
||||
filter = new DenoiserFilterC();
|
||||
}
|
||||
#elif defined(WEBRTC_DETECT_NEON)
|
||||
if (WebRtc_GetCPUFeaturesARM() & kCPUFeatureNEON) {
|
||||
filter = new DenoiserFilterNEON();
|
||||
if (WebRtc_GetCPUFeaturesARM() & kCPUFeatureNEON) {
|
||||
filter = new DenoiserFilterNEON();
|
||||
} else {
|
||||
filter = new DenoiserFilterC();
|
||||
}
|
||||
#else
|
||||
filter = new DenoiserFilterC();
|
||||
#endif
|
||||
} else {
|
||||
filter = new DenoiserFilterC();
|
||||
}
|
||||
#else
|
||||
filter = new DenoiserFilterC();
|
||||
#endif
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ struct DenoiseMetrics {
|
||||
|
||||
class DenoiserFilter {
|
||||
public:
|
||||
static DenoiserFilter* Create();
|
||||
static DenoiserFilter* Create(bool runtime_cpu_detection);
|
||||
|
||||
virtual ~DenoiserFilter() {}
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ uint32_t DenoiserFilterC::Variance16x8(const uint8_t* a,
|
||||
a += a_stride;
|
||||
b += b_stride;
|
||||
}
|
||||
return *sse - ((static_cast<int64_t>(sum) * sum) >> 8);
|
||||
return *sse - ((static_cast<int64_t>(sum) * sum) >> 7);
|
||||
}
|
||||
|
||||
DenoiserDecision DenoiserFilterC::MbDenoise(uint8_t* mc_running_avg_y,
|
||||
@ -72,7 +72,7 @@ DenoiserDecision DenoiserFilterC::MbDenoise(uint8_t* mc_running_avg_y,
|
||||
int adj_val[3] = {3, 4, 6};
|
||||
int shift_inc1 = 0;
|
||||
int shift_inc2 = 1;
|
||||
int col_sum[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
int col_sum[16] = {0};
|
||||
if (motion_magnitude <= kMotionMagnitudeThreshold) {
|
||||
if (increase_denoising) {
|
||||
shift_inc1 = 1;
|
||||
|
||||
@ -47,7 +47,7 @@ static void Get8x8varSse2(const uint8_t* src,
|
||||
vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 8));
|
||||
vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 4));
|
||||
vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 2));
|
||||
*sum = static_cast<int>(_mm_extract_epi16(vsum, 0));
|
||||
*sum = static_cast<int16_t>(_mm_extract_epi16(vsum, 0));
|
||||
|
||||
// sse
|
||||
vsse = _mm_add_epi32(vsse, _mm_srli_si128(vsse, 8));
|
||||
@ -62,7 +62,7 @@ static void VarianceSSE2(const unsigned char* src,
|
||||
int w,
|
||||
int h,
|
||||
uint32_t* sse,
|
||||
uint32_t* sum,
|
||||
int64_t* sum,
|
||||
int block_size) {
|
||||
*sse = 0;
|
||||
*sum = 0;
|
||||
@ -126,9 +126,9 @@ uint32_t DenoiserFilterSSE2::Variance16x8(const uint8_t* src,
|
||||
int src_stride,
|
||||
const uint8_t* ref,
|
||||
int ref_stride,
|
||||
unsigned int* sse) {
|
||||
uint32_t sum = 0;
|
||||
VarianceSSE2(src, src_stride, ref, ref_stride, 16, 8, sse, &sum, 8);
|
||||
uint32_t* sse) {
|
||||
int64_t sum = 0;
|
||||
VarianceSSE2(src, src_stride << 1, ref, ref_stride << 1, 16, 8, sse, &sum, 8);
|
||||
return *sse - ((sum * sum) >> 7);
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,10 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
VideoDenoiser::VideoDenoiser()
|
||||
: width_(0), height_(0), filter_(DenoiserFilter::Create()) {}
|
||||
VideoDenoiser::VideoDenoiser(bool runtime_cpu_detection)
|
||||
: width_(0),
|
||||
height_(0),
|
||||
filter_(DenoiserFilter::Create(runtime_cpu_detection)) {}
|
||||
|
||||
void VideoDenoiser::TrailingReduction(int mb_rows,
|
||||
int mb_cols,
|
||||
@ -78,7 +80,7 @@ void VideoDenoiser::DenoiseFrame(const VideoFrame& frame,
|
||||
int mb_cols = width_ >> 4;
|
||||
int mb_rows = height_ >> 4;
|
||||
if (metrics_.get() == nullptr)
|
||||
metrics_.reset(new DenoiseMetrics[mb_cols * mb_rows]);
|
||||
metrics_.reset(new DenoiseMetrics[mb_cols * mb_rows]());
|
||||
// Denoise on Y plane.
|
||||
uint8_t* y_dst = denoised_frame->buffer(kYPlane);
|
||||
uint8_t* u_dst = denoised_frame->buffer(kUPlane);
|
||||
|
||||
@ -18,7 +18,7 @@ namespace webrtc {
|
||||
|
||||
class VideoDenoiser {
|
||||
public:
|
||||
VideoDenoiser();
|
||||
explicit VideoDenoiser(bool runtime_cpu_detection);
|
||||
void DenoiseFrame(const VideoFrame& frame, VideoFrame* denoised_frame);
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user