FFmpeg/OpenCV 实现全屏斜体水印

news2024/11/18 13:35:53

在这里插入图片描述

实现思路 🤔​

  1. 基于ffmpeg,画布的方式,创建画布 -> 水印 -> 旋转 -> 抠图 -> 叠加到图像上
  2. 基于ffmpeg,旋转图片的方式,填充 -> 水印 -> 顺时针旋转 -> 逆时针旋转 -> 截图
  3. 基于opencv,创建画布 -> 水印 -> 仿射变换 -> 水平垂直拼接 -> 叠加图片上

经测试比对,opencv实现方式效率是最快的


代码实现 💨

  1. FFmpeg
    1. 旋转画布方式

      • 这种方式相对实现简单一些,但经过试验,对于一个2k的视频,实现全屏文字倾斜,效率太慢了 。不知道是我代码问题,还是什么问题。如果有大佬能提高效率,方便指导一二,抱拳了.
      • 实现参考
        • https://blog.csdn.net/qq_38722827/article/details/121549999
        • https://blog.csdn.net/qq_23282479/article/details/119904850
    2. 旋转图片方式

      1. 思路:
        • 首先将图片填充放大,然后计算出水印位置,叠加水印,再逆时针将图片旋转回来,字体就变斜了。然后通过剪裁,将图片剪切成原图大小。
      2. 基于 ffmpeg filter 实现
      	// 填充
      	// [in]scale=原图宽:原图高,pad=填充后宽:填充后高:填充左右大小:填充上下大小:black[out]
      	// 四周填充,  pad 输入尺寸的一半
      	char dtext[512];
      	snprintf(dtext, sizeof(dtext), "[in]scale=%d:%d,pad=%d:%d:%d:%d:black[out]",
      	frameWidth, frameHeight, expandFrameWidth, expandFrameHeight, expandSize, expandSize);
      	
      	// 水印
      	// inxxxx[a];[a]xxx[a];[a]xxx[out]  中间传的文字标签首位对应([a] [a]). 结束没有';'. 
      	char dtext[1000];
      	snprintf(dtext, sizeof(dtext), "[in]drawtext=fontfile=%s:text=%s:x=%d:y=%d:fontsize=%d:fontcolor=%s:alpha=%.1f[a];",kFont, txt.c_str(), start_x, start_y, kFontSize, kFontColor, italicTextInfo_.alpha);
      	
      	// 翻转
      	// [in]rotate=PI* angel /180[out] 
      	char dtext[512];
      	snprintf(dtext, sizeof(dtext), "[in]rotate=PI* %d /180[out]", angel);
      	
      	// 截取 
      	// [in]crop=原图宽:原图高:截取x:截取y[out]
      	char dtext[512];
      	snprintf(dtext, sizeof(dtext), "[in]crop=%d:%d:%d:%d[out]", frameWidth, frameHeight, startx, starty);
      
      1. 创建 AVFilterGraph
      int init_filter_graph(const std::string& filter) {
      
      	filterGraph_ = avfilter_graph_alloc();
      	const AVFilter* buffersrc = avfilter_get_by_name("buffer");
      	const AVFilter* buffersink = avfilter_get_by_name("buffersink");
      	char args[512];
      	snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", frameWidth_, frameHeight_, frameFormat_, 1, videoFps_, 1, 1);
      	int ret = avfilter_graph_create_filter(&buffersrcCtx_, buffersrc, "in", args, NULL, filterGraph_);
      	if (ret < 0)
      	{
      		avfilter_graph_free(&filterGraph_);
      		filterGraph_ = NULL;
      		return -1;
      	}
      
      	// buffer video sink: to terminate the filter chain.
      	enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NV12, AV_PIX_FMT_NONE };
      	AVBufferSinkParams* buffersink_params = av_buffersink_params_alloc();
      	buffersink_params->pixel_fmts = pix_fmts;
      	ret = avfilter_graph_create_filter(&buffersinkCtx_, buffersink, "out", NULL, buffersink_params, filterGraph_);
      	av_free(buffersink_params);
      	if (ret < 0)
      	{
      		avfilter_graph_free(&filterGraph_);
      		filterGraph_ = NULL;
      		return -1;
      	}
      
      	AVFilterInOut* outputs = avfilter_inout_alloc();
      	/* Endpoints for the filter graph. */
      	outputs->name = av_strdup("in");
      	outputs->filter_ctx = buffersrcCtx_;
      	outputs->pad_idx = 0;
      	outputs->next = NULL;
      
      	AVFilterInOut* inputs = avfilter_inout_alloc();
      	inputs->name = av_strdup("out");
      	inputs->filter_ctx = buffersinkCtx_;
      	inputs->pad_idx = 0;
      	inputs->next = NULL;
      	if ((ret = avfilter_graph_parse_ptr(filterGraph_, filter.c_str(), &inputs, &outputs, NULL)) < 0)
      		goto end;
      	ret = avfilter_graph_config(filterGraph_, NULL);
      	if (ret < 0)
      		goto end;
      end:
      	avfilter_inout_free(&inputs);
      	avfilter_inout_free(&outputs);
      	return ret;
      }
      
      1. 向滤波输入帧, 然后从FilterGraph中取出一个AVFrame
      	AVFrame* overlay(AVFrame* in) {
      		if (!isInit_)
      			return nullptr;
      		
      		int ret = av_buffersrc_add_frame(buffersrcCtx_, in);
      		if (ret < 0)
      			return nullptr;
      		ret = av_buffersink_get_frame(buffersinkCtx_, out_);
      		if (ret < 0)
      		{
      			av_frame_unref(out_);
      			return nullptr;
      		}
      		return out_;
      	}
      
      1. 生成对应 filter , 然后 向滤波中输入帧
      // 大概流程思路
      void xxxxx(){
      	 ...
      	 // 生成对应filter
      	 std::string filter = "对应filter";
      	 init_filter_graph(filter);
      	 ...
      	 while (true)
      		{
      			ret = av_read_frame(ifmtCtx, pkt);
      			if (ret < 0)
      				goto end;
      	
      			// audio
      			if (pkt->stream_index == aistream) {
      				av_packet_unref(pkt);
      				continue;
      			}
      			// decode encode pkt
      			ret = avcodec_send_packet(deCodecCtx, pkt);
      			if (ret < 0)
      				goto end;
      	
      			while (ret >= 0) {
      				ret = avcodec_receive_frame(deCodecCtx, frame);
      				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
      					break;
      				}
      				else if (ret < 0) {
      					goto end;
      				}
      				// solove [libx264 @ 0x34b88a0] specified frame type (3) at 0 is not compatible with keyframe interval
      				frame->pict_type = AV_PICTURE_TYPE_NONE;
      	
      				// overlay italic txt info
      				AVFrame* overlay_frame = overlay(frame);
      				if (overlay_frame == nullptr)
      					goto end;
      				ret = encode_frame_to_write_file(enCodecCtx, overlay_frame, pkt, ifmtCtx->streams[vistream]->time_base, ofmtCtx->streams[vostream]->time_base, ofmtCtx);
      				if (ret < 0) {
      					av_frame_free(&overlay_frame);
      					goto end;
      				}
      				av_packet_unref(pkt);
      				av_frame_unref(frame);
      				av_frame_unref(overlay_frame);
      			}
      	#pragma endregion
      			av_packet_unref(pkt);
      		}
      		...
      	 }
      

