【数据安全-02】AI打假利器数字水印,及java+opencv实现


AIGC 的火爆引燃了数字水印,说实话数字水印并不是一项新的技术,但是这时候某些公司拿出来宣传一下特别应景,相应股票蹭蹭地涨。数字水印是什么呢,顾名思义,和我们在pdf中打的水印作用差不多,起到明确版权、防伪验真的作用。但是不同于传统肉眼可见的水印,数字水印也叫隐藏式水印,能够在人眼几乎无法察觉的情况下将水印信息秘密嵌入到音频、图像或视频中去,除了减少对画质的影响外,有个重要的功能就是保护著作权,使得盗版者无法感知水印存在,让版权的鉴定的溯源变得更轻松。

提到数字水印,有个经典案例经常被提到,阿里巴巴的一名员工擅网页截图外传,造成很大的恶劣影响,结果利用数字水印,很快就定位到这名员工,这名员工还奇怪,发的时候我还特意留意图片上没有水印,怎么就能定位到我呢,可见数字水印的强大,数字水印于无形中发挥着强大作用。

背后的科学奥秘

那么数字水印到底是如何实现的呢,那就不得不提到傅里叶变换,任何函数都可以写成正弦函数之和,用直白的话说就是任何二维空间的波形,都可以用简单的正弦和余弦波叠加而成,傅里叶变换交互式入门这篇文章详细的可视化地介绍了这一原理,并且我们可以随意画一条线看看,是不是可以由多条正弦波叠加而成。

在这里插入图片描述
我们拓展到三维空间,同样的道理,任何凹凸不平的面都可以用正弦平面波叠加而成,如下图所示,看一下大脑的图片,是一张灰度图,每个像素都有灰度值,我们加个坐标,Z轴为灰度值的大小,这样整张图就成为凹凸不平的曲面,那么这张凹凸不平的曲面就可以用多个正弦平面波叠加而成,所以在我们也可以用这些个正弦平面波来存储这张图,简而言之,我们能将图像用频域表示,接下来我们聊聊二维频率域K-SPACE,或者叫傅里叶空间
在这里插入图片描述

对于正弦平面波,可以这样理解,在一个方向上存在一个正弦函数,在法线方向上将其拉伸。前面说过三个参数可以确定一个一维的正弦波。哪几个参数可以确定一个二维的正弦平面波呢?答案是四个,其中三个和一维的情况一样,即频率ω,幅度A,相位φ,但是具有相同这些参数的平面波却可以有不同的方向 n ⃗ \vec{n} n ,如下图所示,频率ω,幅度A,相位φ,都有一样,方向 n ⃗ \vec{n} n ,两个平面波叠加出来的效果。

在这里插入图片描述

类比一维中,幅度和相位可以用一个复数表示,它可以作为我们存储的内容。但是还有两个:一个频率一个方向。这时想到向量是有方向的,也是有长度的。所以我们用一个二维的矩阵的来保存分解之后得到的信息。这个矩阵就是K空间。就是说一个二维矩阵点 (μ ,ν) 代表这个平面波的法向量 n ⃗ \vec{n} n ,这个向量的模 μ 2 + ν 2 \sqrt{\mu^2+\nu^2} μ2+ν2 代表这个平面波的频率ω ,这个点里面保存的内容复数就是此平面波的幅度和相位。

复数(complex number):形如a+bi(a、b均为实数)的数为复数,其中,a被称为实部,b被称为虚部,i为虚数单位。

K空间(K Space):也称傅里叶空间,k空间是寻常空间在傅利叶转换下的对偶空间。“K”代表什么,字母“k”在光学、声学、力学和电磁学领域已经使用了一个多世纪,k=1/ λ \lambda λ,其中 λ \lambda λ表示波长,因此,k是每单位距离的波数或周期数。

在这里插入图片描述

好了讲到这里,我们回归正题,数字水印到底怎么实现,以下就是基本流程,简单步骤如下:

  • 原始图像A经过傅里叶变换得到K空间图像B。
  • 将水印内容写到K空间图像B,得到叠加图像C。
  • 对图像C进行傅里叶逆变换等到添加了数字水印的图像D,该图像D在视觉上和A没什么区别。
    在这里插入图片描述

那么解密流程就很简单了,对D进行一次傅里叶变换就能得到图像C,视觉上就能看到水印了。

java+OpenCV实现

可以参考这篇文章opencv的java-maven-idea开发环境配置进行配置OpenCV的开发环境。我用的opencv的4.6.0版本,下面是数据盲水印的java代码实现,仅供学习参考:

package tools;

import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.List;

