FFmpeg在libavfilter模块提供音视频滤镜。所有的视频滤镜都注册在libavfilter/allfilters.c。我们也可以使用ffmpeg -filters命令行来查看当前支持的所有滤镜,前面-v代表视频。本篇文章主要介绍视频滤镜,包括:绘制文字、边缘检测、淡入淡出、高斯模糊、左右镜像、图层叠加、视频旋转。

关于视频滤镜的详细介绍,可查看官方文档:视频滤镜。视频滤镜介绍的上半部分,可以查看上一篇文章:视频滤镜介绍(上)。

1、drawtext

绘制文字,在视频画面上绘制文本文字。需要开启freetype第三方库--enable-freetype,详细介绍可查看:freetype官网。如果要设置字体大小、颜色,需要开启fontconfig第三方库--enable-fontconfig,详情可查看:fontconfig官网。如果要设置字体形状,需要开启libfribidi第三方库--enable-libfribidi,详情可查看:fribidi的GitHub网站。

参数选项如下:

  • box:是否使用背景颜色绘制矩形框, 1 (开启) 或0 (关闭),默认为关闭
  • boxborderw:绘制矩形边框的宽度,默认为0
  • boxcolor:绘制矩形边框的颜色,默认为白色
  • line_spacing:行距,默认为0
  • basetime:开始计时时间,单位微秒
  • fontcolor:字体颜色,默认为黑色
  • font:字体,默认为Sans
  • fontfile:字体文件,需要文件的绝对路径
  • alpha:文字的混合透明通道alpha值,范围为[0.0, 1.0],默认为1.0
  • fontsize:字体大小,默认为16
  • shadowcolor:阴影颜色,默认为黑色
  • shadowx、shadowy:文本阴影相对于文本的x、y偏移量
  • timecode:时间码,默认格式为 "hh:mm:ss[:;.]ff"
  • text:字符串文本,必须为UTF-8编码格式
  • textfile:文本文件,必须为UTF-8编码格式
  • main_h, h, H:输入高度
  • main_w, w, W:输入宽度
  • n:从哪一帧开始绘制文本,默认为0
  • t:时间戳表达式,单位为秒, NAN为未知值
  • text_h, th:文字高度
  • text_w, tw:文字宽度
  • x、y:文本在视频画面的xy坐标点,作为渲染文本的起始位置

绘制文本参考命令,指定文本、xy坐标点、字体大小、字体颜色:

ffmpeg -i in.mp4 -vf drawtext="text='Hello world:x=10:y=20:fontcolor=red" watermark.mp4

添加文字水印的效果如下图所示:

2、edgedetect

边缘检测,用于检测与绘制边缘,使用Canny边缘检测算法。关于Canny边缘检测算法原理,详情可查看:Canny边缘检测的维基百科。边缘检测步骤如下:

  • (1) 应用高斯滤波器平滑图像以去除噪声
  • (2) 计算图像的梯度与方向
  • (3) 应用梯度幅度阈值或下限截止抑制来消除对边缘检测的虚假响应
  • (4) 应用双阈值来确定潜在边缘
  • (5) 滞后跟踪边缘:抑制所有其他弱且未连接到强边缘的边缘

参数选项如下:

  • low、high:Canny边缘检测阈值,范围 [0,1],默认最小值为20/255,  默认最大值为50/255
  • mode:绘制模式,默认为wire,所有模式如下所示:
  • ‘wires’:黑色背景中绘制白灰连线
  • ‘colormix’:混合颜色,类似绘画卡通效果
  • ‘canny’:每个平面都进行Canny检测
  • planes:是否开启平面过滤,默认开启

2.1 边缘检测算法

边缘检测的代码位于libavfilter/vf_edgedetect.c,操作步骤包括高斯滤波、sobel算子、消除响应、双阈值寻找潜在边缘、滞后跟踪边缘,核心代码如下:

static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{......// 从缓冲区获取视频帧数据out = ff_get_video_buffer(outlink, outlink->w, outlink->h);for (p = 0; p < edgedetect->nb_planes; p++) {......// 高斯滤波,图像降噪处理gaussian_blur(ctx, width, height,tmpbuf,      width,in->data[p], in->linesize[p]);// sobel算子: 计算图像梯度和方向sobel(width, height,gradients, width,directions,width,tmpbuf,    width);memset(tmpbuf, 0, width * height);// 应用梯度阈值消除虚假响应non_maximum_suppression(width, height,tmpbuf,    width,directions,width,gradients, width);// 应用高低位双阈值来确定潜在边缘double_threshold(edgedetect->low_u8, edgedetect->high_u8,width, height,out->data[p], out->linesize[p],tmpbuf,       width);// 颜色混合,滞后跟踪边缘if (edgedetect->mode == MODE_COLORMIX) {color_mix(width, height,out->data[p], out->linesize[p],in->data[p], in->linesize[p]);}}if (!direct)av_frame_free(&in);return ff_filter_frame(outlink, out);
}

