Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
572 lines (415 sloc) 14.9 KB
/*
* Copyright (c) 2013 Dirk Farin <dirk.farin@gmail.com>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with FFmpeg; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* @file
* Non-Local Means noise reduction filter.
*
* This implementation is an implementation of the paper below with
* an extension to use several frames. The computation is slightly
* simplified to reduce computation complexity.
*
* A. Buades, B. Coll, J.-M. Morel:
* "A non-local algorithm for image denoising",
* IEEE Computer Vision and Pattern Recognition 2005, Vol 2, pp: 60-65, 2005.
*/
#include <float.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <math.h>
#include <assert.h>
#include <stdio.h>
#include "config.h"
#include "libavutil/pixdesc.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
#include "libavutil/opt.h"
#include "libavfilter/vf_nlmeans.h"
typedef struct
{
uint8_t* img; // point to logical origin (0;0)
int stride;
int w,h;
int border; // extra border on all four sides
uint8_t* mem_start; // start of allocated memory
} MonoImage;
typedef enum {
ImageFormat_Mono,
ImageFormat_YUV420,
ImageFormat_YUV422,
ImageFormat_YUV444,
ImageFormat_RGB
} ImageFormat;
typedef struct
{
MonoImage plane[3];
ImageFormat format;
} ColorImage;
static void alloc_and_copy_image_with_border(MonoImage* out_img,
const uint8_t* input_image, int input_stride,
int w,int h, int req_border)
{
const int border = (req_border+15)/16*16;
const int out_stride = (w+2*border);
const int out_total_height = (h+2*border);
uint8_t* const memory_ptr = (uint8_t*)malloc(out_stride*out_total_height);
uint8_t* const output_image = memory_ptr + border + border*out_stride; // logical output image origin (0,0)
// copy main image content
for (int y=0;y<h;y++) {
memcpy(output_image + y*out_stride, input_image+y*input_stride, w);
}
// top/bottom borders
for (int k=0;k<border;k++) {
memcpy(output_image-(k+1)*out_stride, input_image, w);
memcpy(output_image+(h+k)*out_stride, input_image+(h-1)*input_stride, w);
}
// left/right borders
for (int k=0;k<border;k++) {
for (int y=-border;y<h+border;y++)
{
*(output_image -k-1+y*out_stride) = output_image[y*out_stride];
*(output_image+w+k +y*out_stride) = output_image[y*out_stride+w-1];
}
}
out_img->img = output_image;
out_img->mem_start = memory_ptr;
out_img->stride = out_stride;
out_img->w = w;
out_img->h = h;
out_img->border = border;
}
static void free_mono_image(MonoImage* img)
{
if (img->mem_start) {
free(img->mem_start);
img->mem_start=NULL;
img->img=NULL;
}
}
static void free_color_image(ColorImage* img)
{
for (int c=0;c<3;c++) {
free_mono_image(&(img->plane[c]));
}
}
typedef struct
{
double h_param;
int patch_size;
int range; // search range (must be odd number)
int n_frames; // temporal search depth
} NLMeansParams;
#define MAX_NLMeansImages 32
typedef struct {
const AVClass *class;
int hsub,vsub;
NLMeansParams param;
ColorImage images[MAX_NLMeansImages];
int image_available[MAX_NLMeansImages];
NLMeansFunctions func;
} NLMContext;
static void buildIntegralImage_scalar(uint32_t* integral_image, int integral_stride,
const uint8_t* current_image, int current_image_stride,
const uint8_t* compare_image, int compare_image_stride,
int w,int h,
int dx,int dy)
{
memset(integral_image -1 -integral_stride, 0, (w+1)*sizeof(uint32_t));
for (int y=0;y<h;y++) {
const uint8_t* p1 = current_image + y *current_image_stride;
const uint8_t* p2 = compare_image + (y+dy)*compare_image_stride + dx;
uint32_t* out = integral_image + y*integral_stride -1;
*out++ = 0;
for (int x=0;x<w;x++)
{
int diff = *p1++ - *p2++;
*out = *(out-1) + diff * diff;
out++;
}
if (y>0) {
out = integral_image + y*integral_stride;
for (int x=0;x<w;x++) {
*out += *(out - integral_stride);
out++;
}
}
}
}
struct PixelSum
{
float weight_sum;
float pixel_sum;
};
static void NLMeans_mono_multi(uint8_t* out, int out_stride,
const MonoImage*const* images, int n_images,
const NLMContext* ctx)
{
const int w = images[0]->w;
const int h = images[0]->h;
const int n = (ctx->param.patch_size|1);
const int r = (ctx->param.range |1);
const int n_half = (n-1)/2;
const int r_half = (r-1)/2;
// alloc memory for temporary pixel sums
struct PixelSum* const tmp_data = (struct PixelSum*)calloc(w*h,sizeof(struct PixelSum));
// allocate integral image
const int integral_stride = w+2*16;
uint32_t* const integral_mem = (uint32_t*)malloc( integral_stride*(h+1)*sizeof(uint32_t) );
uint32_t* const integral = integral_mem + integral_stride + 16;
// precompute exponential table
const float weight_factor = 1.0/n/n / (ctx->param.h_param * ctx->param.h_param);
#define EXP_TABSIZE 128
const int table_size=EXP_TABSIZE;
const float min_weight_in_table = 0.0005;
float exptable[EXP_TABSIZE];
const float stretch = table_size/ (-log(min_weight_in_table));
const float weight_fact_table = weight_factor*stretch;
const int diff_max = table_size/weight_fact_table;
for (int i=0;i<table_size;i++) {
exptable[i] = exp(-i/stretch);
}
exptable[table_size-1]=0;
for (int image_idx=0; image_idx<n_images; image_idx++)
{
// copy input image
const uint8_t* current_image = images[0]->img;
int current_image_stride = images[0]->stride;
const uint8_t* compare_image = images[image_idx]->img;
int compare_image_stride = images[image_idx]->stride;
// --- iterate through all displacements ---
for (int dy=-r_half ; dy<=r_half ; dy++)
for (int dx=-r_half ; dx<=r_half ; dx++)
{
// special, simple implementation for no shift (no difference -> weight 1)
if (dx==0 && dy==0 && image_idx==0) {
#pragma omp parallel for
for (int y=n_half;y<h-n+n_half;y++) {
for (int x=n_half;x<w-n+n_half;x++) {
tmp_data[y*w+x].weight_sum += 1;
tmp_data[y*w+x].pixel_sum += current_image[y*current_image_stride+x];
}
}
continue;
}
// --- regular case ---
ctx->func.buildIntegralImage(integral,integral_stride,
current_image, current_image_stride,
compare_image, compare_image_stride,
w,h, dx,dy);
#pragma omp parallel for
for (int y=0;y<=h-n;y++) {
const uint32_t* integral_ptr1 = integral+(y -1)*integral_stride-1;
const uint32_t* integral_ptr2 = integral+(y+n-1)*integral_stride-1;
for (int x=0;x<=w-n;x++) {
const int xc = x+n_half;
const int yc = y+n_half;
// patch difference
int diff = (uint32_t)(integral_ptr2[n] - integral_ptr2[0] - integral_ptr1[n] + integral_ptr1[0]);
// sum pixel with weight
if (diff<diff_max) {
int diffidx = diff*weight_fact_table;
//float weight = exp(-diff*weightFact);
float weight = exptable[diffidx];
tmp_data[yc*w+xc].weight_sum += weight;
tmp_data[yc*w+xc].pixel_sum += weight * compare_image[(yc+dy)*compare_image_stride+xc+dx];
}
integral_ptr1++;
integral_ptr2++;
}
}
}
}
// --- fill output image ---
// copy border area
{
const uint8_t* in = images[0]->img;
int orig_in_stride = images[0]->stride;
for (int y=0; y<n_half ;y++) { memcpy(out+y*out_stride, in+y*orig_in_stride, w); }
for (int y=h-n_half;y<h ;y++) { memcpy(out+y*out_stride, in+y*orig_in_stride, w); }
for (int y=n_half ;y<h-n_half;y++) {
memcpy(out+y*out_stride, in+y*orig_in_stride, n_half);
memcpy(out+y*out_stride+w-n_half, in+y*orig_in_stride+w-n_half, n_half);
}
}
// output main image
for (int y=n_half;y<h-n_half;y++) {
for (int x=n_half;x<w-n_half;x++) {
*(out+y*out_stride+x) = tmp_data[y*w+x].pixel_sum / tmp_data[y*w+x].weight_sum;
}
}
free(tmp_data);
free(integral_mem);
}
static void NLMeans_color_auto(uint8_t** out, int* out_stride,
const ColorImage* img, // function takes ownership
NLMContext* ctx)
{
assert(ctx->param.n_frames >= 1);
assert(ctx->param.n_frames <= MAX_NLMeansImages);
// free oldest image
free_color_image(&ctx->images[ctx->param.n_frames-1]);
// shift old images one down and put new image into entry [0]
for (int i=ctx->param.n_frames-1; i>0; i--) {
ctx->images[i] = ctx->images[i-1];
ctx->image_available[i] = ctx->image_available[i-1];
}
ctx->images[0] = *img;
ctx->image_available[0] = 1;
// process color planes separately
for (int c=0;c<3;c++)
if (ctx->images[0].plane[c].img != NULL)
{
const MonoImage* images[MAX_NLMeansImages];
int i;
for (i=0; ctx->image_available[i]; i++) {
images[i] = &ctx->images[i].plane[c];
}
NLMeans_mono_multi(out[c], out_stride[c],
images, i, ctx);
}
}
static av_cold int init(AVFilterContext *ctx)
{
NLMContext *nlm = ctx->priv;
for (int i=0;i<MAX_NLMeansImages;i++) {
nlm->image_available[i] = 0;
}
nlm->func.buildIntegralImage = buildIntegralImage_scalar;
if (ARCH_X86) {
ff_nlmeans_init_x86(&nlm->func);
}
return 0;
}
static av_cold void uninit(AVFilterContext *ctx)
{
NLMContext *nlm = ctx->priv;
for (int i=0;i<MAX_NLMeansImages;i++) {
if (nlm->image_available[i]) {
free_color_image(&(nlm->images[i]));
nlm->image_available[i] = 0;
}
}
}
static int query_formats(AVFilterContext *ctx)
{
static const enum AVPixelFormat pix_fmts[] = {
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUV422P,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUV410P,
AV_PIX_FMT_YUV411P,
AV_PIX_FMT_YUV440P,
AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUVJ422P,
AV_PIX_FMT_YUVJ444P,
AV_PIX_FMT_YUVJ440P,
AV_PIX_FMT_NONE
};
ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
return 0;
}
static int config_input(AVFilterLink *inlink)
{
NLMContext *nlm = inlink->dst->priv;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
nlm->hsub = desc->log2_chroma_w;
nlm->vsub = desc->log2_chroma_h;
return 0;
}
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
NLMContext *nlm = inlink->dst->priv;
AVFilterLink *outlink = inlink->dst->outputs[0];
ColorImage bordered_image;
AVFrame *out;
int direct, c;
if (av_frame_is_writable(in)) {
direct = 1;
out = in;
} else {
direct = 0;
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
av_frame_free(&in);
return AVERROR(ENOMEM);
}
av_frame_copy_props(out, in);
}
// extend image with border
for (c = 0; c < 3; c++) {
int w = FF_CEIL_RSHIFT(in->width, (!!c * nlm->hsub));
int h = FF_CEIL_RSHIFT(in->height, (!!c * nlm->vsub));
int border = nlm->param.range/2;
alloc_and_copy_image_with_border(&bordered_image.plane[c],
in->data[c], in->linesize[c],
w,h,border);
}
NLMeans_color_auto(out->data, out->linesize,
&bordered_image,
nlm);
if (!direct)
av_frame_free(&in);
return ff_filter_frame(outlink, out);
}
#define OFFSET(x) offsetof(NLMContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
static const AVOption options[] = {
{ "h", "averaging weight decay parameter", OFFSET(param.h_param), AV_OPT_TYPE_DOUBLE, { .dbl = 8.0 }, 0.1, 100.0, FLAGS },
{ "patchsize", "patch width/height", OFFSET(param.patch_size), AV_OPT_TYPE_INT, { .i64 = 7 }, 3, 255, FLAGS },
{ "range", "search range", OFFSET(param.range), AV_OPT_TYPE_INT, { .i64 = 3 }, 1, 255, FLAGS },
{ "temporal", "temporal search range", OFFSET(param.n_frames), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, MAX_NLMeansImages, FLAGS },
{ NULL },
};
static const AVClass nlm_class = {
.class_name = "nlm",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
static const AVFilterPad avfilter_vf_nlm_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_input,
.filter_frame = filter_frame,
},
{ NULL }
};
static const AVFilterPad avfilter_vf_nlm_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO
},
{ NULL }
};
AVFilter ff_vf_nlmeans = {
.name = "nlmeans",
.description = NULL_IF_CONFIG_SMALL("Apply a Non-Local Means filter."),
.priv_size = sizeof(NLMContext),
.priv_class = &nlm_class,
.init = init,
.uninit = uninit,
.query_formats = query_formats,
.inputs = avfilter_vf_nlm_inputs,
.outputs = avfilter_vf_nlm_outputs,
};