1/* 2 * Copyright (c) 2010 Stefano Sabatini 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 * libopencv wrapper functions 24 */ 25 26#include <opencv/cv.h> 27#include <opencv/cxcore.h> 28#include "libavutil/avstring.h" 29#include "libavutil/common.h" 30#include "libavutil/file.h" 31#include "libavutil/opt.h" 32#include "avfilter.h" 33#include "formats.h" 34#include "internal.h" 35#include "video.h" 36 37static void fill_iplimage_from_frame(IplImage *img, const AVFrame *frame, enum AVPixelFormat pixfmt) 38{ 39 IplImage *tmpimg; 40 int depth, channels_nb; 41 42 if (pixfmt == AV_PIX_FMT_GRAY8) { depth = IPL_DEPTH_8U; channels_nb = 1; } 43 else if (pixfmt == AV_PIX_FMT_BGRA) { depth = IPL_DEPTH_8U; channels_nb = 4; } 44 else if (pixfmt == AV_PIX_FMT_BGR24) { depth = IPL_DEPTH_8U; channels_nb = 3; } 45 else return; 46 47 tmpimg = cvCreateImageHeader((CvSize){frame->width, frame->height}, depth, channels_nb); 48 *img = *tmpimg; 49 img->imageData = img->imageDataOrigin = frame->data[0]; 50 img->dataOrder = IPL_DATA_ORDER_PIXEL; 51 img->origin = IPL_ORIGIN_TL; 52 img->widthStep = frame->linesize[0]; 53} 54 55static void fill_frame_from_iplimage(AVFrame *frame, const IplImage *img, enum AVPixelFormat pixfmt) 56{ 57 frame->linesize[0] = img->widthStep; 58 frame->data[0] = img->imageData; 59} 60 61static int query_formats(AVFilterContext *ctx) 62{ 63 static const enum AVPixelFormat pix_fmts[] = { 64 AV_PIX_FMT_BGR24, AV_PIX_FMT_BGRA, AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE 65 }; 66 67 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); 68 return 0; 69} 70 71typedef struct OCVContext { 72 const AVClass *class; 73 char *name; 74 char *params; 75 int (*init)(AVFilterContext *ctx, const char *args); 76 void (*uninit)(AVFilterContext *ctx); 77 void (*end_frame_filter)(AVFilterContext *ctx, IplImage *inimg, IplImage *outimg); 78 void *priv; 79} OCVContext; 80 81typedef struct SmoothContext { 82 int type; 83 int param1, param2; 84 double param3, param4; 85} SmoothContext; 86 87static av_cold int smooth_init(AVFilterContext *ctx, const char *args) 88{ 89 OCVContext *s = ctx->priv; 90 SmoothContext *smooth = s->priv; 91 char type_str[128] = "gaussian"; 92 93 smooth->param1 = 3; 94 smooth->param2 = 0; 95 smooth->param3 = 0.0; 96 smooth->param4 = 0.0; 97 98 if (args) 99 sscanf(args, "%127[^|]|%d|%d|%lf|%lf", type_str, &smooth->param1, &smooth->param2, &smooth->param3, &smooth->param4); 100 101 if (!strcmp(type_str, "blur" )) smooth->type = CV_BLUR; 102 else if (!strcmp(type_str, "blur_no_scale")) smooth->type = CV_BLUR_NO_SCALE; 103 else if (!strcmp(type_str, "median" )) smooth->type = CV_MEDIAN; 104 else if (!strcmp(type_str, "gaussian" )) smooth->type = CV_GAUSSIAN; 105 else if (!strcmp(type_str, "bilateral" )) smooth->type = CV_BILATERAL; 106 else { 107 av_log(ctx, AV_LOG_ERROR, "Smoothing type '%s' unknown.\n", type_str); 108 return AVERROR(EINVAL); 109 } 110 111 if (smooth->param1 < 0 || !(smooth->param1%2)) { 112 av_log(ctx, AV_LOG_ERROR, 113 "Invalid value '%d' for param1, it has to be a positive odd number\n", 114 smooth->param1); 115 return AVERROR(EINVAL); 116 } 117 if ((smooth->type == CV_BLUR || smooth->type == CV_BLUR_NO_SCALE || smooth->type == CV_GAUSSIAN) && 118 (smooth->param2 < 0 || (smooth->param2 && !(smooth->param2%2)))) { 119 av_log(ctx, AV_LOG_ERROR, 120 "Invalid value '%d' for param2, it has to be zero or a positive odd number\n", 121 smooth->param2); 122 return AVERROR(EINVAL); 123 } 124 125 av_log(ctx, AV_LOG_VERBOSE, "type:%s param1:%d param2:%d param3:%f param4:%f\n", 126 type_str, smooth->param1, smooth->param2, smooth->param3, smooth->param4); 127 return 0; 128} 129 130static void smooth_end_frame_filter(AVFilterContext *ctx, IplImage *inimg, IplImage *outimg) 131{ 132 OCVContext *s = ctx->priv; 133 SmoothContext *smooth = s->priv; 134 cvSmooth(inimg, outimg, smooth->type, smooth->param1, smooth->param2, smooth->param3, smooth->param4); 135} 136 137static int read_shape_from_file(int *cols, int *rows, int **values, const char *filename, 138 void *log_ctx) 139{ 140 uint8_t *buf, *p, *pend; 141 size_t size; 142 int ret, i, j, w; 143 144 if ((ret = av_file_map(filename, &buf, &size, 0, log_ctx)) < 0) 145 return ret; 146 147 /* prescan file to get the number of lines and the maximum width */ 148 w = 0; 149 for (i = 0; i < size; i++) { 150 if (buf[i] == '\n') { 151 if (*rows == INT_MAX) { 152 av_log(log_ctx, AV_LOG_ERROR, "Overflow on the number of rows in the file\n"); 153 return AVERROR_INVALIDDATA; 154 } 155 ++(*rows); 156 *cols = FFMAX(*cols, w); 157 w = 0; 158 } else if (w == INT_MAX) { 159 av_log(log_ctx, AV_LOG_ERROR, "Overflow on the number of columns in the file\n"); 160 return AVERROR_INVALIDDATA; 161 } 162 w++; 163 } 164 if (*rows > (SIZE_MAX / sizeof(int) / *cols)) { 165 av_log(log_ctx, AV_LOG_ERROR, "File with size %dx%d is too big\n", 166 *rows, *cols); 167 return AVERROR_INVALIDDATA; 168 } 169 if (!(*values = av_mallocz_array(sizeof(int) * *rows, *cols))) 170 return AVERROR(ENOMEM); 171 172 /* fill *values */ 173 p = buf; 174 pend = buf + size-1; 175 for (i = 0; i < *rows; i++) { 176 for (j = 0;; j++) { 177 if (p > pend || *p == '\n') { 178 p++; 179 break; 180 } else 181 (*values)[*cols*i + j] = !!av_isgraph(*(p++)); 182 } 183 } 184 av_file_unmap(buf, size); 185 186#ifdef DEBUG 187 { 188 char *line; 189 if (!(line = av_malloc(*cols + 1))) 190 return AVERROR(ENOMEM); 191 for (i = 0; i < *rows; i++) { 192 for (j = 0; j < *cols; j++) 193 line[j] = (*values)[i * *cols + j] ? '@' : ' '; 194 line[j] = 0; 195 av_log(log_ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line); 196 } 197 av_free(line); 198 } 199#endif 200 201 return 0; 202} 203 204static int parse_iplconvkernel(IplConvKernel **kernel, char *buf, void *log_ctx) 205{ 206 char shape_filename[128] = "", shape_str[32] = "rect"; 207 int cols = 0, rows = 0, anchor_x = 0, anchor_y = 0, shape = CV_SHAPE_RECT; 208 int *values = NULL, ret; 209 210 sscanf(buf, "%dx%d+%dx%d/%32[^=]=%127s", &cols, &rows, &anchor_x, &anchor_y, shape_str, shape_filename); 211 212 if (!strcmp(shape_str, "rect" )) shape = CV_SHAPE_RECT; 213 else if (!strcmp(shape_str, "cross" )) shape = CV_SHAPE_CROSS; 214 else if (!strcmp(shape_str, "ellipse")) shape = CV_SHAPE_ELLIPSE; 215 else if (!strcmp(shape_str, "custom" )) { 216 shape = CV_SHAPE_CUSTOM; 217 if ((ret = read_shape_from_file(&cols, &rows, &values, shape_filename, log_ctx)) < 0) 218 return ret; 219 } else { 220 av_log(log_ctx, AV_LOG_ERROR, 221 "Shape unspecified or type '%s' unknown.\n", shape_str); 222 return AVERROR(EINVAL); 223 } 224 225 if (rows <= 0 || cols <= 0) { 226 av_log(log_ctx, AV_LOG_ERROR, 227 "Invalid non-positive values for shape size %dx%d\n", cols, rows); 228 return AVERROR(EINVAL); 229 } 230 231 if (anchor_x < 0 || anchor_y < 0 || anchor_x >= cols || anchor_y >= rows) { 232 av_log(log_ctx, AV_LOG_ERROR, 233 "Shape anchor %dx%d is not inside the rectangle with size %dx%d.\n", 234 anchor_x, anchor_y, cols, rows); 235 return AVERROR(EINVAL); 236 } 237 238 *kernel = cvCreateStructuringElementEx(cols, rows, anchor_x, anchor_y, shape, values); 239 av_freep(&values); 240 if (!*kernel) 241 return AVERROR(ENOMEM); 242 243 av_log(log_ctx, AV_LOG_VERBOSE, "Structuring element: w:%d h:%d x:%d y:%d shape:%s\n", 244 rows, cols, anchor_x, anchor_y, shape_str); 245 return 0; 246} 247 248typedef struct DilateContext { 249 int nb_iterations; 250 IplConvKernel *kernel; 251} DilateContext; 252 253static av_cold int dilate_init(AVFilterContext *ctx, const char *args) 254{ 255 OCVContext *s = ctx->priv; 256 DilateContext *dilate = s->priv; 257 char default_kernel_str[] = "3x3+0x0/rect"; 258 char *kernel_str; 259 const char *buf = args; 260 int ret; 261 262 if (args) 263 kernel_str = av_get_token(&buf, "|"); 264 else 265 kernel_str = av_strdup(default_kernel_str); 266 if (!kernel_str) 267 return AVERROR(ENOMEM); 268 if ((ret = parse_iplconvkernel(&dilate->kernel, kernel_str, ctx)) < 0) 269 return ret; 270 av_free(kernel_str); 271 272 if (!buf || sscanf(buf, "|%d", &dilate->nb_iterations) != 1) 273 dilate->nb_iterations = 1; 274 av_log(ctx, AV_LOG_VERBOSE, "iterations_nb:%d\n", dilate->nb_iterations); 275 if (dilate->nb_iterations <= 0) { 276 av_log(ctx, AV_LOG_ERROR, "Invalid non-positive value '%d' for nb_iterations\n", 277 dilate->nb_iterations); 278 return AVERROR(EINVAL); 279 } 280 return 0; 281} 282 283static av_cold void dilate_uninit(AVFilterContext *ctx) 284{ 285 OCVContext *s = ctx->priv; 286 DilateContext *dilate = s->priv; 287 288 cvReleaseStructuringElement(&dilate->kernel); 289} 290 291static void dilate_end_frame_filter(AVFilterContext *ctx, IplImage *inimg, IplImage *outimg) 292{ 293 OCVContext *s = ctx->priv; 294 DilateContext *dilate = s->priv; 295 cvDilate(inimg, outimg, dilate->kernel, dilate->nb_iterations); 296} 297 298static void erode_end_frame_filter(AVFilterContext *ctx, IplImage *inimg, IplImage *outimg) 299{ 300 OCVContext *s = ctx->priv; 301 DilateContext *dilate = s->priv; 302 cvErode(inimg, outimg, dilate->kernel, dilate->nb_iterations); 303} 304 305typedef struct OCVFilterEntry { 306 const char *name; 307 size_t priv_size; 308 int (*init)(AVFilterContext *ctx, const char *args); 309 void (*uninit)(AVFilterContext *ctx); 310 void (*end_frame_filter)(AVFilterContext *ctx, IplImage *inimg, IplImage *outimg); 311} OCVFilterEntry; 312 313static OCVFilterEntry ocv_filter_entries[] = { 314 { "dilate", sizeof(DilateContext), dilate_init, dilate_uninit, dilate_end_frame_filter }, 315 { "erode", sizeof(DilateContext), dilate_init, dilate_uninit, erode_end_frame_filter }, 316 { "smooth", sizeof(SmoothContext), smooth_init, NULL, smooth_end_frame_filter }, 317}; 318 319static av_cold int init(AVFilterContext *ctx) 320{ 321 OCVContext *s = ctx->priv; 322 int i; 323 324 if (!s->name) { 325 av_log(ctx, AV_LOG_ERROR, "No libopencv filter name specified\n"); 326 return AVERROR(EINVAL); 327 } 328 for (i = 0; i < FF_ARRAY_ELEMS(ocv_filter_entries); i++) { 329 OCVFilterEntry *entry = &ocv_filter_entries[i]; 330 if (!strcmp(s->name, entry->name)) { 331 s->init = entry->init; 332 s->uninit = entry->uninit; 333 s->end_frame_filter = entry->end_frame_filter; 334 335 if (!(s->priv = av_mallocz(entry->priv_size))) 336 return AVERROR(ENOMEM); 337 return s->init(ctx, s->params); 338 } 339 } 340 341 av_log(ctx, AV_LOG_ERROR, "No libopencv filter named '%s'\n", s->name); 342 return AVERROR(EINVAL); 343} 344 345static av_cold void uninit(AVFilterContext *ctx) 346{ 347 OCVContext *s = ctx->priv; 348 349 if (s->uninit) 350 s->uninit(ctx); 351 av_free(s->priv); 352} 353 354static int filter_frame(AVFilterLink *inlink, AVFrame *in) 355{ 356 AVFilterContext *ctx = inlink->dst; 357 OCVContext *s = ctx->priv; 358 AVFilterLink *outlink= inlink->dst->outputs[0]; 359 AVFrame *out; 360 IplImage inimg, outimg; 361 362 out = ff_get_video_buffer(outlink, outlink->w, outlink->h); 363 if (!out) { 364 av_frame_free(&in); 365 return AVERROR(ENOMEM); 366 } 367 av_frame_copy_props(out, in); 368 369 fill_iplimage_from_frame(&inimg , in , inlink->format); 370 fill_iplimage_from_frame(&outimg, out, inlink->format); 371 s->end_frame_filter(ctx, &inimg, &outimg); 372 fill_frame_from_iplimage(out, &outimg, inlink->format); 373 374 av_frame_free(&in); 375 376 return ff_filter_frame(outlink, out); 377} 378 379#define OFFSET(x) offsetof(OCVContext, x) 380#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM 381static const AVOption ocv_options[] = { 382 { "filter_name", NULL, OFFSET(name), AV_OPT_TYPE_STRING, .flags = FLAGS }, 383 { "filter_params", NULL, OFFSET(params), AV_OPT_TYPE_STRING, .flags = FLAGS }, 384 { NULL } 385}; 386 387AVFILTER_DEFINE_CLASS(ocv); 388 389static const AVFilterPad avfilter_vf_ocv_inputs[] = { 390 { 391 .name = "default", 392 .type = AVMEDIA_TYPE_VIDEO, 393 .filter_frame = filter_frame, 394 }, 395 { NULL } 396}; 397 398static const AVFilterPad avfilter_vf_ocv_outputs[] = { 399 { 400 .name = "default", 401 .type = AVMEDIA_TYPE_VIDEO, 402 }, 403 { NULL } 404}; 405 406AVFilter ff_vf_ocv = { 407 .name = "ocv", 408 .description = NULL_IF_CONFIG_SMALL("Apply transform using libopencv."), 409 .priv_size = sizeof(OCVContext), 410 .priv_class = &ocv_class, 411 .query_formats = query_formats, 412 .init = init, 413 .uninit = uninit, 414 .inputs = avfilter_vf_ocv_inputs, 415 .outputs = avfilter_vf_ocv_outputs, 416}; 417