2.2 高斯滤波

由于所有的边缘检测结果都容易受到图像噪声的影响,因此有必要消除噪声以避免误检。为了平滑图像,使用高斯滤波器内核与图像卷积。5x5的高斯滤波器代码如下:

static void gaussian_blur(AVFilterContext *ctx, int w, int h,uint8_t *dst, int dst_linesize,const uint8_t *src, int src_linesize)
{int i, j;memcpy(dst, src, w); dst += dst_linesize; src += src_linesize;if (h > 1) {memcpy(dst, src, w); dst += dst_linesize; src += src_linesize;}for (j = 2; j < h - 2; j++) {dst[0] = src[0];if (w > 1)dst[1] = src[1];for (i = 2; i < w - 2; i++) {/* Gaussian mask of size 5x5 with sigma = 1.4 */dst[i] = ((src[-2*src_linesize + i-2] + src[2*src_linesize + i-2]) * 2+ (src[-2*src_linesize + i-1] + src[2*src_linesize + i-1]) * 4+ (src[-2*src_linesize + i  ] + src[2*src_linesize + i  ]) * 5+ (src[-2*src_linesize + i+1] + src[2*src_linesize + i+1]) * 4+ (src[-2*src_linesize + i+2] + src[2*src_linesize + i+2]) * 2+ (src[  -src_linesize + i-2] + src[  src_linesize + i-2]) *  4+ (src[  -src_linesize + i-1] + src[  src_linesize + i-1]) *  9+ (src[  -src_linesize + i  ] + src[  src_linesize + i  ]) * 12+ (src[  -src_linesize + i+1] + src[  src_linesize + i+1]) *  9+ (src[  -src_linesize + i+2] + src[  src_linesize + i+2]) *  4+ src[i-2] *  5+ src[i-1] * 12+ src[i  ] * 15+ src[i+1] * 12+ src[i+2] *  5) / 159;}if (w > 2)dst[i] = src[i];if (w > 3)dst[i + 1] = src[i + 1];dst += dst_linesize;src += src_linesize;}if (h > 2) {memcpy(dst, src, w); dst += dst_linesize; src += src_linesize;}if (h > 3)memcpy(dst, src, w);
}

2.3  sobel算子

图像中的边缘可能指向多个方向,因此 Canny 算法使用四个过滤器来检测模糊图像中的水平、垂直和对角线边缘。这里使用sobel算子来确定图像的梯度与方向,代码如下:

static void sobel(int w, int h,uint16_t *dst, int dst_linesize,int8_t *dir, int dir_linesize,const uint8_t *src, int src_linesize)
{int i, j;for (j = 1; j < h - 1; j++) {dst += dst_linesize;dir += dir_linesize;src += src_linesize;for (i = 1; i < w - 1; i++) {const int gx =-1*src[-src_linesize + i-1] + 1*src[-src_linesize + i+1]-2*src[                i-1] + 2*src[                i+1]-1*src[ src_linesize + i-1] + 1*src[ src_linesize + i+1];const int gy =-1*src[-src_linesize + i-1] + 1*src[ src_linesize + i-1]-2*src[-src_linesize + i  ] + 2*src[ src_linesize + i  ]-1*src[-src_linesize + i+1] + 1*src[ src_linesize + i+1];dst[i] = FFABS(gx) + FFABS(gy);dir[i] = get_rounded_direction(gx, gy);}}
}

边缘检测效果如下图所示:

3、fade

淡入淡出效果,应用淡入淡出效果到视频中。参数选项如下:

  • type, t:效果类型,"in" 代表淡入效果, "out" 代表淡出效果,默认为淡入效果
  • start_frame, s:效果开始的帧数,默认为0
  • nb_frames, n:效果持续的帧数,默认为25
  • alpha:是否开启alpha,如果开启只会应用效果到alpha通道,默认关闭
  • start_time, st:效果开始的时间,默认从0开始
  • duration, d:效果持续的时间
  • color, c:淡入淡出效果颜色,默认为黑色

以帧为单位的参考命令:

fade=t=in:s=0:n=30

以时间为单位的参考命令:

fade=t=in:st=0:d=5.0

4、gblur

