Commit 4af1f376 authored by Timo Rothenpieler's avatar Timo Rothenpieler

avfilter/vf_chromakey: Add chromakey video filter

Signed-off-by: 's avatarTimo Rothenpieler <timo@rothenpieler.org>
parent 0c7ceb1e
......@@ -11,6 +11,7 @@ version <next>:
- rubberband filter
- tremolo filter
- agate filter
- chromakey filter
version 2.8:
......
......@@ -359,6 +359,7 @@ Filters:
avf_avectorscope.c Paul B Mahol
avf_showcqt.c Muhammad Faiz
vf_blend.c Paul B Mahol
vf_chromakey.c Timo Rothenpieler
vf_colorchannelmixer.c Paul B Mahol
vf_colorbalance.c Paul B Mahol
vf_colorkey.c Timo Rothenpieler
......
......@@ -3707,6 +3707,51 @@ boxblur=luma_radius=min(h\,w)/10:luma_power=1:chroma_radius=min(cw\,ch)/10:chrom
@end example
@end itemize
@section chromakey
YUV colorspace color/chroma keying.
The filter accepts the following options:
@table @option
@item color
The color which will be replaced with transparency.
@item similarity
Similarity percentage with the key color.
0.01 matches only the exact key color, while 1.0 matches everything.
@item blend
Blend percentage.
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.
@item yuv
Signals that the color passed is already in YUV instead of RGB.
Litteral colors like "green" or "red" don't make sense with this enabled anymore.
This can be used to pass exact YUV values as hexadecimal numbers.
@end table
@subsection Examples
@itemize
@item
Make every green pixel in the input image transparent:
@example
ffmpeg -i input.png -vf chromakey=green out.png
@end example
@item
Overlay a greenscreen-video on top of a static black background.
@example
ffmpeg -f lavfi -i color=c=black:s=1280x720 -i video.mp4 -shortest -filter_complex "[1:v]chromakey=0x70de77:0.1:0.2[ckout];[0:v][ckout]overlay[out]" -map "[out]" output.mkv
@end example
@end itemize
@section codecview
Visualize information exported by some codecs.
......
......@@ -105,6 +105,7 @@ OBJS-$(CONFIG_BLACKDETECT_FILTER) += vf_blackdetect.o
OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o
OBJS-$(CONFIG_BLEND_FILTER) += vf_blend.o dualinput.o framesync.o
OBJS-$(CONFIG_BOXBLUR_FILTER) += vf_boxblur.o
OBJS-$(CONFIG_CHROMAKEY_FILTER) += vf_chromakey.o
OBJS-$(CONFIG_CODECVIEW_FILTER) += vf_codecview.o
OBJS-$(CONFIG_COLORBALANCE_FILTER) += vf_colorbalance.o
OBJS-$(CONFIG_COLORCHANNELMIXER_FILTER) += vf_colorchannelmixer.o
......
......@@ -127,6 +127,7 @@ void avfilter_register_all(void)
REGISTER_FILTER(BLACKFRAME, blackframe, vf);
REGISTER_FILTER(BLEND, blend, vf);
REGISTER_FILTER(BOXBLUR, boxblur, vf);
REGISTER_FILTER(CHROMAKEY, chromakey, vf);
REGISTER_FILTER(CODECVIEW, codecview, vf);
REGISTER_FILTER(COLORBALANCE, colorbalance, vf);
REGISTER_FILTER(COLORCHANNELMIXER, colorchannelmixer, vf);
......
/*
* Copyright (c) 2015 Timo Rothenpieler <timo@rothenpieler.org>
*
* 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 "libavutil/opt.h"
#include "libavutil/imgutils.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
typedef struct ChromakeyContext {
const AVClass *class;
uint8_t chromakey_rgba[4];
uint8_t chromakey_uv[2];
float similarity;
float blend;
int is_yuv;
} ChromakeyContext;
static uint8_t do_chromakey_pixel(ChromakeyContext *ctx, uint8_t u[9], uint8_t v[9])
{
double diff = 0.0;
int du, dv, i;
for (i = 0; i < 9; ++i) {
du = (int)u[i] - ctx->chromakey_uv[0];
dv = (int)v[i] - ctx->chromakey_uv[1];
diff += sqrt((du * du + dv * dv) / (255.0 * 255.0));
}
diff /= 9.0;
if (ctx->blend > 0.0001) {
return av_clipd((diff - ctx->similarity) / ctx->blend, 0.0, 1.0) * 255.0;
} else {
return (diff > ctx->similarity) ? 255 : 0;
}
}
static av_always_inline void get_pixel_uv(AVFrame *frame, int hsub_log2, int vsub_log2, int x, int y, uint8_t *u, uint8_t *v)
{
if (x < 0 || x >= frame->width || y < 0 || y >= frame->height)
return;
x >>= hsub_log2;
y >>= vsub_log2;
*u = frame->data[1][frame->linesize[1] * y + x];
*v = frame->data[2][frame->linesize[2] * y + x];
}
static int do_chromakey_slice(AVFilterContext *avctx, void *arg, int jobnr, int nb_jobs)
{
AVFrame *frame = arg;
const int slice_start = (frame->height * jobnr) / nb_jobs;
const int slice_end = (frame->height * (jobnr + 1)) / nb_jobs;
ChromakeyContext *ctx = avctx->priv;
int hsub_log2 = 0, vsub_log2 = 0;
int x, y, xo, yo;
uint8_t u[9], v[9];
memset(u, ctx->chromakey_uv[0], sizeof(u));
memset(v, ctx->chromakey_uv[1], sizeof(v));
if (frame->format == AV_PIX_FMT_YUVA420P || frame->format == AV_PIX_FMT_YUVA422P)
hsub_log2 = 1;
if (frame->format == AV_PIX_FMT_YUVA420P)
vsub_log2 = 1;
for (y = slice_start; y < slice_end; ++y) {
for (x = 0; x < frame->width; ++x) {
for (yo = 0; yo < 3; ++yo) {
for (xo = 0; xo < 3; ++xo) {
get_pixel_uv(frame, hsub_log2, vsub_log2, x + xo - 1, y + yo - 1, &u[yo * 3 + xo], &v[yo * 3 + xo]);
}
}
frame->data[3][frame->linesize[3] * y + x] = do_chromakey_pixel(ctx, u, v);
}
}
return 0;
}
static int filter_frame(AVFilterLink *link, AVFrame *frame)
{
AVFilterContext *avctx = link->dst;
int res;
if (res = avctx->internal->execute(avctx, do_chromakey_slice, frame, NULL, FFMIN(frame->height, avctx->graph->nb_threads)))
return res;
return ff_filter_frame(avctx->outputs[0], frame);
}
#define FIXNUM(x) lrint((x) * (1 << 10))
#define RGB_TO_U(rgb) (((- FIXNUM(0.16874) * rgb[0] - FIXNUM(0.33126) * rgb[1] + FIXNUM(0.50000) * rgb[2] + (1 << 9) - 1) >> 10) + 128)
#define RGB_TO_V(rgb) ((( FIXNUM(0.50000) * rgb[0] - FIXNUM(0.41869) * rgb[1] - FIXNUM(0.08131) * rgb[2] + (1 << 9) - 1) >> 10) + 128)
static av_cold int initialize_chromakey(AVFilterContext *avctx)
{
ChromakeyContext *ctx = avctx->priv;
if (ctx->is_yuv) {
ctx->chromakey_uv[0] = ctx->chromakey_rgba[1];
ctx->chromakey_uv[1] = ctx->chromakey_rgba[2];
} else {
ctx->chromakey_uv[0] = RGB_TO_U(ctx->chromakey_rgba);
ctx->chromakey_uv[1] = RGB_TO_V(ctx->chromakey_rgba);
}
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_NONE
};
AVFilterFormats *formats = NULL;
formats = ff_make_format_list(pixel_fmts);
if (!formats)
return AVERROR(ENOMEM);
return ff_set_common_formats(avctx, formats);
}
static const AVFilterPad chromakey_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.needs_writable = 1,
.filter_frame = filter_frame,
},
{ NULL }
};
static const AVFilterPad chromakey_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
},
{ NULL }
};
#define OFFSET(x) offsetof(ChromakeyContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static const AVOption chromakey_options[] = {
{ "color", "set the chromakey key color", OFFSET(chromakey_rgba), AV_OPT_TYPE_COLOR, { .str = "black" }, CHAR_MIN, CHAR_MAX, FLAGS },
{ "similarity", "set the chromakey similarity value", OFFSET(similarity), AV_OPT_TYPE_FLOAT, { .dbl = 0.01 }, 0.01, 1.0, FLAGS },
{ "blend", "set the chromakey key blend value", OFFSET(blend), AV_OPT_TYPE_FLOAT, { .dbl = 0.0 }, 0.0, 1.0, FLAGS },
{ "yuv", "color parameter is in yuv instead of rgb", OFFSET(is_yuv), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
{ NULL }
};
AVFILTER_DEFINE_CLASS(chromakey);
AVFilter ff_vf_chromakey = {
.name = "chromakey",
.description = NULL_IF_CONFIG_SMALL("Turns a certain color into transparency. Operates on YUV colors."),
.priv_size = sizeof(ChromakeyContext),
.priv_class = &chromakey_class,
.init = initialize_chromakey,
.query_formats = query_formats,
.inputs = chromakey_inputs,
.outputs = chromakey_outputs,
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment