FFmpeg源码分析:视频滤镜介绍(上)

FFmpeg在libavfilter模块提供音视频滤镜。所有的视频滤镜都注册在libavfilter/allfilters.c。我们也可以使用ffmpeg -filters命令行来查看当前支持的所有滤镜,前面-v代表视频。本篇文章主要介绍视频滤镜,包括:黑色检测、视频叠加、色彩均衡、去除水印、抗抖动、矩形标注、九宫格。

关于视频滤镜的详细介绍,可查看官方文档:视频滤镜。音频滤镜可参考前面两篇文章:音频滤镜介绍(上)音频滤镜介绍(下)

1、blackdetect

黑色检测,用于检测纯黑的视频间隔时间。此滤波器将其分析结果输出到日志和元数据。如果找到至少具有指定最小持续时间的黑色片段,则打印一行日志,其中包含开始和结束时间戳以及持续时间。参数选项如下:

  • black_min_duration, d:最短的检测黑色时长,单位为s,默认为2.0
  • picture_black_ratio_th, pic_th:设置黑色图像的比率,默认为0.98
  • pixel_black_th, pix_th:设置黑色像素的阈值,默认为0.10

2、blend

混合,把两个视频的所有帧混合在一起,又称为视频叠加。第一个视频在顶层,第二个视频在底层,默认为最长的视频时长作为输出时长。

  • 2.1 从顶层到底层的线性水平过渡:
blend=all_expr='A*(X/W)+B*(1-X/W)'
  • 2.2 从右到左覆盖,可用于转场动画过渡效果:
blend=all_expr='if(gte(N*SW+X,W),A,B)'
  • 2.3 从上到下覆盖,可用于转场动画过渡效果:
blend=all_expr='if(gte(Y-N*SH,0),A,B)'
  • 2.4 沿对角线分割视频,两边分别显示顶层与底层 :
blend=all_expr='if(gt(X,Y*(W/H)),A,B)'

视频混合的代码位于libavfilter/vf_blend.c,主要是遍历像素矩阵,计算顶层像素乘以一个透明度与底层像素乘以透明度的相反数之和,关键代码如下:

static void blend_normal_8bit(const uint8_t *top, ptrdiff_t top_linesize,
                              const uint8_t *bottom, ptrdiff_t bottom_linesize,
                              uint8_t *dst, ptrdiff_t dst_linesize,
                              ptrdiff_t width, ptrdiff_t height,
                              FilterParams *param, double *values, int starty)
{
    const double opacity = param->opacity;
    int i, j;

    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            dst[j] = top[j] * opacity + bottom[j] * (1. - opacity);
        }
        dst    += dst_linesize;
        top    += top_linesize;
        bottom += bottom_linesize;
    }
}

完整命令如下:

ffmpeg -i hello.mp4 -i world.mp4 -filter_complex blend=all_expr='A*(X/W)+B*(1-X/W)' blend.mp4

两个视频混合效果如下图所示:

3、colorbalance

色彩均衡,调整视频帧的RGB分量占比。此滤波器允许在阴影、中间色调或高光区域调整输入帧,以获得红青色、洋红或蓝黄平衡效果。正调整值将平衡移向原色,负调整值则移向补色。参数选项如下:

  • rs、gs、bs:调整红、绿、蓝阴影 (最暗像素)
  • rm、gm、bm:调整红、绿、蓝基调 (中间像素)
  • rh、gh、bh:调整红、绿、蓝高亮(最亮像素)

4、delogo

去除水印,通过对周围像素的简单插值来抑制水印。只需要设置一个覆盖水印的矩形。矩形绘制在最外面的像素上,该像素将被替换为插值。每个方向紧靠矩形外的下一个像素的值,用于计算矩形内的插值像素值。参数如下:

  • x、y:水印的左上角坐标
  • w、h:水印的宽高
  • show:是否显示覆盖水印区域,默认为0

去除水印代码位于libavfilter/vf_delogo.c,核心代码如下:

