基于bmp文件格式的FFT变换域图像隐写


算法概述

本程序采用的编程语言是C#。
最初采用C#的Aforge.imaging库,经过多次调试以及算法函数分析发现该库中的FFT实现存在很大的问题,它只能够将图片傅里叶变换,而不能实现逆变换。
并且在StackOverflow论坛中也有人提出类似的问题,没有能够解决这一问题
后来在Exocortex.DSP库中找到了实现功能相对较好的函数,但是该算法实现还是存在问题的。
在将图片初次变换后是能当场变回去的,但是将变换后的图片作为输入进行逆变换的时候就不能输出图像了。
经过多次调试以及对算法中傅里叶系数值的分析,发现将经过傅里叶变换后图片矩阵转化成傅里叶复数的系数时的系数与将是不一样的,经过调试分析和资料查阅
发现问题出现在该算法中对 Π(Pi)、Log(N)等对精度高要求的数值的处理上,以及变换后图片的图片矩阵中对傅里叶系数转换的问题。
对于大多数 FFT 算法,“非准确度”随着输入元素的大小大致以 O(NlogN) 增长;和 KCS/IEEE754 浮点数携带大约 24 位精度。因此,对于不是超长的 FFT,数据中的噪声、不完善的抗混叠和输入的量化通常大于算术误差。
经过分析后,我想到了将变换后的傅里叶系数直接保留。
我的思路是在原图经过初次快速傅里叶变换后的系数保存下来,这样在还原的时候可以直接根据这些数据较完整地还原出原始图像。
本程序实现的是BMP格式的数据保留,通过修改bmp文件头将系数保留在文件的格式中指定位置并且不会对图像文件的像素数据产生影响。在将秘密信息嵌入到文件头前会先经过AES加密算法加密一下,那么在bmp文件头中的数据是以密文的形式嵌入到文件中,提高了一定的安全性。
如果选择LSB算法嵌入信息,会直接破坏图像像素数据,这样会导致原本计算精度就并不理想的情况下矩阵系数更加不精确,所以采用了不影响文件像素的文件格式方式嵌入。

BMP介绍

BMP是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发,BMP位图格式理所当然地被广泛应用。这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但由此导致了它与生俱来的缺点–占用磁盘空间过大。所以,目前BMP在单机上比较流行。

BMP文件格式各比特位剖析

[0,14)是位图文件头部分。
[0,2):“BM”,即文件类型
[2,6):文件大小(字节为单位)
[6,10)保留位(全0)
[10,14)偏移量(文件头+信息头+调色板)
[14,54)是位图信息头部分,
[14,18)信息头长度
[18,22)宽度
[22,26)高度
[26,28)位面数(恒为1)
[28,30)每个像素的位数(8为灰度图,24为真彩色)
[30,34)压缩说明
[34,38)位图数据大小(4的倍数)
[38,42)水平分辨率 [42,46)垂直分辨率
[46,50)使用的颜色数
[50,54)指定重要的颜色数
[54,偏移地址)是图像的调色板部分
[偏移地址,最后)是文件的位图数据部分,存放图片的像素信息

加密思路

需要嵌入的信息主要分两个部分,第一是我们需要隐藏的信息文件,其次是在原图经过快速傅里叶变换后的系数
[0,64]存放水印文件名称,
[64,68]存放水印文件大小,
[68,68+size]存放文件的书局流,
[length-68-size]存放傅里叶系数
通过修改源文件格式中[6,10]部分保留位置,修改成总体嵌入数据的长度

流程图

加密流程图

在这里插入图片描述

解密流程图

在这里插入图片描述

信息嵌入流程图

在这里插入图片描述

信息提取流程

提取过程是嵌入的逆过程

程序演示

FFT变换

在这里插入图片描述
在这里插入图片描述

加密

在这里插入图片描述

解密

在这里插入图片描述

变换前载体和逆变换后(灰度图)相似度对比

在这里插入图片描述

变换后载体相似度嵌入前后对比

在这里插入图片描述

水印文件相似度嵌入和提取对比

