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

news2024/11/19 8:44:54

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.coloradmin.cn/o/569238.html

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

相关文章

【拒绝爆零】C++编程考试常见栽区

前言 在OI赛制中&#xff0c;我们可能会因为一些细节原因导致题目爆零。下面&#xff0c;是我列举的一些常见的坑&#xff1a; 1.极值未赋值 这个错误在运行时就能检查出来&#xff0c;但还是会浪费一定的时间&#xff0c;所以我们还是避开这些小插曲为好。 2.定义变量遇到…

利用无代码工具开发一款小程序

目录 无代码工具开发小程序的流程需求分析阶段模型设计阶段页面搭建阶段创建项目创建数据表组件搭建 预览发布总结 日常我们开发小程序的时候都是要从写代码开始&#xff0c;但是写代码这个事只有专业开发才可以干&#xff0c;那作为普通人&#xff0c;如果也希望开发小程序&am…

前端小工具:批量修改图片信息

前端小工具一&#xff1a;批量修改文件夹里面的图片名称 步骤&#xff1a; 1.安装nodejs。 2.根据需要修改editFileName(filePath, formatName)函数的参数&#xff0c;也可以不改&#xff0c;直接将renameFile.js和img文件夹放在同一个目录下。 3.在renameFile.js目录下开启…

Linux:ext文件系统配额

1. 创建三个用户test1 test2 test3 2. 创建一个组 test_23 3. 把 test2 和 test3 加入test_23组 首先要有quota这个软件 如果没有用yum安装 yum -y install quota 如果不会搭建yum Linux&#xff1a;rpm查询安装 && yum安装_鲍海超-GNUBHCkalitarro的博客…

计算机组成原理-存储系统-缓存存储器(Cache)

目录 一、Cache基本概念 1.2性能分析 二、 Cache和主存的映射发生 ​​​​​​2.1全相连映射​编辑 2.2直接映射​编辑 2.3组相连映射 三、Cachae的替换算法 3.1 随机算法(RADN) 3.2 先进先出算法(FIFO) 3.3 近期最少使用(LRU) 3.4 最近不经常使用(LFU) 四、写策略 4…

kali安装ARL灯塔过程

&#xff08;一&#xff09;安装docker环境 1、检查是否存在docker环境 docker 2、如果没有docker&#xff0c;就先安装docker apt install docker.io 出现 unable to locate package docker.io这些&#xff0c;这是因为没有跟新 输入跟新命令&#xff1a; apt-get update 在…

把ChatGPT的所有插件整理成中文后!真要说卧槽了..

大家好&#xff0c;我是五竹。 ChatGPT如约向用户开放了联网功能和众多插件&#xff0c;五竹从上周开始满怀着热情等待着&#xff0c;看别人的测评效果都快把我羡慕哭了。最终等来的却是Plus账号给封了&#xff0c;而且至今也没有续上&#xff0c;只能说非常无奈。算了&#x…

探究低代码平台解决企业痛点的能力

近年来&#xff0c;随着越来越多的公司寻找改善数字化转型过程的方法&#xff0c;低代码平台的受欢迎程度一直在上升。低代码平台允许以最小的编码要求创建软件应用程序&#xff0c;从而减少与传统软件开发相关的时间和成本。今天&#xff0c;小编将聊一聊低代码平台能解决哪些…

华为设备这14个广域网命令,值得每位做广域网业务的网工收藏!

你好&#xff0c;这里是网络技术联盟站。 华为设备广域网命令是网络管理员在运维过程中常用的一类命令。该命令集涵盖了DCC配置命令、PPP配置命令、MP配置命令、PPPoE命令、ATM配置命令、帧中继配置命令、HDLC配置命令、LAPB配置命令、X.25配置命令、IP-Trunk配置命令、ISDN配…

全网最新最全面的jmeter性能测试/性能用例模板

性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。 性能测试主要包括5个方面&#xff1a; 预期目标用户测试&#xff1a;预期目标用户测试是指系统在需求分析和设计阶段都会提出一些性能指标&#xff0c;针对这些性能指标测…