static void apply_delogo(uint8_t *dst, int dst_linesize,
                         uint8_t *src, int src_linesize,
                         int w, int h, AVRational sar,
                         int logo_x, int logo_y, int logo_w, int logo_h,
                         unsigned int band, int show, int direct)
{
    int x, y;
    uint64_t interp, weightl, weightr, weightt, weightb, weight;
    uint8_t *xdst, *xsrc;
    uint8_t *topleft, *botleft, *topright;
    unsigned int left_sample, right_sample;
    int xclipl, xclipr, yclipt, yclipb;
    int logo_x1, logo_x2, logo_y1, logo_y2;

    xclipl = FFMAX(-logo_x, 0);
    xclipr = FFMAX(logo_x+logo_w-w, 0);
    yclipt = FFMAX(-logo_y, 0);
    yclipb = FFMAX(logo_y+logo_h-h, 0);
    logo_x1 = logo_x + xclipl;
    logo_x2 = logo_x + logo_w - xclipr - 1;
    logo_y1 = logo_y + yclipt;
    logo_y2 = logo_y + logo_h - yclipb - 1;
    topleft  = src+logo_y1 * src_linesize+logo_x1;
    topright = src+logo_y1 * src_linesize+logo_x2;
    botleft  = src+logo_y2 * src_linesize+logo_x1;

    if (!direct)
        av_image_copy_plane(dst, dst_linesize, src, src_linesize, w, h);

    dst += (logo_y1 + 1) * dst_linesize;
    src += (logo_y1 + 1) * src_linesize;

    for (y = logo_y1+1; y < logo_y2; y++) {
        left_sample = topleft[src_linesize*(y-logo_y1)]   +
                      topleft[src_linesize*(y-logo_y1-1)] +
                      topleft[src_linesize*(y-logo_y1+1)];
        right_sample = topright[src_linesize*(y-logo_y1)]   +
                       topright[src_linesize*(y-logo_y1-1)] +
                       topright[src_linesize*(y-logo_y1+1)];

        for (x = logo_x1+1,
             xdst = dst+logo_x1+1,
             xsrc = src+logo_x1+1; x < logo_x2; x++, xdst++, xsrc++) {

            if (show && (y == logo_y1+1 || y == logo_y2-1 ||
                         x == logo_x1+1 || x == logo_x2-1)) {
                *xdst = 0;
                continue;
            }

            // 基于像素的相对距离进行权重插值,考虑SAR
            weightl = (uint64_t)(logo_x2-x) * (y-logo_y1) * (logo_y2-y) * sar.den;
            weightr = (uint64_t)(x-logo_x1) * (y-logo_y1) * (logo_y2-y) * sar.den;
            weightt = (uint64_t)(x-logo_x1) * (logo_x2-x) * (logo_y2-y) * sar.num;
            weightb = (uint64_t)(x-logo_x1) * (logo_x2-x) * (y-logo_y1) * sar.num;

            interp =
                left_sample * weightl
                +
                right_sample * weightr
                +
                (topleft[x-logo_x1]    +
                 topleft[x-logo_x1-1]  +
                 topleft[x-logo_x1+1]) * weightt
                +
                (botleft[x-logo_x1]    +
                 botleft[x-logo_x1-1]  +
                 botleft[x-logo_x1+1]) * weightb;
            weight = (weightl + weightr + weightt + weightb) * 3U;
            interp = (interp + (weight >> 1)) / weight;
            // 判断是否在水印区域内
            if (y >= logo_y+band && y < logo_y+logo_h-band &&
                x >= logo_x+band && x < logo_x+logo_w-band) {
                *xdst = interp;
            } else {
                unsigned dist = 0;

                if      (x < logo_x+band)
                    dist = FFMAX(dist, logo_x-x+band);
                else if (x >= logo_x+logo_w-band)
                    dist = FFMAX(dist, x-(logo_x+logo_w-1-band));

                if      (y < logo_y+band)
                    dist = FFMAX(dist, logo_y-y+band);
                else if (y >= logo_y+logo_h-band)
                    dist = FFMAX(dist, y-(logo_y+logo_h-1-band));

                *xdst = (*xsrc*dist + interp*(band-dist))/band;
            }
        }

        dst += dst_linesize;
        src += src_linesize;
    }
}

 去水印前后效果,如下图所示:

5、drawbox

绘制矩形,在视频画面绘制矩形框,可用于标注ROI兴趣区域。在人脸检测与人脸识别场景,检测到人脸时会用矩形框标注出来。参数选项如下:

  • x、y:矩形的xy坐标点
  • width, w、height, h:矩形的宽高
  • color, c:矩形边框的颜色
  • thickness, t:矩形边框的厚度,默认为3

指定xy坐标、矩形宽高、边框颜色为红色且透明度为50%,命令如下:

drawbox=x=10:y=20:w=200:h=60:color=red@0.5

6、drawgrid

绘制x宫格,可用于绘制四宫格、九宫格,模拟画面拼接,或者画面分割。参数选项如下:

  • x、y:九宫格的xy坐标点
  • width, w、height, h:每行宫格的宽高
  • color, c:九宫格边框颜色
  • thickness, t:九宫格边框厚度,默认为1
  • iw、ih:输入宽高

绘制3x3的九宫格、边框厚度为2、颜色为蓝色且透明度50%,命令如下:

drawgrid=w=iw/3:h=ih/3:t=2:c=blue@0.5

7、lut、lutyuv和lutrgb

调整yuv或rgb,调整过程:计算查找表,用于绑定每个像素输入值到 输出值,并将其应用到输入视频。相关代码位于vf_lut.c,分为四种类型进行处理:packed 8bits、packed 16bits、planar 8bits、planar 16bits,关键代码如下:

static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
    ......
    if (av_frame_is_writable(in)) {
        direct = 1;
        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);
    }
    // 分为packed 8bits、packed 16bits、planar 8bits、planar 16bits
    if (s->is_rgb && s->is_16bit && !s->is_planar) {
        /* packed, 16-bits */
        PACKED_THREAD_DATA
        ctx->internal->execute(ctx, lut_packed_16bits, &td, NULL,
                               FFMIN(in->height, ff_filter_get_nb_threads(ctx)));
    } else if (s->is_rgb && !s->is_planar) {
        /* packed 8 bits */
        PACKED_THREAD_DATA
        ctx->internal->execute(ctx, lut_packed_8bits, &td, NULL,
                               FFMIN(in->height, ff_filter_get_nb_threads(ctx)));
    } else if (s->is_16bit) {
        /* planar 16 bits depth */
        PLANAR_THREAD_DATA
        ctx->internal->execute(ctx, lut_planar_16bits, &td, NULL,
                               FFMIN(in->height, ff_filter_get_nb_threads(ctx)));
    } else {
        /* planar 8bits depth */
        PLANAR_THREAD_DATA
        ctx->internal->execute(ctx, lut_planar_8bits, &td, NULL,
                               FFMIN(in->height, ff_filter_get_nb_threads(ctx)));
    }

    if (!direct)
        av_frame_free(&in);

    return ff_filter_frame(outlink, out);
}

将彩色视频转换为黑白视频,设置U和V分量为128,参考命令如下:

ffmpeg -i in.mp4 -vf lutyuv='u=128:v=128' gray.mp4

黑白视频的效果如下图所示:

对音视频感兴趣的伙伴,可以到GitHub学习:https://github.com/xufuji456/FFmpegAndroid

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐福记456

您的鼓励和肯定是我创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值