diff mbox series

[FFmpeg-devel] avfilter: add minvar video filter

Message ID CAPYw7P6oHCbNKhJDrV1cRO8sToLTbfi0UcVavQ7Wmr4ZpvuF0w@mail.gmail.com
State New
Headers show
Series [FFmpeg-devel] avfilter: add minvar video filter | expand

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Paul B Mahol Dec. 8, 2022, 5:56 p.m. UTC
Patch attached.

TODO: documentation.
diff mbox series

Patch

From 85063fe1d9f1fb33670068f7d3bb9038e22f2f75 Mon Sep 17 00:00:00 2001
From: Paul B Mahol <onemda@gmail.com>
Date: Wed, 7 Dec 2022 22:09:59 +0100
Subject: [PATCH] avfilter: add minvar video filter

Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/vf_minvar.c  | 330 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 332 insertions(+)
 create mode 100644 libavfilter/vf_minvar.c

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 9a850d5dc5..321bd9a0c8 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -377,6 +377,7 @@  OBJS-$(CONFIG_MESTIMATE_FILTER)              += vf_mestimate.o motion_estimation
 OBJS-$(CONFIG_METADATA_FILTER)               += f_metadata.o
 OBJS-$(CONFIG_MIDEQUALIZER_FILTER)           += vf_midequalizer.o framesync.o
 OBJS-$(CONFIG_MINTERPOLATE_FILTER)           += vf_minterpolate.o motion_estimation.o
+OBJS-$(CONFIG_MINVAR_FILTER)                 += vf_minvar.o
 OBJS-$(CONFIG_MIX_FILTER)                    += vf_mix.o framesync.o
 OBJS-$(CONFIG_MONOCHROME_FILTER)             += vf_monochrome.o
 OBJS-$(CONFIG_MORPHO_FILTER)                 += vf_morpho.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 2ece5c15c8..a29dff0bdf 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -355,6 +355,7 @@  extern const AVFilter ff_vf_mestimate;
 extern const AVFilter ff_vf_metadata;
 extern const AVFilter ff_vf_midequalizer;
 extern const AVFilter ff_vf_minterpolate;
+extern const AVFilter ff_vf_minvar;
 extern const AVFilter ff_vf_mix;
 extern const AVFilter ff_vf_monochrome;
 extern const AVFilter ff_vf_morpho;
