avfilter: add hsvkey and hsvhold video filters

This commit is contained in:
Paul B Mahol 2021-08-20 08:06:38 +02:00
parent 16102cada5
commit 02aa7dc423
6 changed files with 462 additions and 1 deletions

View File

@ -11,6 +11,7 @@ version <next>:
- afwtdn audio filter
- audio and video segment filters
- Apple Graphics (SMC) encoder
- hsvkey and hsvhold video filters
version 4.4:

View File

@ -13519,6 +13519,82 @@ If set to 1, force the output to terminate when the shortest input
terminates. Default value is 0.
@end table
@section hsvhold
Turns a certain HSV range into gray values.
This filter measures color difference between set HSV color in options
and ones measured in video stream. Depending on options, output
colors can be changed to be gray or not.
The filter accepts the following options:
@table @option
@item hue
Set the hue value which will be used if color difference calculation.
Allowed range is from -360 to 360. Default value is 0.
@item sat
Set the saturation value which will be used if color difference calculation.
Allowed range is from -1 to 1. Default value is 0.
@item val
Set the value which will be used if color difference calculation.
Allowed range is from -1 to 1. Default value is 0.
@item similarity
Set similarity percentage with the key color.
Allowed range is from 0 to 1. Default value is 0.01.
0.00001 matches only the exact key color, while 1.0 matches everything.
@item blend
Blend percentage.
Allowed range is from 0 to 1. Default value is 0.
0.0 makes pixels either fully gray, or not gray at all.
Higher values result in more gray pixels, with a higher gray pixel
the more similar the pixels color is to the key color.
@end table
@section hsvkey
Turns a certain HSV range into transparency.
This filter measures color difference between set HSV color in options
and ones measured in video stream. Depending on options, output
colors can be changed to transparent by adding alpha channel.
The filter accepts the following options:
@table @option
@item hue
Set the hue value which will be used if color difference calculation.
Allowed range is from -360 to 360. Default value is 0.
@item sat
Set the saturation value which will be used if color difference calculation.
Allowed range is from -1 to 1. Default value is 0.
@item val
Set the value which will be used if color difference calculation.
Allowed range is from -1 to 1. Default value is 0.
@item similarity
Set similarity percentage with the key color.
Allowed range is from 0 to 1. Default value is 0.01.
0.00001 matches only the exact key color, while 1.0 matches everything.
@item blend
Blend percentage.
Allowed range is from 0 to 1. Default value is 0.
0.0 makes pixels either fully transparent, or not transparent at all.
Higher values result in semi-transparent pixels, with a higher transparency
the more similar the pixels color is to the key color.
@end table
@section hue
Modify the hue and/or the saturation of the input.

View File

@ -296,6 +296,8 @@ OBJS-$(CONFIG_HISTOGRAM_FILTER) += vf_histogram.o
OBJS-$(CONFIG_HQDN3D_FILTER) += vf_hqdn3d.o
OBJS-$(CONFIG_HQX_FILTER) += vf_hqx.o
OBJS-$(CONFIG_HSTACK_FILTER) += vf_stack.o framesync.o
OBJS-$(CONFIG_HSVHOLD_FILTER) += vf_hsvkey.o
OBJS-$(CONFIG_HSVKEY_FILTER) += vf_hsvkey.o
OBJS-$(CONFIG_HUE_FILTER) += vf_hue.o
OBJS-$(CONFIG_HWDOWNLOAD_FILTER) += vf_hwdownload.o
OBJS-$(CONFIG_HWMAP_FILTER) += vf_hwmap.o

View File

@ -281,6 +281,8 @@ extern const AVFilter ff_vf_histogram;
extern const AVFilter ff_vf_hqdn3d;
extern const AVFilter ff_vf_hqx;
extern const AVFilter ff_vf_hstack;
extern const AVFilter ff_vf_hsvhold;
extern const AVFilter ff_vf_hsvkey;
extern const AVFilter ff_vf_hue;
extern const AVFilter ff_vf_hwdownload;
extern const AVFilter ff_vf_hwmap;

View File

@ -30,7 +30,7 @@
#include "libavutil/version.h"
#define LIBAVFILTER_VERSION_MAJOR 8
#define LIBAVFILTER_VERSION_MINOR 3
#define LIBAVFILTER_VERSION_MINOR 4
#define LIBAVFILTER_VERSION_MICRO 100

