[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