在这里插入图片描述

  1. Opencv
    1. 思路
      • 先创建一张斜体水印的画布,然后重复叠加到每一张图片上,这样可以提高很大的效率。
    2. 代码
      1. 创建斜体水印
        int init_water_mark(const std::string& txt, cv::Mat& water) {
        		// 原图宽高
        		int imageW = italicTextInfo_.frameWidth / italicTextInfo_.col;
        		int imageH = italicTextInfo_.frameHeight / italicTextInfo_.row;
        		cv::Mat canvasMat = cv::Mat::zeros(cv::Size(imageW, imageH), CV_8UC3);
        		// 水印,使用 Times New Roman 字体
        		VTText::putTextHuskyC(canvasMat, txt.c_str(), cv::Scalar(255, 255, 255), kFontSize, "Times New Roman", false, false);
        	    // 实际显示的水印行列数
        		int w_count = italicTextInfo_.col * 2 - 1;
        		int h_count = italicTextInfo_.row * 2;
        		// 垂直拼接水印
        		std::vector<cv::Mat> vImgs;
        		cv::Mat vResult;
        		for (int vi = 0; vi < h_count; vi++)
        			vImgs.push_back(canvasMat);
        		vconcat(vImgs, vResult);
        		// 水平拼接水印
        		std::vector<cv::Mat> hImgs;
        		cv::Mat hResult;
        		for (int hi = 0; hi < w_count; hi++)
        			hImgs.push_back(vResult);
        		hconcat(hImgs, hResult);
        		cv::Point2f center(hResult.cols / 2.0, hResult.rows / 2.0);
        		// 通过仿射变换将水印旋转45度,实现全屏斜体效果
        		cv::Mat rotation_matix = getRotationMatrix2D(center, italicTextInfo_.italicAngel, 1.0);
        		cv::Mat rotated_image;
        		warpAffine(hResult, rotated_image, rotation_matix, hResult.size());
        		// 然后将水印图裁剪成原图大小,用于后续叠加到原图上.
        		water = rotated_image(cv::Rect((hResult.cols - italicTextInfo_.frameWidth) / 2, (hResult.rows - italicTextInfo_.frameHeight) / 2, italicTextInfo_.frameWidth, italicTextInfo_.frameHeight));
        		return 0;
        	}
        	
        	// 将水印图叠加到原图上
        	// frame: 解码帧 avframe 
        	AVFrame* overlayItalicTextInfoCVCanvas(AVFrame* frame) {
        		//cv::Mat src_mat;
        		//frame_to_mat(frame, src_mat);
        		int pktSize;
        		uint8_t* buffer_rgb = convertRgb(frame, frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width, frame->height, AV_PIX_FMT_BGR24, pktSize);
        		cv::Mat src_mat = cv::Mat(frame->height, frame->width, CV_8UC3, buffer_rgb);
        	
        		cv::Mat roi;
        		roi = src_mat(cv::Rect(0, 0, watermark_.cols, watermark_.rows));
        		cv::addWeighted(roi, 1.0, watermark_, italicTextInfo_.alpha, 0, roi);
        		AVFrame* dst = mat_to_frame(src_mat, src_mat.cols, src_mat.rows, AV_PIX_FMT_YUV420P);
        		if (buffer_rgb != nullptr)
        			av_free(buffer_rgb);
        		return  dst;
        	}
        
        1. 大概流程
        void xxxxx(){
        	 ...
        	 // 先创建个带有斜体水印的mat
        	cv::Mat water;
        	const std::string txt = "12321321";
        	init_water_mark(txt, water)
        	...
        	 while (true)
        		{
        			ret = av_read_frame(ifmtCtx, pkt);
        			if (ret < 0)
        				goto end;
        	
        			// audio
        			if (pkt->stream_index == aistream) {
        				av_packet_unref(pkt);
        				continue;
        			}
        			// decode encode pkt
        			ret = avcodec_send_packet(deCodecCtx, pkt);
        			if (ret < 0)
        				goto end;
        	
        			while (ret >= 0) {
        				ret = avcodec_receive_frame(deCodecCtx, frame);
        				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
        					break;
        				}
        				else if (ret < 0) {
        					goto end;
        				}
        				// solove [libx264 @ 0x34b88a0] specified frame type (3) at 0 is not compatible with keyframe interval
        				frame->pict_type = AV_PICTURE_TYPE_NONE;
        	
        				// overlay italic txt info
        				AVFrame* overlay_frame = overlayItalicTextInfo(frame);
        				if (overlay_frame == nullptr)
        					goto end;
        				ret = encode_frame_to_write_file(enCodecCtx, overlay_frame, pkt, ifmtCtx->streams[vistream]->time_base, ofmtCtx->streams[vostream]->time_base, ofmtCtx);
        				if (ret < 0) {
        					av_frame_free(&overlay_frame);
        					goto end;
        				}
        				av_packet_unref(pkt);
        				av_frame_unref(frame);
        				av_frame_unref(overlay_frame);
        			}
        	#pragma endregion
        			av_packet_unref(pkt);
        		}
        		...
        	 }
        

