绿幕抠像技术早已不是什么高大上的新兴技术,但是应用场景还是非常多的,今天我们基于ffmpeg滤镜一起探讨一下绿幕抠图原理。
RGB颜色模型
如图用三维空间坐标表示rgb颜色,以rgb24为例rgb各自取值范围为0~255,argb、rgba等带透明度的格式也是在三维rgb基础上加上一个透明度维度,不详细讨论。坐标轴上每一个点代表一个颜色,两点之间的距离可以认为是两个颜色之间的相识度,为了方便表示经常以颜色间距离和灰度线的百分比来表示。
YUV颜色空间
yuv颜色空间是u分量和v分量,可以想象到是一个简单的二维空间,空间内部两点间距离就是两个颜色间的相似程度,一般以相识距离和中轴线的百分比来表示相似度。
ffmpeg源码
- ffmpeg滤镜chromakey针对yuv处理,将对应像素点标记透明。
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 * 2));
}
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 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 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));
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, ctx->hsub_log2, ctx->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;
}
通过源码可以看到ffmpeg的处理是遍历像素点计算和给定色键的相识度并将范围内的像素点做透明标记。
2.ffmpeg滤镜colorkey针对rgb处理,将目标像素点设置为透明。
static uint8_t do_colorkey_pixel(ColorkeyContext *ctx, uint8_t r, uint8_t g, uint8_t b)
{
int dr = (int)r - ctx->colorkey_rgba[0];
int dg = (int)g - ctx->colorkey_rgba[1];
int db = (int)b - ctx->colorkey_rgba[2];
//计算相似度
double diff = sqrt((dr * dr + dg * dg + db * db) / (255.0 * 255.0 * 3.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 int do_colorkey_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;
ColorkeyContext *ctx = avctx->priv;
int o, x, y;
for (y = slice_start; y < slice_end; ++y) {
for (x = 0; x < frame->width; ++x) {
o = frame->linesize[0] * y + x * 4;
// 修改rgba a数据
frame->data[0][o + ctx->co[3]] =
do_colorkey_pixel(ctx,
frame->data[0][o + ctx->co[0]],
frame->data[0][o + ctx->co[1]],
frame->data[0][o + ctx->co[2]]);
}
}
return 0;
}
通过源码可以看到和chromakey类似也是遍历像素点,逐一计算像素点和色键的相似度,将范围内的像素标记为透明。