视频特效-使用ffmpeg滤镜

视频特效-使用ffmpeg滤镜

前言

ffmpeg的滤镜分为简单滤镜和复杂滤镜。
复杂滤镜存在多个输入和多个输出如图:
在这里插入图片描述
在命令行中可以通过 -filter_complex-lavfi 来使用。
简单滤镜则只有一个输入和输出,如图:
在这里插入图片描述
在命令行中可以通过-vf来使用。
ffmpeg中的滤镜有很多,我们可以组合使用他们。举个例子,下图中就使用了多个滤镜(split , crop, vflip,overlay)。split是将输入数据拆分城两份,第一份为main,第二份为tmp。然后再通过crop 裁剪tmp,再通过vflip竖直反转得到flip。然后通过overlay将flip叠加到main上输出。
这些滤镜构成了一个图。每个滤镜都有输入和输出,上一个滤镜的输出是下一个滤镜的输入。

                [main]
input --> split ---------------------> overlay --> output
            |                             ^
            |[tmp]                  [flip]|
            +-----> crop --> vflip -------+

上面的这个滤镜图对应的命令为

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

其中中括号括起来的是对滤镜输出的命名,也可以不指定名称。分号隔开的是每个滤镜链。如果属于同一个链中的滤镜则只需要通过逗号隔开。

我们用张图片试一试上面的命令。原图:
请添加图片描述
执行命令:

ffmpeg -i lyf1.jpeg -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0,vflip [flip]; [main][flip] overlay=0:H/2"  output.jpeg

请添加图片描述
我们也可以直接通过ffplay直接使用滤镜看效果。

ffplay -i lyf1.jpeg -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0,vflip [flip]; [main][flip] overlay=0:H/2"

再来看一个颜色滤镜:hue,hue可以改变图片的色彩,它的参数h代表颜色,取值范围为0~360,它的s代表的是饱和度取值范围是-10~10,它的参数b代表的是亮度,取值范围为-10~10。这些参数所代表的颜色可以参考下图:
在这里插入图片描述
我们用命令试一下:

ffplay -i lyf1.jpeg -vf "hue=90:s=1:b=0"

原图变为了:
请添加图片描述

实践

上面的命令实现的功能,我们一定能用代码实现。
流程如下图:
在这里插入图片描述

创建滤镜

void ImageProcessor::initializeFilterGraph() {
    // 创建滤镜图实例
    this->filter_graph_ = avfilter_graph_alloc();
    // 获取输入滤镜的定义
    const auto avfilter_buffer_src = avfilter_get_by_name("buffer");
    if (avfilter_buffer_src == nullptr) {
        throw std::runtime_error("buffer avfilter get failed.");
    }
    // 获取输出滤镜的定义
    const auto avfilter_buffer_sink = avfilter_get_by_name("buffersink");
    if (avfilter_buffer_sink == nullptr) {
        throw std::runtime_error("buffersink avfilter get failed.");
    }
    // 创建输入实体
    this->filter_inputs_ = avfilter_inout_alloc();
    // 创建输出实体
    this->filter_outputs_ = avfilter_inout_alloc();

    if (this->filter_inputs_ == nullptr || this->filter_outputs_ == nullptr) {
        throw std::runtime_error("filter inputs is null");
    }

    char args[512]{};
    snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             this->width_,
             this->height_,
             1,
             1,
             avframe_origin_->format,
             avframe_origin_->sample_aspect_ratio.num,
             avframe_origin_->sample_aspect_ratio.den);
    // 根据参数创建输入滤镜的上下文,并添加到滤镜图
    int ret = avfilter_graph_create_filter(&this->buffer_src_ctx_, avfilter_buffer_src, "in",
                                           args, nullptr, this->filter_graph_);
    if (ret < 0) {
        throw std::runtime_error("create filter failed .code is " + std::to_string(ret));
    }
    // 根据参数创建输出滤镜的上下文,并添加到滤镜图
    ret = avfilter_graph_create_filter(&this->buffer_sink_ctx_, avfilter_buffer_sink, "out", nullptr,
                                       nullptr, this->filter_graph_);
    if (ret < 0) {
        throw std::runtime_error("create filter failed code is " + std::to_string(ret));
    }
    AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NONE};
    ret = av_opt_set_int_list(buffer_sink_ctx_, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE,
                              AV_OPT_SEARCH_CHILDREN);
    // 为输入滤镜关联滤镜名,滤镜上下文
    this->filter_outputs_->name = av_strdup("in");
    this->filter_outputs_->filter_ctx = this->buffer_src_ctx_;
    this->filter_outputs_->pad_idx = 0;
    this->filter_outputs_->next = nullptr;
    // 为输出滤镜关联滤镜名,滤镜上下文
    this->filter_inputs_->name = av_strdup("out");
    this->filter_inputs_->filter_ctx = this->buffer_sink_ctx_;
    this->filter_inputs_->pad_idx = 0;
    this->filter_inputs_->next = nullptr;
    // 根据传入的字符串解析出滤镜,并加入到滤镜图
    ret = avfilter_graph_parse_ptr(this->filter_graph_,
                               this->filter_desc_.c_str(),
                               &this->filter_inputs_,
                               &this->filter_outputs_,
                               nullptr);
    if (ret < 0) {
        throw std::runtime_error("avfilter graph parse failed .code is " + std::to_string(ret));
    }
    ret = avfilter_graph_config(this->filter_graph_, nullptr);
    if (ret < 0) {
        throw std::runtime_error("avfilter graph config failed.");
    }
}

