“程序设计与算法训练”课程设计“二值图像数字水印技术的实践”
某垃圾大学数据结构课设(题目抄袭自某牛逼985高校)。
github项目地址(含报告等):https://github.com/25thengineer/Data-Structure-Course-Design-Practice-of-Binary-Image-Digital-Watermarking-Technology
(1)操作系统:Ubuntu 16.04 LTS;
(2)开发工具:Qt、Qt 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!\nThe 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(".+\n");
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 \n", &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 \n", &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\narr1[%d][%d] = %d\nIn file %s\narr2[%d][%d] = %d\n\n", 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);