diff --git a/doc/filters.texi b/doc/filters.texi index e217f4e906..1de7e3d71d 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -532,6 +532,52 @@ informations: Follows the list of supported libopencv filters. +@subsection dilate + +Dilate an image by using a specific structuring element. +This filter corresponds to the libopencv function @code{cvDilate}. + +It accepts the parameters: @var{struct_el}:@var{nb_iterations}. + +@var{struct_el} represents a structuring element, and has the syntax: +@var{cols}x@var{rows}+@var{anchor_x}x@var{anchor_y}/@var{shape} + +@var{cols} and @var{rows} represent the number of colums and rows of +the structuring element, @var{anchor_x} and @var{anchor_y} the anchor +point, and @var{shape} the shape for the structuring element, and +can be one of the values "rect", "cross", "ellipse", "custom". + +If the value for @var{shape} is "custom", it must be followed by a +string of the form "=@var{filename}". The file with name +@var{filename} is assumed to represent a binary image, with each +printable character corresponding to a bright pixel. When a custom +@var{shape} is used, @var{cols} and @var{rows} are ignored, the number +or columns and rows of the read file are assumed instead. + +The default value for @var{struct_el} is "3x3+0x0/rect". + +@var{nb_iterations} specifies the number of times the transform is +applied to the image, and defaults to 1. + +Follow some example: +@example +# use the default values +ocv=dilate + +# dilate using a structuring element with a 5x5 cross, iterate two times +ocv=dilate=5x5+2x2/cross:2 + +# read the shape from the file diamond.shape, iterate two times +# the file diamond.shape may contain a pattern of characters like this: +# * +# *** +# ***** +# *** +# * +# the specified cols and rows are ignored (but not the anchor point coordinates) +ocv=0x0+2x2/custom=diamond.shape:2 +@end example + @subsection smooth Smooth the input video. diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h index 01cda126c2..177e1ab29a 100644 --- a/libavfilter/avfilter.h +++ b/libavfilter/avfilter.h @@ -28,7 +28,7 @@ #define LIBAVFILTER_VERSION_MAJOR 1 #define LIBAVFILTER_VERSION_MINOR 70 -#define LIBAVFILTER_VERSION_MICRO 0 +#define LIBAVFILTER_VERSION_MICRO 1 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ LIBAVFILTER_VERSION_MINOR, \ diff --git a/libavfilter/vf_libopencv.c b/libavfilter/vf_libopencv.c index dce22f7ae6..497d4cab85 100644 --- a/libavfilter/vf_libopencv.c +++ b/libavfilter/vf_libopencv.c @@ -23,8 +23,12 @@ * libopencv wrapper functions */ +/* #define DEBUG */ + #include #include +#include "libavutil/avstring.h" +#include "libavutil/file.h" #include "avfilter.h" static void fill_iplimage_from_picref(IplImage *img, const AVFilterBufferRef *picref, enum PixelFormat pixfmt) @@ -127,6 +131,166 @@ static void smooth_end_frame_filter(AVFilterContext *ctx, IplImage *inimg, IplIm cvSmooth(inimg, outimg, smooth->type, smooth->param1, smooth->param2, smooth->param3, smooth->param4); } +static int read_shape_from_file(int *cols, int *rows, int **values, const char *filename, + void *log_ctx) +{ + uint8_t *buf, *p, *pend; + size_t size; + int ret, i, j, w; + + if ((ret = av_file_map(filename, &buf, &size, 0, log_ctx)) < 0) + return ret; + + /* prescan file to get the number of lines and the maximum width */ + w = 0; + for (i = 0; i < size; i++) { + if (buf[i] == '\n') { + if (*rows == INT_MAX) { + av_log(log_ctx, AV_LOG_ERROR, "Overflow on the number of rows in the file\n"); + return AVERROR_INVALIDDATA; + } + ++(*rows); + *cols = FFMAX(*cols, w); + w = 0; + } else if (w == INT_MAX) { + av_log(log_ctx, AV_LOG_ERROR, "Overflow on the number of columns in the file\n"); + return AVERROR_INVALIDDATA; + } + w++; + } + if (*rows > (FF_INTERNAL_MEM_TYPE_MAX_VALUE / (sizeof(int)) / *cols)) { + av_log(log_ctx, AV_LOG_ERROR, "File with size %dx%d is too big\n", + *rows, *cols); + return AVERROR_INVALIDDATA; + } + if (!(*values = av_mallocz(sizeof(int) * *rows * *cols))) + return AVERROR(ENOMEM); + + /* fill *values */ + p = buf; + pend = buf + size-1; + for (i = 0; i < *rows; i++) { + for (j = 0;; j++) { + if (p > pend || *p == '\n') { + p++; + break; + } else + (*values)[*cols*i + j] = !!isgraph(*(p++)); + } + } + av_file_unmap(buf, size); + +#ifdef DEBUG + { + char *line; + if (!(line = av_malloc(*cols + 1))) + return AVERROR(ENOMEM); + for (i = 0; i < *rows; i++) { + for (j = 0; j < *cols; j++) + line[j] = (*values)[i * *cols + j] ? '@' : ' '; + line[j] = 0; + av_log(log_ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line); + } + av_free(line); + } +#endif + + return 0; +} + +static int parse_iplconvkernel(IplConvKernel **kernel, char *buf, void *log_ctx) +{ + char shape_filename[128] = "", shape_str[32] = "rect"; + int cols = 0, rows = 0, anchor_x = 0, anchor_y = 0, shape = CV_SHAPE_RECT; + int *values = NULL, ret; + + sscanf(buf, "%dx%d+%dx%d/%32[^=]=%127s", &cols, &rows, &anchor_x, &anchor_y, shape_str, shape_filename); + + if (!strcmp(shape_str, "rect" )) shape = CV_SHAPE_RECT; + else if (!strcmp(shape_str, "cross" )) shape = CV_SHAPE_CROSS; + else if (!strcmp(shape_str, "ellipse")) shape = CV_SHAPE_ELLIPSE; + else if (!strcmp(shape_str, "custom" )) { + shape = CV_SHAPE_CUSTOM; + if ((ret = read_shape_from_file(&cols, &rows, &values, shape_filename, log_ctx)) < 0) + return ret; + } else { + av_log(log_ctx, AV_LOG_ERROR, + "Shape unspecified or type '%s' unknown\n.", shape_str); + return AVERROR(EINVAL); + } + + if (rows <= 0 || cols <= 0) { + av_log(log_ctx, AV_LOG_ERROR, + "Invalid non-positive values for shape size %dx%d\n", cols, rows); + return AVERROR(EINVAL); + } + + if (anchor_x < 0 || anchor_y < 0 || anchor_x >= cols || anchor_y >= rows) { + av_log(log_ctx, AV_LOG_ERROR, + "Shape anchor %dx%d is not inside the rectangle with size %dx%d.\n", + anchor_x, anchor_y, cols, rows); + return AVERROR(EINVAL); + } + + *kernel = cvCreateStructuringElementEx(cols, rows, anchor_x, anchor_y, shape, values); + av_freep(&values); + if (!*kernel) + return AVERROR(ENOMEM); + + av_log(log_ctx, AV_LOG_INFO, "Structuring element: w:%d h:%d x:%d y:%d shape:%s\n", + rows, cols, anchor_x, anchor_y, shape_str); + return 0; +} + +typedef struct { + int nb_iterations; + IplConvKernel *kernel; +} DilateContext; + +static av_cold int dilate_init(AVFilterContext *ctx, const char *args, void *opaque) +{ + OCVContext *ocv = ctx->priv; + DilateContext *dilate = ocv->priv; + char default_kernel_str[] = "3x3+0x0/rect"; + char *kernel_str; + const char *buf = args; + int ret; + + dilate->nb_iterations = 1; + + if (args) + kernel_str = av_get_token(&buf, ":"); + if ((ret = parse_iplconvkernel(&dilate->kernel, + *kernel_str ? kernel_str : default_kernel_str, + ctx)) < 0) + return ret; + av_free(kernel_str); + + sscanf(buf, ":%d", &dilate->nb_iterations); + av_log(ctx, AV_LOG_INFO, "iterations_nb:%d\n", dilate->nb_iterations); + if (dilate->nb_iterations <= 0) { + av_log(ctx, AV_LOG_ERROR, "Invalid non-positive value '%d' for nb_iterations\n", + dilate->nb_iterations); + return AVERROR(EINVAL); + } + return 0; +} + +static av_cold void dilate_uninit(AVFilterContext *ctx) +{ + OCVContext *ocv = ctx->priv; + DilateContext *dilate = ocv->priv; + + cvReleaseStructuringElement(&dilate->kernel); +} + +static void dilate_end_frame_filter(AVFilterContext *ctx, IplImage *inimg, IplImage *outimg) +{ + OCVContext *ocv = ctx->priv; + DilateContext *dilate = ocv->priv; + cvDilate(inimg, outimg, dilate->kernel, dilate->nb_iterations); +} + typedef struct { const char *name; size_t priv_size; @@ -136,6 +300,7 @@ typedef struct { } OCVFilterEntry; static OCVFilterEntry ocv_filter_entries[] = { + { "dilate", sizeof(DilateContext), dilate_init, dilate_uninit, dilate_end_frame_filter }, { "smooth", sizeof(SmoothContext), smooth_init, NULL, smooth_end_frame_filter }, };