[FFmpeg-devel] [PATCH] histogram filter

Stefano Sabatini stefasab at gmail.com
Thu Jan 31 01:05:23 CET 2013


On date Tuesday 2013-01-29 23:08:55 +0000, Paul B Mahol encoded:
> Signed-off-by: Paul B Mahol <onemda at gmail.com>
> ---
>  doc/filters.texi           |  67 ++++++++++
>  libavfilter/Makefile       |   1 +
>  libavfilter/allfilters.c   |   1 +
>  libavfilter/vf_histogram.c | 313 +++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 382 insertions(+)
>  create mode 100644 libavfilter/vf_histogram.c
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 21e2cff..a417ca1 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -2796,6 +2796,73 @@ the histogram. Possible values are @code{none}, @code{weak} or
>  @code{strong}. It defaults to @code{none}.
>  @end table
>  
> + at section histogram
> +
> +Compute and draw an histogram.
> +

> +Computed histogram is representation of distribution of color components
> +in an image.

The computed histogram is a representation of the color components
distribution in the input image.

> +
> +The filter accepts the following named parameters:
> +
> + at table @option
> + at item graph
> +Draw histogram graph, defaults to enabled, otherwise dumps
> +histogram values to output.

Draw histogram graph if set to 1, which is the default. If set to 0,
dump the histogram values to output.

...

Regarding the last expression "dump histgram values to output" it is
really not clear what it means (what values are "dumped"? what is the
output?).

Also: would it make sense to do both things at the same time? In that
case you may have a flags option (or alternatively two boolean
options).

> +
> + at item mode
> +Set histogram mode, defaults to @code{histogram}. Other possible
> +mode is @code{scope}.

s/defaults/default/ for grammar consistency.

> +
> + at item size
> +Set the video size, the value must be a valid abbreviation or in the
> +form @var{width}x at var{height}. Minimum allowed value is 256x256.
> +Note that @code{scope} mode will have most useful results if @var{width}
> +is multiple of 256.
> +
> + at item fg
> +Set foreground histogram color. Only used in @code{histogram}.
> +
> + at item bg
> +Set background histogram color. Only used in @code{histogram}.
> + at end table
> +

> +Following options specify which color components are used in histogram calculation:

The following options ...

> +
> + at table @option
> + at item @var{y} (Y/luminance component)
> + at item @var{u} (U/Cb component)
> + at item @var{v} (V/Cr component)
> + at item @var{a} (alpha component)
> + at item @var{r} (red component)
> + at item @var{g} (green component)
> + at item @var{b} (blue component)
> + at end table
> +
> + at subsection Examples
> +
> + at itemize
> +
> + at item
> +Calculate and draw histogram for luma plane:
> + at example
> +ffplay -i input -vf format=yuva444p,histogram=fg=yellow:bg=white
> + at end example
> +
> + at item
> +Calculate and draw histogram for chroma planes:
> + at example
> +ffplay -i input -vf format=yuva444p,histogram=y=0:u=1:v=1:a=0
> + at end example
> +
> + at item
> +Calculate and draw histogram for green plane only:
> + at example
> +ffplay -i input -vf format=rgba,histogram=r=0:g=1:b=0:a=0
> + at end example
> +
> + at end itemize
> +
>  @section hqdn3d
>  
>  High precision/quality 3d denoise filter. This filter aims to reduce
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 5835a7e..13bcaf3 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -113,6 +113,7 @@ OBJS-$(CONFIG_GEQ_FILTER)                    += vf_geq.o
>  OBJS-$(CONFIG_GRADFUN_FILTER)                += vf_gradfun.o
>  OBJS-$(CONFIG_HFLIP_FILTER)                  += vf_hflip.o
>  OBJS-$(CONFIG_HISTEQ_FILTER)                 += vf_histeq.o
> +OBJS-$(CONFIG_HISTOGRAM_FILTER)              += vf_histogram.o
>  OBJS-$(CONFIG_HQDN3D_FILTER)                 += vf_hqdn3d.o
>  OBJS-$(CONFIG_HUE_FILTER)                    += vf_hue.o
>  OBJS-$(CONFIG_IDET_FILTER)                   += vf_idet.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 24df561..71922b5 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -107,6 +107,7 @@ void avfilter_register_all(void)
>      REGISTER_FILTER(GRADFUN,        gradfun,        vf);
>      REGISTER_FILTER(HFLIP,          hflip,          vf);
>      REGISTER_FILTER(HISTEQ,         histeq,         vf);
> +    REGISTER_FILTER(HISTOGRAM,      histogram,      vf);
>      REGISTER_FILTER(HQDN3D,         hqdn3d,         vf);
>      REGISTER_FILTER(HUE,            hue,            vf);
>      REGISTER_FILTER(IDET,           idet,           vf);
> diff --git a/libavfilter/vf_histogram.c b/libavfilter/vf_histogram.c
> new file mode 100644
> index 0000000..13dc892
> --- /dev/null
> +++ b/libavfilter/vf_histogram.c
> @@ -0,0 +1,313 @@
> +/*
> + * Copyright (c) 2012 Paul B Mahol
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser 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
> + */
> +
> +#include "libavutil/opt.h"
> +#include "libavutil/parseutils.h"
> +#include "libavutil/pixdesc.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "drawutils.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +enum histogram_mode {

