[FFmpeg-devel] [PATCH] lavfi: edgedetect filter

Nicolas George nicolas.george at normalesup.org
Tue Aug 7 20:02:47 CEST 2012


Le primidi 21 thermidor, an CCXX, Clément Bœsch a écrit :
> FIXME: bump lavfi minor
> ---
> Canny Edge Detection, with no real tweak. Here are the results:
> http://imgur.com/a/ujsG5

I do not know the algorithm, I can not comment on that part. Looks good,
though.

> ---
>  doc/filters.texi            |   5 +
>  libavfilter/Makefile        |   1 +
>  libavfilter/allfilters.c    |   1 +
>  libavfilter/vf_edgedetect.c | 274 ++++++++++++++++++++++++++++++++++++++++++++
>  tests/lavfi-regression.sh   |   1 +
>  tests/ref/lavfi/edgedetect  |   1 +
>  6 files changed, 283 insertions(+)
>  create mode 100644 libavfilter/vf_edgedetect.c
>  create mode 100644 tests/ref/lavfi/edgedetect
> 
> diff --git a/doc/filters.texi b/doc/filters.texi
> index c712f50..18240fe 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -1929,6 +1929,11 @@ For more information about libfreetype, check:
>  For more information about fontconfig, check:
>  @url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}.
>  
> + at section edgedetect
> +
> +Detect and draw edges. The filter uses the Canny Edge Detection algorithm, with
> +no parameter at the moment.
> +
>  @section fade
>  
>  Apply fade-in/out effect to input video.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 727ab4e..da657a2 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -89,6 +89,7 @@ OBJS-$(CONFIG_DELOGO_FILTER)                 += vf_delogo.o
>  OBJS-$(CONFIG_DESHAKE_FILTER)                += vf_deshake.o
>  OBJS-$(CONFIG_DRAWBOX_FILTER)                += vf_drawbox.o
>  OBJS-$(CONFIG_DRAWTEXT_FILTER)               += vf_drawtext.o
> +OBJS-$(CONFIG_EDGEDETECT_FILTER)             += vf_edgedetect.o
>  OBJS-$(CONFIG_FADE_FILTER)                   += vf_fade.o
>  OBJS-$(CONFIG_FIELDORDER_FILTER)             += vf_fieldorder.o
>  OBJS-$(CONFIG_FIFO_FILTER)                   += fifo.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 403383d..858b9e6 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -79,6 +79,7 @@ void avfilter_register_all(void)
>      REGISTER_FILTER (DESHAKE,     deshake,     vf);
>      REGISTER_FILTER (DRAWBOX,     drawbox,     vf);
>      REGISTER_FILTER (DRAWTEXT,    drawtext,    vf);
> +    REGISTER_FILTER (EDGEDETECT,  edgedetect,  vf);
>      REGISTER_FILTER (FADE,        fade,        vf);
>      REGISTER_FILTER (FIELDORDER,  fieldorder,  vf);
>      REGISTER_FILTER (FIFO,        fifo,        vf);
> diff --git a/libavfilter/vf_edgedetect.c b/libavfilter/vf_edgedetect.c
> new file mode 100644
> index 0000000..7b2553e
> --- /dev/null
> +++ b/libavfilter/vf_edgedetect.c
> @@ -0,0 +1,274 @@
> +/*
> + * Copyright (c) 2012 Clément Bœsch
> + *
> + * 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
> + */
> +
> +/**
> + * @file
> + * Edge detection filter
> + */
> +
> +#include "libavutil/mathematics.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +typedef struct {
> +    uint8_t  *tmpbuf;
> +    uint16_t *gradients;
> +    char     *directions;
> +} EdgeDetectContext;
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum PixelFormat pix_fmts[] = {PIX_FMT_GRAY8, PIX_FMT_NONE};
> +    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
> +    return 0;
> +}
> +
> +static int config_props(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    EdgeDetectContext *edgedetect = ctx->priv;
> +
> +    edgedetect->tmpbuf     = av_malloc (inlink->w * inlink->h);

> +    edgedetect->gradients  = av_mallocz(inlink->w * inlink->h * sizeof(*edgedetect->gradients));

av_calloc(), to guard against overflows?

> +    edgedetect->directions = av_malloc (inlink->w * inlink->h);
> +    if (!edgedetect->tmpbuf || !edgedetect->gradients || !edgedetect->directions)
> +        return AVERROR(ENOMEM);
> +    return 0;
> +}
> +
> +static void gaussian_blur(AVFilterContext *ctx, int w, int h,
> +                                uint8_t *dst, int dst_linesize,
> +                          const uint8_t *src, int src_linesize)
> +{
> +    int i, j;
> +
> +    memcpy(dst,                src,                w);
> +    memcpy(dst + dst_linesize, src + src_linesize, w);
> +    for (j = 2; j < h - 2; j++) {
> +        dst[j*dst_linesize  ] = src[j*src_linesize  ];
> +        dst[j*dst_linesize+1] = src[j*src_linesize+1];
> +        for (i = 2; i < w - 2; i++) {
> +            dst[j*dst_linesize + i] =
> +                 ((src[(j-2)*src_linesize + (i-2)] + src[(j+2)*src_linesize + (i-2)]) * 2
> +                + (src[(j-2)*src_linesize + (i-1)] + src[(j+2)*src_linesize + (i-1)]) * 4
> +                + (src[(j-2)*src_linesize +  i   ] + src[(j+2)*src_linesize +  i   ]) * 5
> +                + (src[(j-2)*src_linesize + (i+1)] + src[(j+2)*src_linesize + (i+1)]) * 4
> +                + (src[(j-2)*src_linesize + (i+2)] + src[(j+2)*src_linesize + (i+2)]) * 2
> +
> +                + (src[(j-1)*src_linesize + (i-2)] + src[(j-1)*src_linesize + (i-2)]) *  4
> +                + (src[(j-1)*src_linesize + (i-1)] + src[(j-1)*src_linesize + (i-1)]) *  9
> +                + (src[(j-1)*src_linesize +  i   ] + src[(j-1)*src_linesize +  i   ]) * 12
> +                + (src[(j-1)*src_linesize + (i+1)] + src[(j-1)*src_linesize + (i+1)]) *  9
> +                + (src[(j-1)*src_linesize + (i+2)] + src[(j-1)*src_linesize + (i+2)]) *  4
> +
> +                + src[j*src_linesize + (i-2)] *  5
> +                + src[j*src_linesize + (i-1)] * 12
> +                + src[j*src_linesize +  i   ] * 15
> +                + src[j*src_linesize + (i+1)] * 12
> +                + src[j*src_linesize + (i+2)] *  5) / 159;
> +        }
> +        dst[j*dst_linesize + i  ] = src[j*src_linesize + i  ];
> +        dst[j*dst_linesize + i+1] = src[j*src_linesize + i+1];
> +    }
> +    memcpy(dst +  j   *dst_linesize, src +  j   *src_linesize, w);
> +    memcpy(dst + (j+1)*dst_linesize, src + (j+1)*src_linesize, w);

Is gcc smart enough to avoid all those multiplications by "linesize"? Just
adding "src += linesize; dst += linesize;" would probably be more readable
anyway.

> +}
> +
> +enum {
> +    DIRECTION_45UP,
> +    DIRECTION_45DOWN,
> +    DIRECTION_HORIZONTAL,
> +    DIRECTION_VERTICAL,
> +};
> +
> +static int get_rounded_direction(int gx, int gy)
> +{
> +    float tanpi8gx, tan3pi8gx;
> +
> +    /* reference angles:
> +     *   tan( pi/8) = sqrt(2)-1 ~= 0.41421...
> +     *   tan(3pi/8) = sqrt(2)+1 ~= 2.41421...
> +     * Gy/Gx is the tangent of theta, so Gy/Gx is compared against <ref-angle>,
> +     * or more simply Gy against <ref-angle>*Gx
> +     */
> +    if (gx) {
> +        if (gx < 0) // left side, switch signs
> +            gx = -gx, gy = -gy;
> +        tanpi8gx  = (M_SQRT2-1) * gx;
> +        tan3pi8gx = (M_SQRT2+1) * gx;
> +        if (gy > -tan3pi8gx && gy < -tanpi8gx)  return DIRECTION_45UP;
> +        if (gy > -tanpi8gx  && gy <  tanpi8gx)  return DIRECTION_HORIZONTAL;
> +        if (gy >  tanpi8gx  && gy <  tan3pi8gx) return DIRECTION_45DOWN;
> +    }
> +    return DIRECTION_VERTICAL;
> +}

