2 * Copyright (c) 2012-2014 Clément Bœsch <u pkh me>
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 * Edge detection filter
25 * @see https://en.wikipedia.org/wiki/Canny_edge_detector
28 #include "libavutil/avassert.h"
29 #include "libavutil/imgutils.h"
30 #include "libavutil/mem.h"
31 #include "libavutil/opt.h"
36 #include "edge_common.h"
60 typedef struct EdgeDetectContext
{
62 struct plane_info planes
[3];
66 uint8_t low_u8
, high_u8
;
70 #define OFFSET(x) offsetof(EdgeDetectContext, x)
71 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
72 static const AVOption edgedetect_options
[] = {
73 { "high", "set high threshold", OFFSET(high
), AV_OPT_TYPE_DOUBLE
, {.dbl
=50/255.}, 0, 1, FLAGS
},
74 { "low", "set low threshold", OFFSET(low
), AV_OPT_TYPE_DOUBLE
, {.dbl
=20/255.}, 0, 1, FLAGS
},
75 { "mode", "set mode", OFFSET(mode
), AV_OPT_TYPE_INT
, {.i64
=MODE_WIRES
}, 0, NB_MODE
-1, FLAGS
, .unit
= "mode" },
76 { "wires", "white/gray wires on black", 0, AV_OPT_TYPE_CONST
, {.i64
=MODE_WIRES
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "mode" },
77 { "colormix", "mix colors", 0, AV_OPT_TYPE_CONST
, {.i64
=MODE_COLORMIX
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "mode" },
78 { "canny", "detect edges on planes", 0, AV_OPT_TYPE_CONST
, {.i64
=MODE_CANNY
}, INT_MIN
, INT_MAX
, FLAGS
, .unit
= "mode" },
79 { "planes", "set planes to filter", OFFSET(filter_planes
), AV_OPT_TYPE_FLAGS
, {.i64
=7}, 1, 0x7, FLAGS
, .unit
= "flags" },
80 { "y", "filter luma plane", 0, AV_OPT_TYPE_CONST
, {.i64
=PLANE_Y
}, 0, 0, FLAGS
, .unit
= "flags" },
81 { "u", "filter u plane", 0, AV_OPT_TYPE_CONST
, {.i64
=PLANE_U
}, 0, 0, FLAGS
, .unit
= "flags" },
82 { "v", "filter v plane", 0, AV_OPT_TYPE_CONST
, {.i64
=PLANE_V
}, 0, 0, FLAGS
, .unit
= "flags" },
83 { "r", "filter red plane", 0, AV_OPT_TYPE_CONST
, {.i64
=PLANE_R
}, 0, 0, FLAGS
, .unit
= "flags" },
84 { "g", "filter green plane", 0, AV_OPT_TYPE_CONST
, {.i64
=PLANE_G
}, 0, 0, FLAGS
, .unit
= "flags" },
85 { "b", "filter blue plane", 0, AV_OPT_TYPE_CONST
, {.i64
=PLANE_B
}, 0, 0, FLAGS
, .unit
= "flags" },
89 AVFILTER_DEFINE_CLASS(edgedetect
);
91 static av_cold
int init(AVFilterContext
*ctx
)
93 EdgeDetectContext
*edgedetect
= ctx
->priv
;
95 edgedetect
->low_u8
= edgedetect
->low
* 255. + .5;
96 edgedetect
->high_u8
= edgedetect
->high
* 255. + .5;
100 static int query_formats(const AVFilterContext
*ctx
,
101 AVFilterFormatsConfig
**cfg_in
,
102 AVFilterFormatsConfig
**cfg_out
)
104 const EdgeDetectContext
*edgedetect
= ctx
->priv
;
105 static const enum AVPixelFormat wires_pix_fmts
[] = {AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
};
106 static const enum AVPixelFormat canny_pix_fmts
[] = {AV_PIX_FMT_YUV420P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUV444P
, AV_PIX_FMT_GBRP
, AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
};
107 static const enum AVPixelFormat colormix_pix_fmts
[] = {AV_PIX_FMT_GBRP
, AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
};
108 const enum AVPixelFormat
*pix_fmts
= NULL
;
110 if (edgedetect
->mode
== MODE_WIRES
) {
111 pix_fmts
= wires_pix_fmts
;
112 } else if (edgedetect
->mode
== MODE_COLORMIX
) {
113 pix_fmts
= colormix_pix_fmts
;
114 } else if (edgedetect
->mode
== MODE_CANNY
) {
115 pix_fmts
= canny_pix_fmts
;
119 return ff_set_common_formats_from_list2(ctx
, cfg_in
, cfg_out
, pix_fmts
);
122 static int config_props(AVFilterLink
*inlink
)
125 AVFilterContext
*ctx
= inlink
->dst
;
126 EdgeDetectContext
*edgedetect
= ctx
->priv
;
127 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
129 edgedetect
->nb_planes
= inlink
->format
== AV_PIX_FMT_GRAY8
? 1 : 3;
130 for (p
= 0; p
< edgedetect
->nb_planes
; p
++) {
131 struct plane_info
*plane
= &edgedetect
->planes
[p
];
132 int vsub
= p
? desc
->log2_chroma_h
: 0;
133 int hsub
= p
? desc
->log2_chroma_w
: 0;
135 plane
->width
= AV_CEIL_RSHIFT(inlink
->w
, hsub
);
136 plane
->height
= AV_CEIL_RSHIFT(inlink
->h
, vsub
);
137 plane
->tmpbuf
= av_malloc(plane
->width
* plane
->height
);
138 plane
->gradients
= av_calloc(plane
->width
* plane
->height
, sizeof(*plane
->gradients
));
139 plane
->directions
= av_malloc(plane
->width
* plane
->height
);
140 if (!plane
->tmpbuf
|| !plane
->gradients
|| !plane
->directions
)
141 return AVERROR(ENOMEM
);
146 static void color_mix(int w
, int h
,
147 uint8_t *dst
, int dst_linesize
,
148 const uint8_t *src
, int src_linesize
)
152 for (j
= 0; j
< h
; j
++) {
153 for (i
= 0; i
< w
; i
++)
154 dst
[i
] = (dst
[i
] + src
[i
]) >> 1;
160 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
162 AVFilterContext
*ctx
= inlink
->dst
;
163 EdgeDetectContext
*edgedetect
= ctx
->priv
;
164 AVFilterLink
*outlink
= ctx
->outputs
[0];
168 if (edgedetect
->mode
!= MODE_COLORMIX
&& av_frame_is_writable(in
)) {
172 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
175 return AVERROR(ENOMEM
);
177 av_frame_copy_props(out
, in
);
180 for (p
= 0; p
< edgedetect
->nb_planes
; p
++) {
181 struct plane_info
*plane
= &edgedetect
->planes
[p
];
182 uint8_t *tmpbuf
= plane
->tmpbuf
;
183 uint16_t *gradients
= plane
->gradients
;
184 int8_t *directions
= plane
->directions
;
185 const int width
= plane
->width
;
186 const int height
= plane
->height
;
188 if (!((1 << p
) & edgedetect
->filter_planes
)) {
190 av_image_copy_plane(out
->data
[p
], out
->linesize
[p
],
191 in
->data
[p
], in
->linesize
[p
],
196 /* gaussian filter to reduce noise */
197 ff_gaussian_blur_8(width
, height
,
199 in
->data
[p
], in
->linesize
[p
], 1);
201 /* compute the 16-bits gradients and directions for the next step */
202 ff_sobel_8(width
, height
,
207 /* non_maximum_suppression() will actually keep & clip what's necessary and
208 * ignore the rest, so we need a clean output buffer */
209 memset(tmpbuf
, 0, width
* height
);
210 ff_non_maximum_suppression(width
, height
,
215 /* keep high values, or low values surrounded by high values */
216 ff_double_threshold(edgedetect
->low_u8
, edgedetect
->high_u8
,
218 out
->data
[p
], out
->linesize
[p
],
221 if (edgedetect
->mode
== MODE_COLORMIX
) {
222 color_mix(width
, height
,
223 out
->data
[p
], out
->linesize
[p
],
224 in
->data
[p
], in
->linesize
[p
]);
230 return ff_filter_frame(outlink
, out
);
233 static av_cold
void uninit(AVFilterContext
*ctx
)
236 EdgeDetectContext
*edgedetect
= ctx
->priv
;
238 for (p
= 0; p
< edgedetect
->nb_planes
; p
++) {
239 struct plane_info
*plane
= &edgedetect
->planes
[p
];
240 av_freep(&plane
->tmpbuf
);
241 av_freep(&plane
->gradients
);
242 av_freep(&plane
->directions
);
246 static const AVFilterPad edgedetect_inputs
[] = {
249 .type
= AVMEDIA_TYPE_VIDEO
,
250 .config_props
= config_props
,
251 .filter_frame
= filter_frame
,
255 const FFFilter ff_vf_edgedetect
= {
256 .p
.name
= "edgedetect",
257 .p
.description
= NULL_IF_CONFIG_SMALL("Detect and draw edge."),
258 .p
.priv_class
= &edgedetect_class
,
259 .p
.flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,
260 .priv_size
= sizeof(EdgeDetectContext
),
263 FILTER_INPUTS(edgedetect_inputs
),
264 FILTER_OUTPUTS(ff_video_default_filterpad
),
265 FILTER_QUERY_FUNC2(query_formats
),