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

基于opencv的图片加水印实现方案

加水印应该是个很常见的需求,但是网上找的代码,都感觉不太完善。记录下自己搞出来的一个方案

水印有几个需求:

  1. 中文文字水印
  2. 文字倾斜
  3. 满图都是,而不是只有一个地方
  4. 水印文字所在之处完全展示水印

实现思路

准备水印图

我是这么做的,先手工生成一张水印图,尺寸比较小,约100*100。然后也把文字旋转一定角度(当然如果想做随机角度,也是可以的,代码再复杂点处理就好,不影响这个思路),生成这个一张图片,并且底色选择纯黑

然后用opencv将水印图处理出一张二值图

这个也不难,底色纯黑,这个很好做,用Threshold很容易实现。

然后用水印图和原图叠加

核心就是要用copyTo(),然后要输入mask,二值图的作用就是做掩码,这样就可以实现完美的水印添加效果。

其他细节

因为水印图通常是远小于原图的(其实也可以采用其他方案。总之核心就是要有水印图和对应的二值图),如何实现满图添加水印呢?

思路

  1. 先设置一个水印的间距,比如两百个像素点
  2. 然后通过水印图的大小和间距的值,计算生成一个尺寸大于等于原图的纯水印图(同时也要有二值图)
  3. 然后裁剪成和原图一样大
  4. 然后用copyTo()叠加即可。

这样性能很高,go语言实现处理一张图片不超过10ms。

go语言实现

思路都是通用的,基于opencv都能实现。


func ImgWatermark(img gocv.Mat) {var Watermark gocv.Matvar WatermarkBW gocv.Mat// 不同通道的图片使用不同的水印if img.Channels() == 3 {// 判断是否需要初始化if g.Watermark3 == nil { // g. 是我设置的全局变量,因为不需要每次都加载水印。加载一次到内存即可mat := gocv.IMRead(`watermark.jpg`, gocv.IMReadColor)g.Watermark3 = &mat// 取得水印图片的二值图watermarkGray3 := gocv.NewMat()gocv.CvtColor(*g.Watermark3, &watermarkGray3, gocv.ColorBGRToGray)// 取得二值图的黑白图BW3 := gocv.NewMat()g.WatermarkBW3 = &BW3gocv.Threshold(watermarkGray3, g.WatermarkBW3, 30, 255, gocv.ThresholdBinary)watermarkGray3.Close()}Watermark = *g.Watermark3WatermarkBW = *g.WatermarkBW3} else if img.Channels() == 4 {// 判断是否需要初始化if g.Watermark4 == nil {mat := gocv.IMRead(`watermark.jpg`, gocv.IMReadColor)g.Watermark4 = &matgocv.Merge([]gocv.Mat{mat, gocv.NewMatWithSizeFromScalar(gocv.NewScalar(255, 255, 255, 255), mat.Rows(), mat.Cols(), gocv.MatTypeCV8UC1)}, g.Watermark4)// 取得水印图片的二值图watermarkGray4 := gocv.NewMat()gocv.CvtColor(*g.Watermark4, &watermarkGray4, gocv.ColorBGRToGray)// 取得二值图的黑白图BW4 := gocv.NewMat()g.WatermarkBW4 = &BW4gocv.Threshold(watermarkGray4, g.WatermarkBW4, 30, 255, gocv.ThresholdBinary)watermarkGray4.Close()}Watermark = *g.Watermark4WatermarkBW = *g.WatermarkBW4} else if img.Channels() == 1 {// 判断是否需要初始化if g.Watermark1 == nil {mat := gocv.IMRead(`watermark.jpg`, gocv.IMReadGrayScale) // 直接读取黑白g.Watermark1 = &mat// 取得二值图的黑白图BW1 := gocv.NewMat()g.WatermarkBW1 = &BW1gocv.Threshold(mat, g.WatermarkBW1, 30, 255, gocv.ThresholdBinary)}Watermark = *g.Watermark1WatermarkBW = *g.WatermarkBW1} else {fmt.Println("图片通道数不支持", img.Channels())return}waterCol := Watermark.Cols()waterRow := Watermark.Rows()// 为了水印不要那么密,这里设置一个间距spacing := 200 // 水印的间距waterColAndSpacing := waterCol + spacingwatreRowAndSpacing := waterRow + spacing// 计算需要添加水印的次数watermarkHugeRowTimes := int(math.Ceil(float64(img.Rows()) / float64(watreRowAndSpacing))) //向上取整watermarkHugeColTimes := int(math.Ceil(float64(img.Cols()) / float64(waterColAndSpacing)))imgRect := image.Rectangle{} //每次取图像中哪一块区域的数据结构var croppedMat gocv.Mat      // 每次从原图像上裁剪和水印图像一样大小的一块newWater := WatermarknewWaterBW := WatermarkBW// 循环一块块的给图像添加水印for i := 0; i < watermarkHugeColTimes; i++ {imgRect.Min.X = i * waterColAndSpacingimgRect.Max.X = imgRect.Min.X + waterColif imgRect.Max.X >= img.Cols() { // 判断是否超出原图像的列数imgRect.Max.X = img.Cols()}for j := 0; j < watermarkHugeRowTimes; j++ {imgRect.Min.Y = j * watreRowAndSpacingimgRect.Max.Y = imgRect.Min.Y + waterRowif imgRect.Max.Y >= img.Rows() { // 判断是否超出原图像的行数imgRect.Max.Y = img.Rows()}croppedMat = img.Region(imgRect) // 原图像中一块区域的浅拷贝,修改它会连带修改原图像// 判断裁剪的图像大小是否与水印图像大小一致,不一致则需要重新裁剪if croppedMat.Rows() != Watermark.Rows() || croppedMat.Cols() != Watermark.Cols() {newRect := image.Rectangle{Min: image.Point{X: 0, Y: 0}, Max: image.Point{X: croppedMat.Cols(), Y: croppedMat.Rows()}}newWater = Watermark.Region(newRect)newWaterBW = WatermarkBW.Region(newRect)}newWater.CopyToWithMask(&croppedMat, newWaterBW) // 用水印图覆盖原图像}}
}