在这里插入图片描述


整理个demo,基于opencv实现的。demo中 需依赖 opencv ffmpeg ,自己搭建个环境即可运行。

demo: https://download.csdn.net/download/haiyangyunbao813/87499341


参考文章 ✨​

  • https://blog.csdn.net/qq_23282479/article/details/119904850
  • https://blog.csdn.net/qq_38722827/article/details/121549999

总结 👊​

  1. 经测试,基于opencv方式,相比较ffmpeg filter ,效率至少能提升2倍。(1920 * 1080 i7 6代u)
    • opencv : 平均一帧 20ms
    • ffmpeg : 平均一帧 50ms
  2. 基于ffmpeg filter, 我感觉应该是可以提升效率的,我的想法先创建出来水印,然后重复叠加到每一张frame上就行,但效率总是提升不了,希望哪位大佬有时间能帮忙试试,或者有什么别的好的方式,抱拳了。
  3. 以上就是我分享的全部内容了,有不对的地方好,欢迎留言指正。有需要的小伙伴,可以参考参考。

最后来个三连,今年就属你最牛逼了 🚀​

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370434.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pag渲染过程 -- 背景知识

什么是渲染 渲染是图形程序的核心&#xff0c;无论是我们在电子设备上看到的任何图形或者文字都是利用计算机图形渲染技术给我们呈现出来的结果。在计算机里一开始是直接利用CPU往显示器的FrameBuffer内写入数据即可把图形展示到显示器上&#xff0c;但是随着用户的需求和技术…

