History

avfilter: add morpho filter

Paul B Mahol committed 5 years ago

Commit b2ec4ed1 parent c2057786 files · +982 −1Browse files

Unified · Split

6 files
  1. Changelog +1 −0
  2. doc/filters.texi +39 −0
  3. libavfilter/Makefile +1 −0
  4. libavfilter/allfilters.c +1 −0
  5. libavfilter/version.h +1 −1
  6. libavfilter/vf_morpho.c +939 −0
Changelog+1−0
@@ -20,6 +20,7 @@ version <next>: · 19 unmodified lines
20- MSN Siren decoder20- MSN Siren decoder
21- scharr video filter21- scharr video filter
22- apsyclip audio filter22- apsyclip audio filter
23- morpho video filter
23 24
24 25
25version 4.4:26version 4.4:
doc/filters.texi+39−0
@@ -10257,6 +10257,7 @@ A number representing position of the first frame with respect to the telecine · 10256 unmodified lines
10257pattern. This is to be used if the stream is cut. The default value is @code{0}.10257pattern. This is to be used if the stream is cut. The default value is @code{0}.
10258@end table10258@end table
10259 10259
10260@anchor{dilation}
10260@section dilation10261@section dilation
10261 10262
10262Apply dilation effect to the video.10263Apply dilation effect to the video.
@@ -11577,6 +11578,7 @@ value. · 1314 unmodified lines
11577 11578
11578@end table11579@end table
11579 11580
11581@anchor{erosion}
11580@section erosion11582@section erosion
11581 11583
11582Apply erosion effect to the video.11584Apply erosion effect to the video.
@@ -15352,6 +15354,43 @@ Default value is 0. · 3769 unmodified lines
15352 15354
15353This filter supports the all above options as @ref{commands}.15355This filter supports the all above options as @ref{commands}.
15354 15356
15357@section morpho
15358
15359This filter allows to apply main morphological grayscale transforms,
15360erode and dilate with arbitrary structures set in second input stream.
15361
15362Unlike naive implementation and much slower performance in @ref{erosion}
15363and @ref{dilation} filters, when speed is critical @code{morpho} filter
15364should be used instead.
15365
15366A description of accepted options follows,
15367
15368@table @option
15369@item mode
15370Set morphological transform to apply, can be:
15371
15372@table @samp
15373@item erode
15374@item dilate
15375@item open
15376@item close
15377@end table
15378Default is @code{erode}.
15379
15380@item planes
15381Set planes to filter, by default all planes except alpha are filtered.
15382
15383@item structure
15384Set which structure video frames will be processed from second input stream,
15385can be @var{first} or @var{all}. Default is @var{all}.
15386@end table
15387
15388The @code{morpho} filter also supports the @ref{framesync} options.
15389
15390@subsection Commands
15391
15392This filter supports same @ref{commands} as options.
15393
15355@section mpdecimate15394@section mpdecimate
15356 15395
15357Drop frames that do not differ greatly from the previous frame in15396Drop frames that do not differ greatly from the previous frame in
libavfilter/Makefile+1−0
@@ -344,6 +344,7 @@ OBJS-$(CONFIG_MIDEQUALIZER_FILTER) += vf_midequalizer.o framesync.o · 343 unmodified lines
344OBJS-$(CONFIG_MINTERPOLATE_FILTER) += vf_minterpolate.o motion_estimation.o344OBJS-$(CONFIG_MINTERPOLATE_FILTER) += vf_minterpolate.o motion_estimation.o
345OBJS-$(CONFIG_MIX_FILTER) += vf_mix.o framesync.o345OBJS-$(CONFIG_MIX_FILTER) += vf_mix.o framesync.o
346OBJS-$(CONFIG_MONOCHROME_FILTER) += vf_monochrome.o346OBJS-$(CONFIG_MONOCHROME_FILTER) += vf_monochrome.o
347OBJS-$(CONFIG_MORPHO_FILTER) += vf_morpho.o
347OBJS-$(CONFIG_MPDECIMATE_FILTER) += vf_mpdecimate.o348OBJS-$(CONFIG_MPDECIMATE_FILTER) += vf_mpdecimate.o
348OBJS-$(CONFIG_NEGATE_FILTER) += vf_lut.o349OBJS-$(CONFIG_NEGATE_FILTER) += vf_lut.o
349OBJS-$(CONFIG_NLMEANS_FILTER) += vf_nlmeans.o350OBJS-$(CONFIG_NLMEANS_FILTER) += vf_nlmeans.o
libavfilter/allfilters.c+1−0
@@ -329,6 +329,7 @@ extern const AVFilter ff_vf_midequalizer; · 328 unmodified lines
329extern const AVFilter ff_vf_minterpolate;329extern const AVFilter ff_vf_minterpolate;
330extern const AVFilter ff_vf_mix;330extern const AVFilter ff_vf_mix;
331extern const AVFilter ff_vf_monochrome;331extern const AVFilter ff_vf_monochrome;
332extern const AVFilter ff_vf_morpho;
332extern const AVFilter ff_vf_mpdecimate;333extern const AVFilter ff_vf_mpdecimate;
333extern const AVFilter ff_vf_msad;334extern const AVFilter ff_vf_msad;
334extern const AVFilter ff_vf_negate;335extern const AVFilter ff_vf_negate;
libavfilter/version.h+1−1
@@ -30,7 +30,7 @@ · 29 unmodified lines
30#include "libavutil/version.h"30#include "libavutil/version.h"
31 31
32#define LIBAVFILTER_VERSION_MAJOR 832#define LIBAVFILTER_VERSION_MAJOR 8
33#define LIBAVFILTER_VERSION_MINOR 933#define LIBAVFILTER_VERSION_MINOR 10
34#define LIBAVFILTER_VERSION_MICRO 10034#define LIBAVFILTER_VERSION_MICRO 100
35 35
36 36
libavfilter/vf_morpho.c+939−0
@@ -0,0 +1,939 @@
1/*
2 * Copyright (c) 2016 ReneBrals
3 * Copyright (c) 2021 Paul B Mahol
4 *
5 * This file is part of FFmpeg.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in all
15 * copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26#include "libavutil/avassert.h"
27#include "libavutil/imgutils.h"
28#include "libavutil/intreadwrite.h"
29#include "libavutil/opt.h"
30#include "libavutil/pixdesc.h"
31#include "avfilter.h"
32#include "formats.h"
33#include "framesync.h"
34#include "internal.h"
35#include "video.h"
36
37enum MorphModes {
38 ERODE,
39 DILATE,
40 OPEN,
41 CLOSE,
42 NB_MODES
43};
44
45typedef struct IPlane {
46 uint8_t **img;
47 int w, h;
48 int range;
49 int depth;
50 int type_size;
51
52 void (*max)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x);
53 void (*min)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x);
54 void (*max_in_place)(uint8_t *a, const uint8_t *b, int x);
55 void (*min_in_place)(uint8_t *a, const uint8_t *b, int x);
56} IPlane;
57
58typedef struct LUT {
59 uint8_t ***arr;
60 int min_r;
61 int max_r;
62 int I;
63 int X;
64 int pre_pad_x;
65 int type_size;
66} LUT;
67
68typedef struct chord {
69 int x;
70 int y;
71 int l;
72 int i;
73} chord;
74
75typedef struct chord_set {
76 chord *C;
77 int size;
78 int cap;
79
80 int *R;
81 int Lnum;
82
83 int minX;
84 int maxX;
85 int minY;
86 int maxY;
87 unsigned nb_elements;
88} chord_set;
89
90typedef struct MorphoContext {
91 const AVClass *class;
92 FFFrameSync fs;
93
94 chord_set SE[4];
95 IPlane SEimg[4];
96 IPlane g[4], f[4], h[4];
97 LUT Ty[2][4];
98
99 int mode;
100 int planes;
101 int structures;
102
103 int planewidth[4];
104 int planeheight[4];
105 int splanewidth[4];
106 int splaneheight[4];
107 int depth;
108 int type_size;
109 int nb_planes;
110
111 int got_structure[4];
112
113 AVFrame *temp;
114
115 int64_t *plane_f, *plane_g;
116} MorphoContext;
117
118#define OFFSET(x) offsetof(MorphoContext, x)
119#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM
120
121static const AVOption morpho_options[] = {
122 { "mode", "set morphological transform", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_MODES-1, FLAGS, "mode" },
123 { "erode", NULL, 0, AV_OPT_TYPE_CONST, {.i64=ERODE}, 0, 0, FLAGS, "mode" },
124 { "dilate", NULL, 0, AV_OPT_TYPE_CONST, {.i64=DILATE}, 0, 0, FLAGS, "mode" },
125 { "open", NULL, 0, AV_OPT_TYPE_CONST, {.i64=OPEN}, 0, 0, FLAGS, "mode" },
126 { "close", NULL, 0, AV_OPT_TYPE_CONST, {.i64=CLOSE}, 0, 0, FLAGS, "mode" },
127 { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=7}, 0, 15, FLAGS },
128 { "structure", "when to process structures", OFFSET(structures), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS, "str" },
129 { "first", "process only first structure, ignore rest", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "str" },
130 { "all", "process all structure", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "str" },
131 { NULL }
132};
133
134FRAMESYNC_DEFINE_CLASS(morpho, MorphoContext, fs);
135
136static int query_formats(AVFilterContext *ctx)
137{
138 static const enum AVPixelFormat pix_fmts[] = {
139 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
140 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
141 AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
142 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
143 AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
144 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9,
145 AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, AV_PIX_FMT_GBRP9,
146 AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
147 AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
148 AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
149 AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
150 AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
151 AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
152 AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
153 AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
154 AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
155 AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
156 AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
157 AV_PIX_FMT_NONE
158 };
159
160 return ff_set_common_formats_from_list(ctx, pix_fmts);
161}
162
163static void min_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x)
164{
165 for (int i = 0; i < x; i++)
166 c[i] = FFMIN(b[i], a[i]);
167}
168
169static void mininplace_fun(uint8_t *a, const uint8_t *b, int x)
170{
171 for (int i = 0; i < x; i++)
172 a[i] = FFMIN(a[i], b[i]);
173}
174
175static void max_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x)
176{
177 for (int i = 0; i < x; i++)
178 c[i] = FFMAX(a[i], b[i]);
179}
180
181static void maxinplace_fun(uint8_t *a, const uint8_t *b, int x)
182{
183 for (int i = 0; i < x; i++)
184 a[i] = FFMAX(a[i], b[i]);
185}
186
187static void min16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x)
188{
189 const uint16_t *a = (const uint16_t *)aa;
190 const uint16_t *b = (const uint16_t *)bb;
191 uint16_t *c = (uint16_t *)cc;
192
193 for (int i = 0; i < x; i++)
194 c[i] = FFMIN(b[i], a[i]);
195}
196
197static void mininplace16_fun(uint8_t *aa, const uint8_t *bb, int x)
198{
199 uint16_t *a = (uint16_t *)aa;
200 const uint16_t *b = (const uint16_t *)bb;
201
202 for (int i = 0; i < x; i++)
203 a[i] = FFMIN(a[i], b[i]);
204}
205
206static void max16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x)
207{
208 const uint16_t *a = (const uint16_t *)aa;
209 const uint16_t *b = (const uint16_t *)bb;
210 uint16_t *c = (uint16_t *)cc;
211
212 for (int i = 0; i < x; i++)
213 c[i] = FFMAX(a[i], b[i]);
214}
215
216static void maxinplace16_fun(uint8_t *aa, const uint8_t *bb, int x)
217{
218 uint16_t *a = (uint16_t *)aa;
219 const uint16_t *b = (const uint16_t *)bb;
220
221 for (int i = 0; i < x; i++)
222 a[i] = FFMAX(a[i], b[i]);
223}
224
225static int alloc_lut(LUT *Ty, chord_set *SE, int type_size, int mode)
226{
227 const int size = Ty->max_r + 1 - Ty->min_r;
228 int pre_pad_x = 0;
229
230 if (SE->minX < 0)
231 pre_pad_x = 0 - SE->minX;
232 Ty->pre_pad_x = pre_pad_x;
233 Ty->type_size = type_size;
234
235 Ty->arr = av_calloc(size, sizeof(*Ty->arr));
236 if (!Ty->arr)
237 return AVERROR(ENOMEM);
238 for (int r = 0; r < Ty->max_r - Ty->min_r + 1; r++) {
239 Ty->arr[r] = av_calloc(Ty->I, sizeof(uint8_t *));
240 if (!Ty->arr[r])
241 return AVERROR(ENOMEM);
242 for (int i = 0; i < Ty->I; i++) {
243 Ty->arr[r][i] = av_calloc(Ty->X + pre_pad_x, type_size);
244 if (!Ty->arr[r][i])
245 return AVERROR(ENOMEM);
246 if (mode == ERODE)
247 memset(Ty->arr[r][i], UINT8_MAX, pre_pad_x * type_size);
248 /* Shifting the X index such that negative indices correspond to
249 * the pre-padding.
250 */
251 Ty->arr[r][i] = &(Ty->arr[r][i][pre_pad_x * type_size]);
252 }
253 }
254
255 Ty->arr = &(Ty->arr[0 - Ty->min_r]);
256
257 return 0;
258}
259
260static void free_lut(LUT *table)
261{
262 uint8_t ***rp;
263
264 if (!table->arr)
265 return;
266
267 // The R index was shifted, create a pointer to the original array
268 rp = &(table->arr[table->min_r]);
269
270 for (int r = table->min_r; r <= table->max_r; r++) {
271 for (int i = 0; i < table->I; i++) {
272 // The X index was also shifted, for padding purposes.
273 av_free(table->arr[r][i] - table->pre_pad_x * table->type_size);
274 }
275 av_freep(&table->arr[r]);
276 }
277 av_freep(&rp);
278}
279
280static void circular_swap(LUT *Ty)
281{
282 /*
283 * Swap the pointers to r-indices in a circle. This is useful because
284 * Ty(r,i,x) = Ty-1(r+1,i,x) for r < ymax.
285 */
286 if (Ty->max_r - Ty->min_r > 0) {
287 uint8_t **Ty0 = Ty->arr[Ty->min_r];
288
289 for (int r = Ty->min_r; r < Ty->max_r; r++)
290 Ty->arr[r] = Ty->arr[r + 1];
291
292 Ty->arr[Ty->max_r] = Ty0;
293 }
294}
295
296static void compute_min_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y)
297{
298 if (y + r >= 0 && y + r < f->h) {
299 memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size);
300 } else {
301 memset(Ty->arr[r][0], UINT8_MAX, Ty->X * Ty->type_size);
302 }
303
304 for (int i = 1; i < SE->Lnum; i++) {
305 int d = SE->R[i] - SE->R[i - 1];
306
307 f->min(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size,
308 Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size,
309 Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size,
310 Ty->X + Ty->pre_pad_x - d);
311 memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size,
312 Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size,
313 d * f->type_size);
314 }
315}
316
317static void update_min_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num)
318{
319 for (int i = 0; i < num; i++)
320 circular_swap(Ty);
321
322 compute_min_row(f, Ty, SE, Ty->max_r - tid, y);
323}
324
325static int compute_min_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num)
326{
327 if (Ty->I != SE->Lnum ||
328 Ty->X != f->w ||
329 Ty->min_r != SE->minY ||
330 Ty->max_r != SE->maxY + num - 1) {
331 int ret;
332
333 free_lut(Ty);
334
335 Ty->I = SE->Lnum;
336 Ty->X = f->w;
337 Ty->min_r = SE->minY;
338 Ty->max_r = SE->maxY + num - 1;
339 ret = alloc_lut(Ty, SE, f->type_size, ERODE);
340 if (ret < 0)
341 return ret;
342 }
343
344 for (int r = Ty->min_r; r <= Ty->max_r; r++)
345 compute_min_row(f, Ty, SE, r, y);
346
347 return 0;
348}
349
350static void compute_max_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y)
351{
352 if (y + r >= 0 && y + r < f->h) {
353 memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size);
354 } else {
355 memset(Ty->arr[r][0], 0, Ty->X * Ty->type_size);
356 }
357
358 for (int i = 1; i < SE->Lnum; i++) {
359 int d = SE->R[i] - SE->R[i - 1];
360
361 f->max(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size,
362 Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size,
363 Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size,
364 Ty->X + Ty->pre_pad_x - d);
365 memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size,
366 Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size,
367 d * f->type_size);
368 }
369}
370
371static void update_max_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num)
372{
373 for (int i = 0; i < num; i++)
374 circular_swap(Ty);
375
376 compute_max_row(f, Ty, SE, Ty->max_r - tid, y);
377}
378
379static int compute_max_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num)
380{
381 if (Ty->I != SE->Lnum ||
382 Ty->X != f->w ||
383 Ty->min_r != SE->minY ||
384 Ty->max_r != SE->maxY + num - 1) {
385 int ret;
386
387 free_lut(Ty);
388
389 Ty->I = SE->Lnum;
390 Ty->X = f->w;
391 Ty->min_r = SE->minY;
392 Ty->max_r = SE->maxY + num - 1;
393 ret = alloc_lut(Ty, SE, f->type_size, DILATE);
394 if (ret < 0)
395 return ret;
396 }
397
398 for (int r = Ty->min_r; r <= Ty->max_r; r++)
399 compute_max_row(f, Ty, SE, r, y);
400
401 return 0;
402}
403
404static void line_dilate(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid)
405{
406 memset(g->img[y], 0, g->w * g->type_size);
407
408 for (int c = 0; c < SE->size; c++) {
409 g->max_in_place(g->img[y],
410 Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size,
411 av_clip(g->w - SE->C[c].x, 0, g->w));
412 }
413}
414
415static void line_erode(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid)
416{
417 memset(g->img[y], UINT8_MAX, g->w * g->type_size);
418
419 for (int c = 0; c < SE->size; c++) {
420 g->min_in_place(g->img[y],
421 Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size,
422 av_clip(g->w - SE->C[c].x, 0, g->w));
423 }
424}
425
426static int dilate(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty)
427{
428 int ret = compute_max_lut(Ty, f, SE, 0, 1);
429 if (ret < 0)
430 return ret;
431
432 line_dilate(g, Ty, SE, 0, 0);
433 for (int y = 1; y < f->h; y++) {
434 update_max_lut(f, Ty, SE, y, 0, 1);
435 line_dilate(g, Ty, SE, y, 0);
436 }
437
438 return 0;
439}
440
441static int erode(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty)
442{
443 int ret = compute_min_lut(Ty, f, SE, 0, 1);
444 if (ret < 0)
445 return ret;
446
447 line_erode(g, Ty, SE, 0, 0);
448 for (int y = 1; y < f->h; y++) {
449 update_min_lut(f, Ty, SE, y, 0, 1);
450 line_erode(g, Ty, SE, y, 0);
451 }
452
453 return 0;
454}
455
456static int insert_chord_set(chord_set *chords, chord c)
457{
458 // Checking if chord fits in dynamic array, resize if not.
459 if (chords->size == chords->cap) {
460 chords->C = av_realloc_f(chords->C, chords->cap * 2, sizeof(chord));
461 if (!chords->C)
462 return AVERROR(ENOMEM);
463 chords->cap *= 2;
464 }
465
466 // Add the chord to the dynamic array.
467 chords->C[chords->size].x = c.x;
468 chords->C[chords->size].y = c.y;
469 chords->C[chords->size++].l = c.l;
470
471 // Update minimum/maximum x/y offsets of the chord set.
472 chords->minX = FFMIN(chords->minX, c.x);
473 chords->maxX = FFMAX(chords->maxX, c.x);
474
475 chords->minY = FFMIN(chords->minY, c.y);
476 chords->maxY = FFMAX(chords->maxY, c.y);
477
478 return 0;
479}
480
481static void free_chord_set(chord_set *SE)
482{
483 av_freep(&SE->C);
484 SE->size = 0;
485 SE->cap = 0;
486
487 av_freep(&SE->R);
488 SE->Lnum = 0;
489}
490
491static int init_chordset(chord_set *chords)
492{
493 chords->nb_elements = 0;
494 chords->size = 0;
495 chords->C = av_calloc(1, sizeof(chord));
496 if (!chords->C)
497 return AVERROR(ENOMEM);
498
499 chords->cap = 1;
500 chords->minX = INT16_MAX;
501 chords->maxX = INT16_MIN;
502 chords->minY = INT16_MAX;
503 chords->maxY = INT16_MIN;
504
505 return 0;
506}
507
508static int comp_chord_length(const void *p, const void *q)
509{
510 chord a, b;
511 a = *((chord *)p);
512 b = *((chord *)q);
513
514 return (a.l > b.l) - (a.l < b.l);
515}
516
517static int comp_chord(const void *p, const void *q)
518{
519 chord a, b;
520 a = *((chord *)p);
521 b = *((chord *)q);
522
523 return (a.y > b.y) - (a.y < b.y);
524}
525
526static int build_chord_set(IPlane *SE, chord_set *chords)
527{
528 const int mid = 1 << (SE->depth - 1);
529 int chord_length_index;
530 int chord_start, val, ret;
531 int centerX, centerY;
532 int r_cap = 1;
533 chord c;
534
535 ret = init_chordset(chords);
536 if (ret < 0)
537 return ret;
538 /*
539 * In erosion/dilation, the center of the IPlane has S.E. offset (0,0).
540 * Otherwise, the resulting IPlane would be shifted to the top-left.
541 */
542 centerX = (SE->w - 1) / 2;
543 centerY = (SE->h - 1) / 2;
544
545 /*
546 * Computing the set of chords C.
547 */
548 for (int y = 0; y < SE->h; y++) {
549 int x;
550
551 chord_start = -1;
552 for (x = 0; x < SE->w; x++) {
553 if (SE->type_size == 1) {
554 chords->nb_elements += (SE->img[y][x] >= mid);
555 //A chord is a run of non-zero pixels.
556 if (SE->img[y][x] >= mid && chord_start == -1) {
557 // Chord starts.
558 chord_start = x;
559 } else if (SE->img[y][x] < mid && chord_start != -1) {
560 // Chord ends before end of line.
561 c.x = chord_start - centerX;
562 c.y = y - centerY;
563 c.l = x - chord_start;
564 ret = insert_chord_set(chords, c);
565 if (ret < 0)
566 return AVERROR(ENOMEM);
567 chord_start = -1;
568 }
569 } else {
570 chords->nb_elements += (AV_RN16(&SE->img[y][x * 2]) >= mid);
571 //A chord is a run of non-zero pixels.
572 if (AV_RN16(&SE->img[y][x * 2]) >= mid && chord_start == -1) {
573 // Chord starts.
574 chord_start = x;
575 } else if (AV_RN16(&SE->img[y][x * 2]) < mid && chord_start != -1) {
576 // Chord ends before end of line.
577 c.x = chord_start - centerX;
578 c.y = y - centerY;
579 c.l = x - chord_start;
580 ret = insert_chord_set(chords, c);
581 if (ret < 0)
582 return AVERROR(ENOMEM);
583 chord_start = -1;
584 }
585 }
586 }
587 if (chord_start != -1) {
588 // Chord ends at end of line.
589 c.x = chord_start - centerX;
590 c.y = y - centerY;
591 c.l = x - chord_start;
592 ret = insert_chord_set(chords, c);
593 if (ret < 0)
594 return AVERROR(ENOMEM);
595 }
596 }
597
598 /*
599 * Computing the array of chord lengths R(i).
600 * This is needed because the lookup table will contain a row for each
601 * length index i.
602 */
603 qsort(chords->C, chords->size, sizeof(chord), comp_chord_length);
604 chords->R = av_calloc(1, sizeof(*chords->R));
605 if (!chords->R)
606 return AVERROR(ENOMEM);
607 chords->Lnum = 0;
608 val = 0;
609 r_cap = 1;
610
611 if (chords->size > 0) {
612 val = 1;
613 if (chords->Lnum >= r_cap) {
614 chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R));
615 if (!chords->R)
616 return AVERROR(ENOMEM);
617 r_cap *= 2;
618 }
619 chords->R[chords->Lnum++] = 1;
620 val = 1;
621 }
622
623 for (int i = 0; i < chords->size; i++) {
624 if (val != chords->C[i].l) {
625 while (2 * val < chords->C[i].l && val != 0) {
626 if (chords->Lnum >= r_cap) {
627 chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R));
628 if (!chords->R)
629 return AVERROR(ENOMEM);
630 r_cap *= 2;
631 }
632
633 chords->R[chords->Lnum++] = 2 * val;
634 val *= 2;
635 }
636 val = chords->C[i].l;
637
638 if (chords->Lnum >= r_cap) {
639 chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R));
640 if (!chords->R)
641 return AVERROR(ENOMEM);
642 r_cap *= 2;
643 }
644 chords->R[chords->Lnum++] = val;
645 }
646 }
647
648 /*
649 * Setting the length indices of chords.
650 * These are needed so that the algorithm can, for each chord,
651 * access the lookup table at the correct length in constant time.
652 */
653 chord_length_index = 0;
654 for (int i = 0; i < chords->size; i++) {
655 while (chords->R[chord_length_index] < chords->C[i].l)
656 chord_length_index++;
657 chords->C[i].i = chord_length_index;
658 }
659
660 /*
661 * Chords are sorted on Y. This way, when a row of the lookup table or IPlane
662 * is cached, the next chord offset has a better chance of being on the
663 * same cache line.
664 */
665 qsort(chords->C, chords->size, sizeof(chord), comp_chord);
666
667 return 0;
668}
669
670static void free_iplane(IPlane *imp)
671{
672 av_freep(&imp->img);
673}
674
675static int read_iplane(IPlane *imp, const uint8_t *dst, int dst_linesize,
676 int w, int h, int R, int type_size, int depth)
677{
678 if (!imp->img)
679 imp->img = av_calloc(h, sizeof(*imp->img));
680 if (!imp->img)
681 return AVERROR(ENOMEM);
682
683 imp->w = w;
684 imp->h = h;
685 imp->range = R;
686 imp->depth = depth;
687 imp->type_size = type_size;
688 imp->max = type_size == 1 ? max_fun : max16_fun;
689 imp->min = type_size == 1 ? min_fun : min16_fun;
690 imp->max_in_place = type_size == 1 ? maxinplace_fun : maxinplace16_fun;
691 imp->min_in_place = type_size == 1 ? mininplace_fun : mininplace16_fun;
692
693 for (int y = 0; y < h; y++)
694 imp->img[y] = (uint8_t *)dst + y * dst_linesize;
695
696 return 0;
697}
698
699static int config_input(AVFilterLink *inlink)
700{
701 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
702 MorphoContext *s = inlink->dst->priv;
703
704 s->depth = desc->comp[0].depth;
705 s->type_size = (s->depth + 7) / 8;
706 s->nb_planes = desc->nb_components;
707 s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
708 s->planewidth[0] = s->planewidth[3] = inlink->w;
709 s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
710 s->planeheight[0] = s->planeheight[3] = inlink->h;
711
712 return 0;
713}
714
715static int config_input_structure(AVFilterLink *inlink)
716{
717 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
718 AVFilterContext *ctx = inlink->dst;
719 MorphoContext *s = inlink->dst->priv;
720
721 av_assert0(ctx->inputs[0]->format == ctx->inputs[1]->format);
722
723 s->splanewidth[1] = s->splanewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
724 s->splanewidth[0] = s->splanewidth[3] = inlink->w;
725 s->splaneheight[1] = s->splaneheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
726 s->splaneheight[0] = s->splaneheight[3] = inlink->h;
727
728 return 0;
729}
730
731static int activate(AVFilterContext *ctx)
732{
733 MorphoContext *s = ctx->priv;
734 return ff_framesync_activate(&s->fs);
735}
736
737static int do_morpho(FFFrameSync *fs)
738{
739 AVFilterContext *ctx = fs->parent;
740 AVFilterLink *outlink = ctx->outputs[0];
741 MorphoContext *s = ctx->priv;
742 AVFrame *in = NULL, *structurepic = NULL;
743 AVFrame *out;
744 int ret;
745
746 ret = ff_framesync_dualinput_get(fs, &in, &structurepic);
747 if (ret < 0)
748 return ret;
749 if (!structurepic)
750 return ff_filter_frame(outlink, in);
751
752 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
753 if (!out) {
754 av_frame_free(&in);
755 return AVERROR(ENOMEM);
756 }
757 av_frame_copy_props(out, in);
758
759 for (int p = 0; p < s->nb_planes; p++) {
760 const uint8_t *src = in->data[p];
761 int src_linesize = in->linesize[p];
762 const uint8_t *ssrc = structurepic->data[p];
763 const int ssrc_linesize = structurepic->linesize[p];
764 uint8_t *dst = out->data[p];
765 int dst_linesize = out->linesize[p];
766 const int swidth = s->splanewidth[p];
767 const int sheight = s->splaneheight[p];
768 const int width = s->planewidth[p];
769 const int height = s->planeheight[p];
770 const int depth = s->depth;
771 int type_size = s->type_size;
772
773 if (!(s->planes & (1 << p))) {
774copy:
775 av_image_copy_plane(out->data[p] + 0 * out->linesize[p],
776 out->linesize[p],
777 in->data[p] + 0 * in->linesize[p],
778 in->linesize[p],
779 width * ((s->depth + 7) / 8),
780 height);
781 continue;
782 }
783
784 if (!s->got_structure[p] || s->structures) {
785 free_chord_set(&s->SE[p]);
786
787 ret = read_iplane(&s->SEimg[p], ssrc, ssrc_linesize, swidth, sheight, 1, type_size, depth);
788 if (ret < 0)
789 goto fail;
790 ret = build_chord_set(&s->SEimg[p], &s->SE[p]);
791 if (ret < 0)
792 goto fail;
793 s->got_structure[p] = 1;
794 }
795
796 if (s->SE[p].minX == INT16_MAX ||
797 s->SE[p].minY == INT16_MAX ||
798 s->SE[p].maxX == INT16_MIN ||
799 s->SE[p].maxY == INT16_MIN)
800 goto copy;
801
802 ret = read_iplane(&s->f[p], src, src_linesize, width, height, 1, type_size, depth);
803 if (ret < 0)
804 goto fail;
805
806 ret = read_iplane(&s->g[p], dst, dst_linesize, s->f[p].w, s->f[p].h, s->f[p].range, type_size, depth);
807 if (ret < 0)
808 goto fail;
809
810 switch (s->mode) {
811 case ERODE:
812 ret = erode(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[0][p]);
813 break;
814 case DILATE:
815 ret = dilate(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[0][p]);
816 break;
817 case OPEN:
818 ret = read_iplane(&s->h[p], s->temp->data[p], s->temp->linesize[p], width, height, 1, type_size, depth);
819 if (ret < 0)
820 break;
821 ret = erode(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[0][p]);
822 if (ret < 0)
823 break;
824 ret = dilate(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[1][p]);
825 break;
826 case CLOSE:
827 ret = read_iplane(&s->h[p], s->temp->data[p], s->temp->linesize[p], width, height, 1, type_size, depth);
828 if (ret < 0)
829 break;
830 ret = dilate(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[0][p]);
831 if (ret < 0)
832 break;
833 ret = erode(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[1][p]);
834 break;
835 default:
836 av_assert0(0);
837 }
838
839 if (ret < 0)
840 goto fail;
841 }
842
843 av_frame_free(&in);
844 out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
845 return ff_filter_frame(outlink, out);
846fail:
847 av_frame_free(&in);
848 return ret;
849}
850
851static int config_output(AVFilterLink *outlink)
852{
853 AVFilterContext *ctx = outlink->src;
854 MorphoContext *s = ctx->priv;
855 AVFilterLink *mainlink = ctx->inputs[0];
856 int ret;
857
858 s->fs.on_event = do_morpho;
859 ret = ff_framesync_init_dualinput(&s->fs, ctx);
860 if (ret < 0)
861 return ret;
862 outlink->w = mainlink->w;
863 outlink->h = mainlink->h;
864 outlink->time_base = mainlink->time_base;
865 outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
866 outlink->frame_rate = mainlink->frame_rate;
867
868 if ((ret = ff_framesync_configure(&s->fs)) < 0)
869 return ret;
870 outlink->time_base = s->fs.time_base;
871
872 s->temp = ff_get_video_buffer(outlink, outlink->w, outlink->h);
873 if (!s->temp)
874 return AVERROR(ENOMEM);
875
876 s->plane_f = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_f));
877 s->plane_g = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_g));
878 if (!s->plane_f || !s->plane_g)
879 return AVERROR(ENOMEM);
880
881 return 0;
882}
883
884static av_cold void uninit(AVFilterContext *ctx)
885{
886 MorphoContext *s = ctx->priv;
887
888 for (int p = 0; p < 4; p++) {
889 free_iplane(&s->SEimg[p]);
890 free_iplane(&s->f[p]);
891 free_iplane(&s->g[p]);
892 free_iplane(&s->h[p]);
893 free_chord_set(&s->SE[p]);
894 free_lut(&s->Ty[0][p]);
895 free_lut(&s->Ty[1][p]);
896 }
897
898 ff_framesync_uninit(&s->fs);
899
900 av_frame_free(&s->temp);
901 av_freep(&s->plane_f);
902 av_freep(&s->plane_g);
903}
904
905static const AVFilterPad morpho_inputs[] = {
906 {
907 .name = "default",
908 .type = AVMEDIA_TYPE_VIDEO,
909 .config_props = config_input,
910 },
911 {
912 .name = "structure",
913 .type = AVMEDIA_TYPE_VIDEO,
914 .config_props = config_input_structure,
915 },
916};
917
918static const AVFilterPad morpho_outputs[] = {
919 {
920 .name = "default",
921 .type = AVMEDIA_TYPE_VIDEO,
922 .config_props = config_output,
923 },
924};
925
926const AVFilter ff_vf_morpho = {
927 .name = "morpho",
928 .description = NULL_IF_CONFIG_SMALL("Apply Morphological filter."),
929 .preinit = morpho_framesync_preinit,
930 .priv_size = sizeof(MorphoContext),
931 .priv_class = &morpho_class,
932 .activate = activate,
933 .uninit = uninit,
934 .query_formats = query_formats,
935 FILTER_INPUTS(morpho_inputs),
936 FILTER_OUTPUTS(morpho_outputs),
937 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
938 .process_command = ff_filter_process_command,
939};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t