public class ImgWatermarkUtil {
    private static List<Mat> planes = new ArrayList<Mat>();
    private static List<Mat> allPlanes = new ArrayList<Mat>();
    public static Mat addImageWatermarkWithText(Mat image, String watermarkText){
        Mat complexImage = new Mat();
        Mat padded = splitSrc(image);
        padded.convertTo(padded, CvType.CV_32F);
        planes.add(padded);
        planes.add(Mat.zeros(padded.size(), CvType.CV_32F));
        Core.merge(planes, complexImage);
        Core.dft(complexImage, complexImage);
        Scalar scalar = new Scalar(0, 0, 0);
        Point point = new Point(40, 40);
        Imgproc.putText(complexImage, watermarkText, point, Imgproc.FONT_HERSHEY_DUPLEX, 1D, scalar);
        Core.flip(complexImage, complexImage, -1);
        Imgproc.putText(complexImage, watermarkText, point, Imgproc.FONT_HERSHEY_DUPLEX, 1D, scalar);
        Core.flip(complexImage, complexImage, -1);
        return antitransformImage(complexImage, allPlanes);
    }
    public static Mat getImageKSpace(Mat image){
        Mat complexImage = new Mat();
        Mat padded = splitSrc(image);
        padded.convertTo(padded, CvType.CV_32F);
        planes.add(padded);
        planes.add(Mat.zeros(padded.size(), CvType.CV_32F));
        Core.merge(planes, complexImage);
        Core.dft(complexImage, complexImage);
        Scalar scalar = new Scalar(0, 0, 0);
        Point point = new Point(40, 40);
        Mat magnitude = createOptimizedMagnitude(complexImage);
        planes.clear();
        return magnitude;
    }
    
    public static Mat getImageWatermarkWithText(Mat image){
        List<Mat> planes = new ArrayList<Mat>();
        Mat complexImage = new Mat();
        Mat padded = splitSrc(image);
        padded.convertTo(padded, CvType.CV_32F);
        planes.add(padded);
        planes.add(Mat.zeros(padded.size(), CvType.CV_32F));
        Core.merge(planes, complexImage);
        Core.dft(complexImage, complexImage);
        Mat magnitude = createOptimizedMagnitude(complexImage);
        planes.clear();
        return magnitude;
    }

    private static Mat splitSrc(Mat mat) {
        mat = optimizeImageDim(mat);
        Core.split(mat, allPlanes);
        Mat padded = new Mat();
        if (allPlanes.size() > 1) {
            for (int i = 0; i < allPlanes.size(); i++) {
                if (i == 0) {
                    padded = allPlanes.get(i);
                    break;
                }
            }
        } else {
            padded = mat;
        }
        return padded;
    }
    private static Mat antitransformImage(Mat complexImage, List<Mat> allPlanes) {
        Mat invDFT = new Mat();
        Core.idft(complexImage, invDFT, Core.DFT_SCALE | Core.DFT_REAL_OUTPUT, 0);
        Mat restoredImage = new Mat();
        invDFT.convertTo(restoredImage, CvType.CV_8U);
        if (allPlanes.size() == 0) {
            allPlanes.add(restoredImage);
        } else {
            allPlanes.set(0, restoredImage);
        }
        Mat lastImage = new Mat();
        Core.merge(allPlanes, lastImage);
        return lastImage;
    }

   private static Mat optimizeImageDim(Mat image) {
        Mat padded = new Mat();
        int addPixelRows = Core.getOptimalDFTSize(image.rows());
        int addPixelCols = Core.getOptimalDFTSize(image.cols());
        Core.copyMakeBorder(image, padded, 0, addPixelRows - image.rows(), 0, addPixelCols - image.cols(),
                Core.BORDER_CONSTANT, Scalar.all(0));


        return padded;
    }
    private static Mat createOptimizedMagnitude(Mat complexImage) {
        List<Mat> newPlanes = new ArrayList<Mat>();
        Mat mag = new Mat();
        Core.split(complexImage, newPlanes);
        Core.magnitude(newPlanes.get(0), newPlanes.get(1), mag);
        Core.add(Mat.ones(mag.size(), CvType.CV_32F), mag, mag);
        Core.log(mag, mag);
        shiftDFT(mag);
        mag.convertTo(mag, CvType.CV_8UC1);
        Core.normalize(mag, mag, 0, 255, Core.NORM_MINMAX, CvType.CV_8UC1);
        return mag;
    }
    private static void shiftDFT(Mat image) {
        image = image.submat(new Rect(0, 0, image.cols() & -2, image.rows() & -2));
        int cx = image.cols() / 2;
        int cy = image.rows() / 2;

        Mat q0 = new Mat(image, new Rect(0, 0, cx, cy));
        Mat q1 = new Mat(image, new Rect(cx, 0, cx, cy));
        Mat q2 = new Mat(image, new Rect(0, cy, cx, cy));
        Mat q3 = new Mat(image, new Rect(cx, cy, cx, cy));
        Mat tmp = new Mat();
        q0.copyTo(tmp);
        q3.copyTo(q0);
        tmp.copyTo(q3);
        q1.copyTo(tmp);
        q2.copyTo(q1);
        tmp.copyTo(q2);
    }
}
import org.opencv.imgcodecs.Imgcodecs;