380
libavfilter/vf_hsvkey.c Normal file
View File

@ -0,0 +1,380 @@
/*
* Copyright (c) 2021 Paul B Mahol
*
* 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
*/
#include <float.h>
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
#include "libavutil/intreadwrite.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
typedef struct HSVKeyContext {
const AVClass *class;
float hue, hue_opt, sat, val;
float similarity;
float blend;
float scale;
float half;
int depth;
int max;
int hsub_log2;
int vsub_log2;
int (*do_slice)(AVFilterContext *ctx, void *arg,
int jobnr, int nb_jobs);
} HSVKeyContext;
#define SQR(x) ((x)*(x))
static int do_hsvkey_pixel(HSVKeyContext *s, int y, int u, int v,
float hue_key, float sat_key, float val_key)
{
const float similarity = s->similarity;
const float scale = s->scale;
const float blend = s->blend;
const int imax = s->max;
const float max = imax;
const float half = s->half;
const float uf = u - half;
const float vf = v - half;
const float hue = hue_key < 0.f ? -hue_key : atan2f(uf, vf) + M_PI;
const float sat = sat_key < 0.f ? -sat_key : sqrtf((uf * uf + vf * vf) / (half * half * 2.f));
const float val = val_key < 0.f ? -val_key : scale * y;
float diff;
hue_key = fabsf(hue_key);
sat_key = fabsf(sat_key);
val_key = fabsf(val_key);
diff = sqrtf(fmaxf(SQR(sat) * SQR(val) +
SQR(sat_key) * SQR(val_key) -
2.f * sat * val * sat_key * val_key * cosf(hue_key - hue) +
SQR(val - val_key), 0.f));
if (diff < similarity) {
return 0;
} else if (blend > FLT_MIN) {
return av_clipf((diff - similarity) / blend, 0.f, 1.f) * max;
} else {
return imax;
}
return 0;
}
static int do_hsvkey_slice(AVFilterContext *avctx, void *arg, int jobnr, int nb_jobs)
{
HSVKeyContext *s = avctx->priv;
AVFrame *frame = arg;
const int slice_start = (frame->height * jobnr) / nb_jobs;
const int slice_end = (frame->height * (jobnr + 1)) / nb_jobs;
const int hsub_log2 = s->hsub_log2;
const int vsub_log2 = s->vsub_log2;
const float hue = s->hue;
const float sat = s->sat;
const float val = s->val;
for (int y = slice_start; y < slice_end; y++) {
for (int x = 0; x < frame->width; x++) {
int Y = frame->data[0][frame->linesize[0] * y + x];
int u = frame->data[1][frame->linesize[1] * (y >> vsub_log2) + (x >> hsub_log2)];
int v = frame->data[2][frame->linesize[2] * (y >> vsub_log2) + (x >> hsub_log2)];
frame->data[3][frame->linesize[3] * y + x] = do_hsvkey_pixel(s, Y, u, v, hue, sat, val);
}
}
return 0;
}
static int do_hsvkey16_slice(AVFilterContext *avctx, void *arg, int jobnr, int nb_jobs)
{
HSVKeyContext *s = avctx->priv;
AVFrame *frame = arg;
const int slice_start = (frame->height * jobnr) / nb_jobs;
const int slice_end = (frame->height * (jobnr + 1)) / nb_jobs;
const int hsub_log2 = s->hsub_log2;
const int vsub_log2 = s->vsub_log2;
const float hue = s->hue;
const float sat = s->sat;
const float val = s->val;
for (int y = slice_start; y < slice_end; ++y) {
for (int x = 0; x < frame->width; ++x) {
uint16_t *dst = (uint16_t *)(frame->data[3] + frame->linesize[3] * y);
int Y = AV_RN16(&frame->data[0][frame->linesize[0] * y + 2 * x]);
int u = AV_RN16(&frame->data[1][frame->linesize[1] * (y >> vsub_log2) + 2 * (x >> hsub_log2)]);
int v = AV_RN16(&frame->data[2][frame->linesize[2] * (y >> vsub_log2) + 2 * (x >> hsub_log2)]);
dst[x] = do_hsvkey_pixel(s, Y, u, v, hue, sat, val);
}
}
return 0;
}
static int do_hsvhold_slice(AVFilterContext *avctx, void *arg, int jobnr, int nb_jobs)
{
HSVKeyContext *s = avctx->priv;
AVFrame *frame = arg;
const int hsub_log2 = s->hsub_log2;
const int vsub_log2 = s->vsub_log2;
const int width = frame->width >> hsub_log2;
const int height = frame->height >> vsub_log2;
const int slice_start = (height * jobnr) / nb_jobs;
const int slice_end = (height * (jobnr + 1)) / nb_jobs;
const float scale = s->scale;
const float hue = s->hue;
const float sat = s->sat;
const float val = s->val;
for (int y = slice_start; y < slice_end; ++y) {
for (int x = 0; x < width; ++x) {
uint8_t *dstu = frame->data[1] + frame->linesize[1] * y;
uint8_t *dstv = frame->data[2] + frame->linesize[2] * y;
int Y = frame->data[0][frame->linesize[0] * (y << vsub_log2) + (x << hsub_log2)];
int u = frame->data[1][frame->linesize[1] * y + x];
int v = frame->data[2][frame->linesize[2] * y + x];
int t = do_hsvkey_pixel(s, Y, u, v, hue, sat, val);
if (t > 0) {
float f = 1.f - t * scale;
dstu[x] = 128 + (u - 128) * f;
dstv[x] = 128 + (v - 128) * f;
}
}
}
return 0;
}
static int do_hsvhold16_slice(AVFilterContext *avctx, void *arg, int jobnr, int nb_jobs)
{
HSVKeyContext *s = avctx->priv;
AVFrame *frame = arg;
const int hsub_log2 = s->hsub_log2;
const int vsub_log2 = s->vsub_log2;
const int width = frame->width >> hsub_log2;
const int height = frame->height >> vsub_log2;
const int slice_start = (height * jobnr) / nb_jobs;
const int slice_end = (height * (jobnr + 1)) / nb_jobs;
const float scale = s->scale;
const float half = s->half;
const float hue = s->hue;
const float sat = s->sat;
const float val = s->val;
for (int y = slice_start; y < slice_end; ++y) {
for (int x = 0; x < width; ++x) {
uint16_t *dstu = (uint16_t *)(frame->data[1] + frame->linesize[1] * y);
uint16_t *dstv = (uint16_t *)(frame->data[2] + frame->linesize[2] * y);
int Y = AV_RN16(&frame->data[0][frame->linesize[0] * (y << vsub_log2) + 2 * (x << hsub_log2)]);
int u = AV_RN16(&frame->data[1][frame->linesize[1] * y + 2 * x]);
int v = AV_RN16(&frame->data[2][frame->linesize[2] * y + 2 * x]);
int t = do_hsvkey_pixel(s, Y, u, v, hue, sat, val);
if (t > 0) {
float f = 1.f - t * scale;
dstu[x] = half + (u - half) * f;
dstv[x] = half + (v - half) * f;
}
}
}
return 0;
}
static int filter_frame(AVFilterLink *link, AVFrame *frame)
{
AVFilterContext *avctx = link->dst;
HSVKeyContext *s = avctx->priv;
int res;
s->hue = FFSIGN(s->hue_opt) *M_PI * fmodf(526.f - fabsf(s->hue_opt), 360.f) / 180.f;
if (res = ff_filter_execute(avctx, s->do_slice, frame, NULL,
FFMIN(frame->height, ff_filter_get_nb_threads(avctx))))
return res;
return ff_filter_frame(avctx->outputs[0], frame);
}
static av_cold int config_output(AVFilterLink *outlink)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format);
AVFilterContext *avctx = outlink->src;
HSVKeyContext *s = avctx->priv;
s->depth = desc->comp[0].depth;
s->max = (1 << s->depth) - 1;
s->half = 0.5f * s->max;
s->scale = 1.f / s->max;
if (!strcmp(avctx->filter->name, "hsvkey")) {
s->do_slice = s->depth <= 8 ? do_hsvkey_slice : do_hsvkey16_slice;
} else {
s->do_slice = s->depth <= 8 ? do_hsvhold_slice: do_hsvhold16_slice;
}
return 0;
}
static av_cold int query_formats(AVFilterContext *avctx)
{
static const enum AVPixelFormat pixel_fmts[] = {
AV_PIX_FMT_YUVA420P,
AV_PIX_FMT_YUVA422P,
AV_PIX_FMT_YUVA444P,
AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
AV_PIX_FMT_NONE
};
static const enum AVPixelFormat hold_pixel_fmts[] = {
AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUV422P,
AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUVA420P,
AV_PIX_FMT_YUVA422P,
AV_PIX_FMT_YUVA444P,
AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV420P12,
AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14,
AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
AV_PIX_FMT_NONE
};
const enum AVPixelFormat *pix_fmts;
pix_fmts = !strcmp(avctx->filter->name, "hsvhold") ? hold_pixel_fmts : pixel_fmts;
return ff_set_common_formats_from_list(avctx, pix_fmts);
}
static av_cold int config_input(AVFilterLink *inlink)
{
AVFilterContext *avctx = inlink->dst;
HSVKeyContext *s = avctx->priv;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
s->hsub_log2 = desc->log2_chroma_w;
s->vsub_log2 = desc->log2_chroma_h;
return 0;
}
static const AVFilterPad hsvkey_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
.filter_frame = filter_frame,
.config_props = config_input,
},
};
static const AVFilterPad hsvkey_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_output,
},
};
#define OFFSET(x) offsetof(HSVKeyContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
static const AVOption hsvkey_options[] = {
{ "hue", "set the hue value", OFFSET(hue_opt), AV_OPT_TYPE_FLOAT, { .dbl = 0 }, -360, 360, FLAGS },
{ "sat", "set the saturation value", OFFSET(sat), AV_OPT_TYPE_FLOAT, { .dbl = 0 }, -1, 1, FLAGS },
{ "val", "set the value value", OFFSET(val), AV_OPT_TYPE_FLOAT, { .dbl = 0 }, -1, 1, FLAGS },
{ "similarity", "set the hsvkey similarity value", OFFSET(similarity), AV_OPT_TYPE_FLOAT, { .dbl = 0.01}, 0.00001, 1.0, FLAGS },
{ "blend", "set the hsvkey blend value", OFFSET(blend), AV_OPT_TYPE_FLOAT, { .dbl = 0.0 }, 0.0, 1.0, FLAGS },
{ NULL }
};
AVFILTER_DEFINE_CLASS(hsvkey);
const AVFilter ff_vf_hsvkey = {
.name = "hsvkey",
.description = NULL_IF_CONFIG_SMALL("Turns a certain HSV range into transparency. Operates on YUV colors."),
.priv_size = sizeof(HSVKeyContext),
.priv_class = &hsvkey_class,
.query_formats = query_formats,
FILTER_INPUTS(hsvkey_inputs),
FILTER_OUTPUTS(hsvkey_outputs),
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
.process_command = ff_filter_process_command,
};
static const AVOption hsvhold_options[] = {
{ "hue", "set the hue value", OFFSET(hue_opt), AV_OPT_TYPE_FLOAT, { .dbl = 0 }, -360, 360, FLAGS },
{ "sat", "set the saturation value", OFFSET(sat), AV_OPT_TYPE_FLOAT, { .dbl = 0 }, -1, 1, FLAGS },
{ "val", "set the value value", OFFSET(val), AV_OPT_TYPE_FLOAT, { .dbl = 0 }, -1, 1, FLAGS },
{ "similarity", "set the hsvhold similarity value", OFFSET(similarity), AV_OPT_TYPE_FLOAT, { .dbl = 0.01 }, 0.00001, 1.0, FLAGS },
{ "blend", "set the hsvhold blend value", OFFSET(blend), AV_OPT_TYPE_FLOAT, { .dbl = 0.0 }, 0.0, 1.0, FLAGS },
{ NULL }
};
static const AVFilterPad hsvhold_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
.filter_frame = filter_frame,
.config_props = config_input,
},
};
static const AVFilterPad hsvhold_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_output,
},
};
AVFILTER_DEFINE_CLASS(hsvhold);
const AVFilter ff_vf_hsvhold = {
.name = "hsvhold",
.description = NULL_IF_CONFIG_SMALL("Turns a certain HSV range into gray."),
.priv_size = sizeof(HSVKeyContext),
.priv_class = &hsvhold_class,
.query_formats = query_formats,
FILTER_INPUTS(hsvhold_inputs),
FILTER_OUTPUTS(hsvhold_outputs),
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
.process_command = ff_filter_process_command,
};