相关文章:

基于opencv的图片加水印实现方案

加水印应该是个很常见的需求&#xff0c;但是网上找的代码&#xff0c;都感觉不太完善。记录下自己搞出来的一个方案 水印有几个需求&#xff1a; 中文文字水印文字倾斜满图都是&#xff0c;而不是只有一个地方水印文字所在之处完全展示水印 实现思路 准备水印图 我是这么…...

STM32 IAP 需要关注的一些事

1、首先要知道STM32的程序是如何分布在FLASH中的。 2、升级的时候涉及到两个程序&#xff0c;一个是bootloader&#xff0c;一个是user程序&#xff0c;这两个程序的功能分别的什么作用的&#xff1f; 3、编译的固件是怎么分布的&#xff1f;通过那个配置文件去指导编译器去排布…...

高并发服务器-使用多进程(Multi-Process)实现【C语言】

在上期的socket套接字的使用详解中&#xff08;socket套接字的使用详解&#xff09;最后实现的TCP服务器只能处理一个客户端的请求发送&#xff0c;当有其他客户端请求连接时会被阻塞。为了能同时处理多个客户端的连接请求&#xff0c;本期使用多进程的方式来解决。 解决方案步…...

线程控制

对线程的控制思路和进程相似&#xff0c;创建、等待、终止&#xff0c;只需要调用接口就行。但是在Linux下没有线程的概念&#xff0c;因为Linux的设计者认为&#xff0c;线程是一种轻量级的进程&#xff0c;毕竟创建线程只需要创建PCB。因此Linux中使用多线程必须使用第三方pt…...

Spring Data Jpa 原生SQL联表查询返回自定义DTO

Spring Data Jpa 原生SQL联表查询返回自定义DTO 方案一&#xff1a;返回Map 这个就不说了 方案二&#xff1a;实体定义成接口的形式 该方式最直观&#xff01;&#xff01;推荐&#xff01;&#xff01;&#xff01; 注意&#xff1a;XxxDto是interface接口&#xff0c;而…...

Hadoop3:HDFS存储优化之小文件归档

一、情景说明 我们知道&#xff0c;NameNode存储一个文件元数据&#xff0c;默认是150byte大小的内存空间。 那么&#xff0c;如果出现很多的小文件&#xff0c;就会导致NameNode的内存占用。 但注意&#xff0c;存储小文件所需要的磁盘容量和数据块的大小无关。 例如&#x…...

Golang | Leetcode Golang题解之第234题回文链表

题目&#xff1a; 题解&#xff1a; func reverseList(head *ListNode) *ListNode {var prev, cur *ListNode nil, headfor cur ! nil {nextTmp : cur.Nextcur.Next prevprev curcur nextTmp}return prev }func endOfFirstHalf(head *ListNode) *ListNode {fast : headslo…...

Unity Apple Vision Pro 开发(四):体积相机 Volume Camera

