“程序设计与算法训练”课程设计“二值图像数字水印技术的实践”

  垃圾大学数据结构课设(题目抄袭自某牛逼985高校)。

  github项目地址(含报告等):https://github.com/25thengineer/Data-Structure-Course-Design-Practice-of-Binary-Image-Digital-Watermarking-Technology

  1操作系统:Ubuntu 16.04 LTS

  (2开发工具:QtQt Creator 5.12.0

  (3实现语言:C++

课程编号:0521733B                                                                                                                   课程性质:必修

                    程序设计与算法训练课程设计报告


 

院       系:           计算机与信息系          

 

班       级:                                              

 

姓       名:                                              

 

学       号:                                              

 

指导教师:                                              

 

选题名称:  二值图像数字水印技术实践 

  2019 年 1 月 20 日 至 2019 年 5 月 5 日

 

 

  CSDN博客位置:https://blog.csdn.net/u25th_engineer/article/details/89874906 

一、 实验概述

1.1 课程设计题目             

  题目要求对给定的一种简单的二值图像的数字水印算法编程实现。

 1.2 课程设计目的

        对数字水印技术建立一定的认识,能建立位矩阵、位向量等 ADT,并能用这些 ADT 完成给定二值图像数字水印的嵌入和抽取。

1.3 系统主要内容与功能

1.3.1 设计内容       

  具体包括以下内容:

       (1)图像的读取与保存,及相应的矩阵和向量的运算;

  (2)二值图像水印算法的实现;

  (3)软件的界面和接口设计,信号发送和槽位的设计;

       (4)软件鲁棒性分析、算法鲁棒性分析和相关总结。

1.3.2 设计功能

  (1)设计合理的数据结构,编程实现算法;

       (2)给定测试图片,按照指定的 bmp 格式,保存于外存中。

1.3.3 系统类图

 

 

    watermark

    QString array2byte(byteArray &array);

    QString array2str(byteArray &array);

    byteArray byte2Array(QString &number);

    byteArray decodeImg(uchar* buffer, uchar* dst, const int width, const int height, const int length);

    uchar* edgeExtract(uchar* buffer, const int width, const int height);

    byteArray encode(byteArray src, byteArray key);

    byteArray generateKey(const int length);

    byteArray img2Array(QString &dir);

    uchar* readBmp(const char *bmpName, int& bmpWidth, int& bmpHeight);

    byteArray str2Array(QString &str);

    uchar* substract(uchar* buffer1, uchar* buffer2, const int size);

    uchar* translation(uchar* buffer, const int width, const int height, int x_off, int y_off);

    uchar* watermarkImg(uchar* buffer, uchar* edge, const int size, byteArray code);

    bool savebmp(const char* filename, uchar* buffer, const u_int32_t height, const u_int32_t width);

    BITMAPINFODEADER BMIH;

    BITMAPFILEHEADER BMFH;

    int biWidth;

    int biHeight;

    int biBitCount;

    int lineByte;

    RGBQUAD* pColorTable;

 

 

    MainWindow

    Q_OBJECT

    explicit MainWindow(QWidget *parent = 0);

    ~MainWindow();

    void on_pushButtonBrowse_clicked();

    void on_lineEdit_textChanged(const QString &arg1);

    void on_pushButtonEncode_clicked();

    void on_pushButtonDecode_clicked();

    Ui::MainWindow *ui;

    QPixmap image;

 

    byteArray key;

    uchar* dst;

表 1.1 系统类图 

 

1.3.4 属性和方法定义

  表 1.2 属性和方法定义 

1.3.5 实验环境与工具

  (1)操作系统:Ubuntu 16.04 LTS;

  (2)开发工具:Qt、Qt Creator;

       (3)实现语言:C++。

二、 实验原理

2.1 图像水印技术简述

  随着互联网和信息技术的快速发展,近年来数字内容的未授权获取,传输,操纵和分发的问题变得越来越严重。信息安全研究引起了人们的广泛关注。除了一般采用的数字加密算法之外,近年来用于信息安全的影像视觉算法包括光

学图像加密、认证和水印算法被广泛地研究和应用起来。影像视觉信息安全算法通常拥有并行高速处理和多维能力的优势。信息隐藏技术,即图像水印技术,也称隐写技术是一种隐蔽性地改变载波信号以嵌入隐藏消息,即水印信号的

技术。可以对各种各样类别的信号执行信息隐藏,其中包括但不限于:音频信号、图像信号和视频信号。信息隐藏技术(图像水印技术)允许将特定的信息加入到需要保护的媒体信息中,加入的信息一般为具有特定意义的内容,如版权所

有者信息、发行标志、特定代码等。而图像水印技术也因而成为了数字图像处理专业当下或未来重要的研究领域,在知识产权的保护等方面有着广泛的应用前景。为了确保大规模在线分发的多媒体内容的版权和知识产权,我们需要

通过有效的保护来控制分发和传播,控制来自盗版用户或未经授权普通用户的恶意操纵和恶意拷贝传播。

        为了提高效率,水印需要良好的隐蔽性,并且拥有嵌入高容量和有效载荷,能够在确保有效载荷的安全传输的同时,对最常见的图像处理(恶意或非恶意)进行鲁棒性处理。此外水印技术还有以下的特点:

       (1)水印后的主图像不应存在显著地信息损坏和分辨率降低;

        (2)水印应具有在主图像中良好的隐蔽性,即不可见性,一般用图像的峰值信噪比 (PSNR) 与结构相似形(SSIM)来描述;

        (3)水印应具有良好的鲁棒性,不易从主图像中被损坏。水印应可以承受不同类型的信息损坏打击,例如 JPEG 压缩、裁剪、旋转、缩放、噪声、滤波运算和模糊运算。应该特别指出,该特点仅适用于一部分鲁棒性极好的水印算法中;

        (4)未经授权的用户/盗版用户应很难非法访问到该水印信息。

2.2 图像处理技术简述

  在图像水印技术实现时,需要大量运用到图像处理知识和技术。数字图像处理(Digital Image Processing)是通过计算机对图像进行去除噪声、增强、复原、分割、提取特征等处理的方法和技术。数字图像处理(Digital Image

Processing)又称为计算机图像处理,它是指将图像信号转换成数字信号并利用计算机对其进行处理的过程。数字图像处理的产生和迅速发展主要受三个因素的影响:一是计算机的发展;二是数学的发展(特别是离散数学理论的创立和完

善);三是广泛的农牧业、林业、环境、军事、工业和医学等方面的应用需求的增长。

        在图像水印技术中,本着不损坏原有图像质量的原则,该算法需要对图像快速的读写能力、对分块图像稳定和鲁棒的运算能力,以及优秀的边缘提取能力。

2.3 Qt 及 Qt Creator

  Qt 是一个跨平台的 C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形用户界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。

        作为一个优秀的 C++框架,由于其优秀的信号与槽机制,Qt 被广泛地应用于软件编写中。本次课程设计将采用 Qt 和 QtCreator 作为程序界面的编写工具。

2.4 相关算法简介

  数字图像水印算法是将一段信息附加在图像上的算法,其中被附加信息的图像被称为主机图像(host image),简称主图像。根据水印算法的鲁棒性,水印算法可以分为鲁棒/稳健的水印算法(robust watermarking)和脆弱的水印算法

(fragilewatermarking),其中绝大多数的数字水印算法都属于前者。如上文所提到,鲁棒的水印在主图像受到攻击和扭曲时,仍能保持完整。由于鲁棒的水印很难从主机图像中移除,因此常常用于保护版权;另一方面,脆弱的水印一般仅用

于验证,即主机图像的完整性检查。完整的脆弱水印表示主机图像处于其原始形式,并没有收到编辑、损坏或更改。在本次课程设计中,为了简洁起见,我们采用脆弱的水印算法。

        经典的数字图像水印算法构建的系统包括双随机相位编码(DRPE)系统,离轴全息系统,相移全息系统,优化的仅相位掩模结构,联合变换相关器(JTC),重影成像系统和 ptychography 系统,等等。它们在各自的领域都具有相当不错的效果。在本次课程设计中,为了兼顾效率和效果,我们采用一种简单的二值图像数字水印算法,其基本思路如下:由于水印算法需要改变主机图像的像素值,从而一定程度上改变主机图像的信息。而被改变像素值,因而保存着水印信息的像

素点被称为隐藏点,它决定了隐藏了怎样的信息。而简单的处理方法容易导致图像质量的下降,例如,在全白的图像块中插入一个黑色的噪声点。另一方面,隐藏点也应避免选取在图像中的细线区域、直线边的中间像素、孤立像素等图

像信息熵较大的点。由此,二值图像的数字水印嵌入算法的关键是隐藏点的选取,以下是隐藏点的选取规则:

        二值图像数据应隐藏域图像中黑/白色区域的边界上,但上述诸如细线区域等仍不适于隐藏数据的边界。因此隐藏点应具有以下的特点:它是边界像素,并且不同时是左边界和右边界/上边界和下边界,以确保避免将信息隐藏在细线

区域等。这需要一个优秀的边界提取算法。

        在隐藏点被确定后,水印信息应转换为二进制序列,并保存在隐藏点中。同理,因此所有可被转换为二进制序列的诸如文字信号、音频信号、图像信号和视频信号,并可进行反转换的信号都可以作为该算法的水印信息。

        在本次课程设计中,我们简单地以字符串信息和二进制序列信息为例,来保存相关的水印。值得一提的是,为了保证水印信息更好地隐蔽,水印信息被加入前,需要用一段密钥进行加密,并在提取时,使用该密钥进行解密。

2.5 技术流程

        本次课程设计采用的主要框架包括 C++与 Qt 等,其中 C++作为程序实现语言,Qt 作为软件实现框架,使用 C++中的指针工具作为图像处理工具,BMP 图像的读写采用文件头信息解析模式。

        本次编程是在 Ubuntu 16.04 LTS 上进行的,在该环境下,常用的图像解码工具包括 Qt 的 QPixMap 模块以及 OpenCV 库。其中使用 C++进行 bmp 文件解码过程较为复杂,需要鲁棒的文件读写设计以及接口设计,同时,对于不同类型

的文件需要不同的解码方式,不广泛适用于.png,.img,.jpg,.giff 等等图像格式的使用,但减少了第三方库的利用。而 QPixMap 框架的引入对于文件数据对象的空间开辟和释放过于频繁,降低了程序的效率,并需要引入标准模板库框架。作

为对比,OpenCV 库拥有极高的鲁棒性、延展性和高效性,但需要引入第三方库,软件打包较为复杂。在“尽量引入较少的第三方库”的原则下,我们采用了纯C++语言,根据 BMP 图像的特点设计合理的数据结构进行编码、存储图像,以提

高本软件的实用性。

        本次课程设计的技术流程如下:首先设计并实现边缘信息提取的算法edgeExtract()函数,以确定隐藏点,再设计数字隐藏的算法encodeImage()函数。在软件流程中,首先点击“浏览”按钮,选取二值图像,再输入需要加入的水印,最后点

击“编码”按钮,则生成并显示了附有水印信息的图像。该图像被保存在.pro 的 Qt 项目文件同名文件夹下,被命名为 encode.bmp。此外,对于已编码的encode.bmp,点击“解码”按钮,会以对话框形式弹出被解码出的水印信息。

三、 实验结果

   本次课程设计生成的软件结果如下:

        软件打开后的界面如下,为避免用户每次打开软件后都需要输入水印,过于麻烦,本软件内置了水印二进制序列,如图3.1所示。

图 3.1 软件开启界面

 

  输入二值图像后,会被展示在软件中,如图3.2所示。

图 3.2 显示所输入二值图像

  点击“编码”按钮后,生成编码图像,如图3.3所示。

图 3.3 生成编码图像

   

  点击“解码”按钮,弹出水印信息,如图3.4所示。 

图 3.4 显示水印信息

 

  本程序也支持字符串的水印编码,将水印附带的 comboBox 选取为“QString”,然后编码并解码后的结果,如图3.5所示。

图 3.5 字符串水印编码图像

四、 算法分析

  本程序经过第三方测试发现并无 bug 存在,鲁棒性优秀,可以完整的实现简单的二进制序列的二值数字图像水印算法功能。在第二章节中提到,优秀的水印算法拥有上述的四个特点: a.不改变原图信息;b.隐蔽性;c.鲁棒性;d.不可访问性。本报告将从这四个

角度评判该算法的性能。

       1. 由第三章节的内容易见,该算法具有从肉眼中几乎无法分辨与原图像的区别,同时如表 3 所示,通过计算原图与被水印编码图的峰值信噪比与结构相似比,两者均处于合理的置信区间,因而完美地符合了不改变原图信息和隐蔽性的原则;

  2. 该算法经过轻微地噪声扰动后就无法保存水印信息,如图 6 所示,因此鲁棒性极低,然而这符合脆弱的水印算法的特点,可以用于验证图像的完整性;

       3. 从 encode.bmp 和原图之间的差分影像可以轻易地获得加密后的二进制序列。但是由于有密钥的存在,无密钥的非授权用户和盗版用户无法访问到原始水印的信息,具有不可访问性。关于不可访问性,在附录中有所展示。

表 4.1 峰值信噪比与结构相似性实验数据

 

 

 图 4.1 原图、水印图、加噪声水印图对比

  

  综上所述,本算法在效率极高的同时,完美地满足了优秀的脆弱水印算法的四个特点,同时保证了效果和效率。

五、 总结

  在本次课程设计中,按照题目要求与提示,针对给定的二值图像数字水印算法完成了编程实现、算法分析与相关内容的简单介绍。先将水印信息转换为二进制序列,然后,算法利用密钥对序列进行转换,从而降低了第三方获取水印的

可能性。通过查阅有关资料,对数字水印建立了一定的认识,并构建了位矩阵、位相量等 ADT。综述,本次课程设计完成了既定的各个要求。

附录(源代码与其他)

源代码

  头文件 additional_utility.h:

 1 #ifndef ADDDITIONAL_UTILITY_H
 2 #define ADDDITIONAL_UTILITY_H
 3 
 4 #include<sys/types.h>
 5 #include<iostream>
 6 #include<fstream>
 7 #include<QtCore>
 8 #include<QMessageBox>
 9 #include<stdio.h>
10 #include<string.h>
11 #pragma pack(2)// In the Linux environment
12 
13 
14 using byteArray = QVector<bool>;
15 
16 //******************************************top******************************************************
17 //In the Linux environment
18 typedef struct BITMAPFILEHEADER
19 {
20     u_int16_t bfType;
21     u_int32_t bfSize;
22     u_int16_t bfReserved1;
23     u_int16_t bfReserved2;
24     u_int32_t bfOffBits;
25 }BITMAPFILEHEADER;
26 typedef struct BITMAPINFOHEADER
27 {
28     u_int32_t biSize;
29     u_int32_t biWidth;
30     u_int32_t biHeight;
31     u_int16_t biPlanes;
32     u_int16_t biBitCount;
33     u_int32_t biCompression;
34     u_int32_t biSizeImage;
35     u_int32_t biXPelsPerMeter;
36     u_int32_t biYPelsPerMeter;
37     u_int32_t biClrUsed;
38     u_int32_t biClrImportant;
39     struct BITMAPINFOHEADER &operator=( struct BITMAPINFOHEADER &BMIH )
40     {
41         biSize = BMIH.biSize;
42         biWidth = BMIH.biWidth;
43         biHeight = BMIH.biHeight;
44         biPlanes = BMIH.biPlanes;
45         biClrUsed = BMIH.biClrUsed;
46         biSizeImage = BMIH.biSizeImage;
47         biCompression = BMIH.biCompression;
48         biClrImportant = BMIH.biClrImportant;
49         biXPelsPerMeter = BMIH.biXPelsPerMeter;
50         biYPelsPerMeter = BMIH.biYPelsPerMeter;
51         //qDebug() << "214235423" << endl;
52         return *this;
53     }
54 
55 }BITMAPINFODEADER;
56 
57 typedef unsigned char BYTE;
58 typedef unsigned short WORD;
59 typedef unsigned int DWORD;
60 typedef long LONG;
61 
62 typedef struct tagRGBQUAD
63 {
64     BYTE rgbBlue;
65     BYTE rgbGreen;
66     BYTE rgbRed;
67     BYTE rgbReserved;
68 }RGBQUAD;
69 
70 
71 typedef struct tagIMAGEDATA
72 {
73     BYTE blue;
74     BYTE green;//cancel the annotation by DFZ
75     BYTE red;//cancel the annotation by DFZ
76 }IMAGEDATA;
77 
78 
79 //********************************************bottom*******************************************
80 
81 #endif // ADDDITIONAL_UTILITY_H

  头文件 bmputil.h:

 1 #ifndef BMPUTIL_H
 2 #define BMPUTIL_H
 3 
 4 #include "addditional_utility.h"
 5 
 6 class watermark
 7 {
 8 public:
 9     QString array2byte(byteArray &array);
10     QString array2str(byteArray &array);
11     byteArray byte2Array(QString &number);
12     byteArray decodeImg(uchar* buffer, uchar* dst, const int width, const int height, const int length);
13     uchar* edgeExtract(uchar* buffer, const int width, const int height);
14     byteArray encode(byteArray src, byteArray key);
15     byteArray generateKey(const int length);
16     byteArray img2Array(QString &dir);
17     uchar* readBmp(const char *bmpName, int& bmpWidth, int& bmpHeight);
18     byteArray str2Array(QString &str);
19     uchar* substract(uchar* buffer1, uchar* buffer2, const int size);
20     uchar* translation(uchar* buffer, const int width, const int height, int x_off, int y_off);
21     uchar* watermarkImg(uchar* buffer, uchar* edge, const int size, byteArray code);
22     bool savebmp(const char* filename, uchar* buffer, const u_int32_t height, const u_int32_t width);
23 private:
24     BITMAPINFODEADER BMIH;
25     BITMAPFILEHEADER BMFH;
26     int biWidth;
27     int biHeight;
28     int biBitCount;
29     int lineByte;
30     RGBQUAD* pColorTable;
31 protected:
32 
33 
34 };
35 
36 #endif // BMPUTIL_H

  头文件 mainwindow.h:

 1 #ifndef MAINWINDOW_H
 2 #define MAINWINDOW_H
 3 
 4 #include <QMainWindow>
 5 #include <QPixmap>
 6 
 7 namespace Ui
 8 {
 9 class MainWindow;
10 }
11 
12 using byteArray = QVector<bool>;
13 
14 class MainWindow : public QMainWindow
15 {
16     Q_OBJECT
17 public:
18     explicit MainWindow(QWidget *parent = 0);
19     ~MainWindow();
20 
21 private slots:
22     void on_pushButtonBrowse_clicked();
23     void on_lineEdit_textChanged(const QString &arg1);
24     void on_pushButtonEncode_clicked();
25     void on_pushButtonDecode_clicked();
26 
27 private:
28     Ui::MainWindow *ui;
29     QPixmap image;
30 
31     byteArray key;
32     uchar* dst;
33 };
34 
35 #endif // MAINWINDOW_H

  源文件 additional_utility.cpp:

1 #include "addditional_utility.h"

  源文件 bmputil.cpp:

  1 #include "bmputil.h"
  2 
  3 uchar* watermark::readBmp(const char *bmpName, int& bmpWidth, int& bmpHeight)
  4 {
  5     FILE *fp = fopen(bmpName, "rb");
  6     if(fp == Q_NULLPTR)
  7     {
  8         QMessageBox::warning(Q_NULLPTR, "Error", "Error in Open File!");
  9         return Q_NULLPTR;
 10     }
 11 
 12     // skip the fileheader
 13     fseek(fp, sizeof(BITMAPFILEHEADER), SEEK_CUR);
 14 
 15     // read the infoheader
 16     //BITMAPINFOHEADER* head = new BITMAPINFOHEADER;
 17     watermark WT;
 18     //infohead = &(WT.BMIH);
 19     BITMAPINFODEADER infohead = WT.BMIH;
 20     fread(&infohead,sizeof(BITMAPINFOHEADER),1,fp);
 21     //fread(infoHead, sizeof(BITMAPINFOHEADER), 1, fp);
 22     bmpWidth = infohead.biWidth;
 23     bmpHeight = infohead.biHeight;
 24     biBitCount = infohead.biBitCount;
 25     lineByte = (bmpWidth*biBitCount/8+3)/4*4;
 26     //qDebug() << infohead.biSize << endl;
 27     //qDebug() << infohead.biWidth << endl;
 28     //qDebug() << infohead.biHeight << endl;
 29     //qDebug() << infohead.biBitCount << endl;
 30     if (biBitCount == 8)
 31     {
 32         pColorTable = new RGBQUAD[256];
 33         fread(pColorTable, sizeof(RGBQUAD), 256, fp);
 34 
 35         uchar* pBmpBuf = new uchar[ bmpWidth * bmpHeight ];
 36         fread(pBmpBuf, sizeof(uchar), bmpWidth * bmpHeight, fp);
 37         fclose(fp);
 38 
 39         uchar* buffer = new uchar[bmpWidth * bmpHeight];
 40         for(int i = 0; i < bmpHeight; i++)
 41         {
 42             for(int j = 0; j<bmpWidth; j++)
 43             {
 44                 if(pBmpBuf[(bmpHeight- i - 1)*bmpWidth + j] != 255 && pBmpBuf[(bmpHeight- i - 1)*bmpWidth + j] != 0)
 45                 {
 46                     QMessageBox::warning(Q_NULLPTR, "Error", "This is not a binary image!");
 47                     return Q_NULLPTR;
 48                 }
 49                 buffer[i*bmpWidth + j] = pBmpBuf[(bmpHeight- i - 1)*bmpWidth + j];
 50             }
 51         }
 52         return buffer;
 53     }
 54     else
 55     {
 56         QMessageBox::warning(Q_NULLPTR, "Error", "Our program can only deal with 8-bit image!");
 57         return Q_NULLPTR;
 58     }
 59 }
 60 
 61 bool watermark::savebmp(const char* filename, uchar* buffer, const u_int32_t height, const u_int32_t width)
 62 {
 63     //RGBQUAD *pColorTable = new RGBQUAD;
 64     if(buffer == Q_NULLPTR)
 65     {
 66         QMessageBox::warning(Q_NULLPTR, "Error", "The Buffer is nullptr!");
 67         return false;
 68     }
 69     uchar* data = new uchar[height*width];
 70     for(int i = 0; i < height; i++)
 71     {
 72         for(int j = 0; j<width; j++)
 73         {
 74             data[i*width + j] = buffer[(height- i - 1)*width + j];
 75         }
 76     }
 77 
 78     int colorTableSize = 1024;
 79     BITMAPFILEHEADER fileHeader;
 80     fileHeader.bfType = 0x4D42;
 81     fileHeader.bfReserved1 = 0;
 82     fileHeader.bfReserved2 = 0;
 83     fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTableSize + height*width;
 84     fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTableSize;
 85 
 86     BITMAPINFOHEADER bitmapHeader = { 0 };
 87     bitmapHeader.biSize = sizeof(BITMAPINFOHEADER);
 88     //qDebug() << "In bool watermark::savebmp(const char* filename, uchar* buffer, const u_int32_t height, const u_int32_t width), height = " << height << endl;
 89     //qDebug() << "In bool watermark::savebmp(const char* filename, uchar* buffer, const u_int32_t height, const u_int32_t width), width = " << width << endl;
 90     //qDebug() << sizeof(BITMAPINFOHEADER) << endl;
 91     bitmapHeader.biHeight = height;
 92     bitmapHeader.biWidth = width;
 93     bitmapHeader.biPlanes = 1;
 94     bitmapHeader.biBitCount = 8;
 95     bitmapHeader.biSizeImage = height*width;
 96     bitmapHeader.biCompression = 0;
 97 
 98     FILE *fp = fopen(filename, "wb");
 99     //qDebug() << "OK! line 154 " << endl;
100     if(fp == Q_NULLPTR)
101     {
102         QMessageBox::warning(Q_NULLPTR, "Error", "Error in Save File!");
103         //qDebug() << "OK! line 156 " << endl;
104         return false;
105     }
106     else
107     {
108         fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
109         //qDebug() << "OK! line 162 " << endl;
110         fwrite(&bitmapHeader, sizeof(BITMAPINFOHEADER), 1, fp);
111         //qDebug() << "OK! line 164 " << endl;
112         //qDebug() << "pColorTable address = " << pColorTable << endl;
113         fwrite(pColorTable, sizeof(RGBQUAD), 256, fp);
114         delete pColorTable;
115         //qDebug() << "OK! line 168 " << endl;
116         fwrite(data, height*width, 1, fp);
117         delete []data;
118         //qDebug() << "OK! line 171 " << endl;
119         fclose(fp);
120         //qDebug() << "OK! line 173 " << endl;
121         return true;
122     }
123 }
124 
125 
126 // generate keyArray
127 byteArray watermark::generateKey(const int length)
128 {
129     byteArray res;
130     for(int i = 0; i<length; i++)
131     {
132         if(double(qrand())/RAND_MAX > 0.5)
133         {
134             res.append(true);
135         }
136         else
137         {
138             res.append(false);
139         }
140     }
141 
142     return res;
143 }
144 
145 //获得原buffer图像右移x_off个单位,下移y_off个单位后得到的图像
146 uchar* watermark::translation(uchar* buffer, const int width, const int height, int x_off, int y_off)
147 {
148     uchar* res = new uchar[width*height];
149     for(int i = 0; i<height; i++)
150     {
151         for(int j = 0;j<width; j++)
152         {
153             if(i-x_off < 0 || j-y_off < 0)
154             {
155                 res[i*width + j] = 0;
156             }
157             else
158             {
159                 res[i*width + j] = buffer[(i - x_off)*width + (j - y_off)];
160             }
161         }
162     }
163 
164     return res;
165 }
166 
167 // 获得buffer1和buffer2相减得到的结果
168 uchar* watermark::substract(uchar* buffer1, uchar* buffer2, const int size)
169 {
170     uchar* res = new uchar[size];
171     for(int i = 0; i<size; i++)
172     {
173         res[i] = buffer1[i] > buffer2[i] ? buffer1[i] - buffer2[i] : 0;
174     }
175     return res;
176 }
177 
178 // 获得从buffer提取到的边缘图像,用作添加水印位置的参考
179 uchar* watermark::edgeExtract(uchar* buffer, const int width, const int height)
180 {
181     uchar* BL = substract(translation(buffer, width, height, 1, 0), buffer, width*height);
182     uchar* BR = substract(translation(buffer, width, height, -1, 0), buffer, width*height);
183     uchar* BT = substract(translation(buffer, width, height, 0, 1), buffer, width*height);
184     uchar* BB = substract(translation(buffer, width, height, 0, -1), buffer, width*height);
185 
186     uchar* B1 = new uchar[height*width];
187     for(int i = 0; i<height*width; i++)
188     {
189         BL[i] = BL[i]/255;
190         BR[i] = BR[i]/255;
191         BT[i] = BT[i]/255;
192         BB[i] = BB[i]/255;
193 
194         int lr = BL[i] + BR[i];
195         int tb = BT[i] + BB[i];
196         int b = lr + tb;
197 
198         B1[i] = 0;
199         if((b == 1) ||(b == 2 && lr != 2 && tb != 2))
200         {
201             B1[i] = 1;
202         }
203     }
204 
205     uchar* res = new uchar[height*width];
206     for(int i = 0; i<height; i++)
207     {
208         for(int j = 0; j<width; j++)
209         {
210             res[i*width + j] = B1[i*width + j] * 255;
211             if(B1[i*width + j])
212             {
213                 int sum1 = 0, sum2 = 0;
214                 for(int a = -1; a<2; a++)
215                 {
216                     if(a + i <0 || a+ i >= height)
217                     {
218                         continue;
219                     }
220                     for(int b = -1; b<2; b++)
221                     {
222                         if(b +j <0 || b+j>=width)
223                         {
224                             continue;
225                         }
226                         sum1 += buffer[(a + i) *width + (b + j)]/255;
227                         sum2 += B1[(a + i) *width + (b + j)];
228                     }
229                 }
230                 if(sum1 == sum2)
231                 {
232                     res[i*width + j] = 0;
233                 }
234             }
235         }
236     }
237 
238     return res;
239 }
240 
241 // 由边缘图像和原图获得水印编码后的图像
242 uchar* watermark::watermarkImg(uchar* buffer, uchar* edge, const int size, byteArray code)
243 {
244     uchar* res = new uchar[size];
245     for(int i = 0; i<size; i++)
246     {
247         res[i] = buffer[i];
248     }
249     int count = 0;
250     for(int i = 0; i<size; i++)
251     {
252         if(edge[i]==255)
253         {
254             res[i] = 255*code[count++];
255             if(count == code.length())
256             {
257                 return res;
258             }
259         }
260     }
261     QMessageBox::warning(Q_NULLPTR, "Error", "The image is too small to contain such a code!");
262     return Q_NULLPTR;
263 }
264 
265 byteArray watermark::decodeImg(uchar* buffer, uchar* dst, const int width, const int height, const int length)
266 {
267     uchar* edge = edgeExtract(buffer, width, height);
268     byteArray res;
269     for(int i = 0; i<width*height; i++)
270     {
271         if(edge[i] == 255)
272         {
273             res.append(dst[i]);
274             if(length == res.length())
275             {
276                 return res;
277             }
278         }
279     }
280     QMessageBox::warning(Q_NULLPTR, "Error", "The image is too small to contain such a code!");
281     return byteArray();
282 }
283 
284 byteArray watermark::byte2Array(QString &number)
285 {
286     byteArray res;
287     for(auto byte : number)
288     {
289         if(byte == '1')
290         {
291             res.append(true);
292         }
293         else if(byte == '0')
294         {
295             res.append(false);
296         }
297         else
298         {
299             QMessageBox::warning(nullptr, "Error", "Error in byte2Array: Charater else than 0 and 1!
The application will be forced to abort.");
300             throw EXIT_FAILURE;
301         }
302     }
303     return res;
304 }
305 
306 byteArray watermark::str2Array(QString &str)
307 {
308     QString num;
309     for(auto character : str)
310     {
311         int i = character.unicode();
312         QString ele = QString::number(i, 2);
313         for(int j = 0;j<8-ele.length();j++)
314         {
315             num += '0';
316         }
317         num += QString::number(i, 2);
318     }
319     qDebug()<<num;
320     return byte2Array(num);
321 }
322 
323 byteArray watermark::img2Array(QString &dir)
324 {
325     Q_UNUSED(dir);
326     QString str = "01010101010101010101010101010101";
327     //return byte2Array(QString("01010101010101010101010101010101"));
328     return byte2Array(str);
329 }
330 
331 QString watermark::array2byte(byteArray &array)
332 {
333     QString res;
334     for(auto ele:array)
335     {
336         if(ele)
337         {
338             res.append('1');
339         }
340         else
341         {
342             res.append('0');
343         }
344     }
345     return res;
346 }
347 
348 QString watermark::array2str(byteArray &array)
349 {
350     QString res;
351     for(int i = 0 ; i<array.length(); i+=8)
352     {
353         int num = array[i + 7] + array[i + 6]*2 + array[i + 5]*4 + array[i + 4]*8 +
354                 array[i + 3]*16 + array[i + 2]*32 + array[i + 1]*64 + array[i + 0]*128;
355         res.append(char(num));
356     }
357     return res;
358 }
359 
360 // encode byteArray with keyArray
361 byteArray watermark::encode(byteArray src, byteArray key)
362 {
363     byteArray res;
364     if(src.length() != key.length())
365     {
366         qDebug()<< "The length of keyArray and srcArray doesn't match! ";
367         return byteArray();
368     }
369 
370     for(int i = 0; i < src.length(); i++)
371     {
372         res.append(src[i] ^ key[i]);
373     }
374 
375     return res;
376 }
377 
378 /*
379 BITMAPINFODEADER &watermark::operator=(BITMAPINFODEADER& BMIH)
380 {
381     if( &((*this).BMIH) == &BMIH )
382         return (*this).BMIH;
383     BMIH.biSize = (*this).BMIH.biSize;
384     BMIH.biWidth = (*this).BMIH.biWidth;
385     BMIH.biHeight = (*this).BMIH.biHeight;
386     BMIH.biPlanes = (*this).BMIH.biPlanes;
387     BMIH.biClrUsed = (*this).BMIH.biClrUsed;
388     BMIH.biSizeImage = (*this).BMIH.biSizeImage;
389     BMIH.biCompression = (*this).BMIH.biCompression;
390     BMIH.biClrImportant = (*this).BMIH.biClrImportant;
391     BMIH.biXPelsPerMeter = (*this).BMIH.biXPelsPerMeter;
392     BMIH.biYPelsPerMeter = (*this).BMIH.biYPelsPerMeter;
393     qDebug() << "214235423" << endl;
394     //return (*this).BMIH;
395     return BMIH;
396 }
397 */

  源文件 mainwindow.cpp:

  1 #include "mainwindow.h"
  2 #include "ui_mainwindow.h"
  3 
  4 #include <QtCore>
  5 #include <QFile>
  6 #include <QFileDialog>
  7 #include <QMessageBox>
  8 
  9 #include "bmputil.h"
 10 //#include "bmputil.cpp"
 11 
 12 MainWindow::MainWindow(QWidget *parent) :
 13     QMainWindow(parent),
 14     ui(new Ui::MainWindow)
 15 {
 16     ui->setupUi(this);
 17     image = QPixmap();
 18 
 19     QStringList bands = QStringList() << "QString" << "byteArray";
 20     ui->comboBoxWaterMark->setModel(new QStringListModel(bands));
 21     ui->comboBoxWaterMark->setCurrentIndex(1);
 22     ui->lineEditWaterMark->setText("01010101010101010101010101010101");
 23 }
 24 
 25 MainWindow::~MainWindow()
 26 {
 27     delete ui;
 28 }
 29 
 30 void MainWindow::on_pushButtonBrowse_clicked()
 31 {
 32     auto file = QFileDialog::getOpenFileName(this, tr("Append selected images"));
 33     ui->lineEdit->setText(file);
 34 }
 35 
 36 void MainWindow::on_lineEdit_textChanged(const QString &arg1)
 37 {
 38     Q_UNUSED(arg1);
 39     watermark WT;
 40     if(QFileInfo(ui->lineEdit->displayText()).exists())
 41     {
 42         int height, width;
 43         uchar* buffer = WT.readBmp(ui->lineEdit->displayText().toStdString().data(), width, height);
 44         if(buffer)
 45         {
 46             QPixmap img = QPixmap::fromImage(QImage(buffer, width, height, QImage::Format_Grayscale8));
 47             QGraphicsScene *scene = new QGraphicsScene;
 48             scene->addPixmap(img);
 49             ui->graphicsViewPrevious->setScene(scene);
 50             ui->graphicsViewPrevious->show();
 51             ui->graphicsViewPrevious->fitInView(img.rect(), Qt::KeepAspectRatio);
 52         }
 53     }
 54 }
 55 
 56 void MainWindow::on_pushButtonEncode_clicked()
 57 {
 58     byteArray code;
 59     watermark WT;
 60     if(ui->comboBoxWaterMark->currentIndex() == 1)
 61     {
 62         QRegExp regx("[0-1]+$");
 63         QValidator *validator = new QRegExpValidator(regx, this );
 64         ui->lineEditWaterMark->setValidator( validator );
 65         QString str1 = ui->lineEditWaterMark->text();
 66         code = WT.byte2Array(str1);
 67         delete validator;
 68     }
 69     else
 70     {
 71         QRegExp regx(".+
");
 72         QValidator *validator = new QRegExpValidator(regx, this );
 73         ui->lineEditWaterMark->setValidator( validator );
 74         QString str2 = ui->lineEditWaterMark->text();
 75         //code = str2Array(ui->lineEditWaterMark->text());
 76         code = WT.str2Array(str2);
 77         delete validator;
 78     }
 79     key = WT.generateKey(code.length());
 80     int width, height;
 81     uchar* buffer = WT.readBmp(ui->lineEdit->displayText().toStdString().data(), width, height);
 82     uchar* edge = WT.edgeExtract(buffer, width, height);
 83     dst = WT.watermarkImg(buffer, edge, width*height, WT.encode(code, key));
 84     //imwrite("encode.bmp", dst*255);
 85     image = QPixmap::fromImage(QImage(dst, width, height, QImage::Format_Grayscale8));
 86     QGraphicsScene *scene = new QGraphicsScene;
 87     scene->addPixmap(image);
 88     ui->graphicsViewAfter->setScene(scene);
 89     ui->graphicsViewAfter->show();
 90     ui->graphicsViewAfter->fitInView(image.rect(), Qt::KeepAspectRatio);
 91     //WT.saveBmp(dst);
 92     //qDebug() << "In void MainWindow::on_pushButtonEncode_clicked(), height = " << height << endl;
 93     //qDebug() << "void MainWindow::on_pushButtonEncode_clicked(), width = " << width << endl;
 94     WT.savebmp("encode.bmp", dst, height, width);
 95 }
 96 
 97 void MainWindow::on_pushButtonDecode_clicked()
 98 {
 99     int width, height;
100     watermark WT;
101     uchar* buffer = WT.readBmp(ui->lineEdit->displayText().toStdString().data(), width, height);
102     byteArray code = WT.encode(WT.decodeImg(buffer, dst, width, height, key.length()), key);
103     if(ui->comboBoxWaterMark->currentIndex() == 1)
104     {
105         QMessageBox::warning(this, "Decode", "The watermark is " + WT.array2byte(code)+"!");
106     }
107     if(ui->comboBoxWaterMark->currentIndex() == 0)
108     {
109         QMessageBox::warning(this, "Decode", "The watermark is " + WT.array2str(code)+"!");
110     }
111 }

  源文件 main.cpp:

 1 #include "mainwindow.h"
 2 #include <QApplication>
 3 
 4 int main(int argc, char *argv[])
 5 {
 6     QApplication a(argc, argv);
 7     MainWindow w;
 8     w.setWindowTitle("8 bits Binary BMP picture watermarking program");
 9     w.show();
10 
11     return a.exec();
12 }

  配置文件 my_app.rc:

1 IDI_ICON1 ICON DISCARDABLE t14.ico

  工程文件 ImageEncodeAdvance.pro:

 1 #-------------------------------------------------
 2 #
 3 # Project created by QtCreator 2019-02-03T14:45:42
 4 #
 5 #-------------------------------------------------
 6  
 7 QT       += core gui
 8  
 9 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
10  
11 TARGET = ImageEncodeAdvance
12 TEMPLATE = app
13  
14 # The following define makes your compiler emit warnings if you use
15 # any feature of Qt which has been marked as deprecated (the exact warnings
16 # depend on your compiler). Please consult the documentation of the
17 # deprecated API in order to know how to port your code away from it.
18 DEFINES += QT_DEPRECATED_WARNINGS
19  
20 # You can also make your code fail to compile if you use deprecated APIs.
21 # In order to do so, uncomment the following line.
22 # You can also select to disable deprecated APIs only up to a certain version of Qt.
23 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
24  
25 #INCLUDEPATH += $$PWD/opencv/include
26 INCLUDEPATH += /usr/local/include 
27                /usr/local/include/opencv4 
28                /usr/local/include/opencv4/opencv2
29  
30 #LIBS += $$PWD/opencv/lib/opencv_world320.lib
31 LIBS += /usr/local/lib/libopencv_calib3d.so.4.0.1 
32         /usr/local/lib/libopencv_core.so.4.0.1 
33         /usr/local/lib/libopencv_features2d.so.4.0.1 
34         /usr/local/lib/libopencv_flann.so.4.0.1 
35         /usr/local/lib/libopencv_highgui.so.4.0.1 
36         /usr/local/lib/libopencv_imgcodecs.so.4.0.1 
37         /usr/local/lib/libopencv_imgproc.so.4.0.1 
38         /usr/local/lib/libopencv_ml.so.4.0.1 
39         /usr/local/lib/libopencv_objdetect.so.4.0.1 
40         /usr/local/lib/libopencv_photo.so.4.0.1 
41         /usr/local/lib/libopencv_stitching.so.4.0.1 
42         /usr/local/lib/libopencv_videoio.so.4.0.1 
43         /usr/local/lib/libopencv_video.so.4.0.1
44  
45 SOURCES += 
46         main.cpp 
47         mainwindow.cpp 
48     bmputil.cpp 
49     addditional_utility.cpp
50  
51 HEADERS += 
52         mainwindow.h 
53     bmputil.h 
54     addditional_utility.h
55  
56 FORMS += 
57         mainwindow.ui
58  
59 TARGET = "The Binary BMP Picture watermarking Program"
60  
61 RC_FILE = my_app.rc

  测试用图(256*256,8 位 BMP 二值图像):

图 1 测试用图 Lena(256*256,8 bits)

其他

  在这里简要说明一下程序所编码水印的不可访问性。由数字水印的稳健性可知,数字水印必须难以被除去 , 如果只知道部分数字水印信息 , 那么试图除去或破坏数字水印将导致严重降质或不可用;又由数字水印的安全性知,数字水

印的信息应是安全的 , 难以篡改或伪造。由此推论,当以第三方途径解析带数字水印图像时,结果必然错误。

        bmp 图像的组成格式部分为: bmp 文件头 (14 bytes) + 位图信息头 (40bytes) + 调色板 ( 由颜色索引数决定 ) + 位图数据 ( 由图像尺寸决定 ) ,而设计题目要求处理的格式的为二值 bmp 图像,获取其 RGB 值是方便的。

        基于以上观点,我采用以下方法证明设计中程序所编码难以篡改与不可访问:

        (1 )若二值 bmp 图像未加水印,则可以通过读取原图的 RGB 像素矩阵另存为新图,其与原图像具有相同的性质,可以访问;

        (2 )若二值 bmp 图像添加了数字水印,则无法通过读取原图的 RGB 像素矩阵将其还原,所得图像不具有访问性。

实验代码

  1 #include <stdio.h>  
  2 #include <string.h>  
  3 #include <sys/types.h>  
  4 #include <fstream>
  5 #include <stdlib.h>
  6 #include <time.h>
  7 #include <unistd.h>
  8 #include <string>
  9  
 10 #include <iostream>  
 11 #pragma pack(2)
 12 using namespace std;  
 13  
 14 typedef struct BITMAPFILEHEADER  
 15 {   
 16     u_int16_t bfType;   
 17     u_int32_t bfSize;   
 18     u_int16_t bfReserved1;   
 19     u_int16_t bfReserved2;   
 20     u_int32_t bfOffBits;   
 21 }BITMAPFILEHEADER;   
 22 typedef struct BITMAPINFOHEADER  
 23 {   
 24     u_int32_t biSize;   
 25     u_int32_t biWidth;   
 26     u_int32_t biHeight;   
 27     u_int16_t biPlanes;   
 28     u_int16_t biBitCount;   
 29     u_int32_t biCompression;   
 30     u_int32_t biSizeImage;   
 31     u_int32_t biXPelsPerMeter;   
 32     u_int32_t biYPelsPerMeter;   
 33     u_int32_t biClrUsed;   
 34     u_int32_t biClrImportant;   
 35 }BITMAPINFODEADER;  
 36  
 37  
 38 class OperateBMP
 39 {
 40 public:
 41     int readBmp();
 42     int saveBmp();
 43     void work();
 44     void compareBMP();
 45     ~OperateBMP();
 46  
 47 private:
 48     BITMAPFILEHEADER BMFH;
 49     BITMAPINFODEADER BMIH;
 50     int biWidth;            //图像宽  
 51     int biHeight;           //图像高  
 52     int biBitCount;         //图像类型,每像素位数  
 53     unsigned char *pBmpBuf; //存储图像数据  
 54     int lineByte;           //图像数据每行字节数
 55  
 56     string originFileName_string;
 57     string newFileName_string;
 58     const char *originFileName_char;
 59     const char *newFileName_char;
 60  
 61     string pictureName_without_type;
 62  
 63     //string resultName_string;
 64     char *resultName_char;
 65     int lengthOfResultName_char;
 66  
 67     string compareResult;
 68 };
 69  
 70  
 71 OperateBMP::~OperateBMP()
 72 {
 73     /*
 74     delete pBmpBuf;
 75     delete originFileName_char;
 76     delete newFileName_char;
 77     delete resultName_char;
 78     */
 79 }
 80  
 81 int OperateBMP::readBmp()  
 82 {  
 83     FILE *fp;  
 84     cout << "Please input the name of the origin BMP file:" << endl;
 85     cin >> originFileName_string;
 86     originFileName_char = new char[ originFileName_string.length() ];
 87     originFileName_char = originFileName_string.c_str();
 88  
 89     if( (fp = fopen(originFileName_char,"rb")) == NULL)  //以二进制的方式打开文件  
 90     {  
 91         cout<<"The file "<<originFileName_char<<"was not opened"<<endl;  
 92         return -1;  
 93     }  
 94     if(fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_CUR))  //跳过BITMAPFILEHEADE  
 95     {  
 96         cout<<"跳转失败"<<endl;  
 97         return -1;  
 98     }  
 99     
100     fread(&BMIH,sizeof(BITMAPINFOHEADER),1,fp);   //从fp中读取BITMAPINFOHEADER信息到infoHead中,同时fp的指针移动  
101     biWidth = BMIH.biWidth;  
102     biHeight = BMIH.biHeight;  
103     biBitCount = BMIH.biBitCount;  
104     lineByte = (biWidth*biBitCount/8+3)/4*4;   //lineByte必须为4的倍数
105     //24位bmp没有颜色表,所以就直接到了实际的位图数据的起始位置  
106     pBmpBuf = new unsigned char[lineByte * biHeight];  
107     fread(pBmpBuf,sizeof(char),lineByte * biHeight,fp);  
108     fclose(fp);   //关闭文件  
109     //delete fp;
110     return 0;  
111 }
112  
113 int OperateBMP::saveBmp()  
114 {  
115     FILE *fp;  
116     newFileName_char = new char[ originFileName_string.length() + 4 ];
117     newFileName_string = "New_" + originFileName_string;
118     newFileName_char = newFileName_string.c_str();
119  
120     int pos = originFileName_string.find(".bmp");
121     pictureName_without_type = originFileName_string.erase( pos, 4 );
122     //cout << pictureName_without_type << endl;
123  
124     if( (fp = fopen(newFileName_char,"wb") )== NULL)   //以二进制写入方式打开  
125     {  
126         cout<<"打开失败!"<<endl;  
127         return -1;  
128     }  
129     //设置BITMAPFILEHEADER参数  
130     BMFH.bfType = 0x4D42;     
131     BMFH.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + lineByte * biHeight;  
132     BMFH.bfReserved1 = 0;  
133     BMFH.bfReserved2 = 0;  
134     BMFH.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  
135     fwrite(&BMFH,sizeof(BITMAPFILEHEADER),1,fp);  
136     //设置BITMAPINFOHEADER参数  
137     
138     BMIH.biSize = 40;  
139     BMIH.biWidth = biWidth;  
140     BMIH.biHeight = biHeight;  
141     BMIH.biPlanes = 1;  
142     BMIH.biBitCount = biBitCount;  
143     BMIH.biCompression = 0;  
144     BMIH.biSizeImage = lineByte * biHeight;  
145     BMIH.biXPelsPerMeter = 0;  
146     BMIH.biYPelsPerMeter = 0;  
147     BMIH.biClrUsed = 0;  
148     BMIH.biClrImportant = 0;  
149     //写入  
150     fwrite(&BMIH,sizeof(BITMAPINFOHEADER),1,fp); 
151  
152  
153     fwrite(pBmpBuf,sizeof(char),lineByte * biHeight,fp);  
154     fclose(fp);    //关闭文件  
155     return 0;  
156 }
157 void OperateBMP::work()  
158 {  
159     if(-1 == readBmp())  
160         cout<<"readfile error!"<<endl;  
161     //输出图像的信息  
162     cout<<"Width = "<<biWidth<<" Height = "<<biHeight<<" biBitCount="<<biBitCount<<endl;
163     string TXT = "imageData_" + originFileName_string + ".txt"; //15
164  
165     const char *TXT_char = TXT.c_str();  
166  
167     resultName_char = new char[ 15 + originFileName_string.length() ];
168  
169     strcpy( resultName_char, TXT_char );
170  
171     lengthOfResultName_char = 15 + originFileName_string.length();
172     ofstream outfile(TXT_char,ios::in | ios::trunc);  
173     if(!outfile)  
174     {  
175         cout<<"open error"<<endl;  
176         return ;  
177     }  
178     int count = 0;  
179     //图像数据信息是从左下角按行开始存储的  
180     for(int i = 0; i < biHeight; i++ )  
181     {  
182         for(int j = 0; j < biWidth; j++ )  
183         {  
184             for(int k = 0; k < 3; k++ )  
185             {  
186                 int temp = *(pBmpBuf + i * lineByte + j + k);  
187                 count++;  
188                 outfile<<temp<<" ";  
189                 if(count % 8 == 0)  
190                 {  
191                     outfile<<endl;  
192                 }  
193             }  
194         }  
195     }  
196     cout<<"总的像素数:"<<count / 3<<endl;
197     newFileName_string = "_" + originFileName_string;
198     newFileName_char = new char[ newFileName_string.length() ];
199     newFileName_char = newFileName_string.c_str();  
200     saveBmp();  
201     
202     return ;
203 }  
204  
205  void OperateBMP::compareBMP()
206  {
207     OperateBMP OBMP1, OBMP2;
208     OBMP1.work();
209     OBMP2.work();
210     char *fileName1, *fileName2;
211     fileName1 = new char[OBMP1.lengthOfResultName_char];
212     strcpy(fileName1, OBMP1.resultName_char );
213     fileName2 = new char[OBMP2.lengthOfResultName_char];
214     strcpy( fileName2, OBMP2.resultName_char );
215  
216  
217     FILE *fp1 = fopen( fileName1, "r" ), *fp2 = fopen( fileName2, "r" );
218  
219     int arr1[10], arr2[10], firstRow, firstColumn;
220     int length = 0;
221     long long cnt = 0;
222     compareResult = OBMP1.pictureName_without_type + "_With_" + OBMP2.pictureName_without_type + "_Comparing_Result.txt";
223     char *resultTXT = new char[ compareResult.length() ];//compareResultLength.c_str();
224     strcpy( resultTXT, compareResult.c_str() );
225     freopen( resultTXT, "w", stdout );
226     while( ( fscanf( fp1, "%d %d %d %d %d %d %d %d 
", &arr1[0], &arr1[1], &arr1[2], &arr1[3], &arr1[4], &arr1[5], &arr1[6], &arr1[7] ) != EOF ) && ( fscanf( fp2, "%d %d %d %d %d %d %d %d 
", &arr2[0], &arr2[1], &arr2[2], &arr2[3], &arr2[4], &arr2[5], &arr2[6], &arr2[7] ) != EOF ) )
227     {
228         length ++;
229         for( int i = 0; i < 7; i ++ )
230         {
231             if( arr1[i] != arr2[i] )
232             {
233                 cnt ++;
234                 cout << "NO " << cnt << " difference:" << endl;
235                 cout << "row = " << length << ", " << "column = " << i + 1 << endl;
236                 printf( "In file %s
arr1[%d][%d] = %d
In file %s
arr2[%d][%d] = %d

", fileName1,length, i + 1, arr1[i], fileName2, length, i + 1, arr2[i] );
237                 //break;
238                 //return 0;
239             }
240         }
241     }
242     delete fileName1;
243     delete fileName2;
244     if( cnt == 0 )
245     {
246         cout << "These two pictures come to one!" << endl;
247     }
248  
249     delete fp1;
250     delete fp2;
251     delete resultTXT;
252  }
253  
254  
255 int main(int argc,char *argv[])  
256 { 
257     
258     OperateBMP OBMP;
259     OBMP.compareBMP();
260      
261     return 0;  
262 }

实验结果

  选取的 3 张图的原图、程序解析原图复制的图像,以及水印编码后的图像、程序解析被编码图复制的图像,如图 2 至图 13 所示。

 

图 2 原图 A

 

图 3 程序解析原图 A 复制得图 B

 

图 4 水印编码图 A 得图 C

 

图 5 程序解析图 C 复制得图 D 打开效果

 

 

 

图 6 原图 E

 

图 7 程序解析原图 E 复制得图 F

 

图 8 水印编码图 E 得图 G

 

图 9 程序解析图像 G 复制得图像 H 的打开效果

 

图 10 原图 I

 

图 11 程序解析原图 I 复制得图 J

 

图 12 水印编码图 I 得图 K

 

图 13 程序解析图像 I 复制得图像 L 的打开效果

 

  实验结论:

        综合上述判定方法与结果,原报告中设计的算法编码的二值图像数字水印具有安全性与稳健性。显然被水印编码图像与原图像,无法产生人类视觉明显的差别,因此符合隐蔽性。

        计算图像的 PSNR 与 SSIM 所用 matlab 代码:

  1 clc;
  2 close all;
  3  
  4 path1 = '/home/u25th_engineer/lena256.bmp';
  5 path2 = '/home/u25th_engineer/lena256_encode.bmp';
  6 path3 = '/home/u25th_engineer/lena256_encode_noise.bmp';
  7  
  8  
  9 X = imread(path1);
 10  
 11 Y = imread(path2);
 12  
 13 %KKK = filter2( fspecial('sobel'), Y );
 14 %X1 = mat2gray(Y)
 15 %Z=X1;
 16 Z = imnoise(Y, 'salt & pepper');%添加椒盐噪声,也可以改成其他噪声
 17 %Z = imnoise(Y,'gaussian',0.1,0.004);
 18 imwrite( Z, path3 );
 19 %A = fspecial('average',3); %生成系统预定义的3X3滤波器  
 20  
 21  
 22  
 23 figure;
 24 subplot(1, 3, 1); imshow(X); title('a.Lena256原图像');
 25 subplot(1, 3, 2); imshow(Y); title('b.加水印图像');
 26 subplot(1, 3, 3); imshow(Z); title('c.水印图像加噪声');
 27  
 28  
 29 X = double(X); 
 30 Y = double(Y); 
 31  
 32 D = Y - X;
 33 MSE = sum(D(:).*D(:)) / numel(Y);%均方根误差MSE
 34  
 35  
 36 d = 0;
 37 e = 0;
 38 file_name = path1;       
 39 cover_object = double(imread(file_name));
 40 Mc = size(cover_object, 1);     %原图像行数
 41 Nc = size(cover_object, 2);     %原图像列数
 42 file_name = path2;
 43 watermarked_image = double(imread(file_name));
 44 %计算信噪比
 45 for i = 1 : Mc
 46     for j = 1 : Nc
 47         a(i, j) = cover_object(i, j) ^ 2;
 48         b(i, j) = cover_object(i, j) - watermarked_image(i, j);
 49         d = d + a(i, j);
 50         e = e + b(i, j) ^ 2;
 51     end
 52 end
 53 PSNR = 10 * log10(d / e);
 54  
 55 MAE = mean(mean(abs(D)));%平均绝对误差
 56  
 57 w = fspecial('gaussian', 11, 1.5); %window 加窗  
 58 K(1) = 0.01; 
 59 K(2) = 0.03; 
 60 L = 255; 
 61 Y = double(Y); 
 62 X = double(X); 
 63 C1 = (K(1)*L) ^ 2;
 64 C2 = (K(2)*L) ^ 2; 
 65 w = w/sum(sum(w)); 
 66 ua = filter2(w, Y, 'valid');%对窗口内并没有进行平均处理,而是与高斯卷积,  
 67 ub = filter2(w, X, 'valid'); % 类似加权平均  
 68 ua_sq = ua.*ua;
 69 ub_sq = ub.*ub;
 70 ua_ub = ua.*ub; 
 71 siga_sq = filter2(w, Y.*Y, 'valid') - ua_sq; 
 72 sigb_sq = filter2(w, X.*X, 'valid') - ub_sq; 
 73 sigab = filter2(w, Y.*X, 'valid') - ua_ub;
 74 ssim_map = ((2*ua_ub + C1).*(2*sigab + C2))./((ua_sq + ub_sq + C1).*(siga_sq + sigb_sq + C2)); 
 75 MSSIM = mean2(ssim_map); 
 76  
 77 display(MSE);%均方根误差MSE
 78 display(PSNR);%峰值信噪比
 79 display(MAE);%平均绝对误差
 80 display(MSSIM);%结构相似性SSIM
 81  
 82 %display(d);
 83 %display(e);
 84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 85 X = double(X); 
 86 Y = double(Z); 
 87  
 88 D = Y - X;
 89 MSE1 = sum(D(:).*D(:)) / numel(Y);%均方根误差MSE
 90  
 91  
 92 d = 0;
 93 e = 0;
 94 file_name = path1;       
 95 cover_object = double(imread(file_name));
 96 Mc = size(cover_object, 1);     %原图像行数
 97 Nc = size(cover_object, 2);     %原图像列数
 98 file_name = path3;
 99 watermarked_image = double(imread(file_name));
100 %计算信噪比
101 for i = 1 : Mc
102     for j = 1 : Nc
103         a(i, j) = cover_object(i, j) ^ 2;
104         b(i, j) = cover_object(i, j) - watermarked_image(i, j);
105         d = d + a(i, j);
106         e = e + b(i, j) ^ 2;
107     end
108 end
109 PSNR1 = 10 * log10(d / e);
110  
111 MAE1 = mean(mean(abs(D)));%平均绝对误差
112  
113 w = fspecial('gaussian', 11, 1.5); %window 加窗  
114 K(1) = 0.01; 
115 K(2) = 0.03; 
116 L = 255; 
117 Y = double(Y); 
118 X = double(X); 
119 C1 = (K(1)*L) ^ 2;
120 C2 = (K(2)*L) ^ 2; 
121 w = w/sum(sum(w)); 
122 ua = filter2(w, Y, 'valid');%对窗口内并没有进行平均处理,而是与高斯卷积,  
123 ub = filter2(w, X, 'valid'); % 类似加权平均  
124 ua_sq = ua.*ua;
125 ub_sq = ub.*ub;
126 ua_ub = ua.*ub; 
127 siga_sq = filter2(w, Y.*Y, 'valid') - ua_sq; 
128 sigb_sq = filter2(w, X.*X, 'valid') - ub_sq; 
129 sigab = filter2(w, Y.*X, 'valid') - ua_ub;
130 ssim_map = ((2*ua_ub + C1).*(2*sigab + C2))./((ua_sq + ub_sq + C1).*(siga_sq + sigb_sq + C2)); 
131 MSSIM1 = mean2(ssim_map); 
132  
133 display(MSE1);%均方根误差MSE
134 display(PSNR1);%峰值信噪比
135 display(MAE1);%平均绝对误差
136 display(MSSIM1);%结构相似性SSIM
137  
138 %display(d);
139 %display(e);
原文地址:https://www.cnblogs.com/25th-engineer/p/10818294.html