别担心,ChatGPT还抢不动你的饭碗

前言&#xff1a; “你是谁&#xff1f;” “我是一个由OpenAI训练的大型语言模型。我旨在帮助人们解答问题和提供信息。由于我是一个计算机程序&#xff0c;所以不会感知或者思考&#xff0c;只能通过已有的数据来回答问题。如果您有任何问题&#xff0c;请随时告诉我。” ---…

rabbitmq部署安装(mac)

安装&#xff1a; // 默认已经下载了homebrew&#xff0c;更新brew资源 brew update // 执行安装 brew install rabbitmq 配置&#xff1a; // 切换到MQ目录,注意你的安装版本可能不是3.9.5&#xff08;我的版本&#xff0c;当前最新版 cd /usr/local/Cellar/rabbitmq/3.…

如果不是互联网人,谁会找到这些神器?

一、上线啦 你肯定该问了&#xff0c;这个是什么鬼东西。它本来是一个创建自己网站的网站。 现在使用它可以创建自己的小程序&#xff0c;又不是有点小厉害了。 而且功能强大&#xff0c;还支持微信支付&#xff0c;分销&#xff0c;优惠券&#xff0c;营销等多种功能。 还有多…

DS期末复习卷(九)

一、选择题(30分) 1&#xff0e;下列程序段的时间复杂度为&#xff08;A &#xff09;。 for(i0&#xff1b; i<m&#xff1b; i) for(j0&#xff1b; j<t&#xff1b; j) c[i][j]0&#xff1b; for(i0&#xff1b; i<m&#xff1b; i) for(j0&#xff1b; j<t&am…

LeetCode:二叉树的最大深度104;559. N 叉树的最大深度

104. 二叉树的最大深度 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&#xff0c; 3/ \9 20/ \15 7 返回它的…

看似平平无奇的00后,居然一跃上岸字节,表示真的卷不过......

又到了一年一度的求职旺季金&#xff01;三&#xff01;银&#xff01;四&#xff01;在找工作的时候都必须要经历面试这个环节。在这里我想分享一下自己上岸字节的面试经验&#xff0c;过程还挺曲折的&#xff0c;但是还好成功上岸了。大家可以参考一下&#xff01; 0821测评 …

变则通--通则达--MindSpore社区活动-深度学习模型之数据变换-Transforms学习与体验记录

文章目录已加入 昇思MindSpore社区 证明截图安装 MindSpore 2.0 alpha 步骤打开powershell 或 cmd安装成功的截图实现 数据变换Transforms 任务Common Transforms 通用数据变换Vision Transforms 针对图像数据的变换Text Transforms 文本数据的变换Lambda Transforms结语已加入…

Netty——序列化的作用及自定义协议

序列化的作用及自定义协议序列化的重要性大小对比效率对比自定义协议序列化数据结构自定义编码器自定义解码器安全性验证NettyClientNettyServerNettyClientTestHandlerNettyServerTestHandler结果上一章已经说了怎么解决沾包和拆包的问题&#xff0c;但是这样离一个成熟的通信…

