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:
jackychen
2016-01-11 21:34:07 -08:00
committed by Commit bot
parent b2328d11dc
commit 67e94fb6f2
12 changed files with 236 additions and 72 deletions

View File

@ -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(

View 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

View File

@ -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;
}

View File

@ -30,7 +30,7 @@ struct DenoiseMetrics {
class DenoiserFilter {
public:
static DenoiserFilter* Create();
static DenoiserFilter* Create(bool runtime_cpu_detection);
virtual ~DenoiserFilter() {}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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: