Paul Buxton: > Signed-off-by: Paul Buxton <paulbuxton.m...@googlemail.com> > --- > doc/filters.texi | 12 ++ > libavfilter/Makefile | 1 + > libavfilter/allfilters.c | 1 + > libavfilter/version.h | 2 +- > libavfilter/vf_grayworld.c | 361 +++++++++++++++++++++++++++++++++++++ > 5 files changed, 376 insertions(+), 1 deletion(-) > create mode 100644 libavfilter/vf_grayworld.c > > diff --git a/doc/filters.texi b/doc/filters.texi > index 47ceeb18c6..0f334c3d2a 100644 > --- a/doc/filters.texi > +++ b/doc/filters.texi > @@ -13046,6 +13046,18 @@ Set upper limit for video rate of output stream, > Default value is @var{25}. > This guarantee that output video frame rate will not be higher than this > value. > @end table > > +@section grayworld > +A color constancy filter that applies color correction based on the > grayworld assumption > + > +See: > @url{https://www.researchgate.net/publication/275213614_A_New_Color_Correction_Method_for_Underwater_Imaging} > + > +The algorithm uses linear light, so input > +data should be linearized beforehand (and possibly correctly tagged). > + > +@example > +ffmpeg -i INPUT -vf > zscale=transfer=linear,grayworld,zscale=transfer=bt709,format=yuv420p OUTPUT > +@end example > + > @section greyedge > A color constancy variation filter which estimates scene illumination via > grey edge algorithm > and corrects the scene colors accordingly. > diff --git a/libavfilter/Makefile b/libavfilter/Makefile > index 3166e70b58..1d86110d6b 100644 > --- a/libavfilter/Makefile > +++ b/libavfilter/Makefile > @@ -287,6 +287,7 @@ OBJS-$(CONFIG_GBLUR_FILTER) += vf_gblur.o > OBJS-$(CONFIG_GEQ_FILTER) += vf_geq.o > OBJS-$(CONFIG_GRADFUN_FILTER) += vf_gradfun.o > OBJS-$(CONFIG_GRAPHMONITOR_FILTER) += f_graphmonitor.o > +OBJS-$(CONFIG_GRAYWORLD_FILTER) += vf_grayworld.o > OBJS-$(CONFIG_GREYEDGE_FILTER) += vf_colorconstancy.o > OBJS-$(CONFIG_GUIDED_FILTER) += vf_guided.o > OBJS-$(CONFIG_HALDCLUT_FILTER) += vf_lut3d.o framesync.o > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > index c49c831203..73e5fe1a41 100644 > --- a/libavfilter/allfilters.c > +++ b/libavfilter/allfilters.c > @@ -272,6 +272,7 @@ extern const AVFilter ff_vf_gblur; > extern const AVFilter ff_vf_geq; > extern const AVFilter ff_vf_gradfun; > extern const AVFilter ff_vf_graphmonitor; > +extern const AVFilter ff_vf_grayworld; > extern const AVFilter ff_vf_greyedge; > extern const AVFilter ff_vf_guided; > extern const AVFilter ff_vf_haldclut; > diff --git a/libavfilter/version.h b/libavfilter/version.h > index e9a76c5ac3..e3a86d9b01 100644 > --- a/libavfilter/version.h > +++ b/libavfilter/version.h > @@ -30,7 +30,7 @@ > #include "libavutil/version.h" > > #define LIBAVFILTER_VERSION_MAJOR 8 > -#define LIBAVFILTER_VERSION_MINOR 4 > +#define LIBAVFILTER_VERSION_MINOR 5 > #define LIBAVFILTER_VERSION_MICRO 100 > > > diff --git a/libavfilter/vf_grayworld.c b/libavfilter/vf_grayworld.c > new file mode 100644 > index 0000000000..a356b9003c > --- /dev/null > +++ b/libavfilter/vf_grayworld.c > @@ -0,0 +1,361 @@ > +/* > + * Copyright (c) 2021 Paul Buxton > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +/** > + * @file > + * Color correction filter based on > + * > https://www.researchgate.net/publication/275213614_A_New_Color_Correction_Method_for_Underwater_Imaging > + * > + */ > + > +#include "libavutil/imgutils.h" > +#include "libavutil/opt.h" > +#include "libavutil/pixdesc.h" > +#include "libavutil/intreadwrite.h" > + > +#include "avfilter.h" > +#include "drawutils.h" > +#include "formats.h" > +#include "internal.h" > +#include "video.h" > + > +typedef struct ThreadData { > + AVFrame *in, *out; > + float l_avg; > + float a_avg; > + float b_avg; > + const AVPixFmtDescriptor *desc; > + const AVPixFmtDescriptor *odesc; > +} ThreadData; > + > +typedef struct GrayWorldContext { > + const AVClass* class; > + float *tmpplab; > + int *line_count_pels; > + float *line_sum; > + > +} GrayWorldContext; > + > +#define OFFSET(x) offsetof(GrayWorldContext, x) > +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | > AV_OPT_FLAG_RUNTIME_PARAM > +static const AVOption grayworld_options[] = {};
Missing sentinel (an empty array is illegal in C). > + > +AVFILTER_DEFINE_CLASS(grayworld); > + > +void compute_correction(GrayWorldContext *s, ThreadData *td); > +void apply_matrix(float matrix[3][3], float input[3], float output[3]); > +void rgb2lab(float rgb[3], float lab[3]); > +void lab2rgb(float lab[3], float rgb[3]); > + > +static int query_formats(AVFilterContext *ctx) > +{ > + static const enum AVPixelFormat pix_fmts[] = { > + AV_PIX_FMT_GBRPF32, > + AV_PIX_FMT_GBRAPF32, > + AV_PIX_FMT_NONE, > + }; > + return ff_set_common_formats_from_list(ctx, pix_fmts); > +} > + > +void apply_matrix(float matrix[3][3], float input[3], float output[3]) > +{ > + for (int row = 0; row < 3; ++row) { > + output[row] = matrix[row][0] * input[0] + matrix[row][1] * input[1] > + matrix[row][2] * input[2]; > + } > +} > + > +float lms2lab[3][3] = { > + {0.5774, 0.5774, 0.5774}, > + {0.40825, 0.40825, -0.816458}, > + {0.707, -0.707, 0} > +}; > + > +float lab2lms[3][3] = { > + {0.57735, 0.40825, 0.707}, > + {0.57735, 0.40825, -0.707}, > + {0.57735, -0.8165, 0} > +}; > + > +float rgb2lms[3][3] = { > + {0.3811, 0.5783, 0.0402}, > + {0.1967, 0.7244, 0.0782}, > + {0.0241, 0.1288, 0.8444} > +}; > + > +float lms2rgb[3][3] = { Missing const (also in the correpsonding apply_matrix() parameter. > + {4.4679, -3.5873, 0.1193}, > + {-1.2186, 2.3809, -0.1624}, > + {0.0497, -0.2439, 1.2045} > +}; > + [...] > +static int config_input(AVFilterLink *inlink) > +{ > + GrayWorldContext *s = inlink->dst->priv; > + > + s->tmpplab = av_malloc_array(inlink->h * inlink->w * 3, sizeof(float) ); > + if (!s->tmpplab){ > + return AVERROR(ENOMEM); > + } > + s->line_count_pels = av_malloc_array(inlink->h, sizeof(int)); > + if (!s->line_count_pels){ > + return AVERROR(ENOMEM); > + } > + s->line_sum = av_malloc_array(inlink->h * 2, sizeof(float)); sizeof(*s->line_sum) is preferred (more auto-like). Or use FF_ALLOC_TYPED_ARRAY. > + if (!s->line_sum){ > + return AVERROR(ENOMEM); > + } > + > + return 0; > +} > + > +static av_cold void uninit(AVFilterContext *ctx) > +{ > + GrayWorldContext *s = ctx->priv; > + > + av_freep(&s->tmpplab); > + av_freep(&s->line_count_pels); > + av_freep(&s->line_sum); > +} > + > +static int filter_frame(AVFilterLink *inlink, AVFrame *in) > +{ > + AVFilterContext *ctx = inlink->dst; > + GrayWorldContext *s = ctx->priv; > + AVFilterLink *outlink = ctx->outputs[0]; > + ThreadData td; > + AVFrame *out; > + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); > + const AVPixFmtDescriptor *odesc = av_pix_fmt_desc_get(outlink->format); > + > + if (av_frame_is_writable(in)) { > + out = in; > + } else { > + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); > + if (!out) { > + av_frame_free(&in); > + return AVERROR(ENOMEM); > + } > + av_frame_copy_props(out, in); > + } > + /* input and output transfer will be linear */ > + if (in->color_trc == AVCOL_TRC_UNSPECIFIED) { > + av_log(s, AV_LOG_WARNING, "Untagged transfer, assuming linear > light\n"); > + out->color_trc = AVCOL_TRC_LINEAR; > + } else if (in->color_trc != AVCOL_TRC_LINEAR) { > + av_log(s, AV_LOG_WARNING, "Grey world color correction works on > linear light only\n"); > + } > + > + td.in = in; > + td.out = out; > + td.desc = desc; > + td.odesc = odesc; > + > + ctx->internal->execute(ctx, convert_frame, &td, NULL, FFMIN(outlink->h, > ff_filter_get_nb_threads(ctx))); > + compute_correction(s, &td); > + ctx->internal->execute(ctx, correct_frame, &td, NULL, FFMIN(outlink->h, > ff_filter_get_nb_threads(ctx))); ff_filter_execute() would make this a bit shorter. > + > + if (desc->flags & AV_PIX_FMT_FLAG_ALPHA && desc->flags & > AV_PIX_FMT_FLAG_ALPHA) { > + av_image_copy_plane(out->data[3], out->linesize[3], > + in->data[3], in->linesize[3], > + out->linesize[3], outlink->h); > + } > + > + if (in != out) > + av_frame_free(&in); > + return ff_filter_frame(outlink, out); > +} > + > +static const AVFilterPad grayworld_inputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .filter_frame = filter_frame, > + .config_props = config_input, > + } > +}; > + > +static const AVFilterPad grayworld_outputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + } > +}; > + > +const AVFilter ff_vf_grayworld = { > + .name = "grayworld", > + .description = NULL_IF_CONFIG_SMALL("Adjust white balance using LAB gray > world algorithm"), > + .priv_size = sizeof(GrayWorldContext), > + .priv_class = &grayworld_class, > + .query_formats = query_formats, > + FILTER_INPUTS(grayworld_inputs), > + FILTER_OUTPUTS(grayworld_outputs), > + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | > AVFILTER_FLAG_SLICE_THREADS, > + .uninit = uninit, > +}; > _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".