import java.net.URL;

import static org.opencv.imgcodecs.Imgcodecs.imread;
import static org.opencv.imgcodecs.Imgcodecs.imwrite;

public class Main {
    static{
        loadDll();
    }
    public static void main(String[] args){
        Mat img = imread("E:/software/opencv/Img.jpg");
        Mat kSpaceImg = ImgWatermarkUtil.getImageKSpace(img);
        Mat outImg = ImgWatermarkUtil.addImageWatermarkWithText(img,"zhulangfly");
        imwrite("E:/software/opencv/Img-kSpaceImg.jpg",kSpaceImg);
        imwrite("E:/software/opencv/Img-out.jpg",outImg);
        Mat watermarkImg = ImgWatermarkUtil.getImageWatermarkWithText(outImg);
        imwrite("E:/software/opencv/Img-watermark.jpg",watermarkImg);
    }
    public static void loadDll() {
        System.setProperty("java.awt.headless", "false");
        System.out.println(System.getProperty("java.library.path"));
        URL url = ClassLoader.getSystemResource("dlls/opencv_java460.dll");
        System.load(url.getPath());
    }
}
    <dependency>
         <groupId>org.openpnp</groupId>
         <artifactId>opencv</artifactId>
         <version>4.6.0-0</version>
   </dependency>

参考文献

  • 数字水印技术在前端落地的思考
  • 阿里巴巴公司根据截图查到泄露信息的具体员工的技术是什么?
  • 通俗讲解:图像傅里叶变换
  • 形象理解二维傅里叶变换
  • 傅里叶变换交互式入门
  • opencv的java-maven-idea开发环境配置
  • Java使用OpenCV:基于DCT变换 实现 图片 数字 的盲水印添加和提取

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

拉货搬家货运APP开发分析和功能列表

作为国家经济发展的重要基础设施&#xff0c;物流行业正在面对转型升级的风口。巨大的市场体量&#xff0c;也迎来了激烈的市场竞争。为了从同质化的服务中脱颖而出&#xff0c;开拓更大的市场&#xff0c;并且解决线下司机的载货痛点&#xff0c;货运APP的开发必不可少。 开发…

firewalld防火墙

firewalld防火墙 1&#xff1a;firewalld概述 firewalld防火墙是Centos7系统默认的防火墙管理工具&#xff0c;取代了之前的iptables防火墙&#xff0c;也是工作在网络层&#xff0c;属于包过滤防火墙。firewalld和iptables都是用来管理防火墙的工具&#xff08;属于用户态&a…

学习如何将Jenkins与UI测试报告完美整合,事半功倍,轻松获取高薪职位!

目录 引言 &#xff08;一&#xff09;在本地整合出报告 1.在cmd分别安装pytest和allure-pytest 2.进入需要执行的代码所在的路径 3.运行测试报告&#xff0c;代码如下 4.解析此json文件&#xff0c;代码如下&#xff08;新打开cmd进入路径&#xff09; 5.打开此HTML文件…

在CTEX文档生成中使用WinEit编辑带有公式符号的中文文档应用举例

CTEX文档生成中使用WinEit编辑带有公式符号的中文文档应用举例 CTEX在编辑文档格式和排版时具有优秀的性能&#xff0c;可批量处理文档格式&#xff0c;该用格式时候也非常快捷。下面举例介绍CTEX文档生成中怎样使用WinEit编辑带有公式符号的中文文档。 1.需要的代码 .在WinEi…

TPlinker解读

参考&#xff1a; 关系抽取之TPLinker解读加源码分析 TPLinker 实体关系抽取代码解读 实体关系联合抽取&#xff1a;TPlinker TPLinker中文注释版 Tagging TPLinker模型需要对关系三元组(subject, relation, object)进行手动Tagging&#xff0c;过程分为三部分&#xff1a; &…

springboot+java大学生新生入学报到报道系统+jsp004

新生报到系统分为学院管理员&#xff0c;宿舍管理员&#xff0c;财务管理员&#xff0c;辅导员&#xff0c;学生五种登录身份 学院管理员界面登入后台后有个人信息的展示&#xff0c;可对余下的四种身份信息进行增删改查&#xff0c;可进行对高考信息的导入导出&#xff0c;对报…

(三)ArcGIS空间数据的转换与处理——栅格数据变换

ArcGIS空间数据的转换与处理——栅格数据变换 目录 ArcGIS空间数据的转换与处理——栅格数据变换 1.地理配准2.平移3.扭曲4.旋转5.翻转6.重设比例尺7.镜像 数据变换是指对数据进行诸如放大、缩小、翻转、移动、扭曲等几何位置、形状和方位的改变等操作。对于 栅格数据的相应操…

