History

avfilter: add corr video filter

Paul B Mahol committed 3 years ago

Commit f0f19f31 parent 9ca139b6 files · +366 −1Browse files

Unified · Split

6 files
  1. Changelog +1 −0
  2. doc/filters.texi +24 −0
  3. libavfilter/Makefile +1 −0
  4. libavfilter/allfilters.c +1 −0
  5. libavfilter/version.h +1 −1
  6. libavfilter/vf_corr.c +338 −0
Changelog+1−0
@@ -26,6 +26,7 @@ version <next>: · 25 unmodified lines
26- QSV AV1 encoder26- QSV AV1 encoder
27- QSV decoding and encoding for 10/12bit 422, 10/12bit 444 HEVC and VP927- QSV decoding and encoding for 10/12bit 422, 10/12bit 444 HEVC and VP9
28- showcwt multimedia filter28- showcwt multimedia filter
29- corr video filter
29 30
30 31
31version 5.1:32version 5.1:
doc/filters.texi+24−0
@@ -10110,6 +10110,30 @@ ffmpeg -f lavfi -i nullsrc=s=100x100,coreimage=filter=CIQRCodeGenerator@@inputMe · 10109 unmodified lines
10110@end example10110@end example
10111@end itemize10111@end itemize
10112 10112
10113@section corr
10114
10115Obtain the correlation between two input videos.
10116
10117This filter takes two input videos.
10118
10119Both input videos must have the same resolution and pixel format for
10120this filter to work correctly. Also it assumes that both inputs
10121have the same number of frames, which are compared one by one.
10122
10123The obtained per component, average, min and max correlation is printed through
10124the logging system.
10125
10126The filter stores the calculated correlation of each frame in frame metadata.
10127
10128This filter also supports the @ref{framesync} options.
10129
10130In the below example the input file @file{main.mpg} being processed is compared
10131with the reference file @file{ref.mpg}.
10132
10133@example
10134ffmpeg -i main.mpg -i ref.mpg -lavfi corr -f null -
10135@end example
10136
10113@section cover_rect10137@section cover_rect
10114 10138
10115Cover a rectangular object10139Cover a rectangular object
libavfilter/Makefile+1−0
@@ -241,6 +241,7 @@ OBJS-$(CONFIG_CONVOLUTION_OPENCL_FILTER) += vf_convolution_opencl.o opencl.o · 240 unmodified lines
241OBJS-$(CONFIG_CONVOLVE_FILTER) += vf_convolve.o framesync.o241OBJS-$(CONFIG_CONVOLVE_FILTER) += vf_convolve.o framesync.o
242OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o242OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o
243OBJS-$(CONFIG_COREIMAGE_FILTER) += vf_coreimage.o243OBJS-$(CONFIG_COREIMAGE_FILTER) += vf_coreimage.o
244OBJS-$(CONFIG_CORR_FILTER) += vf_corr.o framesync.o
244OBJS-$(CONFIG_COVER_RECT_FILTER) += vf_cover_rect.o lavfutils.o245OBJS-$(CONFIG_COVER_RECT_FILTER) += vf_cover_rect.o lavfutils.o
245OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o246OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o
246OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o247OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o
libavfilter/allfilters.c+1−0
@@ -221,6 +221,7 @@ extern const AVFilter ff_vf_convolution_opencl; · 220 unmodified lines
221extern const AVFilter ff_vf_convolve;221extern const AVFilter ff_vf_convolve;
222extern const AVFilter ff_vf_copy;222extern const AVFilter ff_vf_copy;
223extern const AVFilter ff_vf_coreimage;223extern const AVFilter ff_vf_coreimage;
224extern const AVFilter ff_vf_corr;
224extern const AVFilter ff_vf_cover_rect;225extern const AVFilter ff_vf_cover_rect;
225extern const AVFilter ff_vf_crop;226extern const AVFilter ff_vf_crop;
226extern const AVFilter ff_vf_cropdetect;227extern const AVFilter ff_vf_cropdetect;
libavfilter/version.h+1−1
@@ -31,7 +31,7 @@ · 30 unmodified lines
31 31
32#include "version_major.h"32#include "version_major.h"
33 33
34#define LIBAVFILTER_VERSION_MINOR 5134#define LIBAVFILTER_VERSION_MINOR 52
35#define LIBAVFILTER_VERSION_MICRO 10035#define LIBAVFILTER_VERSION_MICRO 100
36 36
37 37
libavfilter/vf_corr.c+338−0
@@ -0,0 +1,338 @@
1/*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19/**
20 * @file
21 * Calculate the correlation between two input videos.
22 */
23
24#include "libavutil/avstring.h"
25#include "libavutil/opt.h"
26#include "libavutil/pixdesc.h"
27#include "avfilter.h"
28#include "drawutils.h"
29#include "formats.h"
30#include "framesync.h"
31#include "internal.h"
32#include "video.h"
33
34typedef struct CorrContext {
35 const AVClass *class;
36 FFFrameSync fs;
37 double score, min_score, max_score, score_comp[4];
38 uint64_t nb_frames;
39 int is_rgb;
40 uint8_t rgba_map[4];
41 int max[4];
42 char comps[4];
43 int nb_components;
44 int planewidth[4];
45 int planeheight[4];
46 int (*filter_slice)(AVFilterContext *ctx, void *arg,
47 int jobnr, int nb_jobs);
48} CorrContext;
49
50#define OFFSET(x) offsetof(CorrContext, x)
51#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
52
53static void set_meta(AVFilterContext *ctx,
54 AVDictionary **metadata, const char *key, char comp, float d)
55{
56 char value[128];
57 snprintf(value, sizeof(value), "%f", d);
58 if (comp) {
59 char key2[128];
60 snprintf(key2, sizeof(key2), "lavfi.%s.%s%s%c",
61 ctx->filter->name, ctx->filter->name, key, comp);
62 av_dict_set(metadata, key2, value, 0);
63 } else {
64 char key2[128];
65 snprintf(key2, sizeof(key2), "lavfi.%s.%s%s",
66 ctx->filter->name, ctx->filter->name, key);
67 av_dict_set(metadata, key2, value, 0);
68 }
69}
70
71#define CORR(type, name) \
72static void f##name(AVFilterContext *ctx, AVFrame *master, \
73 AVFrame *ref, double *comp_score) \
74{ \
75 CorrContext *s = ctx->priv; \
76 \
77 for (int c = 0; c < s->nb_components; c++) { \
78 const ptrdiff_t linesize1 = master->linesize[c] / \
79 sizeof(type); \
80 const ptrdiff_t linesize2 = ref->linesize[c] / \
81 sizeof(type); \
82 const type *src1 = (const type *)master->data[c]; \
83 const type *src2 = (const type *)ref->data[c]; \
84 const int h = s->planeheight[c]; \
85 const int w = s->planewidth[c]; \
86 const float scale = 1.f / s->max[c]; \
87 uint64_t sum1 = 0, sum2 = 0; \
88 float sum12, sum1q, sum2q; \
89 float sumq, mean1, mean2; \
90 \
91 for (int y = 0; y < h; y++) { \
92 for (int x = 0; x < w; x++) { \
93 sum1 += src1[x]; \
94 sum2 += src2[x]; \
95 } \
96 \
97 src1 += linesize1; \
98 src2 += linesize2; \
99 } \
100 \
101 mean1 = scale * (sum1 /(double)(w * h)); \
102 mean2 = scale * (sum2 /(double)(w * h)); \
103 \
104 src1 = (const type *)master->data[c]; \
105 src2 = (const type *)ref->data[c]; \
106 \
107 sum12 = 0.f; \
108 sum1q = 0.f; \
109 sum2q = 0.f; \
110 \
111 for (int y = 0; y < h; y++) { \
112 for (int x = 0; x < w; x++) { \
113 const float f1 = scale * src1[x] - mean1; \
114 const float f2 = scale * src2[x] - mean2; \
115 \
116 sum12 += f1 * f2; \
117 sum1q += f1 * f1; \
118 sum2q += f2 * f2; \
119 } \
120 \
121 src1 += linesize1; \
122 src2 += linesize2; \
123 } \
124 \
125 sumq = sqrtf(sum1q * sum2q); \
126 if (sumq > 0.f) { \
127 comp_score[c] = av_clipf(sum12 / sumq,-1.f,1.f); \
128 } else { \
129 comp_score[c] = sum1q == sum2q ? 1.f : 0.f; \
130 } \
131 } \
132}
133
134CORR(uint8_t, corr8)
135CORR(uint16_t, corr16)
136
137static int do_corr(FFFrameSync *fs)
138{
139 AVFilterContext *ctx = fs->parent;
140 CorrContext *s = ctx->priv;
141 AVFrame *master, *ref;
142 double comp_score[4], score = 0.;
143 AVDictionary **metadata;
144 int ret;
145
146 ret = ff_framesync_dualinput_get(fs, &master, &ref);
147 if (ret < 0)
148 return ret;
149 if (ctx->is_disabled || !ref)
150 return ff_filter_frame(ctx->outputs[0], master);
151 metadata = &master->metadata;
152
153 if (s->max[0] > 255) {
154 fcorr16(ctx, master, ref, comp_score);
155 } else {
156 fcorr8(ctx, master, ref, comp_score);
157 }
158
159 for (int c = 0; c < s->nb_components; c++)
160 score += comp_score[c];
161 score /= s->nb_components;
162 s->score += score;
163
164 s->min_score = fmin(s->min_score, score);
165 s->max_score = fmax(s->max_score, score);
166
167 for (int c = 0; c < s->nb_components; c++)
168 s->score_comp[c] += comp_score[c];
169 s->nb_frames++;
170
171 for (int j = 0; j < s->nb_components; j++) {
172 int c = s->is_rgb ? s->rgba_map[j] : j;
173 set_meta(ctx, metadata, ".", s->comps[j], comp_score[c]);
174 }
175 set_meta(ctx, metadata, "_avg", 0, score);
176
177 return ff_filter_frame(ctx->outputs[0], master);
178}
179
180static av_cold int init(AVFilterContext *ctx)
181{
182 CorrContext *s = ctx->priv;
183
184 s->fs.on_event = do_corr;
185
186 return 0;
187}
188
189static const enum AVPixelFormat pix_fmts[] = {
190 AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
191#define PF_NOALPHA(suf) AV_PIX_FMT_YUV420##suf, AV_PIX_FMT_YUV422##suf, AV_PIX_FMT_YUV444##suf
192#define PF_ALPHA(suf) AV_PIX_FMT_YUVA420##suf, AV_PIX_FMT_YUVA422##suf, AV_PIX_FMT_YUVA444##suf
193#define PF(suf) PF_NOALPHA(suf), PF_ALPHA(suf)
194 PF(P), PF(P9), PF(P10), PF_NOALPHA(P12), PF_NOALPHA(P14), PF(P16),
195 AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
196 AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P,
197 AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P,
198 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
199 AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
200 AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
201 AV_PIX_FMT_NONE
202};
203
204static int config_input_ref(AVFilterLink *inlink)
205{
206 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
207 AVFilterContext *ctx = inlink->dst;
208 CorrContext *s = ctx->priv;
209
210 s->nb_components = desc->nb_components;
211 if (ctx->inputs[0]->w != ctx->inputs[1]->w ||
212 ctx->inputs[0]->h != ctx->inputs[1]->h) {
213 av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n");
214 return AVERROR(EINVAL);
215 }
216
217 s->is_rgb = ff_fill_rgba_map(s->rgba_map, inlink->format) >= 0;
218 s->comps[0] = s->is_rgb ? 'R' : 'Y' ;
219 s->comps[1] = s->is_rgb ? 'G' : 'U' ;
220 s->comps[2] = s->is_rgb ? 'B' : 'V' ;
221 s->comps[3] = 'A';
222
223 s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
224 s->planeheight[0] = s->planeheight[3] = inlink->h;
225 s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
226 s->planewidth[0] = s->planewidth[3] = inlink->w;
227
228 s->min_score = +INFINITY;
229 s->max_score = -INFINITY;
230
231 s->max[0] = (1 << desc->comp[0].depth) - 1;
232 s->max[1] = (1 << desc->comp[1].depth) - 1;
233 s->max[2] = (1 << desc->comp[2].depth) - 1;
234 s->max[3] = (1 << desc->comp[3].depth) - 1;
235
236 return 0;
237}
238
239static int config_output(AVFilterLink *outlink)
240{
241 AVFilterContext *ctx = outlink->src;
242 CorrContext *s = ctx->priv;
243 AVFilterLink *mainlink = ctx->inputs[0];
244 int ret;
245
246 ret = ff_framesync_init_dualinput(&s->fs, ctx);
247 if (ret < 0)
248 return ret;
249 outlink->w = mainlink->w;
250 outlink->h = mainlink->h;
251 outlink->time_base = mainlink->time_base;
252 outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
253 outlink->frame_rate = mainlink->frame_rate;
254 if ((ret = ff_framesync_configure(&s->fs)) < 0)
255 return ret;
256
257 outlink->time_base = s->fs.time_base;
258
259 if (av_cmp_q(mainlink->time_base, outlink->time_base) ||
260 av_cmp_q(ctx->inputs[1]->time_base, outlink->time_base))
261 av_log(ctx, AV_LOG_WARNING, "not matching timebases found between first input: %d/%d and second input %d/%d, results may be incorrect!\n",
262 mainlink->time_base.num, mainlink->time_base.den,
263 ctx->inputs[1]->time_base.num, ctx->inputs[1]->time_base.den);
264
265 return 0;
266}
267
268static int activate(AVFilterContext *ctx)
269{
270 CorrContext *s = ctx->priv;
271 return ff_framesync_activate(&s->fs);
272}
273
274static av_cold void uninit(AVFilterContext *ctx)
275{
276 CorrContext *s = ctx->priv;
277
278 if (s->nb_frames > 0) {
279 char buf[256];
280
281 buf[0] = 0;
282 for (int j = 0; j < s->nb_components; j++) {
283 int c = s->is_rgb ? s->rgba_map[j] : j;
284 av_strlcatf(buf, sizeof(buf), " %c:%f", s->comps[j], s->score_comp[c] / s->nb_frames);
285 }
286
287 av_log(ctx, AV_LOG_INFO, "%s%s average:%f min:%f max:%f\n",
288 ctx->filter->name,
289 buf,
290 s->score / s->nb_frames,
291 s->min_score,
292 s->max_score);
293 }
294
295 ff_framesync_uninit(&s->fs);
296}
297
298static const AVFilterPad corr_inputs[] = {
299 {
300 .name = "main",
301 .type = AVMEDIA_TYPE_VIDEO,
302 },{
303 .name = "reference",
304 .type = AVMEDIA_TYPE_VIDEO,
305 .config_props = config_input_ref,
306 },
307};
308
309static const AVFilterPad corr_outputs[] = {
310 {
311 .name = "default",
312 .type = AVMEDIA_TYPE_VIDEO,
313 .config_props = config_output,
314 },
315};
316
317static const AVOption options[] = {
318 { NULL }
319};
320
321#define corr_options options
322FRAMESYNC_DEFINE_CLASS(corr, CorrContext, fs);
323
324const AVFilter ff_vf_corr = {
325 .name = "corr",
326 .description = NULL_IF_CONFIG_SMALL("Calculate the correlation between two video streams."),
327 .preinit = corr_framesync_preinit,
328 .init = init,
329 .uninit = uninit,
330 .activate = activate,
331 .priv_size = sizeof(CorrContext),
332 .priv_class = &corr_class,
333 FILTER_INPUTS(corr_inputs),
334 FILTER_OUTPUTS(corr_outputs),
335 FILTER_PIXFMTS_ARRAY(pix_fmts),
336 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL |
337 AVFILTER_FLAG_METADATA_ONLY,
338};

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