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

2 篇文章 2 订阅
2 篇文章 0 订阅

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>

参考文献

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是使用JavaOpenCV实现数字识别的步骤: 1.准备数据集:首先需要准备一组手写数字的图像数据集,包括数字0-9。可以使用MNIST数据集或自己手写数字的图像数据集。 2.提取特征:使用OpenCV提取数字图像的特征,可以使用HOG特征或LBP特征。 3.训练模型:使用机器学习算法,如SVM或神经网络,对提取的特征进行训练,生成数字识别模型。 4.测试模型:使用测试集对训练好的模型进行测试,评估模型的准确率。 5.应用模型:将训练好的模型应用到实际场景中,对输入的手写数字图像进行识别。 以下是一个使用JavaOpenCV实现数字识别的示例代码: ```java import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.core.MatOfFloat; import org.opencv.core.MatOfInt; import org.opencv.core.Rect; import org.opencv.core.Size; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import org.opencv.ml.SVM; public class DigitRecognizer { private SVM svm; public DigitRecognizer() { svm = SVM.create(); svm.setType(SVM.C_SVC); svm.setKernel(SVM.RBF); svm.setGamma(0.5); svm.setC(1); } public void train(String trainDataPath, String labelsPath) { // 读取训练数据和标签 Mat trainData = Imgcodecs.imread(trainDataPath, Imgcodecs.IMREAD_GRAYSCALE); Mat labels = Imgcodecs.imread(labelsPath, Imgcodecs.IMREAD_GRAYSCALE); // 提取HOG特征 MatOfFloat descriptors = new MatOfFloat(); Size winSize = new Size(20, 20); Size blockSize = new Size(10, 10); Size blockStride = new Size(5, 5); Size cellSize = new Size(5, 5); int nbins = 9; MatOfInt histSize = new MatOfInt(nbins); MatOfInt fromTo = new MatOfInt(0, nbins); MatOfInt channels = new MatOfInt(0); Imgproc.HOGDescriptor hog = new Imgproc.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins); Rect roi = new Rect(0, 0, 20, 20); for (int i = 0; i < trainData.rows(); i++) { Mat digit = trainData.row(i).reshape(1, 20); MatOfFloat descriptor = new MatOfFloat(); hog.compute(digit.submat(roi), descriptor, winSize, blockSize, blockStride); descriptors.push_back(descriptor); } // 训练模型 svm.train(descriptors, SVM.RAW_OUTPUT, labels); // 保存模型 svm.save("svm.xml"); } public int predict(String digitPath) { // 读取待识别的数字图像 Mat digit = Imgcodecs.imread(digitPath, Imgcodecs.IMREAD_GRAYSCALE); // 提取HOG特征 MatOfFloat descriptor = new MatOfFloat(); Size winSize = new Size(20, 20); Size blockSize = new Size(10, 10); Size blockStride = new Size(5, 5); Size cellSize = new Size(5, 5); int nbins = 9; MatOfInt histSize = new MatOfInt(nbins); MatOfInt fromTo = new MatOfInt(0, nbins); MatOfInt channels = new MatOfInt(0); Imgproc.HOGDescriptor hog = new Imgproc.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins); Rect roi = new Rect(0, 0, 20, 20); hog.compute(digit.submat(roi), descriptor, winSize, blockSize, blockStride); // 使用模型进行预测 float[] result = new float[1]; svm.predict(descriptor, result, SVM.RAW_OUTPUT); return (int) result[0]; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhulangfly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值