[FFmpeg-devel] [PATCH] avfilter: add bilateral filter
Paul B Mahol
onemda at gmail.com
Sun Apr 28 17:42:45 EEST 2019
Signed-off-by: Paul B Mahol <onemda at gmail.com>
---
doc/filters.texi | 25 ++++
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_gblur.c | 257 ++++++++++++++++++++++++++++++++++++++-
4 files changed, 280 insertions(+), 4 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 14c33ecf90..04ca946d25 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -5953,6 +5953,31 @@ The filter accepts the following option:
Set the minimal luminance value. Default is @code{16}.
@end table
+ at section bilateral
+Apply bilateral filter, spatial smoothing while preserving edges.
+
+The filter accepts the following options:
+ at table @option
+ at item sigmaS
+Set sigma of gaussian function to calculate spatial weight.
+Allowed range is 0 to 1024. Default is 3.
+
+ at item sigmaR
+Set sigma of gaussian function to calculate range weight.
+Allowed range is 0 to 1024. Default is 0.5
+
+ at item steps
+Set number of gaussian steps. Default is 1.
+Alowed range is from 1 to 6.
+
+ at item bsteps
+Set number of bilateral steps. Default is 4.
+Alowed range is from 2 to 256.
+
+ at item planes
+Set planes to filter. Default is first only.
+ at end table
+
@section bitplanenoise
Show and measure bit plane noise.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 59d12ce069..a2e1477313 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -160,6 +160,7 @@ OBJS-$(CONFIG_AVGBLUR_OPENCL_FILTER) += vf_avgblur_opencl.o opencl.o \
opencl/avgblur.o boxblur.o
OBJS-$(CONFIG_BBOX_FILTER) += bbox.o vf_bbox.o
OBJS-$(CONFIG_BENCH_FILTER) += f_bench.o
+OBJS-$(CONFIG_BILATERAL_FILTER) += vf_gblur.o
OBJS-$(CONFIG_BITPLANENOISE_FILTER) += vf_bitplanenoise.o
OBJS-$(CONFIG_BLACKDETECT_FILTER) += vf_blackdetect.o
OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index ae725cb0e0..931ff9d6c5 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -150,6 +150,7 @@ extern AVFilter ff_vf_avgblur;
extern AVFilter ff_vf_avgblur_opencl;
extern AVFilter ff_vf_bbox;
extern AVFilter ff_vf_bench;
+extern AVFilter ff_vf_bilateral;
extern AVFilter ff_vf_bitplanenoise;
extern AVFilter ff_vf_blackdetect;
extern AVFilter ff_vf_blackframe;
diff --git a/libavfilter/vf_gblur.c b/libavfilter/vf_gblur.c
index f77a3fffc3..778163f53e 100644
--- a/libavfilter/vf_gblur.c
+++ b/libavfilter/vf_gblur.c
@@ -38,6 +38,8 @@ typedef struct GBlurContext {
float sigma;
float sigmaV;
+ float sigmaS;
+ float sigmaR;
int steps;
int planes;
@@ -45,6 +47,8 @@ typedef struct GBlurContext {
int planewidth[4];
int planeheight[4];
float *buffer;
+ float *den;
+ float *num;
float boundaryscale;
float boundaryscaleV;
float postscale;
@@ -52,6 +56,11 @@ typedef struct GBlurContext {
float nu;
float nuV;
int nb_planes;
+
+ float lut[65536];
+ int blut[256];
+ float *bbufers[256];
+ int bsteps;
} GBlurContext;
#define OFFSET(x) offsetof(GBlurContext, x)
@@ -212,7 +221,8 @@ static int query_formats(AVFilterContext *ctx)
static int config_input(AVFilterLink *inlink)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
- GBlurContext *s = inlink->dst->priv;
+ AVFilterContext *ctx = inlink->dst;
+ GBlurContext *s = ctx->priv;
s->depth = desc->comp[0].depth;
s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
@@ -222,9 +232,11 @@ static int config_input(AVFilterLink *inlink)
s->nb_planes = av_pix_fmt_count_planes(inlink->format);
- s->buffer = av_malloc_array(inlink->w, inlink->h * sizeof(*s->buffer));
- if (!s->buffer)
- return AVERROR(ENOMEM);
+ if (strcmp(ctx->filter->name, "bilateral")) {
+ s->buffer = av_malloc_array(inlink->w, inlink->h * sizeof(*s->buffer));
+ if (!s->buffer)
+ return AVERROR(ENOMEM);
+ }
if (s->sigmaV < 0) {
s->sigmaV = s->sigma;
@@ -332,10 +344,19 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
static av_cold void uninit(AVFilterContext *ctx)
{
GBlurContext *s = ctx->priv;
+ int i;
+ av_freep(&s->den);
+ av_freep(&s->num);
av_freep(&s->buffer);
+
+ for (i = 0; i < s->bsteps; i++) {
+ av_freep(&s->bbufers[i]);
+ }
}
+#if CONFIG_GBLUR_FILTER
+
static const AVFilterPad gblur_inputs[] = {
{
.name = "default",
@@ -365,3 +386,231 @@ AVFilter ff_vf_gblur = {
.outputs = gblur_outputs,
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
};
+#endif /* CONFIG_GBLUR_FILTER */
+
+#if CONFIG_BILATERAL_FILTER
+
+static const AVOption bilateral_options[] = {
+ { "sigmaS", "set sigma S", OFFSET(sigmaS), AV_OPT_TYPE_FLOAT, {.dbl=3}, 0.0, 1024, FLAGS },
+ { "sigmaR", "set sigma R", OFFSET(sigmaR), AV_OPT_TYPE_FLOAT, {.dbl=.5},0.0, 1024, FLAGS },
+ { "steps", "set number of gaussian steps", OFFSET(steps), AV_OPT_TYPE_INT, {.i64=1}, 1, 6, FLAGS },
+ { "bsteps", "set number of bilateral steps", OFFSET(bsteps), AV_OPT_TYPE_INT, {.i64=4}, 2, 256, FLAGS },
+ { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=1}, 0, 0xF, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(bilateral);
+
+static float lut_lookup(const float *lut, const int val1, const int val2)
+{
+ return lut[val1 > val2 ? val1 - val2 : val2 - val1];
+}
+
+static int bilateral_filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterContext *ctx = inlink->dst;
+ GBlurContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+ AVFrame *out;
+ int plane;
+
+ s->sigma = s->sigmaS;
+ set_params(s->sigmaS, s->steps, &s->postscale, &s->boundaryscale, &s->nu);
+ set_params(s->sigmaS, s->steps, &s->postscaleV, &s->boundaryscaleV, &s->nuV);
+
+ if (av_frame_is_writable(in)) {
+ out = in;
+ } else {
+ 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);
+ }
+
+ for (plane = 0; plane < s->nb_planes; plane++) {
+ const int height = s->planeheight[plane];
+ const int width = s->planewidth[plane];
+ const uint8_t *src = in->data[plane];
+ const uint16_t *src16 = (const uint16_t *)in->data[plane];
+ const int src_linesize = in->linesize[plane];
+ const int src16_linesize = in->linesize[plane] / 2;
+ uint8_t *dst = out->data[plane];
+ uint16_t *dst16 = (uint16_t *)out->data[plane];
+ const int dst_linesize = out->linesize[plane];
+ const int dst16_linesize = out->linesize[plane] / 2;
+ int k, y, x;
+
+ if (!s->sigmaS || !(s->planes & (1 << plane))) {
+ if (out != in)
+ av_image_copy_plane(out->data[plane], out->linesize[plane],
+ in->data[plane], in->linesize[plane],
+ width * ((s->depth + 7) / 8), height);
+ continue;
+ }
+
+ for (k = 0; k < s->bsteps; k++) {
+ float *num = s->num;
+ float *den = s->den;
+
+ src = in->data[plane];
+ src16 = (uint16_t *)in->data[plane];
+ if (s->depth == 8) {
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ den[x] = lut_lookup(s->lut, s->blut[k], src[x]);
+ num[x] = den[x] * src[x];
+ }
+ den += width;
+ num += width;
+ src += src_linesize;
+ }
+ } else {
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ den[x] = lut_lookup(s->lut, s->blut[k], src16[x]);
+ num[x] = den[x] * src16[x];
+ }
+ den += width;
+ num += width;
+ src16 += src16_linesize;
+ }
+ }
+
+ s->buffer = s->den;
+ gaussianiir2d(ctx, plane);
+ s->buffer = s->num;
+ gaussianiir2d(ctx, plane);
+ s->buffer = NULL;
+
+ num = s->num;
+ den = s->den;
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ s->bbufers[k][y * width + x] = den[x] == 0 ? 0 : num[x] / den[x];
+ }
+ num += width;
+ den += width;
+ }
+ }
+
+ src = in->data[plane];
+ src16 = (uint16_t *)in->data[plane];
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ for (k = 0; k < s->bsteps - 2; k++) {
+ if (s->depth == 8) {
+ if (src[x] < s->blut[k + 1] && src[x] >= s->blut[k])
+ break;
+ } else {
+ if (src16[x] < s->blut[k + 1] && src16[x] >= s->blut[k])
+ break;
+ }
+ }
+
+ if (s->depth == 8) {
+ dst[x] = av_clip_uint8(((s->blut[k + 1] - src[x]) * s->bbufers[k][x + y * width] +
+ (src[x] - s->blut[k]) * s->bbufers[k + 1][x + y * width]) /
+ (s->blut[k + 1] - s->blut[k]) + 0.5f);
+ } else {
+ dst16[x] = av_clip_uintp2_c(((s->blut[k + 1] - src16[x]) * s->bbufers[k][x + y * width] +
+ (src16[x] - s->blut[k]) * s->bbufers[k + 1][x + y * width]) /
+ (s->blut[k + 1] - s->blut[k]) + 0.5f, s->depth);
+ }
+ }
+
+ dst += dst_linesize;
+ src += src_linesize;
+ src16 += src16_linesize;
+ dst16 += dst16_linesize;
+ }
+ }
+
+ if (out != in)
+ av_frame_free(&in);
+ return ff_filter_frame(outlink, out);
+}
+
+static inline float normalized_gaussian(float x, float sigma)
+{
+ x /= sigma;
+ return expf(x*x/-2.f) / (sqrtf(M_PI * 2.f) * sigma);
+}
+
+static void create_lut(const int range, float sigmaR, float *lut)
+{
+ const int upper = FFMIN(range, (int)(sigmaR * 8.f * range + 0.5f));
+ int max = range + 1;
+ int i;
+
+ for (i = 0; i <= upper; i++) {
+ lut[i] = normalized_gaussian(i / (float)range, sigmaR);
+ }
+ if (i < max) {
+ const float value = lut[upper];
+
+ for (; i < max; i++) {
+ lut[i] = value;
+ }
+ }
+}
+
+static int bilateral_config_input(AVFilterLink *inlink)
+{
+ GBlurContext *s = inlink->dst->priv;
+ int ret, i;
+ float max;
+
+ ret = config_input(inlink);
+ if (ret < 0)
+ return ret;
+
+ max = (1 << s->depth) - 1;
+ for (i = 0; i < s->bsteps; i++) {
+ s->blut[i] = max * i / (s->bsteps - 1) + 0.5;
+ s->bbufers[i] = av_malloc_array(inlink->w, inlink->h * sizeof(float));
+ if (!s->bbufers[i])
+ return AVERROR(ENOMEM);
+ }
+
+ s->num = av_malloc_array(inlink->w, inlink->h * sizeof(*s->num));
+ s->den = av_malloc_array(inlink->w, inlink->h * sizeof(*s->den));
+ if (!s->den || !s->num)
+ return AVERROR(ENOMEM);
+
+ create_lut(max, s->sigmaR, s->lut);
+
+ return 0;
+}
+
+static const AVFilterPad bilateral_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = bilateral_config_input,
+ .filter_frame = bilateral_filter_frame,
+ },
+ { NULL }
+};
+
+static const AVFilterPad bilateral_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ },
+ { NULL }
+};
+
+AVFilter ff_vf_bilateral = {
+ .name = "bilateral",
+ .description = NULL_IF_CONFIG_SMALL("Apply Bilateral filter."),
+ .priv_size = sizeof(GBlurContext),
+ .priv_class = &bilateral_class,
+ .uninit = uninit,
+ .query_formats = query_formats,
+ .inputs = bilateral_inputs,
+ .outputs = bilateral_outputs,
+ .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
+};
+#endif /* CONFIG_BILATERAL_FILTER */
--
2.17.1
More information about the ffmpeg-devel
mailing list