Nit+: HistogramMode seems more stylistically consistent (feel free to ignore)

> +    HISTOGRAM,
> +    SCOPE,
> +    MODE_NB
> +};

Nit: MODE_HISTOGRAM, MODE_SCOPE may avoid conflicts (and more grep-friendly)

> +
> +typedef struct HistogramContext {
> +    const AVClass *class;               ///< AVClass context for log and options purpose

> +    int            mode;

enum histogram_mode mode;

> +    unsigned       histogram[1 << 16];
> +    unsigned       histogram_size;
> +    unsigned       max_hval;
> +    int            comp_yuv[4], comp_rgb[4];
> +    uint8_t        rgba_map[4];
> +    int            is_packed_rgb;
> +    int            ncomp;
> +    int            do_graph;
> +    int            w, h;
> +    FFDrawContext  draw;
> +    FFDrawColor    bg, fg;
> +    char          *fg_color_str, *bg_color_str;
> +    uint8_t        fg_color_rgba[4], bg_color_rgba[4];
> +} HistogramContext;
> +
> +#define Y 0
> +#define U 1
> +#define V 2
> +#define R 0
> +#define G 1
> +#define B 2
> +#define A 3
> +
> +#define OFFSET(x) offsetof(HistogramContext, x)
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
> +
> +static const AVOption histogram_options[] = {
> +    { "mode", "set histogram mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=HISTOGRAM}, 0, MODE_NB-1, FLAGS, "mode"},
> +    { "histogram", "standard histogram", 0, AV_OPT_TYPE_CONST, {.i64=HISTOGRAM}, 0, 0, FLAGS, "mode" },
> +    { "scope", "scope", 0, AV_OPT_TYPE_CONST, {.i64=SCOPE}, 0, 0, FLAGS, "mode" },
> +    { "graph", "show histogram graph", OFFSET(do_graph), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
> +    { "size", "set histogram graph video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "256x256"}, 0, 0, FLAGS },
> +    { "fg", "set histogram foreground color", OFFSET(fg_color_str), AV_OPT_TYPE_STRING, {.str = "white"}, CHAR_MIN, CHAR_MAX, FLAGS },
> +    { "bg", "set histogram background color", OFFSET(bg_color_str), AV_OPT_TYPE_STRING, {.str = "black"}, CHAR_MIN, CHAR_MAX, FLAGS },
> +    { "y", "use Y component", OFFSET(comp_yuv[Y]), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
> +    { "u", "use U component", OFFSET(comp_yuv[U]), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS},
> +    { "v", "use V component", OFFSET(comp_yuv[V]), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS},
> +    { "a", "use A component", OFFSET(comp_yuv[A]), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS},
> +    { "r", "use R component", OFFSET(comp_rgb[R]), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
> +    { "g", "use G component", OFFSET(comp_rgb[G]), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
> +    { "b", "use B component", OFFSET(comp_rgb[B]), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
> +    { NULL },
> +};
> +
> +AVFILTER_DEFINE_CLASS(histogram);
> +
> +static av_cold int init(AVFilterContext *ctx, const char *args)
> +{
> +    HistogramContext *h = ctx->priv;
> +    int ret;
> +
> +    h->class = &histogram_class;
> +    av_opt_set_defaults(h);
> +

> +    if ((ret = (av_set_options_string(h, args, "=", ":"))) < 0)

unnecessary parens around (av_set_options_string())

> +        return ret;
> +

> +    if (h->h < 256 || h->w < 256)
> +        return AVERROR(EINVAL);

please tell something about the error or I bet users will complain

> +
> +    if ((ret = av_parse_color(h->fg_color_rgba, h->fg_color_str, -1, ctx)) < 0 ||
> +        (ret = av_parse_color(h->bg_color_rgba, h->bg_color_str, -1, ctx)) < 0 )
> +        return ret;
> +
> +    h->comp_rgb[A] = h->comp_yuv[A];
> +    if (!h->comp_rgb[R] && !h->comp_rgb[G] && !h->comp_rgb[B] && !h->comp_rgb[A] &&
> +        !h->comp_yuv[Y] && !h->comp_yuv[U] && !h->comp_yuv[V] && !h->comp_yuv[A])
> +        return AVERROR(EINVAL);
> +
> +    return 0;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum AVPixelFormat pix_fmts[] = {
> +        AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVJ444P,
> +        AV_PIX_FMT_BGR24, AV_PIX_FMT_RGB24,
> +        AV_PIX_FMT_ABGR, AV_PIX_FMT_0BGR,
> +        AV_PIX_FMT_ARGB, AV_PIX_FMT_0RGB,
> +        AV_PIX_FMT_BGRA, AV_PIX_FMT_BGR0,
> +        AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB0,
> +        AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY8A,
> +        AV_PIX_FMT_NONE
> +    };
> +
> +    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
> +
> +    return 0;
> +}
> +
> +static int config_input(AVFilterLink *inlink)
> +{
> +    HistogramContext *h = inlink->dst->priv;
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
> +
> +    h->histogram_size = 1 << desc->comp[0].depth_minus1 + 1;

Nit: (desc->comp[0].depth_minus1 + 1)

> +    h->ncomp          = desc->nb_components;
> +    h->is_packed_rgb  = ff_fill_rgba_map(h->rgba_map, inlink->format) >= 0;
> +
> +    return 0;
> +}
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    HistogramContext *h = ctx->priv;
> +
> +    if (!h->do_graph)
> +        return 0;
> +
> +    outlink->w = h->w;
> +    outlink->h = h->h;
> +
> +    outlink->sample_aspect_ratio = (AVRational){1,1};
> +
> +    ff_draw_init(&h->draw, outlink->format, 0);
> +    ff_draw_color(&h->draw, &h->bg, h->bg_color_rgba);
> +    ff_draw_color(&h->draw, &h->fg, h->fg_color_rgba);
> +
> +    return 0;
> +}
> +
> +static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *in)
> +{
> +    HistogramContext *h   = inlink->dst->priv;
> +    AVFilterContext *ctx  = inlink->dst;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    const uint8_t *src;
> +    int i, j, k, ret;
> +
> +    if (h->is_packed_rgb) {
> +        src = in->data[0];
> +        for (i = 0; i < in->video->h; i++) {
> +            for (j = 0; j < in->video->w * h->ncomp; j += h->ncomp) {
> +                unsigned v = 0, used = 0;
> +
> +                for (k = 0; k < h->ncomp; k++) {
> +                    if (h->comp_rgb[k]) {
> +                        v += src[j + h->rgba_map[k]];
> +                        used++;
> +                    }
> +                }
> +
> +                if (used)
> +                    h->histogram[v / used]++;
> +            }
> +            src += in->linesize[0];
> +        }
> +    } else {
> +        for (i = 0; i < in->video->h; i++) {
> +            for (j = 0; j < in->video->w; j++) {
> +                unsigned v = 0, used = 0;
> +
> +                for (k = 0; k < h->ncomp; k++) {
> +                    if (h->comp_yuv[k]) {
> +                        src = in->data[k] + i * in->linesize[k];
> +                        v += src[j];
> +                        used++;
> +                    }
> +                }
> +
> +                if (used)
> +                    h->histogram[v / used]++;
> +            }
> +        }
> +    }
> +
> +    for (i = 0; i < h->histogram_size; i++) {
> +        h->max_hval = FFMAX(h->max_hval, h->histogram[i]);
> +        if (h->histogram[i])
> +            av_log(ctx, h->do_graph ? AV_LOG_VERBOSE : AV_LOG_INFO, "i:%d c:%d\n", i, h->histogram[i]);

I'd rather print this only in case log_graph is enabled (see also my
suggestion about the possibility to enable both).

> +    }
> +
> +    if (h->do_graph) {
> +        AVFilterBufferRef *outpicref;
> +        int col_width = h->w / h->histogram_size;
> +
> +        outpicref = ff_get_video_buffer(outlink, AV_PERM_WRITE, outlink->w, outlink->h);
> +        if (!outpicref) {
> +            avfilter_unref_bufferp(&in);
> +            return AVERROR(ENOMEM);
> +        }
> +
> +        outpicref->pts = in->pts;
> +        outpicref->pos = in->pos;
> +
> +        ff_fill_rectangle(&h->draw, &h->bg, outpicref->data, outpicref->linesize, 0, 0, h->w, h->h);
> +        if (h->mode == HISTOGRAM) {
> +            for (i = 0; i < h->histogram_size; i++) {
> +                int col_height = (float) h->histogram[i] / h->max_hval * h->h;
> +
> +                ff_fill_rectangle(&h->draw, &h->fg,
> +                                  outpicref->data, outpicref->linesize,
> +                                  i * col_width, h->h - col_height,
> +                                  col_width, col_height);
> +            }
> +        } else {

Nit: else if (mode == SCOPE)

helps readability a bit, more robust

> +            if (!h->is_packed_rgb) {
> +                for (i = 0; i < in->video->w; i++) {
> +                    for (j = 0; j < in->video->h; j++) {
> +                        for (k = 0; k < h->ncomp; k++) {
> +                            if (h->comp_yuv[k]) {

> +                                if (outpicref->data[k][((h->h - 1) - in->data[k][j * in->video->w + i]) * h->w + i] < 255)
> +                                    outpicref->data[k][((h->h - 1) - in->data[k][j * in->video->w + i]) * h->w + i] += 2;

you could avoid to repeat this long expression, here and especially below

Also I'm not sure what this code is doing, what's this scope mode
about?

> +                            }
> +                        }
> +                    }
> +                }
> +            } else {
> +                for (i = 0; i < in->video->w; i++) {
> +                    for (j = 0; j < in->video->h; j++) {
> +                        for (k = 0; k < h->ncomp; k++) {
> +                            if (h->comp_rgb[k]) {
> +                                if (outpicref->data[0][((h->h - 1) * h->ncomp - in->data[0][j * in->video->w * h->ncomp + i * h->ncomp] + k) * h->w + i * h->ncomp + k] < 255)
> +                                    outpicref->data[0][((h->h - 1) * h->ncomp - in->data[0][j * in->video->w * h->ncomp + i * h->ncomp] + k) * h->w + i * h->ncomp + k] += 2;


j * in->video->w * h->ncomp + i * h->ncomp => h->ncomp can be
factorized out

[...]
-- 
FFmpeg = Freak and Faithless Minimalistic Pure Easy Guru


More information about the ffmpeg-devel mailing list