A pure integer version would be nicer and more FATE-friendly. Since gx and
gy are bounded by ±256, the extra precision is not necessary:

if (gy << 16 > 158217) return ...

(16 instead of 8 if someone wants to implement higher bit depths)

> +
> +static void sobel(AVFilterContext *ctx, int w, int h,
> +                        uint16_t *dst, int dst_linesize,
> +                  const uint8_t  *src, int src_linesize)
> +{
> +    int i, j;
> +    EdgeDetectContext *edgedetect = ctx->priv;
> +
> +    for (j = 1; j < h - 1; j++) {
> +        for (i = 1; i < w - 1; i++) {
> +            const int gx =
> +                -1*src[(j-1)*src_linesize + i-1] + 1*src[(j-1)*src_linesize + i+1]
> +                -2*src[ j   *src_linesize + i-1] + 2*src[ j   *src_linesize + i+1]
> +                -1*src[(j+1)*src_linesize + i-1] + 1*src[(j+1)*src_linesize + i+1];
> +            const int gy =
> +                -1*src[(j-1)*src_linesize + i-1] + 1*src[(j+1)*src_linesize + i-1]
> +                -2*src[(j-1)*src_linesize + i  ] + 2*src[(j+1)*src_linesize + i  ]
> +                -1*src[(j-1)*src_linesize + i+1] + 1*src[(j+1)*src_linesize + i+1];
> +
> +            dst[j*dst_linesize + i] = FFABS(gx) + FFABS(gy);
> +            edgedetect->directions[j*w + i] = get_rounded_direction(gx, gy);
> +        }
> +    }
> +}
> +
> +static void non_maximum_suppression(AVFilterContext *ctx, int w, int h,
> +                                          uint8_t  *dst, int dst_linesize,
> +                                    const uint16_t *src, int src_linesize)
> +{
> +    int i, j;
> +    EdgeDetectContext *edgedetect = ctx->priv;
> +
> +#define COPY_MAXIMA(ay, ax, by, bx) do {                               \
> +    if (src[j*src_linesize + i] > src[(j+(ay))*src_linesize + i+(ax)] &&    \
> +        src[j*src_linesize + i] > src[(j+(by))*src_linesize + i+(bx)])      \
> +        dst[j*dst_linesize + i] = av_clip_uint8(src[j*src_linesize + i]);   \
> +} while (0)
> +
> +    for (j = 1; j < h - 1; j++) {
> +        for (i = 1; i < w - 1; i++) {
> +            switch (edgedetect->directions[j*w + i]) {
> +            case DIRECTION_45UP:        COPY_MAXIMA( 1, -1, -1,  1); break;
> +            case DIRECTION_45DOWN:      COPY_MAXIMA(-1, -1,  1,  1); break;
> +            case DIRECTION_HORIZONTAL:  COPY_MAXIMA( 0, -1,  0,  1); break;
> +            case DIRECTION_VERTICAL:    COPY_MAXIMA(-1,  0,  1,  0); break;
> +            }
> +        }
> +    }
> +}
> +
> +static void double_threshold(AVFilterContext *ctx, int w, int h,
> +                                   uint8_t *dst, int dst_linesize,
> +                             const uint8_t *src, int src_linesize)
> +{
> +    int i, j;
> +
> +#define THRES_HIGH 80
> +#define THRES_LOW  20
> +
> +    for (j = 0; j < h; j++) {
> +        for (i = 0; i < w; i++) {
> +            if (src[j*src_linesize + i] > THRES_HIGH) {
> +                dst[j*dst_linesize + i] = src[j*src_linesize + i];
> +                continue;
> +            }
> +
> +            if ((!i || i == w - 1 || !j || j == h - 1) &&
> +                src[j*src_linesize + i] > THRES_LOW &&
> +                (src[(j-1)*src_linesize + i-1] > THRES_HIGH ||
> +                 src[(j-1)*src_linesize + i  ] > THRES_HIGH ||
> +                 src[(j-1)*src_linesize + i+1] > THRES_HIGH ||
> +                 src[ j   *src_linesize + i-1] > THRES_HIGH ||
> +                 src[ j   *src_linesize + i+1] > THRES_HIGH ||
> +                 src[(j+1)*src_linesize + i-1] > THRES_HIGH ||
> +                 src[(j+1)*src_linesize + i  ] > THRES_HIGH ||
> +                 src[(j+1)*src_linesize + i+1] > THRES_HIGH))
> +                dst[j*dst_linesize + i] = src[j*src_linesize + i];
> +            else
> +                dst[j*dst_linesize + i] = 0;
> +        }
> +    }
> +}
> +
> +static int end_frame(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    EdgeDetectContext *edgedetect = ctx->priv;
> +    AVFilterLink *outlink = inlink->dst->outputs[0];
> +    AVFilterBufferRef  *inpicref = inlink->cur_buf;
> +    AVFilterBufferRef *outpicref = outlink->out_buf;
> +    uint8_t  *tmpbuf    = edgedetect->tmpbuf;
> +    uint16_t *gradients = edgedetect->gradients;
> +
> +    /* gaussian filter to reduce noise  */
> +    gaussian_blur(ctx, inlink->w, inlink->h,
> +                  tmpbuf,            inlink->w,
> +                  inpicref->data[0], inpicref->linesize[0]);
> +
> +    /* compute 16-bits gradients and direction for next step */
> +    sobel(ctx, inlink->w, inlink->h,
> +          gradients, inlink->w,
> +          tmpbuf,    inlink->w);
> +
> +    /* non_maximum_suppression() will actually keep & clip what's necessary and
> +     * ignore the rest, so we need a clean output buffer */
> +    memset(tmpbuf, 0, inlink->w * inlink->h);
> +    non_maximum_suppression(ctx, inlink->w, inlink->h,
> +                            tmpbuf,    inlink->w,
> +                            gradients, inlink->w);
> +
> +    /* keep high values, or low values surrounded by high values */
> +    double_threshold(ctx, inlink->w, inlink->h,
> +                     outpicref->data[0], outpicref->linesize[0],
> +                     tmpbuf,             inlink->w);
> +
> +    ff_draw_slice(outlink, 0, outlink->h, 1);
> +    return ff_end_frame(outlink);
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    EdgeDetectContext *edgedetect = ctx->priv;
> +    av_freep(&edgedetect->tmpbuf);
> +    av_freep(&edgedetect->gradients);
> +    av_freep(&edgedetect->directions);
> +}
> +
> +static int null_draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir) { return 0; }
> +
> +AVFilter avfilter_vf_edgedetect = {
> +    .name          = "edgedetect",
> +    .description   = NULL_IF_CONFIG_SMALL("Detect and draw edge."),
> +    .priv_size     = sizeof(EdgeDetectContext),
> +    .uninit        = uninit,
> +    .query_formats = query_formats,
> +
> +    .inputs    = (const AVFilterPad[]) {{ .name             = "default",
> +                                          .type             = AVMEDIA_TYPE_VIDEO,
> +                                          .draw_slice       = null_draw_slice,
> +                                          .config_props     = config_props,
> +                                          .end_frame        = end_frame,
> +                                          .min_perms        = AV_PERM_READ
> +                                        },
> +                                        { .name = NULL }
> +    },
> +    .outputs   = (const AVFilterPad[]) {{ .name             = "default",
> +                                          .type             = AVMEDIA_TYPE_VIDEO,
> +                                        },
> +                                        { .name = NULL }
> +    },
> +};
> diff --git a/tests/lavfi-regression.sh b/tests/lavfi-regression.sh
> index c763e99..0496d8a 100755
> --- a/tests/lavfi-regression.sh
> +++ b/tests/lavfi-regression.sh
> @@ -42,6 +42,7 @@ do_lavfi "crop_scale"         "crop=iw-100:ih-100:100:100,scale=400:-1"
>  do_lavfi "crop_scale_vflip"   "null,null,crop=iw-200:ih-200:200:200,crop=iw-20:ih-20:20:20,scale=200:200,scale=250:250,vflip,vflip,null,scale=200:200,crop=iw-100:ih-100:100:100,vflip,scale=200:200,null,vflip,crop=iw-100:ih-100:100:100,null"
>  do_lavfi "crop_vflip"         "crop=iw-100:ih-100:100:100,vflip"
>  do_lavfi "drawbox"            "drawbox=224:24:88:72:#FF8010 at 0.5"
> +do_lavfi "edgedetect"         "edgedetect"
>  do_lavfi "fade"               "fade=in:5:15,fade=out:30:15"
>  do_lavfi "null"               "null"
>  do_lavfi "overlay"            "split[m],scale=88:72,pad=96:80:4:4[o2];[m]fifo[o1],[o1][o2]overlay=240:16"
> diff --git a/tests/ref/lavfi/edgedetect b/tests/ref/lavfi/edgedetect
> new file mode 100644
> index 0000000..540ceac
> --- /dev/null
> +++ b/tests/ref/lavfi/edgedetect
> @@ -0,0 +1 @@
> +edgedetect          b48c5204c236304bd0e30ca67c10adcd
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20120807/0a034358/attachment.asc>


More information about the ffmpeg-devel mailing list