| | 1 | /* |
| | 2 | * Copyright (c) 2013 Dirk Farin <dirk.farin@gmail.com> |
| | 3 | * |
| | 4 | * This file is part of FFmpeg. |
| | 5 | * |
| | 6 | * FFmpeg is free software; you can redistribute it and/or modify |
| | 7 | * it under the terms of the GNU General Public License as published by |
| | 8 | * the Free Software Foundation; either version 2 of the License, or |
| | 9 | * (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 |
| | 14 | * GNU General Public License for more details. |
| | 15 | * |
| | 16 | * You should have received a copy of the GNU General Public License along |
| | 17 | * with FFmpeg; if not, write to the Free Software Foundation, Inc., |
| | 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| | 19 | */ |
| | 20 | |
| | 21 | /** |
| | 22 | * @file |
| | 23 | * Non-Local Means noise reduction filter. |
| | 24 | * |
| | 25 | * This implementation is an implementation of the paper below with |
| | 26 | * an extension to use several frames. The computation is slightly |
| | 27 | * simplified to reduce computation complexity. |
| | 28 | * |
| | 29 | * A. Buades, B. Coll, J.-M. Morel: |
| | 30 | * "A non-local algorithm for image denoising", |
| | 31 | * IEEE Computer Vision and Pattern Recognition 2005, Vol 2, pp: 60-65, 2005. |
| | 32 | */ |
| | 33 | |
| | 34 | #include <float.h> |
| | 35 | #include <stdint.h> |
| | 36 | #include <string.h> |
| | 37 | #include <stdlib.h> |
| | 38 | #include <stddef.h> |
| | 39 | #include <math.h> |
| | 40 | #include <assert.h> |
| | 41 | |
| | 42 | #include <stdio.h> |
| | 43 | |
| | 44 | #include "config.h" |
| | 45 | #include "libavutil/pixdesc.h" |
| | 46 | |
| | 47 | #include "avfilter.h" |
| | 48 | #include "formats.h" |
| | 49 | #include "internal.h" |
| | 50 | #include "video.h" |
| | 51 | #include "libavutil/opt.h" |
| | 52 | |
| | 53 | #include "libavfilter/vf_nlmeans.h" |
| | 54 | |
| | 55 | |
| | 56 | |
| | 57 | typedef struct |
| | 58 | { |
| | 59 | uint8_t* img; // point to logical origin (0;0) |
| | 60 | int stride; |
| | 61 | |
| | 62 | int w,h; |
| | 63 | int border; // extra border on all four sides |
| | 64 | |
| | 65 | uint8_t* mem_start; // start of allocated memory |
| | 66 | } MonoImage; |
| | 67 | |
| | 68 | |
| | 69 | typedef enum { |
| | 70 | ImageFormat_Mono, |
| | 71 | ImageFormat_YUV420, |
| | 72 | ImageFormat_YUV422, |
| | 73 | ImageFormat_YUV444, |
| | 74 | ImageFormat_RGB |
| | 75 | } ImageFormat; |
| | 76 | |
| | 77 | |
| | 78 | typedef struct |
| | 79 | { |
| | 80 | MonoImage plane[3]; |
| | 81 | ImageFormat format; |
| | 82 | } ColorImage; |
| | 83 | |
| | 84 | |
| | 85 | static void alloc_and_copy_image_with_border(MonoImage* out_img, |
| | 86 | const uint8_t* input_image, int input_stride, |
| | 87 | int w,int h, int req_border) |
| | 88 | { |
| | 89 | const int border = (req_border+15)/16*16; |
| | 90 | const int out_stride = (w+2*border); |
| | 91 | const int out_total_height = (h+2*border); |
| | 92 | |
| | 93 | uint8_t* const memory_ptr = (uint8_t*)malloc(out_stride*out_total_height); |
| | 94 | uint8_t* const output_image = memory_ptr + border + border*out_stride; // logical output image origin (0,0) |
| | 95 | |
| | 96 | |
| | 97 | // copy main image content |
| | 98 | |
| | 99 | for (int y=0;y<h;y++) { |
| | 100 | memcpy(output_image + y*out_stride, input_image+y*input_stride, w); |
| | 101 | } |
| | 102 | |
| | 103 | // top/bottom borders |
| | 104 | |
| | 105 | for (int k=0;k<border;k++) { |
| | 106 | memcpy(output_image-(k+1)*out_stride, input_image, w); |
| | 107 | memcpy(output_image+(h+k)*out_stride, input_image+(h-1)*input_stride, w); |
| | 108 | } |
| | 109 | |
| | 110 | // left/right borders |
| | 111 | |
| | 112 | for (int k=0;k<border;k++) { |
| | 113 | for (int y=-border;y<h+border;y++) |
| | 114 | { |
| | 115 | *(output_image -k-1+y*out_stride) = output_image[y*out_stride]; |
| | 116 | *(output_image+w+k +y*out_stride) = output_image[y*out_stride+w-1]; |
| | 117 | } |
| | 118 | } |
| | 119 | |
| | 120 | out_img->img = output_image; |
| | 121 | out_img->mem_start = memory_ptr; |
| | 122 | out_img->stride = out_stride; |
| | 123 | out_img->w = w; |
| | 124 | out_img->h = h; |
| | 125 | out_img->border = border; |
| | 126 | } |
| | 127 | |
| | 128 | |
| | 129 | static void free_mono_image(MonoImage* img) |
| | 130 | { |
| | 131 | if (img->mem_start) { |
| | 132 | free(img->mem_start); |
| | 133 | img->mem_start=NULL; |
| | 134 | img->img=NULL; |
| | 135 | } |
| | 136 | } |
| | 137 | |
| | 138 | static void free_color_image(ColorImage* img) |
| | 139 | { |
| | 140 | for (int c=0;c<3;c++) { |
| | 141 | free_mono_image(&(img->plane[c])); |
| | 142 | } |
| | 143 | } |
| | 144 | |
| | 145 | |
| | 146 | |
| | 147 | |
| | 148 | |
| | 149 | typedef struct |
| | 150 | { |
| | 151 | double h_param; |
| | 152 | int patch_size; |
| | 153 | int range; // search range (must be odd number) |
| | 154 | int n_frames; // temporal search depth |
| | 155 | } NLMeansParams; |
| | 156 | |
| | 157 | |
| | 158 | #define MAX_NLMeansImages 32 |
| | 159 | |
| | 160 | typedef struct { |
| | 161 | const AVClass *class; |
| | 162 | |
| | 163 | int hsub,vsub; |
| | 164 | |
| | 165 | NLMeansParams param; |
| | 166 | |
| | 167 | ColorImage images[MAX_NLMeansImages]; |
| | 168 | int image_available[MAX_NLMeansImages]; |
| | 169 | |
| | 170 | NLMeansFunctions func; |
| | 171 | |
| | 172 | } NLMContext; |
| | 173 | |
| | 174 | |
| | 175 | |
| | 176 | static void buildIntegralImage_scalar(uint32_t* integral_image, int integral_stride, |
| | 177 | const uint8_t* current_image, int current_image_stride, |
| | 178 | const uint8_t* compare_image, int compare_image_stride, |
| | 179 | int w,int h, |
| | 180 | int dx,int dy) |
| | 181 | { |
| | 182 | memset(integral_image -1 -integral_stride, 0, (w+1)*sizeof(uint32_t)); |
| | 183 | |
| | 184 | for (int y=0;y<h;y++) { |
| | 185 | const uint8_t* p1 = current_image + y *current_image_stride; |
| | 186 | const uint8_t* p2 = compare_image + (y+dy)*compare_image_stride + dx; |
| | 187 | uint32_t* out = integral_image + y*integral_stride -1; |
| | 188 | |
| | 189 | *out++ = 0; |
| | 190 | |
| | 191 | for (int x=0;x<w;x++) |
| | 192 | { |
| | 193 | int diff = *p1++ - *p2++; |
| | 194 | *out = *(out-1) + diff * diff; |
| | 195 | out++; |
| | 196 | } |
| | 197 | |
| | 198 | if (y>0) { |
| | 199 | out = integral_image + y*integral_stride; |
| | 200 | |
| | 201 | for (int x=0;x<w;x++) { |
| | 202 | *out += *(out - integral_stride); |
| | 203 | out++; |
| | 204 | } |
| | 205 | } |
| | 206 | } |
| | 207 | } |
| | 208 | |
| | 209 | |
| | 210 | |
| | 211 | struct PixelSum |
| | 212 | { |
| | 213 | float weight_sum; |
| | 214 | float pixel_sum; |
| | 215 | }; |
| | 216 | |
| | 217 | |
| | 218 | |
| | 219 | static void NLMeans_mono_multi(uint8_t* out, int out_stride, |
| | 220 | const MonoImage*const* images, int n_images, |
| | 221 | const NLMContext* ctx) |
| | 222 | { |
| | 223 | const int w = images[0]->w; |
| | 224 | const int h = images[0]->h; |
| | 225 | |
| | 226 | const int n = (ctx->param.patch_size|1); |
| | 227 | const int r = (ctx->param.range |1); |
| | 228 | |
| | 229 | const int n_half = (n-1)/2; |
| | 230 | const int r_half = (r-1)/2; |
| | 231 | |
| | 232 | |
| | 233 | // alloc memory for temporary pixel sums |
| | 234 | |
| | 235 | struct PixelSum* const tmp_data = (struct PixelSum*)calloc(w*h,sizeof(struct PixelSum)); |
| | 236 | |
| | 237 | |
| | 238 | // allocate integral image |
| | 239 | |
| | 240 | const int integral_stride = w+2*16; |
| | 241 | uint32_t* const integral_mem = (uint32_t*)malloc( integral_stride*(h+1)*sizeof(uint32_t) ); |
| | 242 | uint32_t* const integral = integral_mem + integral_stride + 16; |
| | 243 | |
| | 244 | |
| | 245 | // precompute exponential table |
| | 246 | |
| | 247 | const float weight_factor = 1.0/n/n / (ctx->param.h_param * ctx->param.h_param); |
| | 248 | |
| | 249 | #define EXP_TABSIZE 128 |
| | 250 | |
| | 251 | const int table_size=EXP_TABSIZE; |
| | 252 | const float min_weight_in_table = 0.0005; |
| | 253 | |
| | 254 | float exptable[EXP_TABSIZE]; |
| | 255 | |
| | 256 | const float stretch = table_size/ (-log(min_weight_in_table)); |
| | 257 | const float weight_fact_table = weight_factor*stretch; |
| | 258 | const int diff_max = table_size/weight_fact_table; |
| | 259 | |
| | 260 | for (int i=0;i<table_size;i++) { |
| | 261 | exptable[i] = exp(-i/stretch); |
| | 262 | } |
| | 263 | exptable[table_size-1]=0; |
| | 264 | |
| | 265 | |
| | 266 | |
| | 267 | for (int image_idx=0; image_idx<n_images; image_idx++) |
| | 268 | { |
| | 269 | // copy input image |
| | 270 | |
| | 271 | const uint8_t* current_image = images[0]->img; |
| | 272 | int current_image_stride = images[0]->stride; |
| | 273 | |
| | 274 | const uint8_t* compare_image = images[image_idx]->img; |
| | 275 | int compare_image_stride = images[image_idx]->stride; |
| | 276 | |
| | 277 | |
| | 278 | // --- iterate through all displacements --- |
| | 279 | |
| | 280 | for (int dy=-r_half ; dy<=r_half ; dy++) |
| | 281 | for (int dx=-r_half ; dx<=r_half ; dx++) |
| | 282 | { |
| | 283 | // special, simple implementation for no shift (no difference -> weight 1) |
| | 284 | |
| | 285 | if (dx==0 && dy==0 && image_idx==0) { |
| | 286 | #pragma omp parallel for |
| | 287 | for (int y=n_half;y<h-n+n_half;y++) { |
| | 288 | for (int x=n_half;x<w-n+n_half;x++) { |
| | 289 | tmp_data[y*w+x].weight_sum += 1; |
| | 290 | tmp_data[y*w+x].pixel_sum += current_image[y*current_image_stride+x]; |
| | 291 | } |
| | 292 | } |
| | 293 | |
| | 294 | continue; |
| | 295 | } |
| | 296 | |
| | 297 | |
| | 298 | // --- regular case --- |
| | 299 | |
| | 300 | ctx->func.buildIntegralImage(integral,integral_stride, |
| | 301 | current_image, current_image_stride, |
| | 302 | compare_image, compare_image_stride, |
| | 303 | w,h, dx,dy); |
| | 304 | |
| | 305 | #pragma omp parallel for |
| | 306 | for (int y=0;y<=h-n;y++) { |
| | 307 | const uint32_t* integral_ptr1 = integral+(y -1)*integral_stride-1; |
| | 308 | const uint32_t* integral_ptr2 = integral+(y+n-1)*integral_stride-1; |
| | 309 | |
| | 310 | for (int x=0;x<=w-n;x++) { |
| | 311 | const int xc = x+n_half; |
| | 312 | const int yc = y+n_half; |
| | 313 | |
| | 314 | // patch difference |
| | 315 | |
| | 316 | int diff = (uint32_t)(integral_ptr2[n] - integral_ptr2[0] - integral_ptr1[n] + integral_ptr1[0]); |
| | 317 | |
| | 318 | |
| | 319 | // sum pixel with weight |
| | 320 | |
| | 321 | if (diff<diff_max) { |
| | 322 | int diffidx = diff*weight_fact_table; |
| | 323 | |
| | 324 | //float weight = exp(-diff*weightFact); |
| | 325 | float weight = exptable[diffidx]; |
| | 326 | |
| | 327 | tmp_data[yc*w+xc].weight_sum += weight; |
| | 328 | tmp_data[yc*w+xc].pixel_sum += weight * compare_image[(yc+dy)*compare_image_stride+xc+dx]; |
| | 329 | } |
| | 330 | |
| | 331 | integral_ptr1++; |
| | 332 | integral_ptr2++; |
| | 333 | } |
| | 334 | } |
| | 335 | } |
| | 336 | } |
| | 337 | |
| | 338 | |
| | 339 | |
| | 340 | // --- fill output image --- |
| | 341 | |
| | 342 | // copy border area |
| | 343 | |
| | 344 | { |
| | 345 | const uint8_t* in = images[0]->img; |
| | 346 | int orig_in_stride = images[0]->stride; |
| | 347 | |
| | 348 | for (int y=0; y<n_half ;y++) { memcpy(out+y*out_stride, in+y*orig_in_stride, w); } |
| | 349 | for (int y=h-n_half;y<h ;y++) { memcpy(out+y*out_stride, in+y*orig_in_stride, w); } |
| | 350 | for (int y=n_half ;y<h-n_half;y++) { |
| | 351 | memcpy(out+y*out_stride, in+y*orig_in_stride, n_half); |
| | 352 | memcpy(out+y*out_stride+w-n_half, in+y*orig_in_stride+w-n_half, n_half); |
| | 353 | } |
| | 354 | } |
| | 355 | |
| | 356 | // output main image |
| | 357 | |
| | 358 | for (int y=n_half;y<h-n_half;y++) { |
| | 359 | for (int x=n_half;x<w-n_half;x++) { |
| | 360 | *(out+y*out_stride+x) = tmp_data[y*w+x].pixel_sum / tmp_data[y*w+x].weight_sum; |
| | 361 | } |
| | 362 | } |
| | 363 | |
| | 364 | free(tmp_data); |
| | 365 | free(integral_mem); |
| | 366 | } |
| | 367 | |
| | 368 | |
| | 369 | static void NLMeans_color_auto(uint8_t** out, int* out_stride, |
| | 370 | const ColorImage* img, // function takes ownership |
| | 371 | NLMContext* ctx) |
| | 372 | { |
| | 373 | assert(ctx->param.n_frames >= 1); |
| | 374 | assert(ctx->param.n_frames <= MAX_NLMeansImages); |
| | 375 | |
| | 376 | // free oldest image |
| | 377 | |
| | 378 | free_color_image(&ctx->images[ctx->param.n_frames-1]); |
| | 379 | |
| | 380 | // shift old images one down and put new image into entry [0] |
| | 381 | |
| | 382 | for (int i=ctx->param.n_frames-1; i>0; i--) { |
| | 383 | ctx->images[i] = ctx->images[i-1]; |
| | 384 | ctx->image_available[i] = ctx->image_available[i-1]; |
| | 385 | } |
| | 386 | |
| | 387 | ctx->images[0] = *img; |
| | 388 | ctx->image_available[0] = 1; |
| | 389 | |
| | 390 | |
| | 391 | // process color planes separately |
| | 392 | |
| | 393 | for (int c=0;c<3;c++) |
| | 394 | if (ctx->images[0].plane[c].img != NULL) |
| | 395 | { |
| | 396 | const MonoImage* images[MAX_NLMeansImages]; |
| | 397 | int i; |
| | 398 | for (i=0; ctx->image_available[i]; i++) { |
| | 399 | images[i] = &ctx->images[i].plane[c]; |
| | 400 | } |
| | 401 | |
| | 402 | NLMeans_mono_multi(out[c], out_stride[c], |
| | 403 | images, i, ctx); |
| | 404 | } |
| | 405 | } |
| | 406 | |
| | 407 | |
| | 408 | |
| | 409 | static av_cold int init(AVFilterContext *ctx) |
| | 410 | { |
| | 411 | NLMContext *nlm = ctx->priv; |
| | 412 | |
| | 413 | for (int i=0;i<MAX_NLMeansImages;i++) { |
| | 414 | nlm->image_available[i] = 0; |
| | 415 | } |
| | 416 | |
| | 417 | |
| | 418 | nlm->func.buildIntegralImage = buildIntegralImage_scalar; |
| | 419 | |
| | 420 | if (ARCH_X86) { |
| | 421 | ff_nlmeans_init_x86(&nlm->func); |
| | 422 | } |
| | 423 | |
| | 424 | return 0; |
| | 425 | } |
| | 426 | |
| | 427 | static av_cold void uninit(AVFilterContext *ctx) |
| | 428 | { |
| | 429 | NLMContext *nlm = ctx->priv; |
| | 430 | |
| | 431 | for (int i=0;i<MAX_NLMeansImages;i++) { |
| | 432 | if (nlm->image_available[i]) { |
| | 433 | free_color_image(&(nlm->images[i])); |
| | 434 | |
| | 435 | nlm->image_available[i] = 0; |
| | 436 | } |
| | 437 | } |
| | 438 | } |
| | 439 | |
| | 440 | static int query_formats(AVFilterContext *ctx) |
| | 441 | { |
| | 442 | static const enum AVPixelFormat pix_fmts[] = { |
| | 443 | AV_PIX_FMT_YUV420P, |
| | 444 | AV_PIX_FMT_YUV422P, |
| | 445 | AV_PIX_FMT_YUV444P, |
| | 446 | AV_PIX_FMT_YUV410P, |
| | 447 | AV_PIX_FMT_YUV411P, |
| | 448 | AV_PIX_FMT_YUV440P, |
| | 449 | |
| | 450 | AV_PIX_FMT_YUVJ420P, |
| | 451 | AV_PIX_FMT_YUVJ422P, |
| | 452 | AV_PIX_FMT_YUVJ444P, |
| | 453 | AV_PIX_FMT_YUVJ440P, |
| | 454 | |
| | 455 | AV_PIX_FMT_NONE |
| | 456 | }; |
| | 457 | |
| | 458 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); |
| | 459 | |
| | 460 | return 0; |
| | 461 | } |
| | 462 | |
| | 463 | static int config_input(AVFilterLink *inlink) |
| | 464 | { |
| | 465 | NLMContext *nlm = inlink->dst->priv; |
| | 466 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
| | 467 | |
| | 468 | nlm->hsub = desc->log2_chroma_w; |
| | 469 | nlm->vsub = desc->log2_chroma_h; |
| | 470 | |
| | 471 | return 0; |
| | 472 | } |
| | 473 | |
| | 474 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) |
| | 475 | { |
| | 476 | NLMContext *nlm = inlink->dst->priv; |
| | 477 | AVFilterLink *outlink = inlink->dst->outputs[0]; |
| | 478 | |
| | 479 | ColorImage bordered_image; |
| | 480 | |
| | 481 | AVFrame *out; |
| | 482 | int direct, c; |
| | 483 | |
| | 484 | if (av_frame_is_writable(in)) { |
| | 485 | direct = 1; |
| | 486 | out = in; |
| | 487 | } else { |
| | 488 | direct = 0; |
| | 489 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| | 490 | if (!out) { |
| | 491 | av_frame_free(&in); |
| | 492 | return AVERROR(ENOMEM); |
| | 493 | } |
| | 494 | |
| | 495 | av_frame_copy_props(out, in); |
| | 496 | } |
| | 497 | |
| | 498 | |
| | 499 | // extend image with border |
| | 500 | |
| | 501 | for (c = 0; c < 3; c++) { |
| | 502 | int w = FF_CEIL_RSHIFT(in->width, (!!c * nlm->hsub)); |
| | 503 | int h = FF_CEIL_RSHIFT(in->height, (!!c * nlm->vsub)); |
| | 504 | int border = nlm->param.range/2; |
| | 505 | |
| | 506 | alloc_and_copy_image_with_border(&bordered_image.plane[c], |
| | 507 | in->data[c], in->linesize[c], |
| | 508 | w,h,border); |
| | 509 | } |
| | 510 | |
| | 511 | NLMeans_color_auto(out->data, out->linesize, |
| | 512 | &bordered_image, |
| | 513 | nlm); |
| | 514 | |
| | 515 | |
| | 516 | if (!direct) |
| | 517 | av_frame_free(&in); |
| | 518 | |
| | 519 | return ff_filter_frame(outlink, out); |
| | 520 | } |
| | 521 | |
| | 522 | #define OFFSET(x) offsetof(NLMContext, x) |
| | 523 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM |
| | 524 | static const AVOption options[] = { |
| | 525 | { "h", "averaging weight decay parameter", OFFSET(param.h_param), AV_OPT_TYPE_DOUBLE, { .dbl = 8.0 }, 0.1, 100.0, FLAGS }, |
| | 526 | { "patchsize", "patch width/height", OFFSET(param.patch_size), AV_OPT_TYPE_INT, { .i64 = 7 }, 3, 255, FLAGS }, |
| | 527 | { "range", "search range", OFFSET(param.range), AV_OPT_TYPE_INT, { .i64 = 3 }, 1, 255, FLAGS }, |
| | 528 | { "temporal", "temporal search range", OFFSET(param.n_frames), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, MAX_NLMeansImages, FLAGS }, |
| | 529 | { NULL }, |
| | 530 | }; |
| | 531 | |
| | 532 | |
| | 533 | static const AVClass nlm_class = { |
| | 534 | .class_name = "nlm", |
| | 535 | .item_name = av_default_item_name, |
| | 536 | .option = options, |
| | 537 | .version = LIBAVUTIL_VERSION_INT, |
| | 538 | }; |
| | 539 | |
| | 540 | |
| | 541 | static const AVFilterPad avfilter_vf_nlm_inputs[] = { |
| | 542 | { |
| | 543 | .name = "default", |
| | 544 | .type = AVMEDIA_TYPE_VIDEO, |
| | 545 | .config_props = config_input, |
| | 546 | .filter_frame = filter_frame, |
| | 547 | }, |
| | 548 | { NULL } |
| | 549 | }; |
| | 550 | |
| | 551 | |
| | 552 | static const AVFilterPad avfilter_vf_nlm_outputs[] = { |
| | 553 | { |
| | 554 | .name = "default", |
| | 555 | .type = AVMEDIA_TYPE_VIDEO |
| | 556 | }, |
| | 557 | { NULL } |
| | 558 | }; |
| | 559 | |
| | 560 | AVFilter ff_vf_nlmeans = { |
| | 561 | .name = "nlmeans", |
| | 562 | .description = NULL_IF_CONFIG_SMALL("Apply a Non-Local Means filter."), |
| | 563 | |
| | 564 | .priv_size = sizeof(NLMContext), |
| | 565 | .priv_class = &nlm_class, |
| | 566 | .init = init, |
| | 567 | .uninit = uninit, |
| | 568 | .query_formats = query_formats, |
| | 569 | |
| | 570 | .inputs = avfilter_vf_nlm_inputs, |
| | 571 | .outputs = avfilter_vf_nlm_outputs, |
| | 572 | }; |