文章目录 &#x1f4d5;教程说明&#x1f4d5;教程内容概括&#x1f4d5;体积相机作用&#x1f4d5;创建体积相机&#x1f4d5;添加体积相机配置文件&#x1f4d5;体积相机配置文件参数&#x1f4d5;体积相机的边界盒大小&#x1f4d5;体积相机边界盒大小和应用边界盒大小的区别…...

C语言 | Leetcode C语言题解之第231题2的幂

题目&#xff1a; 题解&#xff1a; const int BIG 1 << 30;bool isPowerOfTwo(int n) {return n > 0 && BIG % n 0; }...

GitHub备份代码的学习笔记

1. 备份工具&#xff1a;GitHub CLI 2. 认证方式 2.1 公用云服务器&#xff1a;SSH 可以通过使用GitHub CLI(命令行界面)在本地创建一个新的GitHub仓库,并直接使用本地项目代码文件夹的名称作为仓库名称,无需手动输入相同的名称。这可以通过以下步骤实现: 首先,确保您已安装…...

微信小程序与本地MySQL数据库通信

微信小程序与本地MySQL数据库通信 因为本地MySQL服务器没有域名&#xff0c;也没有进行相应的请求操作封装&#xff0c;因此微信小程序没办法和数据库通信。 但是对于开发人员来说&#xff0c;没有数据库&#xff0c;那还能干撒&#xff1f;虽然我尝试过用json-server&#x…...

Flutter热更新技术探索

一&#xff0c;需求背景&#xff1a; APP 发布到市场后&#xff0c;难免会遇到严重的 BUG 阻碍用户使用&#xff0c;因此有在不发布新版本 APP 的情况下使用热更新技术立即修复 BUG 需求。原生 APP&#xff08;例如&#xff1a;Android & IOS&#xff09;的热更新需求已经…...

【机器学习-00】机器学习是什么?

在科技飞速发展的今天&#xff0c;机器学习已成为一个热门话题&#xff0c;广泛应用于各个行业和领域。那么&#xff0c;机器学习到底是什么&#xff1f;它又是如何工作的&#xff1f;本文将深入探讨机器学习的定义、原理及其在各领域的应用&#xff0c;带领读者走进这个神秘而…...

【BUG】已解决:WslRegisterDistribution failed with error: 0x800701bc

已解决&#xff1a;WslRegisterDistribution failed with error: 0x800701bc 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武…...

无人机监测的必要性及方法

为什么需要无人机探测&#xff1f; 无人机的快速发展和广泛使用为各个行业带来了巨大好处&#xff0c;包括送货服务、农业和监控。然而&#xff0c;这种扩散也导致滥用现象增多&#xff0c;造成非法入侵空域、侵犯隐私和安全威胁。监控和探测在特定空域盘旋的无人机的能力变得…...

PHP框架详解:Symfony框架

Symfony是一个功能强大且高度灵活的PHP框架&#xff0c;广泛应用于企业级项目和复杂的Web应用开发。本文将详细介绍Symfony框架的主要特性&#xff0c;并通过实例展示其强大功能。 1. 什么是Symfony&#xff1f; Symfony是一个基于MVC&#xff08;模型-视图-控制器&#xff0…...

在 Navicat BI 创建自定义字段:类型更改字段

早在 Navicat 17 的预览版中&#xff0c;我们就已经介绍了一些新的商业智能&#xff08;BI&#xff09;功能&#xff0c;即图表互动和计算字段。需要说明的是&#xff0c;计算字段不是 Navicat BI 中唯一可用的自定义字段类型。事实上&#xff0c;有五种&#xff1a;类型改变、…...

llama-index,uncharted and llama2:7b run locally to generate Index

题意&#xff1a;本地运行 llama-index、uncharted 以及 llama2:7b 来生成索引 问题背景&#xff1a; I wanted to use llama-index locally with ollama and llama3:8b to index utf-8 json file. I dont have a gpu. I use uncharted to convert docs into json. Now If it …...

vue、js截取视频任意一帧图片

html有本地上传替换部分&#xff0c;可以不看 原理&#xff1a;通过video标签对视频进行加载&#xff0c;随后使用canvas对截取的视频帧生成需要的图片 <template> <el-row :gutter"18" class"preview-video"><h4>视频预览<span&…...

STM32智能家居系统教程

目录 引言环境准备智能家居系统基础代码实现&#xff1a;实现智能家居系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;家居智能化管理问题解决方案与优化收尾与总结 1. 引言 智能家居系统通过STM32嵌入…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南

1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发&#xff0c;使用DevEco Studio作为开发工具&#xff0c;采用Java语言实现&#xff0c;包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解

文章目录 一、开启慢查询日志&#xff0c;定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...

Linux安全加固:从攻防视角构建系统免疫

Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)

零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...