当前位置: 首页 > news >正文

FFmpeg/OpenCV 实现全屏斜体水印

在这里插入图片描述

实现思路 🤔​

  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(){...// 生成对应filterstd::string filter = "对应filter";init_filter_graph(filter);...while (true){ret = av_read_frame(ifmtCtx, pkt);if (ret < 0)goto end;// audioif (pkt->stream_index == aistream) {av_packet_unref(pkt);continue;}// decode encode pktret = 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 intervalframe->pict_type = AV_PICTURE_TYPE_NONE;// overlay italic txt infoAVFrame* 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 endregionav_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(){...// 先创建个带有斜体水印的matcv::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;// audioif (pkt->stream_index == aistream) {av_packet_unref(pkt);continue;}// decode encode pktret = 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 intervalframe->pict_type = AV_PICTURE_TYPE_NONE;// overlay italic txt infoAVFrame* 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 endregionav_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. 以上就是我分享的全部内容了,有不对的地方好,欢迎留言指正。有需要的小伙伴,可以参考参考。

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

在这里插入图片描述

相关文章:

FFmpeg/OpenCV 实现全屏斜体水印

实现思路 &#x1f914;​ 基于ffmpeg&#xff0c;画布的方式&#xff0c;创建画布 -> 水印 -> 旋转 -> 抠图 -> 叠加到图像上基于ffmpeg&#xff0c;旋转图片的方式&#xff0c;填充 -> 水印 -> 顺时针旋转 -> 逆时针旋转 -> 截图基于opencv&#xff…...

Calendar计算两个时间之间相差几个月

目录说明说明 计算两个时间之间相差几个月&#xff1a; public int getMonth(String startDt, String endDt) { int month 0;try {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Calendar satrt Calendar.getInstance();Calendar end Cal…...

FPGA基础知识

FPGA是在PAL、PLA和CPLD等可编程器件的基础上进一步发展起来的一种更复杂的可编程逻辑器件。它是ASIC领域中的一种半定制电路&#xff0c;既解决了定制电路的不足&#xff0c;又克服了原有可编程器件门电路有限的缺点。 由于FPGA需要被反复烧写&#xff0c;它实现组合逻辑的基…...

C语言运算符逻辑运算符位运算符

逻辑运算符 下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1&#xff0c;变量 B 的值为 0&#xff0c;则&#xff1a; 运算符 描述 实例 && 称为逻辑与运算符。如果两个操作数都非零&#xff0c;则条件为真。 (A && B) 为假。 || 称为逻辑…...

机器学习:基于主成分分析(PCA)对数据降维

机器学习&#xff1a;基于主成分分析&#xff08;PCA&#xff09;对数据降维 作者&#xff1a;AOAIYI 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;AOAIYI首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可…...

软件测试之测试模型

软件测试的发展 1960年代是调试时期&#xff08;测试即调试&#xff09; 1960年 - 1978年 论证时期&#xff08;软件测试是验证软件是正确的&#xff09;和 1979年 - 1982年 破坏性测试时期&#xff08;为了发现错误而执行程序的过程&#xff09; 1983年起&#xff0c;软件测…...

【项目精选】网络考试系统的设计与实现(源码+视频+论文)

点击下载源码 网络考试系统主要用于实现高校在线考试&#xff0c;基本功能包括&#xff1a;自动组卷、试卷发布、试卷批阅、试卷成绩统计等。本系统结构如下&#xff1a; &#xff08;1&#xff09;学生端&#xff1a; 登录模块&#xff1a;登录功能&#xff1b; 网络考试模块…...

Python近红外光谱分析与机器学习、深度学习方法融合实践技术

、 第一n入门基础【理论讲解与案 1、Python环境搭建&#xff08; 下载、安装与版本选择&#xff09;。 2、如何选择Python编辑器&#xff1f;&#xff08;IDLE、Notepad、PyCharm、Jupyter…&#xff09; 3、Python基础&#xff08;数据类型和变量、字符串和编码、list和tu…...

实例7:树莓派呼吸灯

实例7&#xff1a;树莓派呼吸灯 实验目的 通过背景知识学习&#xff0c;了解digital与analog的区别。通过GPIO对外部LED灯进行呼吸控制&#xff0c;熟悉PWM技术。 实验要求 通过python编程&#xff0c;用GPIO控制LED灯&#xff0c;使之亮度逐渐增大&#xff0c;随后减小&am…...

java 接口 详解

目录 一、概述 1.介绍 : 2.定义 : 二、特点 1.接口成员变量的特点 : 2.接口成员方法的特点 : 3.接口构造方法的特点 : 4.接口创建对象的特点 : 5.接口继承关系的特点 : 三、应用 : 1.情景 : 2.多态 : ①多态的传递性 : ②关于接口的多态参数和多态…...

用 Visual Studio 升级 .NET 项目

现在&#xff0c;你已可以使用 Visual Studio 将所有 .NET 应用程序升级到最新版本的 .NET&#xff01;这一功能可以从 Visual Studio 扩展包中获取&#xff0c;它会升级你的 .NET Framework 或 .NET Core 网页和桌面应用程序。一些项目类型仍正在开发中并将在不久的未来推出&a…...

JavaWeb中FilterListener的神奇作用

文章目录1&#xff0c;Filter1.1 Filter概述1.2 Filter快速入门1.2.1 开发步骤1.3 Filter执行流程1.4 Filter拦截路径配置1.5 过滤器链1.5.1 概述1.5.2 代码演示1.5.3 问题2&#xff0c;Listener2.1 概述2.2 分类2.3 代码演示最后说一句1&#xff0c;Filter 1.1 Filter概述 F…...

移动端布局

参考链接&#xff1a;抖音-移动端适配 一、移动端布局 flexiblepostcss-pxtorem vue-h5-template 老版本&#xff1a;动态去计算scale&#xff0c;并不影响rem的计算&#xff0c;好处是解决了1px的问题&#xff0c;但是第三方库一般都用dpr为1去做的&#xff0c;这就导致地图或…...

前端无感登录,大文件上传

后端设置token的一个失效时间&#xff0c;前端在token失效后不用重新登录 1&#xff0c;在相应中拦截&#xff0c;判断token返回过期后&#xff0c;调用刷新token的方法 2&#xff0c;后端返回过期的时间&#xff0c;前端判断过期的时间&#xff0c;然后到期后调用对应的方法…...

Spring Boot系列03--自动配置原理

目录1. 相关注解2. 自动配置原理分析3. 自动配置图示Spring Boot的核心优势&#xff1a;自动装配、约定大于配置。 1. 相关注解 ConfigurationProperties(prefix "前缀名")该注解用于自动配置的绑定&#xff0c;可以将application.properties配置中的值注入到 Bean…...

Java多线程(四)---并发编程容器

1.经常使用什么并发容器&#xff0c;为什么&#xff1f;答&#xff1a;Vector、ConcurrentHashMap、HasTable一般软件开发中容器用的最多的就是HashMap、ArrayList&#xff0c;LinkedList &#xff0c;等等但是在多线程开发中就不能乱用容器&#xff0c;如果使用了未加锁&#…...

Apache Hadoop生态部署-Flume采集节点安装

目录 Apache Hadoop生态-目录汇总-持续更新 一&#xff1a;安装包准备 二&#xff1a;安装与常用配置 2.1&#xff1a;下载解压安装包 2.2&#xff1a;解决guava版本问题 2.3&#xff1a;修改配置 三&#xff1a;修复Taildir问题 3.1&#xff1a;Taildir Source能断点续…...

【OpenFOAM】-算例解析合集

【OpenFOAM】-算例解析合集OlaFlowinterFoamOlaFlow 【OpenFOAM】-olaFlow-算例1- baseWaveFlume 【OpenFOAM】-olaFlow-算例2- breakwater 【OpenFOAM】-olaFlow-算例3- currentWaveFlume 【OpenFOAM】-olaFlow-算例4- irreg45degTank 【OpenFOAM】-olaFlow-算例5- oppositeS…...

数据库|(一)数据库和SQL概述

&#xff08;一&#xff09;数据库和SQL概述1.1 数据库的好处1.2 数据库的概念1.3 数据库结构特点1.1 数据库的好处 实现数据持久化使用完整的管理系统统一管理&#xff0c;便于查询 1.2 数据库的概念 DB 数据库&#xff08;database&#xff09;&#xff0c;存储数据的仓库&…...

【java基础】自定义类

文章目录基本介绍自定义类字段方法构造器main方法基本介绍 什么是类这里就不过多赘述了&#xff0c;这里来介绍关于类的几个名词 类是构造对象的模板或蓝图由类构造对象的过程称为创建类的实例封装就是将数据和行为组合在一个包中&#xff0c;并对对象的使用者隐藏具体的实现…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...