1/* 2 * Copyright (c) 2012-2014 Cl��ment B��sch <u pkh me> 3 * 4 * This file is part of FFmpeg. 5 * 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. 10 * 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. 15 * 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 19 */ 20 21/** 22 * @file 23 * Edge detection filter 24 * 25 * @see https://en.wikipedia.org/wiki/Canny_edge_detector 26 */ 27 28#include "libavutil/avassert.h" 29#include "libavutil/opt.h" 30#include "avfilter.h" 31#include "formats.h" 32#include "internal.h" 33#include "video.h" 34 35enum FilterMode { 36 MODE_WIRES, 37 MODE_COLORMIX, 38 NB_MODE 39}; 40 41struct plane_info { 42 uint8_t *tmpbuf; 43 uint16_t *gradients; 44 char *directions; 45}; 46 47typedef struct { 48 const AVClass *class; 49 struct plane_info planes[3]; 50 int nb_planes; 51 double low, high; 52 uint8_t low_u8, high_u8; 53 enum FilterMode mode; 54} EdgeDetectContext; 55 56#define OFFSET(x) offsetof(EdgeDetectContext, x) 57#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM 58static 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" }, 64 { NULL } 65}; 66 67AVFILTER_DEFINE_CLASS(edgedetect); 68 69static av_cold int init(AVFilterContext *ctx) 70{ 71 EdgeDetectContext *edgedetect = ctx->priv; 72 73 edgedetect->low_u8 = edgedetect->low * 255. + .5; 74 edgedetect->high_u8 = edgedetect->high * 255. + .5; 75 return 0; 76} 77 78static int query_formats(AVFilterContext *ctx) 79{ 80 const EdgeDetectContext *edgedetect = ctx->priv; 81 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)); 88 } else { 89 av_assert0(0); 90 } 91 return 0; 92} 93 94static int config_props(AVFilterLink *inlink) 95{ 96 int p; 97 AVFilterContext *ctx = inlink->dst; 98 EdgeDetectContext *edgedetect = ctx->priv; 99 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]; 103 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); 109 } 110 return 0; 111} 112 113static void gaussian_blur(AVFilterContext *ctx, int w, int h, 114 uint8_t *dst, int dst_linesize, 115 const uint8_t *src, int src_linesize) 116{ 117 int i, j; 118 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++) { 122 dst[0] = src[0]; 123 dst[1] = src[1]; 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 131 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 137 138 + src[i-2] * 5 139 + src[i-1] * 12 140 + src[i ] * 15 141 + src[i+1] * 12 142 + src[i+2] * 5) / 159; 143 } 144 dst[i ] = src[i ]; 145 dst[i + 1] = src[i + 1]; 146 147 dst += dst_linesize; 148 src += src_linesize; 149 } 150 memcpy(dst, src, w); dst += dst_linesize; src += src_linesize; 151 memcpy(dst, src, w); 152} 153 154enum { 155 DIRECTION_45UP, 156 DIRECTION_45DOWN, 157 DIRECTION_HORIZONTAL, 158 DIRECTION_VERTICAL, 159}; 160 161static int get_rounded_direction(int gx, int gy) 162{ 163 /* reference angles: 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 168 * 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 172 */ 173 if (gx) { 174 int tanpi8gx, tan3pi8gx; 175 176 if (gx < 0) 177 gx = -gx, gy = -gy; 178 gy <<= 16; 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; 184 } 185 return DIRECTION_VERTICAL; 186} 187 188static 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) 192{ 193 int i, j; 194 195 for (j = 1; j < h - 1; j++) { 196 dst += dst_linesize; 197 dir += dir_linesize; 198 src += src_linesize; 199 for (i = 1; i < w - 1; i++) { 200 const int gx = 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]; 204 const int gy = 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]; 208 209 dst[i] = FFABS(gx) + FFABS(gy); 210 dir[i] = get_rounded_direction(gx, gy); 211 } 212 } 213} 214 215static 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) 219{ 220 int i, j; 221 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]); \ 226} while (0) 227 228 for (j = 1; j < h - 1; j++) { 229 dst += dst_linesize; 230 dir += dir_linesize; 231 src += src_linesize; 232 for (i = 1; i < w - 1; i++) { 233 switch (dir[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; 238 } 239 } 240 } 241} 242 243static 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) 246{ 247 int i, j; 248 249 for (j = 0; j < h; j++) { 250 for (i = 0; i < w; i++) { 251 if (src[i] > high) { 252 dst[i] = src[i]; 253 continue; 254 } 255 256 if ((!i || i == w - 1 || !j || j == h - 1) && 257 src[i] > low && 258 (src[-src_linesize + i-1] > high || 259 src[-src_linesize + i ] > high || 260 src[-src_linesize + i+1] > high || 261 src[ i-1] > high || 262 src[ i+1] > high || 263 src[ src_linesize + i-1] > high || 264 src[ src_linesize + i ] > high || 265 src[ src_linesize + i+1] > high)) 266 dst[i] = src[i]; 267 else 268 dst[i] = 0; 269 } 270 dst += dst_linesize; 271 src += src_linesize; 272 } 273} 274 275static void color_mix(int w, int h, 276 uint8_t *dst, int dst_linesize, 277 const uint8_t *src, int src_linesize) 278{ 279 int i, j; 280 281 for (j = 0; j < h; j++) { 282 for (i = 0; i < w; i++) 283 dst[i] = (dst[i] + src[i]) >> 1; 284 dst += dst_linesize; 285 src += src_linesize; 286 } 287} 288 289static int filter_frame(AVFilterLink *inlink, AVFrame *in) 290{ 291 AVFilterContext *ctx = inlink->dst; 292 EdgeDetectContext *edgedetect = ctx->priv; 293 AVFilterLink *outlink = ctx->outputs[0]; 294 int p, direct = 0; 295 AVFrame *out; 296 297 if (edgedetect->mode != MODE_COLORMIX && av_frame_is_writable(in)) { 298 direct = 1; 299 out = in; 300 } else { 301 out = ff_get_video_buffer(outlink, outlink->w, outlink->h); 302 if (!out) { 303 av_frame_free(&in); 304 return AVERROR(ENOMEM); 305 } 306 av_frame_copy_props(out, in); 307 } 308 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; 314 315 /* gaussian filter to reduce noise */ 316 gaussian_blur(ctx, inlink->w, inlink->h, 317 tmpbuf, inlink->w, 318 in->data[p], in->linesize[p]); 319 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, 324 tmpbuf, inlink->w); 325 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, 330 tmpbuf, inlink->w, 331 directions,inlink->w, 332 gradients, inlink->w); 333 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], 338 tmpbuf, inlink->w); 339 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]); 344 } 345 } 346 347 if (!direct) 348 av_frame_free(&in); 349 return ff_filter_frame(outlink, out); 350} 351 352static av_cold void uninit(AVFilterContext *ctx) 353{ 354 int p; 355 EdgeDetectContext *edgedetect = ctx->priv; 356 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); 362 } 363} 364 365static const AVFilterPad edgedetect_inputs[] = { 366 { 367 .name = "default", 368 .type = AVMEDIA_TYPE_VIDEO, 369 .config_props = config_props, 370 .filter_frame = filter_frame, 371 }, 372 { NULL } 373}; 374 375static const AVFilterPad edgedetect_outputs[] = { 376 { 377 .name = "default", 378 .type = AVMEDIA_TYPE_VIDEO, 379 }, 380 { NULL } 381}; 382 383AVFilter ff_vf_edgedetect = { 384 .name = "edgedetect", 385 .description = NULL_IF_CONFIG_SMALL("Detect and draw edge."), 386 .priv_size = sizeof(EdgeDetectContext), 387 .init = init, 388 .uninit = uninit, 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, 394}; 395