Message ID | 20230111140532.26467-1-yizhuo.liu753@gmail.com |
---|---|
State | New |
Headers | show |
Series | [FFmpeg-devel,add,video,filter,hsl(Hue,Saturation,Lightness)] | expand |
Context | Check | Description |
---|---|---|
yinshiyou/commit_msg_loongarch64 | warning | The first line of the commit message must start with a context terminated by a colon and a space, for example "lavu/opt: " or "doc: ". |
andriy/commit_msg_x86 | warning | The first line of the commit message must start with a context terminated by a colon and a space, for example "lavu/opt: " or "doc: ". |
yinshiyou/make_fate_loongarch64 | success | Make fate finished |
yinshiyou/make_loongarch64 | warning | New warnings during build |
andriy/make_fate_x86 | success | Make fate finished |
andriy/make_x86 | warning | New warnings during build |
On 1/11/23, liuyizhuo <yizhuo.liu753@gmail.com> wrote: > From: "2498228118@qq.com" <2498228118@qq.com> > > --- > libavfilter/Makefile | 1 + > libavfilter/allfilters.c | 1 + > libavfilter/vf_hsl.c | 310 ++++++++++++++++++++++++++++++++++++ > tests/fate-run.sh | 2 + > tests/fate/filter-video.mak | 3 + > tests/ref/fate/filter-hsl | 1 + > 6 files changed, 318 insertions(+) > create mode 100644 libavfilter/vf_hsl.c > create mode 100644 tests/ref/fate/filter-hsl > > diff --git a/libavfilter/Makefile b/libavfilter/Makefile > index 5783be281d..b9ed9e065a 100644 > --- a/libavfilter/Makefile > +++ b/libavfilter/Makefile > @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER) += > asrc_sine.o > OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o > > # video filters > +OBJS-$(CONFIG_HSL_FILTER) += vf_hsl.o > OBJS-$(CONFIG_ADDROI_FILTER) += vf_addroi.o > OBJS-$(CONFIG_ALPHAEXTRACT_FILTER) += vf_extractplanes.o > OBJS-$(CONFIG_ALPHAMERGE_FILTER) += vf_alphamerge.o > framesync.o > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > index 52741b60e4..6910189ce4 100644 > --- a/libavfilter/allfilters.c > +++ b/libavfilter/allfilters.c > @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine; > > extern const AVFilter ff_asink_anullsink; > > +extern const AVFilter ff_vf_hsl; > extern const AVFilter ff_vf_addroi; > extern const AVFilter ff_vf_alphaextract; > extern const AVFilter ff_vf_alphamerge; > diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c > new file mode 100644 > index 0000000000..47c9af9120 > --- /dev/null > +++ b/libavfilter/vf_hsl.c > @@ -0,0 +1,310 @@ > +/* > + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com> > + * Copyright (c) 2022 Liu yizhuo <yizhuo.liu753@gmail.com> > + * > + * 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 > + * Calculate the HSL color space information. > + * HSL is a color space that is more perceptually uniform than RGB : > + * https://en.wikipedia.org/wiki/HSL_and_HSV better integrate this into signalstats filter, as it does not do compare of 2 inputs. > + */ > + > +#include <math.h> > + > +#include "libavutil/imgutils.h" > +#include "libavutil/internal.h" > +#include "libavutil/opt.h" > +#include "libswscale/swscale.h" > + > +#include "avfilter.h" > +#include "formats.h" > +#include "internal.h" > +#include "video.h" > + > +typedef float num; > + > +typedef struct HSL_COLOR { > + num H; > + num S; > + num L; > +} HSL_COLOR; > + > +typedef struct HSLContext { > + const AVClass *class; > + int width, height; > + uint64_t nb_frames; > + float max_hue, max_sat, max_light; > + float min_hue, min_sat, min_light; > + float sum_hue, sum_sat, sum_light; > + int print_summary; > + AVFrame *rgbFrame; > + enum AVPixelFormat rgb_format; > +} HSLContext; > + > +static const enum AVPixelFormat pix_fmts[] = { > + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, > + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, > + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, > + AV_PIX_FMT_NONE > +}; > + > +static av_cold int init(AVFilterContext *ctx) > +{ > + // User options but no input data > + HSLContext *s = ctx->priv; > + s->max_hue = 0; > + s->max_sat = 0; > + s->max_light = 0; > + > + return 0; > +} > + > +static av_cold void uninit(AVFilterContext *ctx) > +{ > + HSLContext *s = ctx->priv; > + > + if (s->print_summary) { > + float avg_hue = s->sum_hue / s->nb_frames; > + float avg_sat = s->sum_sat / s->nb_frames; > + float avg_light = s->sum_light / s->nb_frames; > + av_log(ctx, AV_LOG_INFO, > + "HSL Summary:\nTotal frames: %"PRId64"\n\n" > + "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n" > + "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin: > %.1f\n\n" > + "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin: > %.1f\n\n", > + s->nb_frames, > + avg_hue, s->max_hue, s->min_hue, > + avg_sat * 100, s->max_sat * 100, s->min_sat * 100, > + avg_light * 100, s->max_light * 100, s->min_light * 100 > + ); > + } > + > + av_frame_free(&s->rgbFrame); > +} > + > +static int config_input(AVFilterLink *inlink) > +{ > + // Video input data avilable > + AVFilterContext *ctx = inlink->dst; > + HSLContext *s = ctx->priv; > + > + // free previous buffers in case they are allocated already > + av_frame_free(&s->rgbFrame); > + s->width = inlink->w; > + s->height = inlink->h; > + s->rgb_format = AV_PIX_FMT_RGB24; > + s->rgbFrame = av_frame_alloc(); > + if (!s->rgbFrame) { > + return AVERROR(ENOMEM); > + } > + > + s->rgbFrame->format = s->rgb_format; > + s->rgbFrame->width = s->width; > + s->rgbFrame->height = s->height; > + if (av_frame_get_buffer(s->rgbFrame, 0)) { > + return AVERROR(ENOMEM); > + } > + > + return 0; > +} > + > +static void set_meta(AVDictionary **metadata, const char *key, float d) > +{ > + char value[128]; > + snprintf(value, sizeof(value), "%0.1f", d); > + av_dict_set(metadata, key, value, 0); > +} > + > +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat, > AVFrame* dst) > +{ > + int width = src->width; > + int height = src->height; > + > + struct SwsContext* conversion = NULL; > + conversion = sws_getContext(width, > + height, > + (enum AVPixelFormat)src->format, > + width, > + height, > + dstFormat, > + SWS_FAST_BILINEAR, > + NULL, > + NULL, > + NULL); > + sws_scale(conversion, src->data, src->linesize, 0, height, dst->data, > dst->linesize); > + sws_freeContext(conversion); > +} > + > +static const float EPSILON = 1e-9; > + > +/** @brief Min of A and B */ > +#define MIN(A,B) (((A) <= (B)) ? (A) : (B)) > + > +/** @brief Max of A and B */ > +#define MAX(A,B) (((A) >= (B)) ? (A) : (B)) > + > +/** @brief Min of A, B, and C */ > +#define MIN3(A,B,C) (((A) <= (B)) ? MIN(A,C) : MIN(B,C)) > + > +/** @brief Max of A, B, and C */ > +#define MAX3(A,B,C) (((A) >= (B)) ? MAX(A,C) : MAX(B,C)) > + > +/** @brief Equal of A and B */ > +#define EQ(A,B) ((fabs((A) - (B)) < EPSILON) ? 1 : 0) > + > + > +/** > + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL) > + * > + * @param H, S, L pointers to hold the result > + * @param R, G, B the input sRGB values scaled in [0,1] > + * > + * This routine transforms from sRGB to the double hexcone HSL color space > + * The sRGB values are assumed to be between 0 and 1. The outputs are > + * H = hexagonal hue angle (0 <= H < 360), > + * S = { C/(2L) if L <= 1/2 (0 <= S <= 1), > + * { C/(2 - 2L) if L > 1/2 > + * L = (max(R',G',B') + min(R',G',B'))/2 (0 <= L <= 1), > + * where C = max(R',G',B') - min(R',G',B'). > + * > + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV > + */ > +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B) > +{ > + num Max = MAX3(R, G, B); > + num Min = MIN3(R, G, B); > + num C = Max - Min; > + > + *L = (Max + Min) / 2; > + > + if (C > 0) { > + if (EQ(Max, R)) { > + *H = (G - B) / C; > + > + if (G < B) { > + *H += 6; > + } > + } else if (EQ(Max, G)) { > + *H = 2 + (B - R) / C; > + } else { > + *H = 4 + (R - G) / C; > + } > + > + *H *= 60; > + *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L))); > + } else { > + *H = *S = 0; > + } > +} > + > +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl) > +{ > + // Convert to RGB > + YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame); > + > + int cnt = frame->width * frame->height; > + num R = 0, G = 0, B = 0; > + num H = 0, S = 0, L = 0; > + num H_sum = 0, S_sum = 0, L_sum = 0; > + > + for (int i = 0; i < cnt; i++) { > + R = ctx->rgbFrame->data[0][3 * i]; > + G = ctx->rgbFrame->data[0][3 * i + 1]; > + B = ctx->rgbFrame->data[0][3 * i + 2]; > + RGB2HSL(&H, &S, &L, R/255, G/255, B/255); > + H_sum += H; > + S_sum += S; > + L_sum += L; > + } > + > + hsl->H = H_sum / cnt; > + hsl->S = S_sum / cnt; > + hsl->L = L_sum / cnt; > + > + // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n", > + // hsl->H, hsl->S * 100, hsl->L * 100); > +} > + > +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) > +{ > + AVFilterContext *ctx = inlink->dst; > + HSLContext *s = ctx->priv; > + s->nb_frames++; > + > + HSL_COLOR hsl; > + calcHSL(s, frame, &hsl); > + > + // Calculate statistics > + s->max_hue = fmaxf(hsl.H, s->max_hue); > + s->max_sat = fmaxf(hsl.S, s->max_sat); > + s->max_light = fmaxf(hsl.L, s->max_light); > + s->sum_hue += hsl.H; > + s->sum_sat += hsl.S; > + s->sum_light += hsl.L; > + s->min_hue = s->nb_frames == 1 ? hsl.H : fminf(hsl.H, s->min_hue); > + s->min_sat = s->nb_frames == 1 ? hsl.S : fminf(hsl.S, s->min_sat); > + s->min_light = s->nb_frames == 1 ? hsl.L : fminf(hsl.L, > s->min_light); > + > + // Set HSL information in frame metadata > + set_meta(&frame->metadata, "lavfi.hsl.hue", hsl.H); > + set_meta(&frame->metadata, "lavfi.hsl.sat", hsl.S * 100); > + set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100); > + > + return ff_filter_frame(inlink->dst->outputs[0], frame); > +} > + > +#define OFFSET(x) offsetof(HSLContext, x) > +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM > + > +static const AVOption hsl_options[] = { > + { "print_summary", "Print summary showing average values", > OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS }, > + { NULL } > +}; > + > +AVFILTER_DEFINE_CLASS(hsl); > + > +static const AVFilterPad avfilter_vf_hsl_inputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .config_props = config_input, > + .filter_frame = filter_frame, > + }, > +}; > + > +static const AVFilterPad avfilter_vf_hsl_outputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO > + }, > +}; > + > +const AVFilter ff_vf_hsl = { > + .name = "hsl", > + .description = NULL_IF_CONFIG_SMALL("Calculate the HSL color space > information."), > + .priv_size = sizeof(HSLContext), > + .priv_class = &hsl_class, > + .init = init, > + .uninit = uninit, > + .flags = AVFILTER_FLAG_METADATA_ONLY, > + FILTER_PIXFMTS_ARRAY(pix_fmts), > + FILTER_INPUTS(avfilter_vf_hsl_inputs), > + FILTER_OUTPUTS(avfilter_vf_hsl_outputs), > +}; > diff --git a/tests/fate-run.sh b/tests/fate-run.sh > index 61cc59acc0..c350570f5b 100755 > --- a/tests/fate-run.sh > +++ b/tests/fate-run.sh > @@ -1,5 +1,7 @@ > #! /bin/sh > > +set -x > + > export LC_ALL=C > > base=$(dirname $0) > diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak > index 63873a7a07..aa9812aa18 100644 > --- a/tests/fate/filter-video.mak > +++ b/tests/fate/filter-video.mak > @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter > "scale=w=200:h=200" > FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += > fate-filter-scale500 > fate-filter-scale500: CMD = video_filter "scale=w=500:h=500" > > +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += fate-filter-hsl > +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1" > + > FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER > NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) += > fate-filter-scale2ref_keep_aspect > fate-filter-scale2ref_keep_aspect: > tests/data/filtergraphs/scale2ref_keep_aspect > fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5 > -filter_complex_script > $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map "[main]" > diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl > new file mode 100644 > index 0000000000..ae0a064f46 > --- /dev/null > +++ b/tests/ref/fate/filter-hsl > @@ -0,0 +1 @@ > +hsl fcb007249fba9371fe84a61c974fcb00 > -- > 2.24.3 (Apple Git-128) > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >
On 1/11/23, Paul B Mahol <onemda@gmail.com> wrote: > On 1/11/23, liuyizhuo <yizhuo.liu753@gmail.com> wrote: >> From: "2498228118@qq.com" <2498228118@qq.com> >> >> --- >> libavfilter/Makefile | 1 + >> libavfilter/allfilters.c | 1 + >> libavfilter/vf_hsl.c | 310 ++++++++++++++++++++++++++++++++++++ >> tests/fate-run.sh | 2 + >> tests/fate/filter-video.mak | 3 + >> tests/ref/fate/filter-hsl | 1 + >> 6 files changed, 318 insertions(+) >> create mode 100644 libavfilter/vf_hsl.c >> create mode 100644 tests/ref/fate/filter-hsl >> >> diff --git a/libavfilter/Makefile b/libavfilter/Makefile >> index 5783be281d..b9ed9e065a 100644 >> --- a/libavfilter/Makefile >> +++ b/libavfilter/Makefile >> @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER) += >> asrc_sine.o >> OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o >> >> # video filters >> +OBJS-$(CONFIG_HSL_FILTER) += vf_hsl.o >> OBJS-$(CONFIG_ADDROI_FILTER) += vf_addroi.o >> OBJS-$(CONFIG_ALPHAEXTRACT_FILTER) += vf_extractplanes.o >> OBJS-$(CONFIG_ALPHAMERGE_FILTER) += vf_alphamerge.o >> framesync.o >> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c >> index 52741b60e4..6910189ce4 100644 >> --- a/libavfilter/allfilters.c >> +++ b/libavfilter/allfilters.c >> @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine; >> >> extern const AVFilter ff_asink_anullsink; >> >> +extern const AVFilter ff_vf_hsl; >> extern const AVFilter ff_vf_addroi; >> extern const AVFilter ff_vf_alphaextract; >> extern const AVFilter ff_vf_alphamerge; >> diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c >> new file mode 100644 >> index 0000000000..47c9af9120 >> --- /dev/null >> +++ b/libavfilter/vf_hsl.c >> @@ -0,0 +1,310 @@ >> +/* >> + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com> >> + * Copyright (c) 2022 Liu yizhuo <yizhuo.liu753@gmail.com> >> + * >> + * 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 >> + * Calculate the HSL color space information. >> + * HSL is a color space that is more perceptually uniform than RGB : >> + * https://en.wikipedia.org/wiki/HSL_and_HSV > > better integrate this into signalstats filter, as it does not do > compare of 2 inputs. > Also use already available macros, FFMAX(3) FFMIN(3) ... >> + */ >> + >> +#include <math.h> >> + >> +#include "libavutil/imgutils.h" >> +#include "libavutil/internal.h" >> +#include "libavutil/opt.h" >> +#include "libswscale/swscale.h" >> + >> +#include "avfilter.h" >> +#include "formats.h" >> +#include "internal.h" >> +#include "video.h" >> + >> +typedef float num; >> + >> +typedef struct HSL_COLOR { >> + num H; >> + num S; >> + num L; >> +} HSL_COLOR; >> + >> +typedef struct HSLContext { >> + const AVClass *class; >> + int width, height; >> + uint64_t nb_frames; >> + float max_hue, max_sat, max_light; >> + float min_hue, min_sat, min_light; >> + float sum_hue, sum_sat, sum_light; >> + int print_summary; >> + AVFrame *rgbFrame; >> + enum AVPixelFormat rgb_format; >> +} HSLContext; >> + >> +static const enum AVPixelFormat pix_fmts[] = { >> + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, >> + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, >> + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, >> + AV_PIX_FMT_NONE >> +}; >> + >> +static av_cold int init(AVFilterContext *ctx) >> +{ >> + // User options but no input data >> + HSLContext *s = ctx->priv; >> + s->max_hue = 0; >> + s->max_sat = 0; >> + s->max_light = 0; >> + >> + return 0; >> +} >> + >> +static av_cold void uninit(AVFilterContext *ctx) >> +{ >> + HSLContext *s = ctx->priv; >> + >> + if (s->print_summary) { >> + float avg_hue = s->sum_hue / s->nb_frames; >> + float avg_sat = s->sum_sat / s->nb_frames; >> + float avg_light = s->sum_light / s->nb_frames; >> + av_log(ctx, AV_LOG_INFO, >> + "HSL Summary:\nTotal frames: %"PRId64"\n\n" >> + "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin: >> %.1f\n\n" >> + "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin: >> %.1f\n\n" >> + "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin: >> %.1f\n\n", >> + s->nb_frames, >> + avg_hue, s->max_hue, s->min_hue, >> + avg_sat * 100, s->max_sat * 100, s->min_sat * 100, >> + avg_light * 100, s->max_light * 100, s->min_light * 100 >> + ); >> + } >> + >> + av_frame_free(&s->rgbFrame); >> +} >> + >> +static int config_input(AVFilterLink *inlink) >> +{ >> + // Video input data avilable >> + AVFilterContext *ctx = inlink->dst; >> + HSLContext *s = ctx->priv; >> + >> + // free previous buffers in case they are allocated already >> + av_frame_free(&s->rgbFrame); >> + s->width = inlink->w; >> + s->height = inlink->h; >> + s->rgb_format = AV_PIX_FMT_RGB24; >> + s->rgbFrame = av_frame_alloc(); >> + if (!s->rgbFrame) { >> + return AVERROR(ENOMEM); >> + } >> + >> + s->rgbFrame->format = s->rgb_format; >> + s->rgbFrame->width = s->width; >> + s->rgbFrame->height = s->height; >> + if (av_frame_get_buffer(s->rgbFrame, 0)) { >> + return AVERROR(ENOMEM); >> + } >> + >> + return 0; >> +} >> + >> +static void set_meta(AVDictionary **metadata, const char *key, float d) >> +{ >> + char value[128]; >> + snprintf(value, sizeof(value), "%0.1f", d); >> + av_dict_set(metadata, key, value, 0); >> +} >> + >> +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat, >> AVFrame* dst) >> +{ >> + int width = src->width; >> + int height = src->height; >> + >> + struct SwsContext* conversion = NULL; >> + conversion = sws_getContext(width, >> + height, >> + (enum AVPixelFormat)src->format, >> + width, >> + height, >> + dstFormat, >> + SWS_FAST_BILINEAR, >> + NULL, >> + NULL, >> + NULL); >> + sws_scale(conversion, src->data, src->linesize, 0, height, >> dst->data, >> dst->linesize); >> + sws_freeContext(conversion); >> +} >> + >> +static const float EPSILON = 1e-9; >> + >> +/** @brief Min of A and B */ >> +#define MIN(A,B) (((A) <= (B)) ? (A) : (B)) >> + >> +/** @brief Max of A and B */ >> +#define MAX(A,B) (((A) >= (B)) ? (A) : (B)) >> + >> +/** @brief Min of A, B, and C */ >> +#define MIN3(A,B,C) (((A) <= (B)) ? MIN(A,C) : MIN(B,C)) >> + >> +/** @brief Max of A, B, and C */ >> +#define MAX3(A,B,C) (((A) >= (B)) ? MAX(A,C) : MAX(B,C)) >> + >> +/** @brief Equal of A and B */ >> +#define EQ(A,B) ((fabs((A) - (B)) < EPSILON) ? 1 : 0) >> + >> + >> +/** >> + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL) >> + * >> + * @param H, S, L pointers to hold the result >> + * @param R, G, B the input sRGB values scaled in [0,1] >> + * >> + * This routine transforms from sRGB to the double hexcone HSL color >> space >> + * The sRGB values are assumed to be between 0 and 1. The outputs are >> + * H = hexagonal hue angle (0 <= H < 360), >> + * S = { C/(2L) if L <= 1/2 (0 <= S <= 1), >> + * { C/(2 - 2L) if L > 1/2 >> + * L = (max(R',G',B') + min(R',G',B'))/2 (0 <= L <= 1), >> + * where C = max(R',G',B') - min(R',G',B'). >> + * >> + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV >> + */ >> +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B) >> +{ >> + num Max = MAX3(R, G, B); >> + num Min = MIN3(R, G, B); >> + num C = Max - Min; >> + >> + *L = (Max + Min) / 2; >> + >> + if (C > 0) { >> + if (EQ(Max, R)) { >> + *H = (G - B) / C; >> + >> + if (G < B) { >> + *H += 6; >> + } >> + } else if (EQ(Max, G)) { >> + *H = 2 + (B - R) / C; >> + } else { >> + *H = 4 + (R - G) / C; >> + } >> + >> + *H *= 60; >> + *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L))); >> + } else { >> + *H = *S = 0; >> + } >> +} >> + >> +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl) >> +{ >> + // Convert to RGB >> + YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame); >> + >> + int cnt = frame->width * frame->height; >> + num R = 0, G = 0, B = 0; >> + num H = 0, S = 0, L = 0; >> + num H_sum = 0, S_sum = 0, L_sum = 0; >> + >> + for (int i = 0; i < cnt; i++) { >> + R = ctx->rgbFrame->data[0][3 * i]; >> + G = ctx->rgbFrame->data[0][3 * i + 1]; >> + B = ctx->rgbFrame->data[0][3 * i + 2]; >> + RGB2HSL(&H, &S, &L, R/255, G/255, B/255); >> + H_sum += H; >> + S_sum += S; >> + L_sum += L; >> + } >> + >> + hsl->H = H_sum / cnt; >> + hsl->S = S_sum / cnt; >> + hsl->L = L_sum / cnt; >> + >> + // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n", >> + // hsl->H, hsl->S * 100, hsl->L * 100); >> +} >> + >> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) >> +{ >> + AVFilterContext *ctx = inlink->dst; >> + HSLContext *s = ctx->priv; >> + s->nb_frames++; >> + >> + HSL_COLOR hsl; >> + calcHSL(s, frame, &hsl); >> + >> + // Calculate statistics >> + s->max_hue = fmaxf(hsl.H, s->max_hue); >> + s->max_sat = fmaxf(hsl.S, s->max_sat); >> + s->max_light = fmaxf(hsl.L, s->max_light); >> + s->sum_hue += hsl.H; >> + s->sum_sat += hsl.S; >> + s->sum_light += hsl.L; >> + s->min_hue = s->nb_frames == 1 ? hsl.H : fminf(hsl.H, >> s->min_hue); >> + s->min_sat = s->nb_frames == 1 ? hsl.S : fminf(hsl.S, >> s->min_sat); >> + s->min_light = s->nb_frames == 1 ? hsl.L : fminf(hsl.L, >> s->min_light); >> + >> + // Set HSL information in frame metadata >> + set_meta(&frame->metadata, "lavfi.hsl.hue", hsl.H); >> + set_meta(&frame->metadata, "lavfi.hsl.sat", hsl.S * 100); >> + set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100); >> + >> + return ff_filter_frame(inlink->dst->outputs[0], frame); >> +} >> + >> +#define OFFSET(x) offsetof(HSLContext, x) >> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM >> + >> +static const AVOption hsl_options[] = { >> + { "print_summary", "Print summary showing average values", >> OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS }, >> + { NULL } >> +}; >> + >> +AVFILTER_DEFINE_CLASS(hsl); >> + >> +static const AVFilterPad avfilter_vf_hsl_inputs[] = { >> + { >> + .name = "default", >> + .type = AVMEDIA_TYPE_VIDEO, >> + .config_props = config_input, >> + .filter_frame = filter_frame, >> + }, >> +}; >> + >> +static const AVFilterPad avfilter_vf_hsl_outputs[] = { >> + { >> + .name = "default", >> + .type = AVMEDIA_TYPE_VIDEO >> + }, >> +}; >> + >> +const AVFilter ff_vf_hsl = { >> + .name = "hsl", >> + .description = NULL_IF_CONFIG_SMALL("Calculate the HSL color space >> information."), >> + .priv_size = sizeof(HSLContext), >> + .priv_class = &hsl_class, >> + .init = init, >> + .uninit = uninit, >> + .flags = AVFILTER_FLAG_METADATA_ONLY, >> + FILTER_PIXFMTS_ARRAY(pix_fmts), >> + FILTER_INPUTS(avfilter_vf_hsl_inputs), >> + FILTER_OUTPUTS(avfilter_vf_hsl_outputs), >> +}; >> diff --git a/tests/fate-run.sh b/tests/fate-run.sh >> index 61cc59acc0..c350570f5b 100755 >> --- a/tests/fate-run.sh >> +++ b/tests/fate-run.sh >> @@ -1,5 +1,7 @@ >> #! /bin/sh >> >> +set -x >> + >> export LC_ALL=C >> >> base=$(dirname $0) >> diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak >> index 63873a7a07..aa9812aa18 100644 >> --- a/tests/fate/filter-video.mak >> +++ b/tests/fate/filter-video.mak >> @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter >> "scale=w=200:h=200" >> FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += >> fate-filter-scale500 >> fate-filter-scale500: CMD = video_filter "scale=w=500:h=500" >> >> +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += >> fate-filter-hsl >> +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1" >> + >> FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER >> NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) += >> fate-filter-scale2ref_keep_aspect >> fate-filter-scale2ref_keep_aspect: >> tests/data/filtergraphs/scale2ref_keep_aspect >> fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5 >> -filter_complex_script >> $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map >> "[main]" >> diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl >> new file mode 100644 >> index 0000000000..ae0a064f46 >> --- /dev/null >> +++ b/tests/ref/fate/filter-hsl >> @@ -0,0 +1 @@ >> +hsl fcb007249fba9371fe84a61c974fcb00 >> -- >> 2.24.3 (Apple Git-128) >> >> _______________________________________________ >> ffmpeg-devel mailing list >> ffmpeg-devel@ffmpeg.org >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel >> >> To unsubscribe, visit link above, or email >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >> >
dear Paul, hsl integrate into signalstats has been done, thank u for advice looking forward to your reply besh wishes attachment ( recent commit ) https://patchwork.ffmpeg.org/project/ffmpeg/patch/20230309133517.26683-1-yizhuo.liu753@gmail.com/ Paul B Mahol <onemda@gmail.com> 于2023年1月12日周四 05:11写道: > On 1/11/23, Paul B Mahol <onemda@gmail.com> wrote: > > On 1/11/23, liuyizhuo <yizhuo.liu753@gmail.com> wrote: > >> From: "2498228118@qq.com" <2498228118@qq.com> > >> > >> --- > >> libavfilter/Makefile | 1 + > >> libavfilter/allfilters.c | 1 + > >> libavfilter/vf_hsl.c | 310 ++++++++++++++++++++++++++++++++++++ > >> tests/fate-run.sh | 2 + > >> tests/fate/filter-video.mak | 3 + > >> tests/ref/fate/filter-hsl | 1 + > >> 6 files changed, 318 insertions(+) > >> create mode 100644 libavfilter/vf_hsl.c > >> create mode 100644 tests/ref/fate/filter-hsl > >> > >> diff --git a/libavfilter/Makefile b/libavfilter/Makefile > >> index 5783be281d..b9ed9e065a 100644 > >> --- a/libavfilter/Makefile > >> +++ b/libavfilter/Makefile > >> @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER) += > >> asrc_sine.o > >> OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o > >> > >> # video filters > >> +OBJS-$(CONFIG_HSL_FILTER) += vf_hsl.o > >> OBJS-$(CONFIG_ADDROI_FILTER) += vf_addroi.o > >> OBJS-$(CONFIG_ALPHAEXTRACT_FILTER) += vf_extractplanes.o > >> OBJS-$(CONFIG_ALPHAMERGE_FILTER) += vf_alphamerge.o > >> framesync.o > >> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > >> index 52741b60e4..6910189ce4 100644 > >> --- a/libavfilter/allfilters.c > >> +++ b/libavfilter/allfilters.c > >> @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine; > >> > >> extern const AVFilter ff_asink_anullsink; > >> > >> +extern const AVFilter ff_vf_hsl; > >> extern const AVFilter ff_vf_addroi; > >> extern const AVFilter ff_vf_alphaextract; > >> extern const AVFilter ff_vf_alphamerge; > >> diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c > >> new file mode 100644 > >> index 0000000000..47c9af9120 > >> --- /dev/null > >> +++ b/libavfilter/vf_hsl.c > >> @@ -0,0 +1,310 @@ > >> +/* > >> + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com> > >> + * Copyright (c) 2022 Liu yizhuo <yizhuo.liu753@gmail.com> > >> + * > >> + * 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 > >> + * Calculate the HSL color space information. > >> + * HSL is a color space that is more perceptually uniform than RGB : > >> + * https://en.wikipedia.org/wiki/HSL_and_HSV > > > > better integrate this into signalstats filter, as it does not do > > compare of 2 inputs. > > > > Also use already available macros, FFMAX(3) FFMIN(3) ... > > >> + */ > >> + > >> +#include <math.h> > >> + > >> +#include "libavutil/imgutils.h" > >> +#include "libavutil/internal.h" > >> +#include "libavutil/opt.h" > >> +#include "libswscale/swscale.h" > >> + > >> +#include "avfilter.h" > >> +#include "formats.h" > >> +#include "internal.h" > >> +#include "video.h" > >> + > >> +typedef float num; > >> + > >> +typedef struct HSL_COLOR { > >> + num H; > >> + num S; > >> + num L; > >> +} HSL_COLOR; > >> + > >> +typedef struct HSLContext { > >> + const AVClass *class; > >> + int width, height; > >> + uint64_t nb_frames; > >> + float max_hue, max_sat, max_light; > >> + float min_hue, min_sat, min_light; > >> + float sum_hue, sum_sat, sum_light; > >> + int print_summary; > >> + AVFrame *rgbFrame; > >> + enum AVPixelFormat rgb_format; > >> +} HSLContext; > >> + > >> +static const enum AVPixelFormat pix_fmts[] = { > >> + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, > >> + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, > >> + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, > >> + AV_PIX_FMT_NONE > >> +}; > >> + > >> +static av_cold int init(AVFilterContext *ctx) > >> +{ > >> + // User options but no input data > >> + HSLContext *s = ctx->priv; > >> + s->max_hue = 0; > >> + s->max_sat = 0; > >> + s->max_light = 0; > >> + > >> + return 0; > >> +} > >> + > >> +static av_cold void uninit(AVFilterContext *ctx) > >> +{ > >> + HSLContext *s = ctx->priv; > >> + > >> + if (s->print_summary) { > >> + float avg_hue = s->sum_hue / s->nb_frames; > >> + float avg_sat = s->sum_sat / s->nb_frames; > >> + float avg_light = s->sum_light / s->nb_frames; > >> + av_log(ctx, AV_LOG_INFO, > >> + "HSL Summary:\nTotal frames: %"PRId64"\n\n" > >> + "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin: > >> %.1f\n\n" > >> + "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin: > >> %.1f\n\n" > >> + "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin: > >> %.1f\n\n", > >> + s->nb_frames, > >> + avg_hue, s->max_hue, s->min_hue, > >> + avg_sat * 100, s->max_sat * 100, s->min_sat * 100, > >> + avg_light * 100, s->max_light * 100, s->min_light * 100 > >> + ); > >> + } > >> + > >> + av_frame_free(&s->rgbFrame); > >> +} > >> + > >> +static int config_input(AVFilterLink *inlink) > >> +{ > >> + // Video input data avilable > >> + AVFilterContext *ctx = inlink->dst; > >> + HSLContext *s = ctx->priv; > >> + > >> + // free previous buffers in case they are allocated already > >> + av_frame_free(&s->rgbFrame); > >> + s->width = inlink->w; > >> + s->height = inlink->h; > >> + s->rgb_format = AV_PIX_FMT_RGB24; > >> + s->rgbFrame = av_frame_alloc(); > >> + if (!s->rgbFrame) { > >> + return AVERROR(ENOMEM); > >> + } > >> + > >> + s->rgbFrame->format = s->rgb_format; > >> + s->rgbFrame->width = s->width; > >> + s->rgbFrame->height = s->height; > >> + if (av_frame_get_buffer(s->rgbFrame, 0)) { > >> + return AVERROR(ENOMEM); > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static void set_meta(AVDictionary **metadata, const char *key, float d) > >> +{ > >> + char value[128]; > >> + snprintf(value, sizeof(value), "%0.1f", d); > >> + av_dict_set(metadata, key, value, 0); > >> +} > >> + > >> +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat, > >> AVFrame* dst) > >> +{ > >> + int width = src->width; > >> + int height = src->height; > >> + > >> + struct SwsContext* conversion = NULL; > >> + conversion = sws_getContext(width, > >> + height, > >> + (enum AVPixelFormat)src->format, > >> + width, > >> + height, > >> + dstFormat, > >> + SWS_FAST_BILINEAR, > >> + NULL, > >> + NULL, > >> + NULL); > >> + sws_scale(conversion, src->data, src->linesize, 0, height, > >> dst->data, > >> dst->linesize); > >> + sws_freeContext(conversion); > >> +} > >> + > >> +static const float EPSILON = 1e-9; > >> + > >> +/** @brief Min of A and B */ > >> +#define MIN(A,B) (((A) <= (B)) ? (A) : (B)) > >> + > >> +/** @brief Max of A and B */ > >> +#define MAX(A,B) (((A) >= (B)) ? (A) : (B)) > >> + > >> +/** @brief Min of A, B, and C */ > >> +#define MIN3(A,B,C) (((A) <= (B)) ? MIN(A,C) : MIN(B,C)) > >> + > >> +/** @brief Max of A, B, and C */ > >> +#define MAX3(A,B,C) (((A) >= (B)) ? MAX(A,C) : MAX(B,C)) > >> + > >> +/** @brief Equal of A and B */ > >> +#define EQ(A,B) ((fabs((A) - (B)) < EPSILON) ? 1 : 0) > >> + > >> + > >> +/** > >> + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL) > >> + * > >> + * @param H, S, L pointers to hold the result > >> + * @param R, G, B the input sRGB values scaled in [0,1] > >> + * > >> + * This routine transforms from sRGB to the double hexcone HSL color > >> space > >> + * The sRGB values are assumed to be between 0 and 1. The outputs are > >> + * H = hexagonal hue angle (0 <= H < 360), > >> + * S = { C/(2L) if L <= 1/2 (0 <= S <= 1), > >> + * { C/(2 - 2L) if L > 1/2 > >> + * L = (max(R',G',B') + min(R',G',B'))/2 (0 <= L <= 1), > >> + * where C = max(R',G',B') - min(R',G',B'). > >> + * > >> + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV > >> + */ > >> +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B) > >> +{ > >> + num Max = MAX3(R, G, B); > >> + num Min = MIN3(R, G, B); > >> + num C = Max - Min; > >> + > >> + *L = (Max + Min) / 2; > >> + > >> + if (C > 0) { > >> + if (EQ(Max, R)) { > >> + *H = (G - B) / C; > >> + > >> + if (G < B) { > >> + *H += 6; > >> + } > >> + } else if (EQ(Max, G)) { > >> + *H = 2 + (B - R) / C; > >> + } else { > >> + *H = 4 + (R - G) / C; > >> + } > >> + > >> + *H *= 60; > >> + *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L))); > >> + } else { > >> + *H = *S = 0; > >> + } > >> +} > >> + > >> +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl) > >> +{ > >> + // Convert to RGB > >> + YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame); > >> + > >> + int cnt = frame->width * frame->height; > >> + num R = 0, G = 0, B = 0; > >> + num H = 0, S = 0, L = 0; > >> + num H_sum = 0, S_sum = 0, L_sum = 0; > >> + > >> + for (int i = 0; i < cnt; i++) { > >> + R = ctx->rgbFrame->data[0][3 * i]; > >> + G = ctx->rgbFrame->data[0][3 * i + 1]; > >> + B = ctx->rgbFrame->data[0][3 * i + 2]; > >> + RGB2HSL(&H, &S, &L, R/255, G/255, B/255); > >> + H_sum += H; > >> + S_sum += S; > >> + L_sum += L; > >> + } > >> + > >> + hsl->H = H_sum / cnt; > >> + hsl->S = S_sum / cnt; > >> + hsl->L = L_sum / cnt; > >> + > >> + // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n", > >> + // hsl->H, hsl->S * 100, hsl->L * 100); > >> +} > >> + > >> +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) > >> +{ > >> + AVFilterContext *ctx = inlink->dst; > >> + HSLContext *s = ctx->priv; > >> + s->nb_frames++; > >> + > >> + HSL_COLOR hsl; > >> + calcHSL(s, frame, &hsl); > >> + > >> + // Calculate statistics > >> + s->max_hue = fmaxf(hsl.H, s->max_hue); > >> + s->max_sat = fmaxf(hsl.S, s->max_sat); > >> + s->max_light = fmaxf(hsl.L, s->max_light); > >> + s->sum_hue += hsl.H; > >> + s->sum_sat += hsl.S; > >> + s->sum_light += hsl.L; > >> + s->min_hue = s->nb_frames == 1 ? hsl.H : fminf(hsl.H, > >> s->min_hue); > >> + s->min_sat = s->nb_frames == 1 ? hsl.S : fminf(hsl.S, > >> s->min_sat); > >> + s->min_light = s->nb_frames == 1 ? hsl.L : fminf(hsl.L, > >> s->min_light); > >> + > >> + // Set HSL information in frame metadata > >> + set_meta(&frame->metadata, "lavfi.hsl.hue", hsl.H); > >> + set_meta(&frame->metadata, "lavfi.hsl.sat", hsl.S * 100); > >> + set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100); > >> + > >> + return ff_filter_frame(inlink->dst->outputs[0], frame); > >> +} > >> + > >> +#define OFFSET(x) offsetof(HSLContext, x) > >> +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM > >> + > >> +static const AVOption hsl_options[] = { > >> + { "print_summary", "Print summary showing average values", > >> OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS }, > >> + { NULL } > >> +}; > >> + > >> +AVFILTER_DEFINE_CLASS(hsl); > >> + > >> +static const AVFilterPad avfilter_vf_hsl_inputs[] = { > >> + { > >> + .name = "default", > >> + .type = AVMEDIA_TYPE_VIDEO, > >> + .config_props = config_input, > >> + .filter_frame = filter_frame, > >> + }, > >> +}; > >> + > >> +static const AVFilterPad avfilter_vf_hsl_outputs[] = { > >> + { > >> + .name = "default", > >> + .type = AVMEDIA_TYPE_VIDEO > >> + }, > >> +}; > >> + > >> +const AVFilter ff_vf_hsl = { > >> + .name = "hsl", > >> + .description = NULL_IF_CONFIG_SMALL("Calculate the HSL color > space > >> information."), > >> + .priv_size = sizeof(HSLContext), > >> + .priv_class = &hsl_class, > >> + .init = init, > >> + .uninit = uninit, > >> + .flags = AVFILTER_FLAG_METADATA_ONLY, > >> + FILTER_PIXFMTS_ARRAY(pix_fmts), > >> + FILTER_INPUTS(avfilter_vf_hsl_inputs), > >> + FILTER_OUTPUTS(avfilter_vf_hsl_outputs), > >> +}; > >> diff --git a/tests/fate-run.sh b/tests/fate-run.sh > >> index 61cc59acc0..c350570f5b 100755 > >> --- a/tests/fate-run.sh > >> +++ b/tests/fate-run.sh > >> @@ -1,5 +1,7 @@ > >> #! /bin/sh > >> > >> +set -x > >> + > >> export LC_ALL=C > >> > >> base=$(dirname $0) > >> diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak > >> index 63873a7a07..aa9812aa18 100644 > >> --- a/tests/fate/filter-video.mak > >> +++ b/tests/fate/filter-video.mak > >> @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter > >> "scale=w=200:h=200" > >> FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += > >> fate-filter-scale500 > >> fate-filter-scale500: CMD = video_filter "scale=w=500:h=500" > >> > >> +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += > >> fate-filter-hsl > >> +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1" > >> + > >> FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER > >> NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) += > >> fate-filter-scale2ref_keep_aspect > >> fate-filter-scale2ref_keep_aspect: > >> tests/data/filtergraphs/scale2ref_keep_aspect > >> fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5 > >> -filter_complex_script > >> $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map > >> "[main]" > >> diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl > >> new file mode 100644 > >> index 0000000000..ae0a064f46 > >> --- /dev/null > >> +++ b/tests/ref/fate/filter-hsl > >> @@ -0,0 +1 @@ > >> +hsl fcb007249fba9371fe84a61c974fcb00 > >> -- > >> 2.24.3 (Apple Git-128) > >> > >> _______________________________________________ > >> ffmpeg-devel mailing list > >> ffmpeg-devel@ffmpeg.org > >> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > >> > >> To unsubscribe, visit link above, or email > >> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > >> > > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". >
diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 5783be281d..b9ed9e065a 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -182,6 +182,7 @@ OBJS-$(CONFIG_SINE_FILTER) += asrc_sine.o OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o # video filters +OBJS-$(CONFIG_HSL_FILTER) += vf_hsl.o OBJS-$(CONFIG_ADDROI_FILTER) += vf_addroi.o OBJS-$(CONFIG_ALPHAEXTRACT_FILTER) += vf_extractplanes.o OBJS-$(CONFIG_ALPHAMERGE_FILTER) += vf_alphamerge.o framesync.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 52741b60e4..6910189ce4 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -169,6 +169,7 @@ extern const AVFilter ff_asrc_sine; extern const AVFilter ff_asink_anullsink; +extern const AVFilter ff_vf_hsl; extern const AVFilter ff_vf_addroi; extern const AVFilter ff_vf_alphaextract; extern const AVFilter ff_vf_alphamerge; diff --git a/libavfilter/vf_hsl.c b/libavfilter/vf_hsl.c new file mode 100644 index 0000000000..47c9af9120 --- /dev/null +++ b/libavfilter/vf_hsl.c @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2022 Wang Wei <wangwei1237@gmail.com> + * Copyright (c) 2022 Liu yizhuo <yizhuo.liu753@gmail.com> + * + * 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 + * Calculate the HSL color space information. + * HSL is a color space that is more perceptually uniform than RGB : + * https://en.wikipedia.org/wiki/HSL_and_HSV + */ + +#include <math.h> + +#include "libavutil/imgutils.h" +#include "libavutil/internal.h" +#include "libavutil/opt.h" +#include "libswscale/swscale.h" + +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef float num; + +typedef struct HSL_COLOR { + num H; + num S; + num L; +} HSL_COLOR; + +typedef struct HSLContext { + const AVClass *class; + int width, height; + uint64_t nb_frames; + float max_hue, max_sat, max_light; + float min_hue, min_sat, min_light; + float sum_hue, sum_sat, sum_light; + int print_summary; + AVFrame *rgbFrame; + enum AVPixelFormat rgb_format; +} HSLContext; + +static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, + AV_PIX_FMT_NONE +}; + +static av_cold int init(AVFilterContext *ctx) +{ + // User options but no input data + HSLContext *s = ctx->priv; + s->max_hue = 0; + s->max_sat = 0; + s->max_light = 0; + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + HSLContext *s = ctx->priv; + + if (s->print_summary) { + float avg_hue = s->sum_hue / s->nb_frames; + float avg_sat = s->sum_sat / s->nb_frames; + float avg_light = s->sum_light / s->nb_frames; + av_log(ctx, AV_LOG_INFO, + "HSL Summary:\nTotal frames: %"PRId64"\n\n" + "Hue Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n" + "Saturation Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n" + "Lightness Information:\nAverage: %.1f\nMax: %.1f\nMin: %.1f\n\n", + s->nb_frames, + avg_hue, s->max_hue, s->min_hue, + avg_sat * 100, s->max_sat * 100, s->min_sat * 100, + avg_light * 100, s->max_light * 100, s->min_light * 100 + ); + } + + av_frame_free(&s->rgbFrame); +} + +static int config_input(AVFilterLink *inlink) +{ + // Video input data avilable + AVFilterContext *ctx = inlink->dst; + HSLContext *s = ctx->priv; + + // free previous buffers in case they are allocated already + av_frame_free(&s->rgbFrame); + s->width = inlink->w; + s->height = inlink->h; + s->rgb_format = AV_PIX_FMT_RGB24; + s->rgbFrame = av_frame_alloc(); + if (!s->rgbFrame) { + return AVERROR(ENOMEM); + } + + s->rgbFrame->format = s->rgb_format; + s->rgbFrame->width = s->width; + s->rgbFrame->height = s->height; + if (av_frame_get_buffer(s->rgbFrame, 0)) { + return AVERROR(ENOMEM); + } + + return 0; +} + +static void set_meta(AVDictionary **metadata, const char *key, float d) +{ + char value[128]; + snprintf(value, sizeof(value), "%0.1f", d); + av_dict_set(metadata, key, value, 0); +} + +static void YUV2RGB(const AVFrame* src, enum AVPixelFormat dstFormat, AVFrame* dst) +{ + int width = src->width; + int height = src->height; + + struct SwsContext* conversion = NULL; + conversion = sws_getContext(width, + height, + (enum AVPixelFormat)src->format, + width, + height, + dstFormat, + SWS_FAST_BILINEAR, + NULL, + NULL, + NULL); + sws_scale(conversion, src->data, src->linesize, 0, height, dst->data, dst->linesize); + sws_freeContext(conversion); +} + +static const float EPSILON = 1e-9; + +/** @brief Min of A and B */ +#define MIN(A,B) (((A) <= (B)) ? (A) : (B)) + +/** @brief Max of A and B */ +#define MAX(A,B) (((A) >= (B)) ? (A) : (B)) + +/** @brief Min of A, B, and C */ +#define MIN3(A,B,C) (((A) <= (B)) ? MIN(A,C) : MIN(B,C)) + +/** @brief Max of A, B, and C */ +#define MAX3(A,B,C) (((A) >= (B)) ? MAX(A,C) : MAX(B,C)) + +/** @brief Equal of A and B */ +#define EQ(A,B) ((fabs((A) - (B)) < EPSILON) ? 1 : 0) + + +/** + * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL) + * + * @param H, S, L pointers to hold the result + * @param R, G, B the input sRGB values scaled in [0,1] + * + * This routine transforms from sRGB to the double hexcone HSL color space + * The sRGB values are assumed to be between 0 and 1. The outputs are + * H = hexagonal hue angle (0 <= H < 360), + * S = { C/(2L) if L <= 1/2 (0 <= S <= 1), + * { C/(2 - 2L) if L > 1/2 + * L = (max(R',G',B') + min(R',G',B'))/2 (0 <= L <= 1), + * where C = max(R',G',B') - min(R',G',B'). + * + * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV + */ +static void RGB2HSL(num *H, num *S, num *L, num R, num G, num B) +{ + num Max = MAX3(R, G, B); + num Min = MIN3(R, G, B); + num C = Max - Min; + + *L = (Max + Min) / 2; + + if (C > 0) { + if (EQ(Max, R)) { + *H = (G - B) / C; + + if (G < B) { + *H += 6; + } + } else if (EQ(Max, G)) { + *H = 2 + (B - R) / C; + } else { + *H = 4 + (R - G) / C; + } + + *H *= 60; + *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L))); + } else { + *H = *S = 0; + } +} + +static void calcHSL(HSLContext *ctx, AVFrame *frame, HSL_COLOR *hsl) +{ + // Convert to RGB + YUV2RGB(frame, ctx->rgb_format, ctx->rgbFrame); + + int cnt = frame->width * frame->height; + num R = 0, G = 0, B = 0; + num H = 0, S = 0, L = 0; + num H_sum = 0, S_sum = 0, L_sum = 0; + + for (int i = 0; i < cnt; i++) { + R = ctx->rgbFrame->data[0][3 * i]; + G = ctx->rgbFrame->data[0][3 * i + 1]; + B = ctx->rgbFrame->data[0][3 * i + 2]; + RGB2HSL(&H, &S, &L, R/255, G/255, B/255); + H_sum += H; + S_sum += S; + L_sum += L; + } + + hsl->H = H_sum / cnt; + hsl->S = S_sum / cnt; + hsl->L = L_sum / cnt; + + // av_log(ctx, AV_LOG_INFO, "H: %.1f, S: %.1f, L: %.1f\n", + // hsl->H, hsl->S * 100, hsl->L * 100); +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + HSLContext *s = ctx->priv; + s->nb_frames++; + + HSL_COLOR hsl; + calcHSL(s, frame, &hsl); + + // Calculate statistics + s->max_hue = fmaxf(hsl.H, s->max_hue); + s->max_sat = fmaxf(hsl.S, s->max_sat); + s->max_light = fmaxf(hsl.L, s->max_light); + s->sum_hue += hsl.H; + s->sum_sat += hsl.S; + s->sum_light += hsl.L; + s->min_hue = s->nb_frames == 1 ? hsl.H : fminf(hsl.H, s->min_hue); + s->min_sat = s->nb_frames == 1 ? hsl.S : fminf(hsl.S, s->min_sat); + s->min_light = s->nb_frames == 1 ? hsl.L : fminf(hsl.L, s->min_light); + + // Set HSL information in frame metadata + set_meta(&frame->metadata, "lavfi.hsl.hue", hsl.H); + set_meta(&frame->metadata, "lavfi.hsl.sat", hsl.S * 100); + set_meta(&frame->metadata, "lavfi.hsl.light", hsl.L * 100); + + return ff_filter_frame(inlink->dst->outputs[0], frame); +} + +#define OFFSET(x) offsetof(HSLContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption hsl_options[] = { + { "print_summary", "Print summary showing average values", OFFSET(print_summary), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(hsl); + +static const AVFilterPad avfilter_vf_hsl_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + .filter_frame = filter_frame, + }, +}; + +static const AVFilterPad avfilter_vf_hsl_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO + }, +}; + +const AVFilter ff_vf_hsl = { + .name = "hsl", + .description = NULL_IF_CONFIG_SMALL("Calculate the HSL color space information."), + .priv_size = sizeof(HSLContext), + .priv_class = &hsl_class, + .init = init, + .uninit = uninit, + .flags = AVFILTER_FLAG_METADATA_ONLY, + FILTER_PIXFMTS_ARRAY(pix_fmts), + FILTER_INPUTS(avfilter_vf_hsl_inputs), + FILTER_OUTPUTS(avfilter_vf_hsl_outputs), +}; diff --git a/tests/fate-run.sh b/tests/fate-run.sh index 61cc59acc0..c350570f5b 100755 --- a/tests/fate-run.sh +++ b/tests/fate-run.sh @@ -1,5 +1,7 @@ #! /bin/sh +set -x + export LC_ALL=C base=$(dirname $0) diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 63873a7a07..aa9812aa18 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -408,6 +408,9 @@ fate-filter-scale200: CMD = video_filter "scale=w=200:h=200" FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += fate-filter-scale500 fate-filter-scale500: CMD = video_filter "scale=w=500:h=500" +FATE_FILTER_VSYNTH_VIDEO_FILTER-$(CONFIG_SCALE_FILTER) += fate-filter-hsl +fate-filter-hsl: CMD = video_filter "hsl=print_summary=1" + FATE_FILTER_VSYNTH-$(call ALLYES, TESTSRC_FILTER SCALE2REF_FILTER NULLSINK_FILTER FRAMEMD5_MUXER FILE_PROTOCOL PIPE_PROTOCOL) += fate-filter-scale2ref_keep_aspect fate-filter-scale2ref_keep_aspect: tests/data/filtergraphs/scale2ref_keep_aspect fate-filter-scale2ref_keep_aspect: CMD = framemd5 -frames:v 5 -filter_complex_script $(TARGET_PATH)/tests/data/filtergraphs/scale2ref_keep_aspect -map "[main]" diff --git a/tests/ref/fate/filter-hsl b/tests/ref/fate/filter-hsl new file mode 100644 index 0000000000..ae0a064f46 --- /dev/null +++ b/tests/ref/fate/filter-hsl @@ -0,0 +1 @@ +hsl fcb007249fba9371fe84a61c974fcb00
From: "2498228118@qq.com" <2498228118@qq.com> --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_hsl.c | 310 ++++++++++++++++++++++++++++++++++++ tests/fate-run.sh | 2 + tests/fate/filter-video.mak | 3 + tests/ref/fate/filter-hsl | 1 + 6 files changed, 318 insertions(+) create mode 100644 libavfilter/vf_hsl.c create mode 100644 tests/ref/fate/filter-hsl