【华为OD机试模拟题】用 C++ 实现 - 停车场最大距离(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 分积木(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 吃火锅(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - RSA 加密算法(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 构成的正方形数量(2023.Q1) 【华为OD机试模拟…

MyBatis快速开发

查询user表中的所有数据 步骤&#xff1a; 创建user表 打开Navicat&#xff0c;新建查询&#xff0c;将下面SQL代码复制粘贴并执行&#xff1a; create database mybatis; use mybatis;drop table if exists tb_user;create table tb_user(id int primary key auto_incremen…

抽象类与接口的区别

抽象类什么是抽象类&#xff1f;抽象类是特殊的类&#xff0c;只是不能被实例化&#xff1b;除此以外&#xff0c;具有类的其他特性&#xff1b;重要的是抽象类可以包括抽象方法&#xff0c;这是普通类所不能的。抽象方法只能声明于抽象类中&#xff0c;且不包含任何实现&#…

Docker安装YApi

目录0、Docker 环境准备1、数据库准备 MongoDB2、启动 YAPI3、官网教程0、Docker 环境准备 Docker 容器之间网络互通需要使用 docker network create yapi 创建一个自定义网络 docker network create yapi1、数据库准备 MongoDB YAPI 的数据库是 MongoDB&#xff0c;准备镜像…

Java线程池使用与原理解析1(线程池优点、使用方法、参数含义及线程池运转机制)

为什么要使用线程池&#xff1f; JDK1.5后JUC包添加了线程池相关接口&#xff0c;在Java诞生之初并没有线程池这个概念。刚开始Java程序都是自行创建线程去处理任务。随着应用使用的线程越来越多&#xff0c;JDK开发者们发现有必要使用一个统一的类来管理这些线程&#xff0c;…

从应届毕业生到大型开源社区 Committer,我做对了什么?

熟悉李成龙的人都叫他小龙。“小龙&#xff0c;2.2.3 Milvus 版本&#xff0c;日志会一直积累在磁盘上吗&#xff1f;默认会定期清除吗&#xff1f;”“小龙&#xff0c;请教一个问题&#xff0c;为啥取不到 field 中的 type 字段&#xff1f;”“小龙……”熟悉 Milvus 社区群…

django项目中如何添加自定义的django command

项目目录 1.我们自己建立的application叫做app&#xff0c;首先在这个app目录下&#xff0c;我们需要新建management目录&#xff0c;这个目录里应该包括&#xff1a;__ init__.py&#xff08;内容为空&#xff0c;用于打包&#xff09;和commands目录&#xff0c;然后在comma…

Linux中使用Docker部署Mysql数据库

前言 和朋友一起搞一个项目&#xff0c;分了一下工作&#xff0c;但是mysql迟迟安装不上&#xff0c;程序都在一个环境里确实容易出现很多问题&#xff0c;浪费时间和经历在这些配置上&#xff0c;好在有docker了&#xff0c;就在docker里搭建一个Mysql数据库使用吧&#xff0…

【计算机网络】计算机网络

目录一、概述计算机网络体系结构二、应用层DNS应用文件传输应用DHCP 应用电子邮件应用Web应用当访问一个网页的时候&#xff0c;都会发生什么三、传输层UDP 和 TCP 的特点UDP 首部格式TCP 首部格式TCP 的三次握手TCP 的四次挥手TCP 流量控制TCP 拥塞控制三、网络层IP 数据报格式…

李宏毅《机器学习》Bert笔记

李宏毅《机器学习》Bert笔记和工作原理解释1.参考2. self-supervised learning--bert3. bert的一些用法3.1情感分析3.2词性标注3.3常识推理和NLI3.4 QA问题4.bert的工作原理解释1.参考 bert论文 李宏毅《机器学习》自监督训练-bert 2. self-supervised learning–bert Bert模…

图解JVM

1.JVM的一些概念 ​ JVM:Java虚拟机,可以存储代码以及在运行是可以动态调用一些指令的项目集。 ​ JDK:Java运行时所依赖的环境,[jar包、类]。 ​ JVM的作用:负责将.class文件解释成机器能够识别的机器码。 ​ JVM的位置:在操作系统之上,介于应用与系统之间。 ​ 机器码…