在这里插入图片描述
由于是将图像信息嵌入到文件格式中而非像素点中,所以当然不会对嵌入后的图片的相似度有任何影响

分析

1.就FFT和DFT的复杂度分析而言,FFT的算法复杂度是O(NlogN),DFT的算法复杂度是O(N^2),优化效果很好
2.进行傅里叶变换之后的图片再次打开没有办法进行还原,通过将初次变换的系数信息和需嵌入的秘密文件信息嵌入到载体的二进制文件中,同时保证了变换后的图片能够实现逆变换,并且嵌入的水印文件是在文件位图数据和信息头之间,不会影响到载体的像素信息。
3.嵌入在二进制比特流中的信息是经过AES加密过的,就算攻击者掌握了算法,没有密钥也是无法还原出信息,实现了信息的保密性
4.变换前将载体图片转化成灰度图,是为了计算傅里叶系数的需要,没有能够实现彩色图片的信息隐藏和变换。
5.对于傅里叶变换的精度问题没有得到实质性解决,问题在于所使用算法存在一定的缺陷,其次,通过将系数嵌入到图片中的方法并不一定是科学的,只是能够满足变换后再变回去的操作
6.通过分析比较变换前后的傅里叶图像、变换前后的载体图像、以及嵌入的水印图片的PSNR、MSE等图片相似度后,发现对于修改文件头来嵌入数据是不会对文件像素产生影响,并且提取出傅里叶系数并还原后的图片与原载体图像的灰度图的差异也是在合理范围内。

部分代码展示

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows.Forms;
using 基于图片格式的信息隐写.Helper;
using 基于图片格式的信息隐写.Exceptions;
using 基于图片格式的信息隐写.Encryption;