使用滤镜

    // send to filter graph.
    int ret = av_buffersrc_add_frame(this->buffer_src_ctx_, this->avframe_origin_);
    if (ret < 0) {
        throw std::runtime_error("add frame to filter failed code is " + std::to_string(ret));
    }
    ret = av_buffersink_get_frame(this->buffer_sink_ctx_, this->avframe_dst_);
    if (ret < 0) {
        throw std::runtime_error("get avframe from sink failed code is " + std::to_string(ret));
    }

其中avframe_orgin_为输入frame,avframe_dst_为输出frame。

完整代码

ImageProcessor.h

#ifndef FFMPEG_DECODER_JPEG_IMAGEPROCESSOR_H
#define FFMPEG_DECODER_JPEG_IMAGEPROCESSOR_H

#include <string>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>

#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>

#include <libavutil/avutil.h>
#include <libavutil/opt.h>
}

using namespace std;

class ImageProcessor {
public:
    ImageProcessor();

    ~ImageProcessor();

    void setInput(string input);

    void setOutput(string output);

    void setFilterString(string filter_desc);

    void initialize();

    void process();

private:

    void initializeFilterGraph();

    string input_path_;
    string output_path_;
    string filter_desc_;

    AVFormatContext *avformat_ctx_;
    AVCodecContext *avcodec_decoder_ctx_;
    AVCodecContext *avcodec_encoder_ctx_;

    const AVCodec *avcodec_encoder_;

    AVPacket *avpacket_origin_;
    AVFrame *avframe_origin_;
    AVFrame *avframe_dst_;
    AVPacket *avpacket_dst_;

    int width_;
    int height_;

    AVFilterContext *buffer_src_ctx_;
    AVFilterContext *buffer_sink_ctx_;
    AVFilterGraph *filter_graph_;

    AVFilterInOut * filter_inputs_;
    AVFilterInOut * filter_outputs_;

};


#endif //FFMPEG_DECODER_JPEG_IMAGEPROCESSOR_H

ImageProcess.cpp

#include "ImageProcessor.h"
#include <fstream>


ImageProcessor::ImageProcessor() {}

ImageProcessor::~ImageProcessor() {
    avformat_free_context(avformat_ctx_);

    av_packet_free(&avpacket_origin_);
    av_packet_free(&avpacket_dst_);

    av_frame_free(&avframe_origin_);
    av_frame_free(&avframe_dst_);

    avfilter_inout_free(&filter_inputs_);
    avfilter_inout_free(&filter_outputs_);

    if (buffer_src_ctx_) {
        avfilter_free(buffer_src_ctx_);
    }
    if (buffer_sink_ctx_) {
        avfilter_free(buffer_sink_ctx_);
    }

    avfilter_graph_free(&filter_graph_);
}

void ImageProcessor::setInput(string input) {
    this->input_path_ = std::move(input);
}

void ImageProcessor::setOutput(string output) {
    this->output_path_ = std::move(output);
}