微信小程序解密并拆包获取源码教程

第一步:电脑端提取微信小程序包 一般在微信安装目录下的,比如我微信安装在d盘当中,那么下载的wxapkg包就在下方 D:\qq\wechatfile\WeChat Files\Applet那么微信小程序加载的wxapkg包都在这里 比如下方的一个微信小程序的包就在这里 第二步:解密wxapkg包 工具下载地址 https:/…

人工智能-知识推理

本章可以回忆下离散中的内容&#xff0c;直接看最后的两个期末题↓。 1.基于知识的Agent 基于知识的Agent的核心是知识库KB&#xff0c;知识库中的有些语句是直接给定的而不是推导得到的为公理。基于知识的Agent使用TELL方法将新的语句添加到知识库&#xff0c;使用ASK询问来…

数据库+Python----------Python操作MySQL

目录 1.在Python中安装pymysql 2.创建数据 1.连接数据库&#xff0c;创建一个数据库对象 2.开启游标功能&#xff0c;创建游标对象 3.发送指令 获取查询结果集的方法 4.实操 5.注意 不可以&#xff01;&#xff01;&#xff01; 正确写法 6.用户输入数据 3.查询数…

设计模式之单例模式入门介绍

一、设计模式概念 设计模式是被广泛使用的软件开发中的一种解决方案&#xff0c;它提供了一套被验证过的、可重用的设计思想&#xff0c;帮助开发人员更加高效地开发出可维护、易扩展的软件系统。 设计模式可以分为三类&#xff1a;创建型模式、结构型模式和行为型模式。 1.1…

jsonschema fge json-schema-validator 高级能力 自定义类校验

入参校验产品化 schema_个人渣记录仅为自己搜索用的博客-CSDN博客 自定义的string format可以使用. 详见 fpe的 addFormatValidator ajv 的 addFormat能力 借鉴自chatgpt, 谷歌了半天,没看到好答案. Q: "jsonschema 自定义 object 校验" A: 如下 2014年后未更新…

使用 GNU 汇编语法编写 Hello World 程序的三种方法

本篇我们使用汇编来写一个经典的 Hello world 程序。 运行环境&#xff1a; OS&#xff1a;Ubuntu 18.04.5 LTS x86-64gcc&#xff1a;version 4.8.5 在用户空间编写汇编程序输出字符串&#xff0c;有三种方法&#xff1a; 调用C库函数 printf使用软中断 int 0x80使用 sysc…

Java的CookieManager

文章目录 1. 简介2. CookieStore 1. 简介 Java5包括一个抽象类Java.net.CookieHandler&#xff0c;它定义了存储和获取Cookie的一个API&#xff0c;但不包括这个抽象类的实现&#xff0c;所以还有很多工作要做。Java6进一步作了补充&#xff0c;为CookieManager增加了一个可以…

领导让我搭建Appium环境,还好我看到了这篇文章

首先介绍一下&#xff0c;Appium是一个APP的自动化框架&#xff0c;可用于测试APP、网页(web)、混合型应用&#xff0c;而且是跨平台(可以针对不同平台用一套api来编写测试用例)的。 pythonAppium自动化测试框架【项目实战合集】&#xff0c;轻松掌握app高级自动化测试_哔哩哔…

Redis 的数据类型和命令帮助

文章结构 Redis 数据类型1. Redis全局命令&#xff08;跟key有关系&#xff0c;而跟value无关&#xff09;2. StringsGetting and setting StringsManaging counters 3. Lists(L)Basic commandsBlocking commands 4. Sets(S)Basic commands 5. Hashes(H)Basic commands 6. Sort…

2.VirtualBox安装CentOS 7

安装VirtualBox 到https://www.virtualbox.org/wiki/Downloads下载并且安装&#xff0c;请选择对应系统的版本进行安装&#xff0c;我是Mac OS。一路Next。 下载CentOS虚拟镜像 到https://www.osboxes.org/centos/下载CentOS的虚拟镜像。我下载的是CentOS 7&#xff0c;64bi…