00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00028 #include "libavutil/opt.h"
00029 #include "avfilter.h"
00030 #include "formats.h"
00031 #include "internal.h"
00032 #include "video.h"
00033
00034 typedef struct {
00035 const AVClass *class;
00036 uint8_t *tmpbuf;
00037 uint16_t *gradients;
00038 char *directions;
00039 double low, high;
00040 uint8_t low_u8, high_u8;
00041 } EdgeDetectContext;
00042
00043 #define OFFSET(x) offsetof(EdgeDetectContext, x)
00044 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
00045 static const AVOption edgedetect_options[] = {
00046 { "high", "set high threshold", OFFSET(high), AV_OPT_TYPE_DOUBLE, {.dbl=50/255.}, 0, 1, FLAGS },
00047 { "low", "set low threshold", OFFSET(low), AV_OPT_TYPE_DOUBLE, {.dbl=20/255.}, 0, 1, FLAGS },
00048 { NULL },
00049 };
00050
00051 AVFILTER_DEFINE_CLASS(edgedetect);
00052
00053 static av_cold int init(AVFilterContext *ctx, const char *args)
00054 {
00055 int ret;
00056 EdgeDetectContext *edgedetect = ctx->priv;
00057
00058 edgedetect->class = &edgedetect_class;
00059 av_opt_set_defaults(edgedetect);
00060
00061 if ((ret = av_set_options_string(edgedetect, args, "=", ":")) < 0)
00062 return ret;
00063
00064 edgedetect->low_u8 = edgedetect->low * 255. + .5;
00065 edgedetect->high_u8 = edgedetect->high * 255. + .5;
00066 return 0;
00067 }
00068
00069 static int query_formats(AVFilterContext *ctx)
00070 {
00071 static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE};
00072 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
00073 return 0;
00074 }
00075
00076 static int config_props(AVFilterLink *inlink)
00077 {
00078 AVFilterContext *ctx = inlink->dst;
00079 EdgeDetectContext *edgedetect = ctx->priv;
00080
00081 edgedetect->tmpbuf = av_malloc(inlink->w * inlink->h);
00082 edgedetect->gradients = av_calloc(inlink->w * inlink->h, sizeof(*edgedetect->gradients));
00083 edgedetect->directions = av_malloc(inlink->w * inlink->h);
00084 if (!edgedetect->tmpbuf || !edgedetect->gradients || !edgedetect->directions)
00085 return AVERROR(ENOMEM);
00086 return 0;
00087 }
00088
00089 static void gaussian_blur(AVFilterContext *ctx, int w, int h,
00090 uint8_t *dst, int dst_linesize,
00091 const uint8_t *src, int src_linesize)
00092 {
00093 int i, j;
00094
00095 memcpy(dst, src, w); dst += dst_linesize; src += src_linesize;
00096 memcpy(dst, src, w); dst += dst_linesize; src += src_linesize;
00097 for (j = 2; j < h - 2; j++) {
00098 dst[0] = src[0];
00099 dst[1] = src[1];
00100 for (i = 2; i < w - 2; i++) {
00101
00102 dst[i] = ((src[-2*src_linesize + i-2] + src[2*src_linesize + i-2]) * 2
00103 + (src[-2*src_linesize + i-1] + src[2*src_linesize + i-1]) * 4
00104 + (src[-2*src_linesize + i ] + src[2*src_linesize + i ]) * 5
00105 + (src[-2*src_linesize + i+1] + src[2*src_linesize + i+1]) * 4
00106 + (src[-2*src_linesize + i+2] + src[2*src_linesize + i+2]) * 2
00107
00108 + (src[ -src_linesize + i-2] + src[ src_linesize + i-2]) * 4
00109 + (src[ -src_linesize + i-1] + src[ src_linesize + i-1]) * 9
00110 + (src[ -src_linesize + i ] + src[ src_linesize + i ]) * 12
00111 + (src[ -src_linesize + i+1] + src[ src_linesize + i+1]) * 9
00112 + (src[ -src_linesize + i+2] + src[ src_linesize + i+2]) * 4
00113
00114 + src[i-2] * 5
00115 + src[i-1] * 12
00116 + src[i ] * 15
00117 + src[i+1] * 12
00118 + src[i+2] * 5) / 159;
00119 }
00120 dst[i ] = src[i ];
00121 dst[i + 1] = src[i + 1];
00122
00123 dst += dst_linesize;
00124 src += src_linesize;
00125 }
00126 memcpy(dst, src, w); dst += dst_linesize; src += src_linesize;
00127 memcpy(dst, src, w);
00128 }
00129
00130 enum {
00131 DIRECTION_45UP,
00132 DIRECTION_45DOWN,
00133 DIRECTION_HORIZONTAL,
00134 DIRECTION_VERTICAL,
00135 };
00136
00137 static int get_rounded_direction(int gx, int gy)
00138 {
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149 if (gx) {
00150 int tanpi8gx, tan3pi8gx;
00151
00152 if (gx < 0)
00153 gx = -gx, gy = -gy;
00154 gy <<= 16;
00155 tanpi8gx = 27146 * gx;
00156 tan3pi8gx = 158218 * gx;
00157 if (gy > -tan3pi8gx && gy < -tanpi8gx) return DIRECTION_45UP;
00158 if (gy > -tanpi8gx && gy < tanpi8gx) return DIRECTION_HORIZONTAL;
00159 if (gy > tanpi8gx && gy < tan3pi8gx) return DIRECTION_45DOWN;
00160 }
00161 return DIRECTION_VERTICAL;
00162 }
00163
00164 static void sobel(AVFilterContext *ctx, int w, int h,
00165 uint16_t *dst, int dst_linesize,
00166 const uint8_t *src, int src_linesize)
00167 {
00168 int i, j;
00169 EdgeDetectContext *edgedetect = ctx->priv;
00170
00171 for (j = 1; j < h - 1; j++) {
00172 dst += dst_linesize;
00173 src += src_linesize;
00174 for (i = 1; i < w - 1; i++) {
00175 const int gx =
00176 -1*src[-src_linesize + i-1] + 1*src[-src_linesize + i+1]
00177 -2*src[ i-1] + 2*src[ i+1]
00178 -1*src[ src_linesize + i-1] + 1*src[ src_linesize + i+1];
00179 const int gy =
00180 -1*src[-src_linesize + i-1] + 1*src[ src_linesize + i-1]
00181 -2*src[-src_linesize + i ] + 2*src[ src_linesize + i ]
00182 -1*src[-src_linesize + i+1] + 1*src[ src_linesize + i+1];
00183
00184 dst[i] = FFABS(gx) + FFABS(gy);
00185 edgedetect->directions[j*w + i] = get_rounded_direction(gx, gy);
00186 }
00187 }
00188 }
00189
00190 static void non_maximum_suppression(AVFilterContext *ctx, int w, int h,
00191 uint8_t *dst, int dst_linesize,
00192 const uint16_t *src, int src_linesize)
00193 {
00194 int i, j;
00195 EdgeDetectContext *edgedetect = ctx->priv;
00196
00197 #define COPY_MAXIMA(ay, ax, by, bx) do { \
00198 if (src[i] > src[(ay)*src_linesize + i+(ax)] && \
00199 src[i] > src[(by)*src_linesize + i+(bx)]) \
00200 dst[i] = av_clip_uint8(src[i]); \
00201 } while (0)
00202
00203 for (j = 1; j < h - 1; j++) {
00204 dst += dst_linesize;
00205 src += src_linesize;
00206 for (i = 1; i < w - 1; i++) {
00207 switch (edgedetect->directions[j*w + i]) {
00208 case DIRECTION_45UP: COPY_MAXIMA( 1, -1, -1, 1); break;
00209 case DIRECTION_45DOWN: COPY_MAXIMA(-1, -1, 1, 1); break;
00210 case DIRECTION_HORIZONTAL: COPY_MAXIMA( 0, -1, 0, 1); break;
00211 case DIRECTION_VERTICAL: COPY_MAXIMA(-1, 0, 1, 0); break;
00212 }
00213 }
00214 }
00215 }
00216
00217 static void double_threshold(AVFilterContext *ctx, int w, int h,
00218 uint8_t *dst, int dst_linesize,
00219 const uint8_t *src, int src_linesize)
00220 {
00221 int i, j;
00222 EdgeDetectContext *edgedetect = ctx->priv;
00223 const int low = edgedetect->low_u8;
00224 const int high = edgedetect->high_u8;
00225
00226 for (j = 0; j < h; j++) {
00227 for (i = 0; i < w; i++) {
00228 if (src[i] > high) {
00229 dst[i] = src[i];
00230 continue;
00231 }
00232
00233 if ((!i || i == w - 1 || !j || j == h - 1) &&
00234 src[i] > low &&
00235 (src[-src_linesize + i-1] > high ||
00236 src[-src_linesize + i ] > high ||
00237 src[-src_linesize + i+1] > high ||
00238 src[ i-1] > high ||
00239 src[ i+1] > high ||
00240 src[ src_linesize + i-1] > high ||
00241 src[ src_linesize + i ] > high ||
00242 src[ src_linesize + i+1] > high))
00243 dst[i] = src[i];
00244 else
00245 dst[i] = 0;
00246 }
00247 dst += dst_linesize;
00248 src += src_linesize;
00249 }
00250 }
00251
00252 static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *in)
00253 {
00254 AVFilterContext *ctx = inlink->dst;
00255 EdgeDetectContext *edgedetect = ctx->priv;
00256 AVFilterLink *outlink = inlink->dst->outputs[0];
00257 uint8_t *tmpbuf = edgedetect->tmpbuf;
00258 uint16_t *gradients = edgedetect->gradients;
00259 AVFilterBufferRef *out;
00260
00261 out = ff_get_video_buffer(outlink, AV_PERM_WRITE, outlink->w, outlink->h);
00262 if (!out) {
00263 avfilter_unref_bufferp(&in);
00264 return AVERROR(ENOMEM);
00265 }
00266 avfilter_copy_buffer_ref_props(out, in);
00267
00268
00269 gaussian_blur(ctx, inlink->w, inlink->h,
00270 tmpbuf, inlink->w,
00271 in->data[0], in->linesize[0]);
00272
00273
00274 sobel(ctx, inlink->w, inlink->h,
00275 gradients, inlink->w,
00276 tmpbuf, inlink->w);
00277
00278
00279
00280 memset(tmpbuf, 0, inlink->w * inlink->h);
00281 non_maximum_suppression(ctx, inlink->w, inlink->h,
00282 tmpbuf, inlink->w,
00283 gradients, inlink->w);
00284
00285
00286 double_threshold(ctx, inlink->w, inlink->h,
00287 out->data[0], out->linesize[0],
00288 tmpbuf, inlink->w);
00289
00290 avfilter_unref_bufferp(&in);
00291 return ff_filter_frame(outlink, out);
00292 }
00293
00294 static av_cold void uninit(AVFilterContext *ctx)
00295 {
00296 EdgeDetectContext *edgedetect = ctx->priv;
00297 av_freep(&edgedetect->tmpbuf);
00298 av_freep(&edgedetect->gradients);
00299 av_freep(&edgedetect->directions);
00300 }
00301
00302 static const AVFilterPad edgedetect_inputs[] = {
00303 {
00304 .name = "default",
00305 .type = AVMEDIA_TYPE_VIDEO,
00306 .config_props = config_props,
00307 .filter_frame = filter_frame,
00308 .min_perms = AV_PERM_READ,
00309 },
00310 { NULL }
00311 };
00312
00313 static const AVFilterPad edgedetect_outputs[] = {
00314 {
00315 .name = "default",
00316 .type = AVMEDIA_TYPE_VIDEO,
00317 },
00318 { NULL }
00319 };
00320
00321 AVFilter avfilter_vf_edgedetect = {
00322 .name = "edgedetect",
00323 .description = NULL_IF_CONFIG_SMALL("Detect and draw edge."),
00324 .priv_size = sizeof(EdgeDetectContext),
00325 .init = init,
00326 .uninit = uninit,
00327 .query_formats = query_formats,
00328 .inputs = edgedetect_inputs,
00329 .outputs = edgedetect_outputs,
00330 .priv_class = &edgedetect_class,
00331 };