当前位置: 首页 > 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) // 用水印图覆盖原图像}}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • ubuntu 通讯学习笔记
  • GESP CCF C++ 三级认证真题 2024年6月
  • 常用的设计模式有哪些
  • List数据的几种数据输出方式
  • Qt中 .pro、.pri、.prf、.prl文件简解
  • LG 选择 Flutter 来增强其智能电视操作系统 webOS
  • winform 去掉Chart左侧空白
  • 智慧水利:迈向水资源管理的新时代,结合物联网、云计算等先进技术,阐述智慧水利解决方案在提升水灾害防控能力、优化水资源配置中的关键作用
  • 电脑显示mfc140u.dll丢失的修复方法,总结7种有效的方法
  • WHAT - Tailwind CSS 的灵活布局(Flex Grid)
  • live555 rtsp服务器实战之doGetNextFrame
  • 生成式 AI 的发展方向,是 Chat 还是 Agent?
  • CSS基础学习之元素定位(6)
  • CentOS软件安装与vim使用操作
  • 嵌入式linux相机 转换模块
  • CSS盒模型深入
  • Electron入门介绍
  • gitlab-ci配置详解(一)
  • HTTP请求重发
  • iOS 系统授权开发
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • npx命令介绍
  • NSTimer学习笔记
  • Python学习之路16-使用API
  • React as a UI Runtime(五、列表)
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 从零搭建Koa2 Server
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 给新手的新浪微博 SDK 集成教程【一】
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 使用putty远程连接linux
  • 双管齐下,VMware的容器新战略
  • 仓管云——企业云erp功能有哪些?
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​Benvista PhotoZoom Pro 9.0.4新功能介绍
  • (4)STL算法之比较
  • (Oracle)SQL优化基础(三):看懂执行计划顺序
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (佳作)两轮平衡小车(原理图、PCB、程序源码、BOM等)
  • (每日一问)操作系统:常见的 Linux 指令详解
  • (七)Knockout 创建自定义绑定
  • (游戏设计草稿) 《外卖员模拟器》 (3D 科幻 角色扮演 开放世界 AI VR)
  • (转)关于pipe()的详细解析
  • .Net Core中Quartz的使用方法
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .Net转Java自学之路—基础巩固篇十三(集合)
  • .php结尾的域名,【php】php正则截取url中域名后的内容
  • @NestedConfigurationProperty 注解用法
  • @property括号内属性讲解
  • @requestBody写与不写的情况
  • @vue/cli 3.x+引入jQuery
  • [04]Web前端进阶—JS伪数组