void ImageProcessor::setFilterString(string filter_desc) {
    this->filter_desc_ = std::move(filter_desc);
}

/**
 * Before initialize you should setInput,setOutput,setFilterString first.
 */
void ImageProcessor::initialize() {

    avpacket_origin_ = av_packet_alloc();
    avpacket_dst_ = av_packet_alloc();
    avframe_origin_ = av_frame_alloc();
    avframe_dst_ = av_frame_alloc();

    avformat_ctx_ = avformat_alloc_context();
    //  open format
    int ret = avformat_open_input(&avformat_ctx_, this->input_path_.c_str(), nullptr, nullptr);
    if (ret < 0) {
        throw std::runtime_error("avformat open input error code is : " + std::to_string(ret));
    }
    // init decoder
    const auto codec_decoder = avcodec_find_decoder(avformat_ctx_->streams[0]->codecpar->codec_id);
    if (codec_decoder == nullptr) {
        throw std::runtime_error("avcodec find decoder failed.");
    }
    avcodec_decoder_ctx_ = avcodec_alloc_context3(codec_decoder);
    ret = avcodec_parameters_to_context(avcodec_decoder_ctx_, avformat_ctx_->streams[0]->codecpar);
    if (ret < 0) {
        throw std::runtime_error("avcodec_parameters_to_context failed code is " + std::to_string(ret));
    }
    // read frame
    ret = av_read_frame(avformat_ctx_, avpacket_origin_);
    if (ret < 0) {
        throw std::runtime_error("av read frame " + std::to_string(ret));
    }
    // open decoder
    ret = avcodec_open2(avcodec_decoder_ctx_, codec_decoder, nullptr);
    if (ret < 0) {
        throw std::runtime_error("avcodec open failed. codes is " + std::to_string(ret));
    }
    // decode packet to frame
    ret = avcodec_send_packet(avcodec_decoder_ctx_, avpacket_origin_);
    if (ret < 0) {
        throw std::runtime_error("avcodec send packet failed. code is " + std::to_string(ret));
    }
    ret = avcodec_receive_frame(avcodec_decoder_ctx_, avframe_origin_);
    if (ret < 0) {
        throw std::runtime_error("avcodec_receive_frame failed. code is " + std::to_string(ret));
    }
    this->width_ = avframe_origin_->width;
    this->height_ = avframe_origin_->height;

    // init encoder
    avcodec_encoder_ = avcodec_find_encoder_by_name("mjpeg");
    if (avcodec_encoder_ == nullptr) {
        throw std::runtime_error("find encoder by name failed.");
    }
    avcodec_encoder_ctx_ = avcodec_alloc_context3(avcodec_encoder_);
    if (avcodec_encoder_ctx_ == nullptr) {
        throw std::runtime_error("avcodec alloc failed");
    }
    avcodec_encoder_ctx_->width = this->width_;
    avcodec_encoder_ctx_->height = this->height_;
    avcodec_encoder_ctx_->pix_fmt = AVPixelFormat(avframe_origin_->format);
    avcodec_encoder_ctx_->time_base = {1, 1};

    initializeFilterGraph();
}

void ImageProcessor::process() {
    // send to filter graph.
    int ret = av_buffersrc_add_frame(this->buffer_src_ctx_, this->avframe_origin_);
    if (ret < 0) {
        throw std::runtime_error("add frame to filter failed code is " + std::to_string(ret));
    }
    ret = av_buffersink_get_frame(this->buffer_sink_ctx_, this->avframe_dst_);
    if (ret < 0) {
        throw std::runtime_error("get avframe from sink failed code is " + std::to_string(ret));
    }

    ret = avcodec_open2(avcodec_encoder_ctx_, avcodec_encoder_, nullptr);
    if (ret < 0) {
        throw std::runtime_error("open encode failed code is " + std::to_string(ret));
    }

    ret = avcodec_send_frame(avcodec_encoder_ctx_, this->avframe_dst_);
    if (ret < 0) {
        throw std::runtime_error("encoder failed,code is " + std::to_string(ret));
    }
    ret = avcodec_receive_packet(avcodec_encoder_ctx_, avpacket_dst_);
    if (ret < 0) {
        throw std::runtime_error("avcodec receive packet failed code is " + std::to_string(ret));
    }

    std::ofstream output_file(this->output_path_);
    output_file.write(reinterpret_cast<const char *>(this->avpacket_dst_->data), avpacket_dst_->size);
}