chatgpt赋能python:Pythonsearchsorted:用于搜索排序数组的快速工具

Python searchsorted&#xff1a;用于搜索排序数组的快速工具 在Python编程中&#xff0c;有时需要在有序数组中快速查找值的位置。Python searchsorted工具提供了一种快速而高效的方法&#xff0c;可用于在已排序的数组中搜索值的位置。在本文中&#xff0c;将深入探讨Python…

震惊!人工智能引发灰色经济,ChatGPT变身罪魁祸首!

人工智能技术的日益发展和普及&#xff0c;其呈现出无边界的开发空间&#xff0c;引领出无数的商业应用&#xff0c;越来越多的领域开始依赖这一技术&#xff0c;各种应用场景日益丰富&#xff0c;而其内在的巨大潜力也被不断开发。随之而来的则是&#xff0c;因为技术的滥用和…

java泛型详解

一、什么是泛型&#xff1f; 泛型&#xff08;Generic type 或者 generics&#xff09;是对 Java 语言的类型系统的一种扩展&#xff0c;以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符&#xff0c;就像方法的形式参数是运…

最全iOS 上架指南

一、基本需求信息。 1、苹果开发人员账户&#xff08;公司已经可以无需申请&#xff0c;需要开启开发者功能&#xff0c;每年99美元&#xff09; 2、开发好应用程序 二、证书 上架版本需要使用正式证书。 1、创建Apple Developer证书 2、上传证书Sign In - Apple 3、点击开发者…

TCP通讯(三次握手、四次挥手;滑动窗口;TCP状态转换;端口复用;TCP心跳检测机制)

前言&#xff1a;建议看着图片&#xff0c;根据文字描述走一遍TCP通讯过程&#xff0c;加深理解。 目录 TCP通信时序&#xff1a; 1&#xff09;建立连接&#xff08;三次握手&#xff09;的过程&#xff1a; 2&#xff09;数据传输的过程&#xff1a; 3&#xff09;关闭连…

【笔试强训编程题】Day5.( 统计回文 45842 ) 和( 连续最大和 58539)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训编程题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;! 文章目录…

C++内存管理 (new、delete)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏

绪论 我成功是因为我有决心&#xff0c;从不踌躇。——拿破仑 本章是关于c内存管理的文章&#xff0c;字数不多&#xff0c;内容简单&#xff0c;希望对你有所帮助&#xff01;&#xff01; 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&a…

windows下cplex20.1.0的下载、安装、IDE编程及相关问题解决

其他文章&#xff1a; 通过0-1背包问题看穷举法、贪心算法、启发式算法&#xff08;JAVA) 模拟退火(SA)算法实例介绍&#xff08;JAVA) 遗传算法&#xff08;GA&#xff09;实例介绍&#xff08;JAVA) CPLEX求解器入门案例 java集成Cplex&#xff1a;Cplex下载、IDEA环境搭…

20230522-win11删除文件失败-需要SYSTEM提供的权限

20230522-win11删除文件失败-需要SYSTEM提供的权限 一、软件环境 标签&#xff1a;win11 SYSTEM权限分栏&#xff1a;windows编译器&#xff1a;VS2019 二、问题描述 删除D:\WindowsApps\36186RuoFan.USB_5.8.1.0_x64__q3e6crc0w375t目录下的文件时&#xff0c;提示【文件访…

聊聊我在阿里第一年375晋升的心得

前言 思来想去&#xff0c;觉得这个事情除了领导赏识大佬抬爱之外&#xff0c;还是挺不容易的&#xff0c;主观认为有一定的参考价值&#xff0c;然后也是复盘一下&#xff0c;继续完善自己。 绩效 首先晋升的条件就是要有个好绩效&#xff0c;那么我们就先基于绩效这个维度…

视频理解学习笔记(一):双流卷积神经网络

视频理解学习笔记&#xff08;一&#xff09;&#xff1a;双流卷积神经网络 两句话总结双流卷积神经网络论文概览方法详解Spatial stream ConvNetTemporal stream ConvNet测试方法 光流什么是光流怎么预处理光流 数据集UCF101&#xff08;已被刷爆&#xff09;HMDB51 Experimen…

JavaScript 中如何计算代码段运行时间

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是全栈 …

为什么C++这么复杂还不被淘汰?

C是一门广泛使用的编程语言&#xff0c;主要用于系统和应用程序的开发。尽管C具有一些复杂的语法和概念&#xff0c;但它仍然是编程界的重量级选手&#xff0c;在编程语言排行榜中一直位居前列。为什么C这么复杂还不被淘汰呢&#xff1f; C有以下优势 1、C具有高性能 C是一门编…
最新文章