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/opt.h"
49 struct plane_info planes
[3];
52 uint8_t low_u8
, high_u8
;
56 #define OFFSET(x) offsetof(EdgeDetectContext, x)
57 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
58 static const AVOption edgedetect_options
[] = {
59 { "high", "set high threshold", OFFSET(high
), AV_OPT_TYPE_DOUBLE
, {.dbl
=50/255.}, 0, 1, FLAGS
},
60 { "low", "set low threshold", OFFSET(low
), AV_OPT_TYPE_DOUBLE
, {.dbl
=20/255.}, 0, 1, FLAGS
},
61 { "mode", "set mode", OFFSET(mode
), AV_OPT_TYPE_INT
, {.i64
=MODE_WIRES
}, 0, NB_MODE
-1, FLAGS
, "mode" },
62 { "wires", "white/gray wires on black", 0, AV_OPT_TYPE_CONST
, {.i64
=MODE_WIRES
}, INT_MIN
, INT_MAX
, FLAGS
, "mode" },
63 { "colormix", "mix colors", 0, AV_OPT_TYPE_CONST
, {.i64
=MODE_COLORMIX
}, INT_MIN
, INT_MAX
, FLAGS
, "mode" },
67 AVFILTER_DEFINE_CLASS(edgedetect
);
69 static av_cold
int init(AVFilterContext
*ctx
)
71 EdgeDetectContext
*edgedetect
= ctx
->priv
;
73 edgedetect
->low_u8
= edgedetect
->low
* 255. + .5;
74 edgedetect
->high_u8
= edgedetect
->high
* 255. + .5;
78 static int query_formats(AVFilterContext
*ctx
)
80 const EdgeDetectContext
*edgedetect
= ctx
->priv
;
82 if (edgedetect
->mode
== MODE_WIRES
) {
83 static const enum AVPixelFormat pix_fmts
[] = {AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
};
84 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
85 } else if (edgedetect
->mode
== MODE_COLORMIX
) {
86 static const enum AVPixelFormat pix_fmts
[] = {AV_PIX_FMT_GBRP
, AV_PIX_FMT_GRAY8
, AV_PIX_FMT_NONE
};
87 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
94 static int config_props(AVFilterLink
*inlink
)
97 AVFilterContext
*ctx
= inlink
->dst
;
98 EdgeDetectContext
*edgedetect
= ctx
->priv
;
100 edgedetect
->nb_planes
= inlink
->format
== AV_PIX_FMT_GRAY8
? 1 : 3;
101 for (p
= 0; p
< edgedetect
->nb_planes
; p
++) {
102 struct plane_info
*plane
= &edgedetect
->planes
[p
];
104 plane
->tmpbuf
= av_malloc(inlink
->w
* inlink
->h
);
105 plane
->gradients
= av_calloc(inlink
->w
* inlink
->h
, sizeof(*plane
->gradients
));
106 plane
->directions
= av_malloc(inlink
->w
* inlink
->h
);
107 if (!plane
->tmpbuf
|| !plane
->gradients
|| !plane
->directions
)
108 return AVERROR(ENOMEM
);
113 static void gaussian_blur(AVFilterContext
*ctx
, int w
, int h
,
114 uint8_t *dst
, int dst_linesize
,
115 const uint8_t *src
, int src_linesize
)
119 memcpy(dst
, src
, w
); dst
+= dst_linesize
; src
+= src_linesize
;
120 memcpy(dst
, src
, w
); dst
+= dst_linesize
; src
+= src_linesize
;
121 for (j
= 2; j
< h
- 2; j
++) {
124 for (i
= 2; i
< w
- 2; i
++) {
125 /* Gaussian mask of size 5x5 with sigma = 1.4 */
126 dst
[i
] = ((src
[-2*src_linesize
+ i
-2] + src
[2*src_linesize
+ i
-2]) * 2
127 + (src
[-2*src_linesize
+ i
-1] + src
[2*src_linesize
+ i
-1]) * 4
128 + (src
[-2*src_linesize
+ i
] + src
[2*src_linesize
+ i
]) * 5
129 + (src
[-2*src_linesize
+ i
+1] + src
[2*src_linesize
+ i
+1]) * 4
130 + (src
[-2*src_linesize
+ i
+2] + src
[2*src_linesize
+ i
+2]) * 2
132 + (src
[ -src_linesize
+ i
-2] + src
[ src_linesize
+ i
-2]) * 4
133 + (src
[ -src_linesize
+ i
-1] + src
[ src_linesize
+ i
-1]) * 9
134 + (src
[ -src_linesize
+ i
] + src
[ src_linesize
+ i
]) * 12
135 + (src
[ -src_linesize
+ i
+1] + src
[ src_linesize
+ i
+1]) * 9
136 + (src
[ -src_linesize
+ i
+2] + src
[ src_linesize
+ i
+2]) * 4
142 + src
[i
+2] * 5) / 159;
145 dst
[i
+ 1] = src
[i
+ 1];
150 memcpy(dst
, src
, w
); dst
+= dst_linesize
; src
+= src_linesize
;
157 DIRECTION_HORIZONTAL
,
161 static int get_rounded_direction(int gx
, int gy
)
164 * tan( pi/8) = sqrt(2)-1
165 * tan(3pi/8) = sqrt(2)+1
166 * Gy/Gx is the tangent of the angle (theta), so Gy/Gx is compared against
167 * <ref-angle>, or more simply Gy against <ref-angle>*Gx
169 * Gx and Gy bounds = [-1020;1020], using 16-bit arithmetic:
170 * round((sqrt(2)-1) * (1<<16)) = 27146
171 * round((sqrt(2)+1) * (1<<16)) = 158218
174 int tanpi8gx
, tan3pi8gx
;
179 tanpi8gx
= 27146 * gx
;
180 tan3pi8gx
= 158218 * gx
;
181 if (gy
> -tan3pi8gx
&& gy
< -tanpi8gx
) return DIRECTION_45UP
;
182 if (gy
> -tanpi8gx
&& gy
< tanpi8gx
) return DIRECTION_HORIZONTAL
;
183 if (gy
> tanpi8gx
&& gy
< tan3pi8gx
) return DIRECTION_45DOWN
;
185 return DIRECTION_VERTICAL
;
188 static void sobel(int w
, int h
,
189 uint16_t *dst
, int dst_linesize
,
190 int8_t *dir
, int dir_linesize
,
191 const uint8_t *src
, int src_linesize
)
195 for (j
= 1; j
< h
- 1; j
++) {
199 for (i
= 1; i
< w
- 1; i
++) {
201 -1*src
[-src_linesize
+ i
-1] + 1*src
[-src_linesize
+ i
+1]
202 -2*src
[ i
-1] + 2*src
[ i
+1]
203 -1*src
[ src_linesize
+ i
-1] + 1*src
[ src_linesize
+ i
+1];
205 -1*src
[-src_linesize
+ i
-1] + 1*src
[ src_linesize
+ i
-1]
206 -2*src
[-src_linesize
+ i
] + 2*src
[ src_linesize
+ i
]
207 -1*src
[-src_linesize
+ i
+1] + 1*src
[ src_linesize
+ i
+1];
209 dst
[i
] = FFABS(gx
) + FFABS(gy
);
210 dir
[i
] = get_rounded_direction(gx
, gy
);
215 static void non_maximum_suppression(int w
, int h
,
216 uint8_t *dst
, int dst_linesize
,
217 const int8_t *dir
, int dir_linesize
,
218 const uint16_t *src
, int src_linesize
)
222 #define COPY_MAXIMA(ay, ax, by, bx) do { \
223 if (src[i] > src[(ay)*src_linesize + i+(ax)] && \
224 src[i] > src[(by)*src_linesize + i+(bx)]) \
225 dst[i] = av_clip_uint8(src[i]); \
228 for (j
= 1; j
< h
- 1; j
++) {
232 for (i
= 1; i
< w
- 1; i
++) {
234 case DIRECTION_45UP
: COPY_MAXIMA( 1, -1, -1, 1); break;
235 case DIRECTION_45DOWN
: COPY_MAXIMA(-1, -1, 1, 1); break;
236 case DIRECTION_HORIZONTAL
: COPY_MAXIMA( 0, -1, 0, 1); break;
237 case DIRECTION_VERTICAL
: COPY_MAXIMA(-1, 0, 1, 0); break;
243 static void double_threshold(int low
, int high
, int w
, int h
,
244 uint8_t *dst
, int dst_linesize
,
245 const uint8_t *src
, int src_linesize
)
249 for (j
= 0; j
< h
; j
++) {
250 for (i
= 0; i
< w
; i
++) {
256 if ((!i
|| i
== w
- 1 || !j
|| j
== h
- 1) &&
258 (src
[-src_linesize
+ i
-1] > high
||
259 src
[-src_linesize
+ i
] > high
||
260 src
[-src_linesize
+ i
+1] > high
||
263 src
[ src_linesize
+ i
-1] > high
||
264 src
[ src_linesize
+ i
] > high
||
265 src
[ src_linesize
+ i
+1] > high
))
275 static void color_mix(int w
, int h
,
276 uint8_t *dst
, int dst_linesize
,
277 const uint8_t *src
, int src_linesize
)
281 for (j
= 0; j
< h
; j
++) {
282 for (i
= 0; i
< w
; i
++)
283 dst
[i
] = (dst
[i
] + src
[i
]) >> 1;
289 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
291 AVFilterContext
*ctx
= inlink
->dst
;
292 EdgeDetectContext
*edgedetect
= ctx
->priv
;
293 AVFilterLink
*outlink
= ctx
->outputs
[0];
297 if (edgedetect
->mode
!= MODE_COLORMIX
&& av_frame_is_writable(in
)) {
301 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
304 return AVERROR(ENOMEM
);
306 av_frame_copy_props(out
, in
);
309 for (p
= 0; p
< edgedetect
->nb_planes
; p
++) {
310 struct plane_info
*plane
= &edgedetect
->planes
[p
];
311 uint8_t *tmpbuf
= plane
->tmpbuf
;
312 uint16_t *gradients
= plane
->gradients
;
313 int8_t *directions
= plane
->directions
;
315 /* gaussian filter to reduce noise */
316 gaussian_blur(ctx
, inlink
->w
, inlink
->h
,
318 in
->data
[p
], in
->linesize
[p
]);
320 /* compute the 16-bits gradients and directions for the next step */
321 sobel(inlink
->w
, inlink
->h
,
322 gradients
, inlink
->w
,
323 directions
,inlink
->w
,
326 /* non_maximum_suppression() will actually keep & clip what's necessary and
327 * ignore the rest, so we need a clean output buffer */
328 memset(tmpbuf
, 0, inlink
->w
* inlink
->h
);
329 non_maximum_suppression(inlink
->w
, inlink
->h
,
331 directions
,inlink
->w
,
332 gradients
, inlink
->w
);
334 /* keep high values, or low values surrounded by high values */
335 double_threshold(edgedetect
->low_u8
, edgedetect
->high_u8
,
336 inlink
->w
, inlink
->h
,
337 out
->data
[p
], out
->linesize
[p
],
340 if (edgedetect
->mode
== MODE_COLORMIX
) {
341 color_mix(inlink
->w
, inlink
->h
,
342 out
->data
[p
], out
->linesize
[p
],
343 in
->data
[p
], in
->linesize
[p
]);
349 return ff_filter_frame(outlink
, out
);
352 static av_cold
void uninit(AVFilterContext
*ctx
)
355 EdgeDetectContext
*edgedetect
= ctx
->priv
;
357 for (p
= 0; p
< edgedetect
->nb_planes
; p
++) {
358 struct plane_info
*plane
= &edgedetect
->planes
[p
];
359 av_freep(&plane
->tmpbuf
);
360 av_freep(&plane
->gradients
);
361 av_freep(&plane
->directions
);
365 static const AVFilterPad edgedetect_inputs
[] = {
368 .type
= AVMEDIA_TYPE_VIDEO
,
369 .config_props
= config_props
,
370 .filter_frame
= filter_frame
,
375 static const AVFilterPad edgedetect_outputs
[] = {
378 .type
= AVMEDIA_TYPE_VIDEO
,
383 AVFilter ff_vf_edgedetect
= {
384 .name
= "edgedetect",
385 .description
= NULL_IF_CONFIG_SMALL("Detect and draw edge."),
386 .priv_size
= sizeof(EdgeDetectContext
),
389 .query_formats
= query_formats
,
390 .inputs
= edgedetect_inputs
,
391 .outputs
= edgedetect_outputs
,
392 .priv_class
= &edgedetect_class
,
393 .flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,