高斯模糊,使用高斯模糊来打马赛克。关于高斯模糊算法,主要思想是对像素邻近区域进行加权平均,详情可查看:高斯模糊算法的维基百科。参数选项如下:

  • sigma:水平方向sigma, 高斯模糊标准差,默认为 0.5
  • steps:高斯近似值的步数,默认为1
  • planes:选择哪一平面进行滤波,默认所有平面
  • sigmaV:垂直方向sigma,如果为-1则与水平方向相同,默认为-1

4.1 高斯模糊算法

高斯模糊的代码位于vf_gblur.c, 关键代码如下:

static void gaussianiir2d(AVFilterContext *ctx, int plane)
{GBlurContext *s = ctx->priv;const int width = s->planewidth[plane];const int height = s->planeheight[plane];const int nb_threads = ff_filter_get_nb_threads(ctx);ThreadData td;if (s->sigma <= 0 || s->steps < 0)return;td.width = width;td.height = height;// 水平方向滤波ctx->internal->execute(ctx, filter_horizontally, &td, NULL, FFMIN(height, nb_threads));// 垂直方向滤波ctx->internal->execute(ctx, filter_vertically, &td, NULL, FFMIN(width, nb_threads));// 缩放后处理ctx->internal->execute(ctx, filter_postscale, &td, NULL, FFMIN(width * height, nb_threads));
}

4.2 水平滤波

首先,看看水平方向滤波filter_horizontally()函数:

static int filter_horizontally(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
{......// 对每个slice条带进行滤波s->horiz_slice(buffer + width * slice_start, width, slice_end - slice_start,steps, nu, boundaryscale);emms_c();return 0;
}

而horiz_slice是个函数指针(在初始化进行赋值),指向horiz_slice_c()函数:

static void horiz_slice_c(float *buffer, int width, int height, int steps,float nu, float bscale)
{int step, x, y;float *ptr;for (y = 0; y < height; y++) {for (step = 0; step < steps; step++) {ptr = buffer + width * y;ptr[0] *= bscale;// 向右滤波for (x = 1; x < width; x++)ptr[x] += nu * ptr[x - 1];ptr[x = width - 1] *= bscale;// 向左滤波for (; x > 0; x--)ptr[x - 1] += nu * ptr[x];}}
}

4.3 垂直滤波

垂直方向滤波,filter_vertically()函数如下:

static int filter_vertically(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
{......// 沿着垂直方向逐个滤波(每步处理8列)do_vertical_columns(buffer, width, height, slice_start, aligned_end,steps, nu, boundaryscale, 8);// 对于非对齐的列逐个滤波do_vertical_columns(buffer, width, height, aligned_end, slice_end,steps, nu, boundaryscale, 1);return 0;
}

内部调用到的do_vertical_columns()函数如下:

static void do_vertical_columns(float *buffer, int width, int height,int column_begin, int column_end, int steps,float nu, float boundaryscale, int column_step)
{const int numpixels = width * height;int i, x, k, step;float *ptr;for (x = column_begin; x < column_end;) {for (step = 0; step < steps; step++) {ptr = buffer + x;for (k = 0; k < column_step; k++) {ptr[k] *= boundaryscale;}// 向下滤波for (i = width; i < numpixels; i += width) {for (k = 0; k < column_step; k++) {ptr[i + k] += nu * ptr[i - width + k];}}i = numpixels - width;for (k = 0; k < column_step; k++)ptr[i + k] *= boundaryscale;// 向上滤波for (; i > 0; i -= width) {for (k = 0; k < column_step; k++)ptr[i - width + k] += nu * ptr[i + k];}}x += column_step;}
}

4.4 缩放后处理

postscale_slice也是个函数指针,指向postscale_c()函数,主要进行两步操作:缩放与裁剪:

static void postscale_c(float *buffer, int length,float postscale, float min, float max)
{for (int i = 0; i < length; i++) {// 缩放buffer[i] *= postscale;// 裁剪buffer[i] = av_clipf(buffer[i], min, max);}
}

5、hflip

水平翻转,视频沿着水平方向进行翻转。与之对应的是vflip,垂直翻转。

水平翻转的命令如下:

ffmpeg -i in.mp4 -vf "hflip" out.mp4

水平翻转又称为左右镜像,镜像前后如下图所示:

 

6、hstack

水平拼接,两个视频以水平方向进行上下拼接。与之对应的是vstack,左右拼接。

水平拼接的命令如下:

ffmpeg -i one.mp4 -i two.mp4 -vf "hstack" out.mp4

7、rotate

旋转,以弧度表示的任意角度来旋转视频,可以顺时针方向旋转,也可以逆时针方向旋转。参数选项如下:

  • angle, a:使用弧度角表示要旋转的角度,默认为0,如果为负数则表示逆时针旋转
  • out_w, ow:输出的视频宽度,默认与输入视频相同 ,即"iw"
  • out_h, oh:输出的视频高度,默认与输入视频相同,即"ih"
  • bilinear:双线性插值,默认开启,0代表关闭,1代表开启
  • fillcolor, c:填充颜色,默认为黑色
  • n:输入视频帧的序号
  • t:输入视频帧的时间,单位为秒
  • hsub、vsub:水平垂直方向的子采样,比如像素格式为"yuv422p",那么hsub=2、vsub=1
  • in_w, iw、in_h, ih:输入视频的宽高
  • out_w, ow、out_h, oh:输出视频的宽高
  • rotw(a)、roth(a):旋转视频的最小宽高

以顺时针旋转90°为例,旋转前后的效果如下图所示:

 

8、xfade

转场过渡动画,应用于从一个视频到另一个视频的转场过渡。需要注意的是,所有输入视频的帧率、像素格式、分辨率、时间基要保持一致。

支持的转场动画包括淡入淡出、上下左右擦除、上下左右划出、圆形剪裁、矩形剪裁、圆形打开、溶解、模糊、缩放等,默认为淡入淡出。如下列表所示:

  • ‘custom’
  • ‘fade’
  • ‘wipeleft’
  • ‘wiperight’
  • ‘wipeup’
  • ‘wipedown’
  • ‘slideleft’
  • ‘slideright’
  • ‘slideup’
  • ‘slidedown’
  • ‘circlecrop’
  • ‘rectcrop’
  • ‘distance’
  • ‘fadeblack’
  • ‘fadewhite’
  • ‘radial’
  • ‘smoothleft’
  • ‘smoothright’
  • ‘smoothup’
  • ‘smoothdown’
  • ‘circleopen’
  • ‘circleclose’
  • ‘vertopen’
  • ‘vertclose’
  • ‘horzopen’
  • ‘horzclose’
  • ‘dissolve’
  • ‘pixelize’
  • ‘diagtl’
  • ‘diagtr’
  • ‘diagbl’
  • ‘diagbr’
  • ‘hlslice’
  • ‘hrslice’
  • ‘vuslice’
  • ‘vdslice’
  • ‘hblur’
  • ‘fadegrays’
  • ‘wipetl’
  • ‘wipetr’
  • ‘wipebl’
  • ‘wipebr’
  • ‘squeezeh’
  • ‘squeezev’
  • ‘zoomin’

参数选项如下所示:

  • duration:转场动画时长,范围为[0, 60],默认为1
  • offset:转场动画相对第一个视频的偏移时间,单位为秒,默认为0

9、overlay

视频叠加,把另一个图层叠加到视频上面,可以做文字水印、图片水印、GIF水印等。参数选项如下:

  • x、y:设置叠加图层的xy坐标点
  • format:输出视频的像素格式,默认为yuv420,完整列表如下:
  • ‘yuv420’
  • ‘yuv420p10’
  • ‘yuv422’
  • ‘yuv422p10’
  • ‘yuv444’
  • ‘rgb’
  • ‘gbrp’(平面RGB)
  • ‘auto’(自动选择)
  • alpha:设置透明度格式,straight或premultiplied,默认为straight
  • main_w, W、main_h, H:输入视频的宽高
  • overlay_w, w:overlay_h, h:叠加图层的宽高
  • n:视频帧的偏移数量,默认为0
  • pos:输入帧在文件的位置
  • t:时间戳

添加图片水印命令如下:

ffmpeg -i in.mp4 -i logo.png -filter_complex overlay=10:20 out.mp4

如果要配置左上角、右上角、左下角、右下角方位,可以使用如下方法:

    private static String obtainOverlay(int offsetX, int offsetY, int location) {switch (location) {case 2: // 右上角return "overlay='(main_w-overlay_w)-" + offsetX + ":" + offsetY + "'";case 3: // 左下角return "overlay='" + offsetX + ":(main_h-overlay_h)-" + offsetY + "'";case 4: // 右下角return "overlay='(main_w-overlay_w)-" + offsetX + ":(main_h-overlay_h)-" + offsetY + "'";case 1: // 左上角default:return "overlay=" + offsetX + ":" + offsetY;}}

添加图片水印效果如下(使用雷神制作的logo,以此致敬与缅怀雷神):

添加GIF动画水印的参考命令如下(-ignore_loop 0表示循环播放GIF):

ffmpeg -i in.mp4 -ignore_loop 0 -i in.gif -filter_complex overlay=10:20 out.mp4

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

FFmpeg源码分析:视频滤镜介绍(下)相关推荐

  1. FFMPEG 源码分析

    FFMPEG基本概念: ffmpeg是一个开源的编解码框架,它提供了一个音视频录制,解码和编码库.FFMPEG是在linux下开发的,但也有windows下的编译版本. ffmpeg项目由以下几部分组 ...

  2. ffmpeg源码分析-parse_optgroup

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  3. ffmpeg源码分析-ffmpeg_parse_options

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  4. ffmpeg源码分析-transcode_step

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  5. FFMPEG源码分析(一)

    FFMPEG源码分析(一) ffmpeg之前公司项目中就使用过,但是多停留于应用层面,实现某个功能时,需要哪些结构体以及调用哪些函数.最近想系统的学习一下ffmpeg,于是开始看雷霄骅https:// ...

  6. FFMPEG源码分析(二)

    ffmpeg源码分析之数据流 本文主要介绍ffmpeg的数据流,在ffmpeg中主要分有三个主要用途用于媒体流的解码播放,媒体流的转换(解码之后再编码)和媒体流录制. 媒体流的解码播放 在ffmpeg ...

  7. FFmpeg源码分析-直播延迟-内存泄漏

    FFmpeg源码分析-直播延迟-内存泄漏|FFmpeg源码分析方法|ffmpeg播放为什么容易产生延迟|解复用.解码内存泄漏分析 专注后台服务器开发,包括C/C++,Linux,Nginx,ZeroM ...

  8. 【SemiDrive源码分析】【X9芯片启动流程】08 - X9平台 lk 目录源码分析 之 目录介绍

    [SemiDrive源码分析][X9芯片启动流程]08 - X9平台 lk 目录源码分析 之 目录介绍 一./rtos/lk/ 目录结构分析 1.1 /rtos/lk_boot/ 目录结构分析 1.2 ...

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

    FFmpeg在libavfilter模块提供音视频滤镜.所有的视频滤镜都注册在libavfilter/allfilters.c.我们也可以使用ffmpeg -filters命令行来查看当前支持的所有滤 ...

最新文章

  1. 关于读取数据库进行数据处理的一些小问题
  2. 分享:手机应用存5个严重的信息安全隐患你晓得吗?
  3. Selenium WebDriver问题--Internet Explorer保护模式设置问题
  4. mysql int zerofill_Mysql 中int[M]—zerofill-阿里云开发者社区
  5. Android 学习笔记四:创建工具栏按钮
  6. if函数python_pythonif函数
  7. Python -- post方式上传文件
  8. 阿里如何实现100%容器化镜像化?八年技术演进之路回顾(转)
  9. 模拟ios_王者荣耀策划Donny:安卓IOS今年或实现互通!模拟战一周一更新
  10. 电脑下载python3.5.2教程_Win10系统如何搭建Python 3.5.2开发环境
  11. php for 循环 try_重新学习php基础之循环遍历(for循环和while循环)(六)
  12. php 图片上传打印路径,php上传图片到指定位置路径保存到数据库的具体实现
  13. IHttpModule接口事件执行 获取Session .
  14. 易语言PHP查询卡号,易语言卡密管理源码,易语言卡号密码管理软件源码
  15. 模电:集成运算放大器2
  16. 全志A33N切换分支.repo/repo/repo forall -c git checkout exdroid-7.1.1_r23-a33-v7.0rc2.1
  17. 聊天记录软件工作记录
  18. 20952磁盘存储器的管理
  19. Bzoj 2563: 阿狸和桃子的游戏 题解
  20. 【应用推荐】如何选择适合自己的笔记应用?附热门笔记应用上手总结

热门文章

  1. java计算机毕业设计框架的报修系统源程序+mysql+系统+lw文档+远程调试
  2. 台式计算机如何连接投影仪,终极:如何将投影仪连接到笔记本电脑?如何将台式计算机连接到投影仪?...
  3. 华为终打破美国垄断,夺取5G信道25%的控制权!
  4. win7 64 php mysql_Win7 64位操作系统下配置PHP+MySql+Apache环境
  5. “恰好装满求最值”背包问题的初始化解析
  6. Sketch 55 mac版(矢量绘图软件)新功能介绍
  7. 店宝宝:消费升级时代之“干饭人”的新玩法
  8. Div的宽度与高度的100%设定
  9. Linux shell 脚本文件@echo Off 关闭命令回显
  10. LeetCode80-删除排序数组中的重复项 II