void ImageProcessor::initializeFilterGraph() {
    // 创建滤镜图实例
    this->filter_graph_ = avfilter_graph_alloc();
    // 获取输入滤镜的定义
    const auto avfilter_buffer_src = avfilter_get_by_name("buffer");
    if (avfilter_buffer_src == nullptr) {
        throw std::runtime_error("buffer avfilter get failed.");
    }
    // 获取输出滤镜的定义
    const auto avfilter_buffer_sink = avfilter_get_by_name("buffersink");
    if (avfilter_buffer_sink == nullptr) {
        throw std::runtime_error("buffersink avfilter get failed.");
    }
    // 创建输入实体
    this->filter_inputs_ = avfilter_inout_alloc();
    // 创建输出实体
    this->filter_outputs_ = avfilter_inout_alloc();

    if (this->filter_inputs_ == nullptr || this->filter_outputs_ == nullptr) {
        throw std::runtime_error("filter inputs is null");
    }

    char args[512]{};
    snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             this->width_,
             this->height_,
             1,
             1,
             avframe_origin_->format,
             avframe_origin_->sample_aspect_ratio.num,
             avframe_origin_->sample_aspect_ratio.den);
    // 根据参数创建输入滤镜的上下文,并添加到滤镜图
    int ret = avfilter_graph_create_filter(&this->buffer_src_ctx_, avfilter_buffer_src, "in",
                                           args, nullptr, this->filter_graph_);
    if (ret < 0) {
        throw std::runtime_error("create filter failed .code is " + std::to_string(ret));
    }
    // 根据参数创建输出滤镜的上下文,并添加到滤镜图
    ret = avfilter_graph_create_filter(&this->buffer_sink_ctx_, avfilter_buffer_sink, "out", nullptr,
                                       nullptr, this->filter_graph_);
    if (ret < 0) {
        throw std::runtime_error("create filter failed code is " + std::to_string(ret));
    }
    AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_NONE};
    ret = av_opt_set_int_list(buffer_sink_ctx_, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE,
                              AV_OPT_SEARCH_CHILDREN);
    // 为输入滤镜关联滤镜名,滤镜上下文
    this->filter_outputs_->name = av_strdup("in");
    this->filter_outputs_->filter_ctx = this->buffer_src_ctx_;
    this->filter_outputs_->pad_idx = 0;
    this->filter_outputs_->next = nullptr;
    // 为输出滤镜关联滤镜名,滤镜上下文
    this->filter_inputs_->name = av_strdup("out");
    this->filter_inputs_->filter_ctx = this->buffer_sink_ctx_;
    this->filter_inputs_->pad_idx = 0;
    this->filter_inputs_->next = nullptr;
    // 根据传入的字符串解析出滤镜,并加入到滤镜图
    ret = avfilter_graph_parse_ptr(this->filter_graph_,
                               this->filter_desc_.c_str(),
                               &this->filter_inputs_,
                               &this->filter_outputs_,
                               nullptr);
    if (ret < 0) {
        throw std::runtime_error("avfilter graph parse failed .code is " + std::to_string(ret));
    }
    ret = avfilter_graph_config(this->filter_graph_, nullptr);
    if (ret < 0) {
        throw std::runtime_error("avfilter graph config failed.");
    }
}

使用

#include "ImageProcessor.h"

int main(int argc, const char *argv[]) {
    ImageProcessor processor;
    processor.setInput("../res/lyf1.jpeg");
    processor.setOutput("../res/output.jpeg");
    // 根据想要的效果传入滤镜描述字符串
    processor.setFilterString("hue=120:s=1");
//    processor.setFilterString("split [main][tmp]; [tmp] crop=iw:ih/2:0:0,vflip [flip]; [main][flip] overlay=0:H/2");
    processor.initialize();
    processor.process();
    return 0;
}

根据调用侧传入的滤镜描述字符,我们就可以得到跟命令行使用一样的效果。

参考

https://www.ffmpeg.org/ffmpeg.html#Simple-filtergraphs
https://www.ffmpeg.org/ffmpeg-filters.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值