namespace 基于图片格式的信息隐写
{
    public partial class Form1 : Form
    {
        private byte [] fileflow;
        private byte[] bmpflow;
        private uint file_size;
        private string filename_;
        private uint encrypted_length;
        private List<Byte> File_list=new List<byte> { };
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog S = new OpenFileDialog())
            {
                S.DefaultExt = "";
                S.Filter = "文件|*.*";
                S.InitialDirectory = "";
                if (S.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    this.label2.Text = S.FileName;

                    FileInfo file_ = new FileInfo(S.FileName);
                    filename_ = S.FileName.Substring(S.FileName.LastIndexOf("\\") + 1);
                    string fileNameBinary = Converter.StringToBinary(filename_, 64);
                    fileflow = File.ReadAllBytes(S.FileName);
                    byte[] temp = Converter.BinaryToByteArray(fileNameBinary);
                    for (int i = 0; i < temp.Length; i++)
                    {
                        File_list.Add(temp[i]);
                    }
                    for (int i = 64; i < fileflow.Length+64; i++)
                    {
                        File_list.Add(fileflow[i-64]);
                    }         
                    try
                    {
                        pictureBox2.Image = Image.FromFile(S.FileName);                      
                    }
                    catch (Exception)
                    {                       
                    }
                }
            }
        }
        private void button2_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog S = new OpenFileDialog())
            {
                S.DefaultExt = "";
                S.Filter = "BMP图像|*.bmp;*.BMP";
                S.InitialDirectory = "";
                if (S.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    try
                    {
                        pictureBox1.Image = Image.FromFile(S.FileName);
                        this.label1.Text = S.FileName;
                        bmpflow = File.ReadAllBytes(S.FileName);
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("无效的图片文件");
                    }
                }
            }              
        }
        private void button3_Click(object sender, EventArgs e)
        {        
          
                byte[] fileflow_encrypted = new byte[]{ };
                fileflow_encrypted = AES.Encrypt(File_list.ToArray(), this.textBox1.Text);
                encrypted_length = (uint)fileflow_encrypted.Length;
                byte[] encrypted_length_array = BitConverter.GetBytes(encrypted_length);
                file_size = encrypted_length;
                uint filesize = file_size;
                for (int i = 6; i < 10; i++)
                {
                bmpflow[i] = encrypted_length_array[i - 6]; 
                }
                byte[] size = new byte[4];
                //文件大小
                Array.Copy(bmpflow, 2, size, 0, 4);
                var size_int = BitConverter.ToUInt32(size, 0);
                size_int += filesize;
                byte[] temp = BitConverter.GetBytes(size_int);
                for (int i = 2; i < 6; i++)
                {
                    bmpflow[i] = temp[i-2]; //111
                }
                byte[] offset_array = new byte[4];
                Array.Copy(bmpflow, 10, offset_array, 0, 4);
            //偏移量
                var offset_int = BitConverter.ToUInt32(offset_array, 0);
                uint offset_origin = offset_int;
                offset_int += encrypted_length;
                byte[] temp2 = BitConverter.GetBytes(offset_int);

                for (int i = 10; i < 14; i++)
                {
                    bmpflow[i] = temp2[i-10];//1111
                }
                List<byte> arr1 = new List<byte> { };
                for (int i = 0; i < bmpflow.Length; i++)
                {
                    arr1.Add(bmpflow[i]);
                }
                List<byte> arr2 = new List<byte> { };
                for (int i = 0; i < fileflow_encrypted.Length; i++)
                {
                    arr2.Add(fileflow_encrypted[i]);
                }
                arr1.InsertRange((int)offset_origin, arr2);//插入
                byte[] compound_flow = arr1.ToArray();
            string stegoPath="";
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                stegoPath = dlg.SelectedPath;
            }
            string tempname = this.label1.Text.Substring(this.label1.Text.LastIndexOf("\\") + 1);
            tempname = tempname.Insert(tempname.LastIndexOf('.'), "_compound");
            stegoPath += "\\" + tempname;
            File.WriteAllBytes(stegoPath, 
                    compound_flow
                  );
        }
        private void button4_Click(object sender, EventArgs e)
        {
            byte[] compound = new byte[] { };
            using (OpenFileDialog S = new OpenFileDialog())
            {
                S.DefaultExt = "";
                S.Filter = "BMP图像|*.bmp;*.BMP";
                S.InitialDirectory = "";
                if (S.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    try
                    {
                        pictureBox3.Image = Image.FromFile(S.FileName);
                        this.label6.Text = S.FileName;
                        compound = File.ReadAllBytes(S.FileName);
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("无效的图片文件");
                    }
                }
                string filename;
                byte[] offset2 = new byte[4]; 
                //偏移量
                Array.Copy(compound,10 , offset2, 0, 4);
                var offset_int = BitConverter.ToUInt32(offset2, 0);
                byte[] length_array = new byte[4];
                //偏移量
                Array.Copy(compound, 6, length_array, 0, 4);
                var length_ = BitConverter.ToUInt32(length_array, 0);

                byte[] encrypted_flow = new byte[length_];
                Array.Copy(compound, offset_int - length_, encrypted_flow, 0, length_);
                try
                {
                    byte[] extracted_flow =
                   AES.Decrypt(encrypted_flow, this.textBox1.Text);
                    int size = extracted_flow.Length;
                    byte[] name = new byte[64];
                    byte[] extracted_fileflow = new byte[size - 64];
                    Array.Copy(extracted_flow, 0, name, 0, 64);
                    Array.Copy(extracted_flow, 64, extracted_flow, 0, size - 64);
                    filename = Converter.ByteArrayToBinary(name);
                    filename = Converter.BinaryToString(filename).Replace("\0", "");
                    FolderBrowserDialog s = new FolderBrowserDialog();
                    string result_path = "";
                    if (s.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        result_path = s.SelectedPath + "\\" + filename.Insert(filename.LastIndexOf('.'), "_extracted");
                    }
                    File.WriteAllBytes(result_path,
                       extracted_flow
                     );
                    this.label8.Text = result_path;
                    try
                    {
                        pictureBox4.Image = Image.FromFile(result_path);
                    }
                    catch (Exception)
                    {
                        
                    }
                }
               catch(Exception)
                {
                    MessageBox.Show("无效的秘钥");
                }
               
            }
        }
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值