diff --git a/libavfilter/vf_minvar.c b/libavfilter/vf_minvar.c
new file mode 100644
index 0000000000..55ec98e133
--- /dev/null
+++ b/libavfilter/vf_minvar.c
@@ -0,0 +1,330 @@ 
+/*
+ * Copyright (c) 2022 Paul B Mahol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "libavutil/imgutils.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "internal.h"
+#include "video.h"
+
+typedef struct MinVarContext {
+    const AVClass *class;
+
+    double variance;
+    int sizew;
+    int sizeh;
+    int planes;
+
+    int depth;
+    int max;
+    int planewidth[4];
+    int planeheight[4];
+
+    uint64_t *ii[4];
+    uint64_t *i2[4];
+    ptrdiff_t i_linesize[4];
+    size_t i_size[4];
+
+    int nb_planes;
+
+    void (*compute_sat)(const uint8_t *ssrc,
+                        int linesize,
+                        int w, int h,
+                        int max,
+                        uint64_t *ii,
+                        uint64_t *i2,
+                        int i_linesize);
+    int (*filter)(AVFilterContext *ctx, void *arg,
+                  int jobnr, int nb_jobs);
+} MinVarContext;
+
+#define OFFSET(x) offsetof(MinVarContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
+
+static const AVOption minvar_options[] = {
+    { "var", "set global noise variance", OFFSET(variance), AV_OPT_TYPE_DOUBLE,{.dbl=0}, 0,  16, FLAGS },
+    { "rw",  "set horizontal patch size", OFFSET(sizew),    AV_OPT_TYPE_INT,   {.i64=5}, 1,  16, FLAGS },
+    { "rh",  "set vertical patch size",   OFFSET(sizeh),    AV_OPT_TYPE_INT,   {.i64=5}, 1,  16, FLAGS },
+    { "planes", "set planes to filter",   OFFSET(planes),   AV_OPT_TYPE_INT,   {.i64=7}, 0, 0xF, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(minvar);
+
+typedef struct ThreadData {
+    AVFrame *in, *out;
+} ThreadData;
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    MinVarContext *s = ctx->priv;
+
+    for (int p = 0; p < 4; p++) {
+        av_freep(&s->ii[p]);
+        av_freep(&s->i2[p]);
+    }
+}
+
+#define COMPUTE_SAT(type, depth)                     \
+static void compute_sat##depth(const uint8_t *ssrc,  \
+                               int linesize,         \
+                               int w, int h,         \
+                               int max,              \
+                               uint64_t *ii,         \
+                               uint64_t *i2,         \
+                               int i_linesize)       \
+{                                                    \
+    const type *src = (const type *)ssrc;            \
+                                                     \
+    linesize /= sizeof(type);                        \
+    ii += i_linesize;                                \
+    i2 += i_linesize;                                \
+                                                     \
+    for (int y = 0; y < h; y++) {                    \
+        uint64_t sum2 = 0;                           \
+        uint64_t sum = 0;                            \
+                                                     \
+        for (int x = 1; x < w; x++) {                \
+            const int v = src[x-1];                  \
+            sum += v;                                \
+            sum2 += v*v;                             \
+            ii[x] = sum + ii[x - i_linesize];        \
+            i2[x] = sum2 + i2[x - i_linesize];       \
+        }                                            \
+                                                     \
+        src += linesize;                             \
+        ii += i_linesize;                            \
+        i2 += i_linesize;                            \
+    }                                                \
+}
+
+COMPUTE_SAT(uint8_t, 8)
+COMPUTE_SAT(uint16_t, 16)
+
+#define SQR(x) ((x) * (x))
+
+#define FILTER(type, ftype, ddepth, LRINT)           \
+static int filter##ddepth(AVFilterContext *ctx,      \
+                          void *arg,                 \
+                          int jobnr, int nb_jobs)    \
+{                                                    \
+    MinVarContext *s = ctx->priv;                    \
+    const int depth = s->depth;                      \
+    const ftype gvar = SQR(s->variance * ((1 << (depth - 8))) - 1); \
+    ThreadData *td = arg;                            \
+    AVFrame *out = td->out;                          \
+    AVFrame *in = td->in;                            \
+                                                               \
+    for (int plane = 0; plane < s->nb_planes; plane++) {       \
+        const ptrdiff_t slinesize = in->linesize[plane] / sizeof(type); \
+        const ptrdiff_t linesize = out->linesize[plane] / sizeof(type); \
+        const int height = s->planeheight[plane];              \
+        const int width = s->planewidth[plane];                \
+        const int slice_start = (height * jobnr) / nb_jobs;    \
+        const int slice_end = (height * (jobnr+1)) / nb_jobs;  \
+        const int i_linesize = s->i_linesize[plane];           \
+        const int sizeh = s->sizeh;                            \
+        const int sizew = s->sizew;                            \
+        const uint64_t *ii = s->ii[plane] + i_linesize;        \
+        const uint64_t *i2 = s->i2[plane] + i_linesize;                   \
+        const type *src = ((const type *)in->data[plane]) + slice_start * slinesize; \
+        type *dst = ((type *)out->data[plane]) + slice_start * linesize;         \
+                                                                          \
+        if (!(s->planes & (1 << plane)))                                  \
+            continue;                                                     \
+        for (int y = slice_start; y < slice_end; y++) {                   \
+            const int t = FFMIN(sizeh, y);                                \
+            const int b = FFMIN(sizeh, height - y - 1);                   \
+            const int tb = t+b;                                           \
+            const int yb = (y+b) * i_linesize;                            \
+            const int yt = (y-t) * i_linesize;                            \
+                                                                          \
+            for (int x = 0; x < width; x++) {                             \
+                const int l = FFMIN(sizew, x);                            \
+                const int r = FFMIN(sizew, width - x - 1);                \
+                const int xl = x - l;                                     \
+                const int xr = x + r;                                     \
+                uint64_t tl2 = i2[yt + xl];                               \
+                uint64_t tr2 = i2[yt + xr];                               \
+                uint64_t bl2 = i2[yb + xl];                               \
+                uint64_t br2 = i2[yb + xr];                               \
+                uint64_t tl  = ii[yt + xl];                               \
+                uint64_t tr  = ii[yt + xr];                               \
+                uint64_t bl  = ii[yb + xl];                               \
+                uint64_t br  = ii[yb + xr];                               \
+                const ftype scale = 1.f / ((l + r) * tb);                 \
+                ftype I2 = br2 - bl2 - tr2 + tl2;                         \
+                ftype I1 = br - bl - tr + tl;                             \
+                const ftype mean = I1 * scale;                            \
+                const ftype lvar = I2 * scale - mean * mean;              \
+                                                                          \
+                if (lvar > gvar)                                          \
+                    dst[x] = av_clip_uintp2_c(LRINT(src[x] + ((gvar / lvar) * (mean - src[x]))), depth); \
+                else                                                      \
+                    dst[x] = mean;                                        \
+            }                                                             \
+                                                                          \
+            src += slinesize;                                             \
+            dst += linesize;                                              \
+        }                                                                 \
+    }                                                                     \
+                                                                          \
+    return 0;                                                             \
+}
+
+FILTER(uint8_t, float, 8, lrintf)
+FILTER(uint16_t, double, 16, lrint)
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+    MinVarContext *s = ctx->priv;
+
+    uninit(ctx);
+
+    s->depth = desc->comp[0].depth;
+    s->max = (1 << s->depth) - 1;
+    s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
+    s->planewidth[0] = s->planewidth[3] = inlink->w;
+    s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
+    s->planeheight[0] = s->planeheight[3] = inlink->h;
+
+    s->compute_sat = s->depth <= 8 ? compute_sat8 : compute_sat16;
+    s->filter = s->depth <= 8 ? filter8 : filter16;
+    s->nb_planes = av_pix_fmt_count_planes(inlink->format);
+
+    for (int p = 0; p < s->nb_planes; p++) {
+        s->i_linesize[p] = (s->planewidth[p] + 1);
+        s->i_size[p] = s->i_linesize[p] * (s->planeheight[p] + 1);
+        s->ii[p] = av_calloc(s->i_size[p], sizeof(*s->ii[0]));
+        s->i2[p] = av_calloc(s->i_size[p], sizeof(*s->i2[0]));
+        if (!s->ii[p] || !s->i2[p])
+            return AVERROR(ENOMEM);
+    }
+
+    return 0;
+}
+
+static const enum AVPixelFormat pix_fmts[] = {
+    AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
+    AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
+    AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
+    AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
+    AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
+    AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
+    AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
+    AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
+    AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
+    AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
+    AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
+    AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
+    AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
+    AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
+    AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
+    AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
+    AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
+    AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
+    AV_PIX_FMT_NONE
+};
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterContext *ctx = inlink->dst;
+    MinVarContext *s = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+    ThreadData td;
+    AVFrame *out;
+
+    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 (int plane = 0; plane < s->nb_planes; plane++) {
+        const int height = s->planeheight[plane];
+        const int i_linesize = s->i_linesize[plane];
+        const int width = s->planewidth[plane];
+        uint64_t *ii = s->ii[plane];
+        uint64_t *i2 = s->i2[plane];
+
+        if (!(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;
+        }
+
+        s->compute_sat(in->data[plane], in->linesize[plane],
+                       width, height, s->max,
+                       ii, i2, i_linesize);
+    }
+
+    td.in = in;
+    td.out = out;
+
+    ff_filter_execute(ctx, s->filter, &td, NULL,
+                      FFMIN(s->planeheight[1], ff_filter_get_nb_threads(ctx)));
+
+    if (out != in)
+        av_frame_free(&in);
+    return ff_filter_frame(outlink, out);
+}
+
+static const AVFilterPad minvar_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .config_props = config_input,
+        .filter_frame = filter_frame,
+    },
+};
+
+static const AVFilterPad minvar_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+};
+
+const AVFilter ff_vf_minvar = {
+    .name          = "minvar",
+    .description   = NULL_IF_CONFIG_SMALL("Apply Minimum Variance filter."),
+    .priv_size     = sizeof(MinVarContext),
+    .priv_class    = &minvar_class,
+    .uninit        = uninit,
+    FILTER_INPUTS(minvar_inputs),
+    FILTER_OUTPUTS(minvar_outputs),
+    FILTER_PIXFMTS_ARRAY(pix_fmts),
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
+                     AVFILTER_FLAG_SLICE_THREADS,
+    .process_command = ff_filter_process_command,
+};
-- 
2.37.2