20221320 冯泰瑞 《信息安全综合实践》课程设计报告——基于文本文件信息隐藏和二值图像信息隐藏的回声信息隐藏算法实现

20221320 冯泰瑞 《信息安全综合实践》课程设计报告——基于文本文件信息隐藏和二值图像信息隐藏的回声信息隐藏算法实现

任务简介

隐藏原理

研究发现,HAS(Human Audio System,人类听觉系统)存在感知掩蔽效应,即强信号的存在会使其附近的弱信号难以被感知。因此,当回声与原声的间隔充分接近时(如图所示),人耳难以区别回声和原声。

由图可知,回声和原声间的延迟在一定范围内人耳都难以察觉,因此,可人为添加不同延迟的回声表征要“隐藏”(嵌入)的0、1比特。
回声信号,可简单模拟为原始信号经过时延和幅度衰减后产生的信号(如图所示)。设原信号为x(t),时延为δ,衰减为α,则叠加回声的信号为:y(t)= x(t) + αx(t - δ)。

通过上述方法嵌入秘密信息后,在接收端,要提取秘密信息(即0、1比特),只需要检测回声延迟。
根据数字信号处理相关知识可知,检测回声可计算隐写载体的倒谱自相关系数,在回声位置处,倒谱自相关系数将出现局部最大值。这个过程可以进一步简化,根据隐藏步骤,回声只可能出现在特定位置,故而,只需比较特定位置处倒谱自相关系数大小即可判决。
一段音频可以嵌入若干比特信息,即将原始音频分段,每个分段叠加不同延迟(表征0、1比特)的回声。这个过程如图所示。

  • 产生延迟为d0和d1的回声表征比特0和1

  • 根据要嵌入的消息产生混频器(用于截取回声)

  • 根据要嵌入的消息产生叠加于不同分段的回声信号

课设内容

实现简单回声信息隐藏算法并进行性能分析,准备一段单声道,采样频率为8000Hz,时长56秒,每样点用16比特有符号数表示的话音文件(WAV格式)。

分段大小对算法性能的影响研究

固定回声延迟和衰减系数(例如,延迟为8、12个样点,衰减系数为0.6),依次设置分段大小为64、128、256、512、1024、2048个样点,记录该参数下,算法的稳健性(无攻击以及叠加25dB高斯噪声情况下的误码率)。

衰减系数对算法性能的影响研究

固定回声延迟和分段大小(例如,延迟为8、12个样点,分段大小为1024个样点),依次设置衰减系数为0.3~0.9,记录该参数下,算法的稳健性(无攻击以及叠加25dB高斯噪声情况下的误码率)。

回声延迟对算法性能的影响研究

固定衰减系数和分段大小(例如,衰减系数为0.6,分段大小为1024个样点),依次设置回声延迟对为:(1,2),(4,6),(8,12),记录该参数下,算法的稳健性(无攻击以及叠加25dB高斯噪声情况下的误码率)。

回声隐藏算法稳健性

固定参数(分段:1024,延迟:8和12,衰减系数:0.6),分析稳健性。主要检测隐写对象经下述攻击后的误码率(误码率低于20%则可接受,可经由纠错编码恢复),攻击包括:叠加高斯白噪、重采样(函数:resample)。

系统设计

系统组成

信息隐藏模块

核心函数:simple_param_code
功能:将用户输入的消息文本编码为比特序列,并利用音频信号和指定的延迟参数、片段参数、衰减参数等,通过调用hide_simple函数,将比特序列隐藏到音频信号中,生成嵌入信息后的音频信号,并保存为文件。

信息提取模块

核心函数:simple_param_decode
功能:从隐藏信息的音频文件中读取音频信号,调用dh_simple函数对音频信号进行解码,提取出隐藏的二进制比特序列,然后与原始的比特序列及消息文本进行对比,计算错误率,并将提取出的比特序列转换回字符串形式输出。

辅助功能模块

字符串与比特序列转换:
str2bit:将输入的字符串转换为对应的二进制比特序列,为信息隐藏模块提供输入数据格式转换。
bit2str:将输入的二进制比特序列转换为对应的字符组成的字符串,用于信息提取模块将解码后的比特序列转换为可读文本。
toStr:将由0和1组成的数组转换为对应的字符串,辅助bit2str函数进行数据格式转换。

音频处理与测试模块:

simple_test:作为测试脚本,用于测试整个信息隐藏与提取系统的功能。它定义了相关参数,调用信息隐藏和提取模块的函数,并进行一些额外的音频处理操作(如添加噪声、重采样等),以验证系统在不同情况下的性能。还定义了compareAudioWaveforms函数,用于绘制多个音频文件的波形对比图,直观展示处理效果。

功能模块

信息隐藏模块(simple_param_code)

输入参数:延迟参数D0、D1,片段参数FRAG,衰减参数ATTEN,要隐藏的消息文本msg。
处理流程:将消息文本转换为比特序列。读取音频文件converted.wav中的音频信号和采样率,对音频信号进行转置处理。根据比特序列长度和片段参数计算所需音频信号长度,若当前音频信号长度不足,则通过重复音频信号的方式进行补充。调用hide_simple函数,将比特序列隐藏到音频信号中。该函数通过生成不同延迟的回声信号,并根据数据位选择不同延迟回声的片段,实现信息隐藏。将隐藏信息后的音频信号保存为hide.wav文件。

信息提取模块(simple_param_decode)

输入参数:延迟参数D0、D1,片段参数FRAG,原始的加密文本msg,原始的二进制比特序列bits。
处理流程:从hide.wav文件中读取音频信号并转置为行向量。调用dh_simple函数对音频信号进行解码。该函数使用倒谱分析方法,通过快速傅里叶变换(FFT)、取对数幅度、逆快速傅里叶变换(IFFT)等操作,获取倒谱,并根据延迟参数对应位置的值的大小关系确定解码的比特值,从而提取出隐藏的二进制信息序列。取原始比特序列和提取出的比特序列长度的最小值,计算错误率,并输出相关信息。将提取出的二进制比特序列转换为字符串形式,与原始加密文本进行对比,计算文本的错误码率,并输出解码后的文本及其相关信息。

辅助功能模块

字符串与比特序列转换:
str2bit:接收输入字符串,计算转换后的总比特数,初始化全零向量,遍历字符串中的每个字符,将其转换为8位二进制字符串并连接起来,再将连接后的二进制字符串中的每个字符转换为对应的数值(0或1),存入向量中,若遇到非0非1字符则输出错误信息并返回。
bit2str:初始化输出字符串为空,计算输入二进制比特序列可组成的8位字符单元数量,遍历每个单元,提取8位比特位,转换为字符串形式,再转换为十进制数值并对应为字符,存入输出字符串中。
toStr:初始化输出字符串为空,遍历输入数组的每个元素,根据元素值(0或1)将对应字符连接到输出字符串末尾。

音频处理与测试模块(simple_test):

定义相关参数,如衰减因子、回声延迟时间、片段长度等。
读取文件hidden.txt的内容并转换为字符数组。调用simple_param_code函数进行信息嵌入,得到比特流和状态信息。使用simple_param_decode函数对有回声音频进行信息提取,验证结果。对hide.wav文件重命名,调用passchannel函数添加噪声,再次进行解码验证。对hide_no_noise.wav文件进行重采样操作,再进行解码验证。
定义并调用compareAudioWaveforms函数,绘制多个音频文件的波形对比图。该函数读取多个音频文件的音频数据和采样率,计算时间向量,绘制波形,并设置相关标签和标题。

流程图



系统实现(伪代码)

信息隐藏模块

function simple_param_code(D0, D1, FRAG, ATTEN, msg)
    % 将消息文本转换为比特序列
    bits = str2bit(msg)
    % 从音频文件读取音频信号和采样率
    s, fs = audioread('converted.wav')
    print sampling rate
    % 转置音频信号
    s = s'
    s_len = length(s)
    t_len = length(bits) * FRAG
    if s_len < t_len
        // 计算需要重复音频信号的倍数和剩余长度
        multiple = floor(t_len / s_len)
        remainder = mod(t_len, s_len)
        tmp = []
        for i = 1 to multiple
            tmp = [tmp s]
        tmp = [tmp s(1:remainder)]
        s = tmp
    end
    % 使用指定参数将比特序列隐藏到音频信号中
    bld = hide_simple(D0, D1, FRAG, bits, s, ATTEN)
    % 保存隐藏信息后的音频信号
    audiowrite('hide.wav', bld, fs)
end

function hide_simple(d0, d1, fragment, data, s, atten)
    % 确保音频信号是行向量
    if s 的行数 > 列数
        s = s'
    end
    len = length(s)
    % 生成不同延迟的回声信号
    s0 = atten * [s(d0+1:len), zeros(1, d0)] 
    s1 = atten * [s(d1+1:len), zeros(1, d1)] 
    i = 0
    N = floor(len / fragment)
    lend = length(data)
    o = s0
    for i = 0 to N - 1
        if (i + 1) > lend
            bit = 0
        else
            bit = data(i + 1)
        end
        if bit == 1
            st = i * fragment + 1
            ed = (i + 1) * fragment
            o(st : ed) = s1(st : ed)
        end
    end
    % 相加得到最终音频信号
    o = s + o
end

信息提取模块

function simple_param_decode(D0, D1, FRAG, msg, bits)
    % 打开标准输出
    fid = 1 
    % 从文件读取音频信号
    y = audioread('hide.wav')' 
    % 对音频信号进行解码
    out = dh_simple(D0, D1, FRAG, y) 
    len = min(length(bits), length(out)) 
    % 计算错误率
    rate = sum(abs(bits(1:len)-out(1:len)))/len 
    print len, error rate, error num
    % 将提取的比特序列转换为字符串
    dc_msg = bit2str(out(1:len)) 
    len = min(length(msg), length(dc_msg)) 
    if len!= 0
        err = zeros(1, len) 
        for n = 1 to len
            err(n) = 1 - strcmp(msg(n), dc_msg(n)) 
        end
        print decoded text and its length
        print error code rate
    else
        print too much error, msg can not be recovered
    end
end

function dh_simple(d0, d1, fragment, in)
    if in 的行数 > 列数
        in = in'
    end
    i = 0
    out = []
    N = floor(length(in) / fragment)
    for i = 0 to N - 1
        st = i * fragment + 1
        ed = (i + 1) * fragment
        p = in(st : ed)
        p = fft(p)
        if p!= 0
            p = ifft(log(abs(p)))
        end
        p = abs(p)
        d11 = p(1 * d1 + 1)
        d01 = p(1 * d0 + 1)
        if d11 > d01
            out(i + 1) = 1
        else
            out(i + 1) = 0
        end
    end
end

辅助功能模块

function passchannel(varargin)
    check input parameters count (1 to 3)
    snr = varargin{1} 
    if input parameters count == 1
        % 选择源文件和保存文件
        [fn, pn] = uigetfile
        infn = strcat(pn, fn) 
        [fn, pn] = uiputfile
        outfn = strcat(pn, fn) 
    end
    if input parameters count == 3
        infn = varargin{2} 
        outfn = varargin{3} 
    end
    % 读取音频数据
    [x, fs] = audioread(infn)
    % 添加噪声
    y = awgn(x, snr,'measured') 
    % 保存处理后的音频数据
    audiowrite(outfn, y, fs) 
end

function bit2str(in)
    out = ''
    len = round(length(in)/8)
    for n = 1 to len
        temp = in((n - 1) * 8 + 1:n * 8)
        temp = toStr(temp)
        out(n) = bin2dec(temp)
    end
end

function str2bit(varargin)
    source = ''
    str = ''
    if no input parameters
        source = input user for plain text
    else
        source = varargin{1}
    end
    source_len = length(source) * 8
    data = zeros(1, source_len)
    for n = 1 to length(source)
        temp = dec2bin(source(n), 8)
        str = strcat(str, temp)
    end
    for n = 1 to source_len
        if str(n) == '0'
            data(n) = 0 
        else if str(n) == '1'
            data(n) = 1
        else
            print error bit
            return
        end
    end
    end
end

function toStr(x)
    y = ''
    for n = 1 to length(x)
        if x(n) == 1
            y = strcat(y, '1')
        else
            y = strcat(y, '0')
        end
    end
end

音频处理与测试模块

function simple_test()
    clear all
    close all
    clc
    atten = 0.95 
    D0 = 8
    D1 = 12
    FRAG = 1024 
    msg = read from 'hidden.txt'
    % 信息嵌入
    [bits, s] = simple_param_code(D0, D1, FRAG, atten, msg)
    print 'hide_no_noise'
    % 信息提取验证
    simple_param_decode(D0, D1, FRAG, msg, bits)
    % 重命名文件
    movefile('hide.wav', 'hide_no_noise.wav', 'f')
    % 添加噪声
    passchannel(25, 'hide_no_noise.wav', 'hide.wav') 
    print 'hide'
    % 对添加噪声后的音频文件解码
    simple_param_decode(D0, D1, FRAG, msg, bits)
    % 重采样操作
    m, fs = audioread('hide_no_noise.wav')
    s_fs = fs
    m = m'
    m = resample(m, 6000, fs)
    audiowrite('hide_resamplemid.wav', m, 6000)
    n, fs = audioread('hide_resamplemid.wav')
    n = n'
    n = resample(n, s_fs, fs)
    audiowrite('hide.wav', n, s_fs)
    print'resample-attack'
    % 对重采样后的音频文件解码
    simple_param_decode(D0, D1, FRAG, msg, bits)
    % 绘制多个音频文件的波形对比图
    compareAudioWaveforms()
end

function compareAudioWaveforms()
    allFiles = {'converted.wav', 'hide.wav', 'hide_no_noise.wav', 'hide_resamplemid.wav'}
    numFiles = length(allFiles)
    fileDescriptions = {'原始的音频波形', '有噪声的隐藏回声信息后的波形', '未添加噪声的隐藏回声信息后的波形', '重采样后的隐藏波形'}
    create new figure
    for i = 1 to numFiles
        audioData, sampleRate = audioread(allFiles{i})
        time = (0:length(audioData) - 1) / sampleRate
        subplot(numFiles, 1, i)
        plot(time, audioData)
        set y-axis label
        set x-axis label
        set subplot title
    end
    set figure title
end

系统测试

简单回声信息隐藏算法性能分析

分段大小对算法性能的影响研究

固定回声延迟和衰减系数(例如,延迟为8、12个样点,衰减系数为0.6),依次设置分段大小为64、128、256、512、1024、2048个样点,隐藏的信息为abcdefg123456789,记录该参数下,算法的容量(bit/s)、稳健性(无攻击以及叠加25dB高斯噪声情况下的误码率)。

分段大小 稳健性(误码率) 稳健性(误码率)
无高斯白噪攻击 高斯白噪攻击
64 0.875000 1.000000
128 0.437500 0.750000
256 0.375000 0.500000
512 0.000000 0.125000
1024 0.000000 0.125000
2048 0.000000 0.062500

当延时和衰减系数固定的时候,分段大小对误码率的影响较大,随着分段大小的不断增大,误码率在有无攻击的情况下均先减小后增大。

衰减系数对算法性能的影响研究

固定回声延迟和分段大小(例如,延迟为8、12个样点,分段大小为1024个样点),依次设置衰减系数为0.3~0.9,记录该参数下,算法的容量(bit/s)、稳健性(无攻击以及叠加25dB高斯白噪声情况下的误码率)。

衰减系数 稳健性(误码率) 稳健性(误码率)
无高斯白噪攻击 高斯白噪攻击
0.3 0.562500 0.375000
0.4 0.062500 0.250000
0.5 0.000000 0.250000
0.6 0.000000 0.125000
0.7 0.000000 0.125000
0.8 0.000000 0.062500
0.9 0.000000 0.062500

当回声延迟和分段大小固定的时候,随着衰系数的不断增加,误码率在有无攻击的情况下均越来越低,说明回声的强度越大,抵抗干扰的能力越强,但是被发现的可能性越大。

回声延迟对算法性能的影响研究

固定衰减系数和分段大小(例如,衰减系数为0.6,分段大小为512个样点),依次设置回声延迟对为:(1,2),(4,6),(8,12),记录该参数下,算法的容量(bit/s)、稳健性(无攻击以及叠加25dB高斯噪声情况下的误码率)。

回声延迟对 稳健性(误码率) 稳健性(误码率)
无高斯白噪攻击 高斯白噪攻击
(1,2) 0.937500 1.000000
(4,6) 0.062500 0.125000
(8,12) 0.000000 0.125000

在回声延迟为(8,12)时可以明显的听到有回声在原来的音频上播放,虽然不影响获取信息,但是已经能发现有回声。从后两列可以看出随着回声延迟的增加,误码率越来越低。

回声隐藏算法稳健性

固定参数(分段:1024,延迟:8和12,衰减系数:0.6),分析稳健性。主要检测隐写对象经下述攻击后的误码率(误码率低于20%则可接受,可经由纠错编码恢复),攻击包括:叠加高斯白噪、重采样(函数resample)。隐藏的消息:'abcdefg123456789'

攻击手法 参数 误码率
高斯白噪 awgn(x,25%,’measured’) 0.062500
高斯白噪 awgn(x,15%,’measured’) 0.375000
高斯白噪 awgn(x,90%,’measured’) 0.000000
重采样 8000->6000->8000 0.000000
重采样 8000->3000->8000 0.187500
重采样 8000->9000->8000 0.000000

受到以上攻击后,除信噪较小情况外,音频的误码率均小于20%,可经由纠错编码恢复,因此在上述参数情况下,该算法能够抵抗重采样攻击。

算法改进

最初系统隐藏的信息为字符串,由回声隐藏算法稳健性可知,字符串在信噪比小的信道中传输误码率较高,稳健性较差。因此,在后续实验中,我们将嵌入信息由原来的字符串转化为二值图像。二值图像,作为一种特殊的图像格式,只包含两个颜色:黑色和白色,相当于像素值为0或1。相比灰度图或彩色图,二值图像具有数据量小、处理速度快、占用存储空间少等优势。由于其颜色的极端简化,使得二值图像在图像识别、数据加密和信息嵌入等领域的应用具有独特优势。
其次,最开始的系统主要依赖命令行输入和输出,而这次我们通过simple_test_gui函数创建了一个直观的GUI,用户可以通过点击按钮来选择音频和图片文件,操作更加简便、直观,降低了系统的使用门槛,提高了用户体验。
系统流程图如下:




改进后系统实现(伪代码)

信息隐藏模块

function simple_param_code(D0, D1, FRAG, ATTEN, img_path, audio_path)
    % 读取图像
    img = imread(img_path)
    if img 是彩色图像
        img = rgb2gray(img)
    end
    if img 不是二值图像
        img = imbinarize(img)
    end
    % 将二值图像矩阵转换为比特序列
    bits = img(:)'
    % 从音频文件读取音频信号和采样率
    s, fs = audioread(audio_path)
    print sampling rate
    s = s'
    s_len = length(s)
    t_len = length(bits) * FRAG
    if s_len < t_len
        multiple = floor(t_len / s_len)
        remainder = mod(t_len, s_len)
        tmp = []
        for i = 1 to multiple
            tmp = [tmp s]
        end
        tmp = [tmp s(1:remainder)]
        s = tmp
    end
    % 保存原始音频到 audio-used.wav
    audiowrite('audio-used.wav', s, fs)
    % 隐藏信息到音频信号
    bld = hide_simple(D0, D1, FRAG, bits, s, ATTEN)
    % 保存隐藏信息后的音频文件
    audiowrite('hide.wav', bld, fs)
end

function hide_simple(d0, d1, fragment, data, s, atten)
    if s 的行数 > 列数
        s = s'
    end
    len = length(s)
    s0 = atten * [s(d0+1:len), zeros(1, d0)] 
    s1 = atten * [s(d1+1:len), zeros(1, d1)] 
    i = 0
    N = floor(len / fragment)
    [data_rows, data_cols] = size(data)
    o = s0
    for i = 0 to N - 1
        if (i + 1) > data_rows * data_cols
            bit = 0
        else
            row_idx = ceil((i + 1) / data_cols)
            col_idx = mod(i, data_cols) + 1
            bit = data(row_idx, col_idx)
        end
        if bit == 1
            st = i * fragment + 1
            ed = (i + 1) * fragment
            o(st : ed) = s1(st : ed)
        end
    end
    o = s + o
end

信息提取模块

function simple_param_decode(D0, D1, FRAG, img_path, bits, output_img_path)
    fid = 1
    % 从文件读取音频信号
    y = audioread('hide.wav')' 
    % 对音频信号进行解码
    out = dh_simple(D0, D1, FRAG, y) 
    % 读取原始二值图像
    img = imread(img_path)
    if img 是彩色图像
        img = rgb2gray(img)
    end
    if img 不是二值图像
        img = imbinarize(img)
    end
    original_bits = img(:)'
    len = min(length(original_bits), length(out)) 
    % 将提取的比特序列转换为二值图像矩阵
    [img_rows, img_cols] = size(img)
    dc_img = reshape(out(1:len), img_rows, img_cols)
    % 保存解码后的图像
    imwrite(dc_img, output_img_path)
    print 解码图像的保存路径
end

function dh_simple(d0, d1, fragment, in)
    if in 的行数 > 列数
        in = in'
    end
    i = 0
    out = []
    N = floor(length(in) / fragment)
    for i = 0 to N - 1
        st = i * fragment + 1
        ed = (i + 1) * fragment
        p = in(st : ed)
        p = fft(p)
        if p!= 0
            p = ifft(log(abs(p)))
        end
        p = abs(p)
        d11 = p(1 * d1 + 1)
        d01 = p(1 * d0 + 1)
        if d11 > d01
            out(i + 1) = 1
        else
            out(i + 1) = 0
        end
    end
end

function passchannel(varargin)
    check 输入参数数量是否在 13 之间
    snr = varargin{1} 
    if 输入参数数量 == 1
        [fn, pn] = uigetfile 选择源文件
        infn = strcat(pn, fn) 
        [fn, pn] = uiputfile 保存文件
        outfn = strcat(pn, fn) 
    end
    if 输入参数数量 == 3
        infn = varargin{2} 
        outfn = varargin{3} 
    end
    x, fs = audioread(infn)
    % 添加噪声
    y = awgn(x, snr,'measured') 
    % 保存添加噪声后的音频文件
    audiowrite(outfn, y, fs) 
end

音频处理与测试模块

function simple_test_gui
    % 创建 GUI 界面
    fig = uifigure
    audioButton = uibutton
    imageButton = uibutton
    okButton = uibutton
    audio_path = ''
    img_path = ''
    设置按钮的回调函数
    显示 GUI 界面
end

function selectAudioFile(button, fig)
    [fn, pn] = uigetfile 选择音频文件
    if 选择了文件
        audio_path = fullfile(pn, fn)
        更新按钮文本
        更新 fig 的 UserData 属性
    end
end

function selectImageFile(button, fig)
    [fn, pn] = uigetfile 选择图片文件
    if 选择了文件
        img_path = fullfile(pn, fn)
        更新按钮文本
        更新 fig 的 UserData 属性
        保存图片为 hidden.png
    end
end

function onOkButtonClicked(fig)
    audio_path = fig.UserData.audio_path
    img_path = fig.UserData.img_path
    if 未选择音频或图片文件
        显示错误消息
        return
    end
    关闭 GUI 界面
    atten = 0.9 
    D0 = 8
    D1 = 12
    FRAG = 1024 
    % 信息隐藏
    [bits, s] = simple_param_code(D0, D1, FRAG, atten, img_path, audio_path)
    print 'hide-no-noise mode'
    % 信息提取
    simple_param_decode(D0, D1, FRAG, img_path, bits, 'decoded-image-no-noise.png')
    print 'hide-have-noise mode'
    重命名文件
    % 添加噪声
    passchannel(10, 'hide-no-noise.wav', 'hide.wav') 
    % 对添加噪声后的音频文件解码
    simple_param_decode(D0, D1, FRAG, img_path, bits, 'decoded-image-with-noise.png')
    print'resample-attack mode'
    m, fs = audioread('hide-no-noise.wav')
    s_fs = fs
    m = m'
    m = resample(m, 2000, fs)
    保存重采样音频
    n, fs = audioread('hide-resampled.wav')
    n = n'
    n = resample(n, s_fs, fs)
    保存恢复采样率后的音频
    % 对重采样后的音频文件解码
    simple_param_decode(D0, D1, FRAG, img_path, bits, 'decoded-image-resampled.png')
    % 绘制音频波形对比图
    compareAudioWaveforms()
    % 绘制图像对比图
    compareImages(fig)
end

function compareAudioWaveforms()
    allFiles = {'hide.wav', 'hide-no-noise.wav', 'hide-resampled.wav'}
    numFiles = length(allFiles)
    fileDescriptions = {'有噪声的隐藏回声信息后的波形', '未添加噪声的隐藏回声信息后的波形', '重采样后的隐藏波形'}
    创建新图形窗口
    读取 audio-used.wav 的音频数据和采样率
    计算时间向量
    for i = 1 to numFiles
        读取当前音频文件的音频数据和采样率
        计算时间向量
        绘制音频波形
        绘制 audio-used.wav 波形
        设置轴标签和标题
        添加注释
    end
    设置图形标题
end

function compareImages(fig)
    img_path = fig.UserData.img_path
    img_path = strrep(img_path, '\', '/')
    imageFiles = {'hidden.png', 'decoded-image-no-noise.png', 'decoded-image-with-noise.png', 'decoded-image-resampled.png'}
    numImages = length(imageFiles)
    创建新图形窗口
    for i = 1 to numImages
        读取图像
        显示图像
        设置子图标题
    end
    设置图形标题
end

改进后系统测试

测试样例1:

图形用户界面模块:




波形对比:

图像对比:

测试样例2

图形用户界面模块:




波形对比:

图像对比:

课程设计总结(包括成员分工)

课设背景与意义

在信息安全领域,信息隐藏技术是保障数据传输安全的重要手段之一。回声信息隐藏算法作为一种音频信息隐藏技术,利用人类听觉系统的掩蔽效应,在音频信号中嵌入秘密信息,具有隐蔽性强、不影响音频质量等优点。本课程设计旨在实现并优化回声信息隐藏算法,提高其在不同攻击情况下的稳健性,对于保护音频传输过程中的敏感信息具有重要意义。

研究内容与方法

算法原理研究:深入研究了回声信息隐藏算法的基本原理,包括人类听觉系统的掩蔽效应、回声信号的生成与叠加、信息的嵌入与提取等关键环节。
性能分析:从分段大小、衰减系数、回声延迟等参数出发,分析了不同参数设置对算法容量和稳健性的影响,通过实验数据对比,找出最优参数组合。
算法改进:针对原有算法在信噪比较低的信道中稳健性较差的问题,将嵌入信息由字符串转化为二值图像,利用二值图像数据量小、处理速度快的特点,提高算法的稳健性;同时,开发了图形用户界面,简化用户操作,提升用户体验。

实验与结果

性能测试:在固定参数条件下,对算法进行了无攻击和叠加高斯白噪声情况下的误码率测试,结果显示在大多数攻击情况下,误码率低于20%,可经由纠错编码恢复,证明了算法的稳健性。
改进效果验证:通过对比改进前后算法在不同攻击条件下的性能,发现改进后的算法在信噪比较低的情况下,误码率显著降低,且图形用户界面的引入使得操作更加简便直观。

关键结论

参数对性能的影响:分段大小、衰减系数、回声延迟等参数对算法性能有显著影响,合理设置这些参数可以有效提高算法的容量和稳健性。
改进措施的有效性:将嵌入信息转化为二值图像,能够有效降低信噪比较低情况下的误码率,提高算法的稳健性;图形用户界面的开发,降低了系统的使用门槛,提高了用户体验。

成员分工与贡献

20221305赵月溪:负责基于二值图像信息隐藏的回声信息隐藏算法代码文件的编写,以及基于文本文件信息隐藏的回声信息隐藏算法代码运行数据的测试与给出。
20221318倪怡丹:负责撰写课设报告,整理实验数据和分析结果,确保报告内容的完整性和准确性。
20221320冯泰瑞:负责论文教材资料收集与学习、基于文本文件信息隐藏的回声信息隐藏算法代码文件的编写、基于二值图像信息隐藏的回声信息隐藏算法GUI界面实现代码的编写,以及基于二值图像信息隐藏的回声信息隐藏算法代码运行样例的测试与给出。

反思与展望

反思:在实验过程中,部分参数的最优值可能受到实验条件限制,未能充分探索;算法在面对更复杂的攻击手段时,如音频压缩、格式转换等,其稳健性有待进一步验证。
展望:未来可以进一步优化算法参数选择方法,引入机器学习等智能算法,实现参数的自适应调整;探索算法在更广泛的应用场景下的性能表现,如视频音频同步隐藏、多通道音频隐藏等,为信息隐藏技术的发展提供新的思路和方法。

参考资料

[1].杨榆,白剑,徐迎晖,等.回声隐藏的研究与实现[J].中山大学学报(自然科学版),2004,(S2):50-52.
[2].杨榆,雷敏,白剑,等.回声隐藏DSP快速算法的研究与实现[J].计算机应用研究,2006,(04):121-122.
[3].雷鸣,王新房,刘侨,等.一种基于回声隐藏的数字音频水印改进算法[J].计算机系统应用,2013,22(07):81-84+94.

附录源代码

基于文本文件信息隐藏的回声信息隐藏

信息隐藏模块

simple_param_code.m
function [bits,s] = simple_param_code(D0, D1, FRAG, ATTEN, msg)
% 函数功能:该函数主要用于将消息编码隐藏到音频文件中,并返回原始消息对应的比特序列和原始音频信号
% 输入参数说明:
% D0: 延迟参数0
% D1: 延迟参数1
% FRAG: 片段参数
% ATTEN: 回声的衰减参数
% msg: 要隐藏的消息文本
% 返回值说明:
% bits: 原始消息转换后的比特序列
% s: 原始音频信号

% 将消息文本转换为比特序列
bits = str2bit(msg);

% 从音频文件'converted.wav'中读取音频信号和采样率
[s, fs] = audioread('converted.wav');
% 打印采样率信息
fprintf('sampling rate is:%f\n', fs);
% 将音频信号矩阵转置
s = s';
% 获取音频信号的长度
s_len = length(s);
% 计算所需音频信号长度(根据比特序列长度和片段参数)
t_len = length(bits) * FRAG;
% 如果当前音频信号长度小于所需长度
if s_len < t_len
% 计算需要重复音频信号的倍数
multiple = floor(t_len / s_len);
% 计算重复后剩余的长度
remainder = mod(t_len, s_len);
% 初始化临时音频信号矩阵
tmp = [];
% 重复音频信号
for i = 1:multiple
tmp = [tmp s];
end;
% 将剩余部分拼接上
tmp = [tmp s(1:remainder)];
% 更新音频信号
s = tmp;
end;

% 使用指定参数将比特序列隐藏到音频信号中
bld = hide_simple(D0, D1, FRAG, bits, s, ATTEN);

% 将隐藏信息后的音频信号保存为'hide.wav'文件
audiowrite('hide.wav', bld, fs);
end
hide_simple.m
function o = hide_simple(d0, d1, fragment, data, s, atten)
% 函数功能:通过在音频信号中添加不同延迟的回声,并根据数据位选择不同延迟回声的片段,从而将信息隐藏在音频信号中
% 输入参数说明:
% d0: 延迟参数0,用于生成正向回声的延迟
% d1: 延迟参数1,用于生成反向回声的延迟
% fragment: 片段长度,用于划分音频信号为不同片段以嵌入信息
% data: 要隐藏的信息,以二进制数据形式表示
% s: 原始音频信号
% atten: 回声的衰减系数
% 输出参数说明:
% o: 嵌入信息后的音频信号

% 获取音频信号 s 的行数和列数
[row, col] = size(s);
% 如果行数大于列数,将音频信号转置,确保它是行向量形式
if(row > col)
s = s';
end;

% 获取音频信号的长度
len = length(s);
% 生成延迟为 d0 的回声信号,将原始音频信号 s 向右移动 d0 个样本,并进行衰减
s0 = atten * [s(d0+1:len),zeros(1, d0)]; 
% 生成延迟为 d1 的回声信号,将原始音频信号 s 向右移动 d1 个样本,并进行衰减
s1 = atten * [s(d1+1:len),zeros(1, d1)]; 

% 初始化索引变量
i = 0;
% 计算音频信号可以划分成多少个片段
N = floor(len / fragment);
% 获取要隐藏数据的长度
lend = length(data);
% 初始化输出音频信号为延迟为 d0 的回声信号
o = s0;
% 遍历每个片段
for i = 0 : N - 1
% 如果当前片段索引超出了数据长度
if((i + 1) > lend)
% 使用默认比特值 0
bit = 0;
else
% 获取当前片段对应的要隐藏的数据位
bit = data(i + 1);
end;
% 如果当前数据位为 1
if bit == 1
% 计算当前片段在音频信号中的起始位置
st = i * fragment + 1;
% 计算当前片段在音频信号中的结束位置
ed = (i + 1) * fragment;
% 将输出音频信号中对应片段替换为延迟为 d1 的回声信号的相应片段
o(st : ed) = s1(st : ed);
end;
end;
% 将原始音频信号与处理后的回声信号相加,得到嵌入信息后的音频信号
o = s + o;
end

信息提取模块

simple_param_decode.m
function y = simple_param_decode(D0, D1, FRAG, msg, bits)
% 函数功能:从隐藏信息的音频文件中解码提取信息,并与原始信息对比计算错误率
% 输入参数说明:
% D0: 延迟参数0,用于解码过程中的相关计算
% D1: 延迟参数1,用于解码过程中的相关计算
% FRAG: 片段参数,用于解码过程中的相关计算
% msg: 原始的加密文本(用于与解码结果对比)
% bits: 原始的二进制比特序列(用于与解码结果对比)
% 输出参数说明:
% y: 从隐藏信息的音频文件中提取出的二进制比特序列(解码结果)

% 打开文件描述符,1 通常表示标准输出(屏幕)
fid = 1; 

% 从 'hide.wav' 文件中读取音频信号,并转置为行向量
y = audioread('hide.wav')'; 

% 使用 dh_simple 函数对音频信号进行解码,提取隐藏的二进制比特序列
out = dh_simple(D0, D1, FRAG, y); 

% 取原始比特序列和提取出的比特序列长度的最小值
len = min(length(bits), length(out)); 
% 计算提取出的比特序列与原始比特序列之间的错误率
rate = sum(abs(bits(1:len)-out(1:len)))/len; 
% 打印出对比信息的长度、错误率以及错误数量
fprintf(fid, 'len:%d\nerror rate:%f\nerror num:%d\n', len, rate, len * rate); 

% 将提取出的二进制比特序列转换为字符串形式
dc_msg = sprintf('%s', bit2str(out(1:len))); 
% 取原始加密文本和转换后的字符串长度的最小值
len = min([length(msg), length(dc_msg)]); 
% 如果长度不为 0
if len ~= 0
% 初始化一个错误向量
err = zeros(1, len); 
% 逐个字符对比原始加密文本和解码后的字符串,标记不同的字符
for n = 1:len
err(n) = 1 - strcmp(msg(n), dc_msg(n)); 
end;
% 打印出解码后的文本及其长度
fprintf(fid, 'the text you send is:%s\nlen is:%d\n', dc_msg(1:len), len); 
% 打印出文本的错误码率
fprintf(fid, 'error code rate is:%f\n', sum(err) / len); 
else
% 如果长度为 0,说明错误过多,无法恢复消息
fprintf(fid, 'too much error, msg can not be recovered!\n'); 
end;
end
dh_simple.m
function out = dh_simple(d0, d1, fragment, in)
% 函数功能:该函数使用倒谱分析从输入的混合音频信号中解码隐藏信息
% 输入参数说明:
% d0: 延迟参数0,用于解码过程中的相关计算
% d1: 延迟参数1,用于解码过程中的相关计算
% fragment: 片段长度,用于将输入音频信号划分为不同片段进行处理
% in: 输入的混合音频信号,即包含隐藏信息的音频信号
% 输出参数说明:
% out: 解码后得到的二进制信息序列

% 获取输入音频信号 in 的行数和列数
[row, col] = size(in);
% 如果行数大于列数,将音频信号转置,确保它是行向量形式
if(row > col)
in = in';
end;

% 初始化索引变量 i 和用于存储解码结果的空数组 out
i = 0;
out = [];
% 计算输入音频信号可以划分成多少个片段
N = floor(length(in) / fragment);
% 遍历每个片段
for i = 0 : N - 1
% 计算当前片段在音频信号中的起始位置
st = i * fragment + 1;
% 计算当前片段在音频信号中的结束位置
ed = (i + 1) * fragment;
% 提取当前片段的音频信号
p = in(st : ed);
% 对当前片段音频信号进行快速傅里叶变换(FFT),将时域信号转换到频域
p = fft(p);
% 如果频域信号不为零
if p ~= 0
% 对频域信号取对数幅度后再进行逆快速傅里叶变换(IFFT),转换回时域,得到倒谱
p = ifft(log(abs(p)));
end;
% 取倒谱的幅度值
p = abs(p);
% 获取倒谱中与延迟 d1 对应的位置的值
d11 = p(1 * d1 + 1);
% 获取倒谱中与延迟 d0 对应的位置的值
d01 = p(1 * d0 + 1);
% 根据 d1 和 d0 对应位置的值的大小关系来确定解码的比特值
if d11 > d01
% 如果 d1 对应的值更大,认为该片段隐藏的比特为 1
out(i + 1) = 1;
else
% 否则,认为该片段隐藏的比特为 0
out(i + 1) = 0;
end;
end;
end

加性噪声模块

passchannel.m
% 定义函数passchannel,该函数接受可变数量的输入参数
function passchannel(varargin)
% 检查输入参数的数量是否在1到3之间,如果不在则抛出错误
error(nargchk(1, 3, nargin)); 
% 从输入参数中提取信噪比(Signal - to - Noise Ratio)
snr = varargin{1}; 
% 如果输入参数只有1个
if nargin == 1
% 弹出文件选择对话框,让用户选择源音频文件
[fn, pn] = uigetfile({'*.wav', 'WAV Files(*.wav)'}, '选择源文件'); 
% 将路径和文件名拼接成完整的输入文件路径
infn = strcat(pn, fn); 
% 弹出文件保存对话框,让用户指定保存处理后音频文件的位置和文件名
[fn, pn] = uiputfile({'*.wav', 'WAV Files(*.wav)'}, '文件另存为'); 
% 将路径和文件名拼接成完整的输出文件路径
outfn = strcat(pn, fn); 
end;
% 如果输入参数有3个
if nargin == 3
% 从输入参数中提取输入文件路径
infn = varargin{2}; 
% 从输入参数中提取输出文件路径
outfn = varargin{3}; 
end;
% 读取输入音频文件的音频数据x和采样率fs
[x,fs] = audioread(infn);
% 对音频数据x添加高斯白噪声,噪声水平由snr指定,'measured'表示根据输入信号的功率来测量噪声功率
y = awgn(x, snr,'measured'); 
% 将添加噪声后的音频数据y写入输出文件,使用原来的采样率fs
audiowrite(outfn, y, fs); 
end

辅助功能模块

bit2str.m
function out = bit2str(in)
% 函数功能:将输入的二进制比特序列转换为对应的字符组成的字符串
% 输入参数:
% in:一个二进制比特序列的向量
% 输出参数:
% out:由二进制比特序列转换而来的字符串

% 初始化输出字符串为空字符串
out = [''];
% 计算输入二进制比特序列可以组成多少个8位的字符单元
len = round(length(in)/8);
for n = 1:len
% 提取每8个连续的比特位作为一个字符对应的二进制表示
temp = in((n - 1) * 8 + 1:n * 8);
% 将提取的8位二进制比特向量转换为字符串形式
temp = toStr(temp);
% 将8位二进制字符串转换为十进制数值,再将其转换为对应的字符,存入输出字符串out中
out(n) = bin2dec(temp);
end;
end
str2bit.m
function data = str2bit(varargin)
% 函数功能:将输入的字符串转换为对应的二进制比特序列
% 输入参数:接受可变数量的输入参数,如果没有传入参数,会提示用户输入字符串
% 输出参数:data,一个包含输入字符串对应二进制比特序列的向量

% 初始化源字符串和临时字符串
source = '';
str = '';

% 如果没有传入参数
if nargin == 0
% 提示用户输入要发送的明文文本
source = input('please enter the plain text you want to send:\n','s');
else
% 如果有传入参数,取第一个参数作为源字符串
source = varargin{1};
end;

% 计算源字符串转换为二进制后所需的总比特数(每个字符8比特)
source_len = length(source) * 8;
% 初始化一个全零向量,用于存储二进制比特序列
data = zeros(1, source_len);

% 遍历源字符串中的每个字符
for n = 1:length(source)
% 将每个字符转换为8位二进制字符串
temp = dec2bin(source(n), 8);
% 将每个字符的二进制字符串连接起来
str = strcat(str, temp);
end;

% 遍历连接后的二进制字符串
for n = 1:source_len
% 如果当前字符是'0'
if str(n) == '0'
data(n) = 0; 
else
% 如果当前字符是'1'
if str(n) == '1'
data(n) = 1;
else
% 如果遇到非'0'非'1'的字符,输出错误信息并返回
fprintf(1, 'error bit');
return;
end;
end;
end;
end
toStr.m
function y = toStr(x)
% 函数功能:将由0和1组成的数组转换为对应的字符串
% 输入参数:
% x:一个由0和1组成的数组
% 输出参数:
% y:由输入数组转换得到的字符串,其中数组中的每个0或1对应字符串中的一个字符 '0' 或 '1'

% 初始化输出字符串为空字符串
y = '';

% 遍历输入数组x的每一个元素
for n = 1:length(x)
% 如果当前元素为1
if(x(n) == 1)
% 将字符'1'连接到输出字符串y的末尾
y = strcat(y, '1');
else
% 如果当前元素为0,将字符'0'连接到输出字符串y的末尾
y = strcat(y, '0');
end;
end;
end

音频处理与测试模块

simple_test.m
% 清除工作区中的所有变量
clear all;
% 关闭所有打开的图形窗口
close all;
% 清除命令行窗口
clc;

% 定义衰减因子
atten = 0.95; 
% 定义回声延迟时间D0
D0 = 8;
% 定义回声延迟时间D1
D1 = 12;
% 定义片段长度
FRAG = 1024; 
% 从hidden.txt文件中读取内容,并将其转换为字符数组
fileContent = fileread('hidden.txt');
msg = char(fileContent);
% 使用simple_param_code函数对音频进行信息嵌入,得到信息嵌入后的比特流和一些状态信息
[bits, s] = simple_param_code(D0, D1, FRAG, atten, msg);
fprintf('hide_no_noise\n');
% 使用simple_param_decode函数对有回声音频进行信息提取,验证提取信息结果正误
simple_param_decode(D0, D1, FRAG, msg, bits);

% 将文件hide.wav重命名为hide_no_noise.wav
movefile('hide.wav', 'hide_no_noise.wav', 'f');
% 调用passchannel函数,向hide_no_noise.wav文件添加噪声,生成hide.wav文件
% 参数25是信噪比
passchannel(25, 'hide_no_noise.wav', 'hide.wav'); 
fprintf('hide\n');
% 对添加噪声后的音频文件进行解码,验证解码结果
simple_param_decode(D0, D1, FRAG, msg, bits);

% 对hide_no_noise.wav文件进行重采样操作
% 读取hide_no_noise.wav文件的音频数据和采样率
[m, fs] = audioread('hide_no_noise.wav');
% 保存原始采样率
s_fs = fs;
% 将音频数据转置,使其符合后续处理的格式
m = m';
% 将音频数据从采样率fs重采样到6000Hz
m = resample(m, 6000, fs);
% 将重采样后的音频数据写入hide_resamplemid.wav文件
audiowrite('hide_resamplemid.wav', m, 6000);
% 读取hide_resamplemid.wav文件的音频数据和采样率
[n, fs] = audioread('hide_resamplemid.wav');
% 将音频数据转置,使其符合后续处理的格式
n = n';
% 将音频数据从重采样后的采样率fs恢复到原始采样率s_fs
n = resample(n, s_fs, fs);
% 将恢复采样率后的音频数据写入hide.wav文件
audiowrite('hide.wav', n, s_fs);
% 在命令行输出提示信息
fprintf('resample - attack\n');
% 对重采样后的音频文件进行解码,验证解码结果
simple_param_decode(D0, D1, FRAG, msg, bits);

% 调用自定义函数compareAudioWaveforms,绘制多个音频文件的波形对比图
compareAudioWaveforms();

% 定义函数compareAudioWaveforms,用于绘制多个音频文件的波形对比图
function compareAudioWaveforms()
% 定义包含多个音频文件名的单元格数组
allFiles = {'converted.wav', 'hide.wav', 'hide_no_noise.wav', 'hide_resamplemid.wav'};
% 获取音频文件的数量
numFiles = length(allFiles);
% 定义包含每个音频文件描述的单元格数组
fileDescriptions = {'原始的音频波形', '有噪声的隐藏回声信息后的波形', '未添加噪声的隐藏回声信息后的波形', '重采样后的隐藏波形'};

% 创建一个新的图形窗口
figure;
% 遍历每个音频文件
for i = 1:numFiles
% 读取当前音频文件的音频数据和采样率
[audioData, sampleRate] = audioread(allFiles{i});
% 根据音频数据长度和采样率计算时间向量
time = (0:length(audioData) - 1) / sampleRate;
% 在当前子图中绘制音频数据的波形
subplot(numFiles, 1, i);
plot(time, audioData);
% 设置y轴标签
ylabel('Amplitude');
% 设置x轴标签
xlabel('Time (s)');
% 设置当前子图的标题,包含文件描述和文件名
title([fileDescriptions{i},' - ', allFiles{i}]);
end
% 设置整个图形的标题
sgtitle('回声信息隐藏提取相关音频文件的波形对比');
end

基于二值图像信息隐藏的回声信息隐藏

信息隐藏模块

simple_param_code.m
function [bits, s] = simple_param_code(D0, D1, FRAG, ATTEN, img_path, audio_path)
% 函数功能:该函数主要用于将二值图像矩阵编码隐藏到音频文件中,并返回原始图像对应的比特序列和原始音频信号
% 输入参数说明:
% D0: 延迟参数0
% D1: 延迟参数1
% FRAG: 片段参数
% ATTEN: 回声的衰减参数
% img_path: 要隐藏的二值图像文件路径
% audio_path: 用户选择的音频文件路径
% 返回值说明:
% bits: 原始图像转换后的比特序列
% s: 原始音频信号

% 读取图像
img = imread(img_path);
% 确保图像是二值图像
if size(img, 3) == 3
img = rgb2gray(img);
end
% 检查图像是否已经是二值图像
if islogical(img)
% 图像已经是二值图像,直接使用
bin_img = img;
else
% 图像不是二值图像,进行二值化处理
bin_img = imbinarize(img);
end

% 将二值图像矩阵转换为比特序列
bits = bin_img(:)';

% 从用户选择的音频文件中读取音频信号和采样率
[s, fs] = audioread(audio_path);
% 打印采样率信息
fprintf('sampling rate is:%f\n', fs);
% 将音频信号矩阵转置
s = s';
% 获取音频信号的长度
s_len = length(s);
% 计算所需音频信号长度(根据比特序列长度和片段参数)
t_len = length(bits) * FRAG;
% 如果当前音频信号长度小于所需长度
if s_len < t_len
% 计算需要重复音频信号的倍数
multiple = floor(t_len / s_len);
% 计算重复后剩余的长度
remainder = mod(t_len, s_len);
% 初始化临时音频信号矩阵
tmp = [];
% 重复音频信号
for i = 1:multiple
tmp = [tmp s];
end;
% 将剩余部分拼接上
tmp = [tmp s(1:remainder)];
% 更新音频信号
s = tmp;
end;

% 将最终使用原始的音频写在audio-used.wav文件里
audiowrite('audio-used.wav', s, fs);
% 使用指定参数将比特序列隐藏到音频信号中
bld = hide_simple(D0, D1, FRAG, bits, s, ATTEN);

% 将隐藏信息后的音频信号保存为'hide.wav'文件
audiowrite('hide.wav', bld, fs);
end
hide_simple.m
function o = hide_simple(d0, d1, fragment, data, s, atten)
% 函数功能:通过在音频信号中添加不同延迟的回声,并根据数据位选择不同延迟回声的片段,从而将信息隐藏在音频信号中
% 输入参数说明:
% d0: 延迟参数0,用于生成正向回声的延迟
% d1: 延迟参数1,用于生成反向回声的延迟
% fragment: 片段长度,用于划分音频信号为不同片段以嵌入信息
% data: 要隐藏的信息,以二值图像矩阵形式表示
% s: 原始音频信号
% atten: 回声的衰减系数
% 输出参数说明:
% o: 嵌入信息后的音频信号

% 获取音频信号 s 的行数和列数
[row, col] = size(s);
% 如果行数大于列数,将音频信号转置,确保它是行向量形式
if(row > col)
s = s';
end;

% 获取音频信号的长度
len = length(s);
% 生成延迟为 d0 的回声信号,将原始音频信号 s 向右移动 d0 个样本,并进行衰减
s0 = atten * [s(d0+1:len),zeros(1, d0)]; 
% 生成延迟为 d1 的回声信号,将原始音频信号 s 向右移动 d1 个样本,并进行衰减
s1 = atten * [s(d1+1:len),zeros(1, d1)]; 

% 初始化索引变量
i = 0;
% 计算音频信号可以划分成多少个片段
N = floor(len / fragment);
% 获取要隐藏数据的行数和列数
[data_rows, data_cols] = size(data);
% 初始化输出音频信号为延迟为 d0 的回声信号
o = s0;
% 遍历每个片段
for i = 0 : N - 1
% 如果当前片段索引超出了数据长度
if((i + 1) > data_rows * data_cols)
% 使用默认比特值 0
bit = 0;
else
% 获取当前片段对应的要隐藏的数据位
row_idx = ceil((i + 1) / data_cols);
col_idx = mod(i, data_cols) + 1;
bit = data(row_idx, col_idx);
end;
% 如果当前数据位为 1
if bit == 1
% 计算当前片段在音频信号中的起始位置
st = i * fragment + 1;
% 计算当前片段在音频信号中的结束位置
ed = (i + 1) * fragment;
% 将输出音频信号中对应片段替换为延迟为 d1 的回声信号的相应片段
o(st : ed) = s1(st : ed);
end;
end;
% 将原始音频信号与处理后的回声信号相加,得到嵌入信息后的音频信号
o = s + o;
end

信息提取模块

simple_param_decode.m
function y = simple_param_decode(D0, D1, FRAG, img_path, bits, output_img_path)
% 函数功能:从隐藏信息的音频文件中解码提取信息,并与原始二值图像对比计算错误率
% 输入参数说明:
% D0: 延迟参数0,用于解码过程中的相关计算
% D1: 延迟参数1,用于解码过程中的相关计算
% FRAG: 片段参数,用于解码过程中的相关计算
% img_path: 原始的二值图像文件路径(用于与解码结果对比)
% bits: 原始的二进制比特序列(用于与解码结果对比)
% output_img_path: 解码后图像的保存路径
% 输出参数说明:
% y: 从隐藏信息的音频文件中提取出的二进制比特序列(解码结果)

% 打开文件描述符,1 通常表示标准输出(屏幕)
fid = 1; 

% 从 'hide.wav' 文件中读取音频信号,并转置为行向量
y = audioread('hide.wav')'; 

% 使用 dh_simple 函数对音频信号进行解码,提取隐藏的二进制比特序列
out = dh_simple(D0, D1, FRAG, y); 

% 读取原始二值图像
img = imread(img_path);
% 确保图像是二值图像
if size(img, 3) == 3
img = rgb2gray(img);
end
if ~islogical(img)
img = imbinarize(img);
end

% 将二值图像矩阵转换为比特序列
original_bits = img(:)';

% 取原始比特序列和提取出的比特序列长度的最小值
len = min(length(original_bits), length(out)); 

% 将提取出的二进制比特序列转换为二值图像矩阵
[img_rows, img_cols] = size(img);
dc_img = reshape(out(1:len), img_rows, img_cols);
% 保存解码后的二值图像
imwrite(dc_img, output_img_path);
% 打印出解码后的图像路径
fprintf(fid, 'Decoded image saved to: %s\n', output_img_path);
end
dh_simple.m
function out = dh_simple(d0, d1, fragment, in)
% 函数功能:该函数使用倒谱分析从输入的混合音频信号中解码隐藏信息
% 输入参数说明:
% d0: 延迟参数0,用于解码过程中的相关计算
% d1: 延迟参数1,用于解码过程中的相关计算
% fragment: 片段长度,用于将输入音频信号划分为不同片段进行处理
% in: 输入的混合音频信号,即包含隐藏信息的音频信号
% 输出参数说明:
% out: 解码后得到的二进制信息序列

% 获取输入音频信号 in 的行数和列数
[row, col] = size(in);
% 如果行数大于列数,将音频信号转置,确保它是行向量形式
if(row > col)
in = in';
end;

% 初始化索引变量 i 和用于存储解码结果的空数组 out
i = 0;
out = [];
% 计算输入音频信号可以划分成多少个片段
N = floor(length(in) / fragment);
% 遍历每个片段
for i = 0 : N - 1
% 计算当前片段在音频信号中的起始位置
st = i * fragment + 1;
% 计算当前片段在音频信号中的结束位置
ed = (i + 1) * fragment;
% 提取当前片段的音频信号
p = in(st : ed);
% 对当前片段音频信号进行快速傅里叶变换(FFT),将时域信号转换到频域
p = fft(p);
% 如果频域信号不为零
if p ~= 0
% 对频域信号取对数幅度后再进行逆快速傅里叶变换(IFFT),转换回时域,得到倒谱
p = ifft(log(abs(p)));
end;
% 取倒谱的幅度值
p = abs(p);
% 获取倒谱中与延迟 d1 对应的位置的值
d11 = p(1 * d1 + 1);
% 获取倒谱中与延迟 d0 对应的位置的值
d01 = p(1 * d0 + 1);
% 根据 d1 和 d0 对应位置的值的大小关系来确定解码的比特值
if d11 > d01
% 如果 d1 对应的值更大,认为该片段隐藏的比特为 1
out(i + 1) = 1;
else
% 否则,认为该片段隐藏的比特为 0
out(i + 1) = 0;
end;
end;
end

加性噪声模块

passchannel.m
% 定义函数passchannel,该函数接受可变数量的输入参数
function passchannel(varargin)
% 检查输入参数的数量是否在1到3之间,如果不在则抛出错误
error(nargchk(1, 3, nargin)); 
% 从输入参数中提取信噪比(Signal - to - Noise Ratio)
snr = varargin{1}; 
% 如果输入参数只有1个
if nargin == 1
% 弹出文件选择对话框,让用户选择源音频文件
[fn, pn] = uigetfile({'*.wav', 'WAV Files(*.wav)'}, '选择源文件'); 
% 将路径和文件名拼接成完整的输入文件路径
infn = strcat(pn, fn); 
% 弹出文件保存对话框,让用户指定保存处理后音频文件的位置和文件名
[fn, pn] = uiputfile({'*.wav', 'WAV Files(*.wav)'}, '文件另存为'); 
% 将路径和文件名拼接成完整的输出文件路径
outfn = strcat(pn, fn); 
end;
% 如果输入参数有3个
if nargin == 3
% 从输入参数中提取输入文件路径
infn = varargin{2}; 
% 从输入参数中提取输出文件路径
outfn = varargin{3}; 
end;
% 读取输入音频文件的音频数据x和采样率fs
[x,fs] = audioread(infn);
% 对音频数据x添加高斯白噪声,噪声水平由snr指定,'measured'表示根据输入信号的功率来测量噪声功率
y = awgn(x, snr,'measured'); 
% 将添加噪声后的音频数据y写入输出文件,使用原来的采样率fs
audiowrite(outfn, y, fs); 
end

音频处理与测试模块

simple_test.m
function simple_test_gui
% 创建一个新的图形用户界面
fig = uifigure('Name', '选择音频和图片', 'Position', [100, 100, 400, 200]);

% 创建音频文件选择按钮
audioButton = uibutton(fig, 'push', 'Position', [50, 120, 100, 22], 'Text', '选择音频文件');
% 创建图片文件选择按钮
imageButton = uibutton(fig, 'push', 'Position', [250, 120, 100, 22], 'Text', '选择图片文件');
% 创建确定按钮
okButton = uibutton(fig, 'push', 'Position', [150, 50, 100, 22], 'Text', '确定');

% 初始化音频和图片路径变量
audio_path = '';
img_path = '';

% 音频文件选择按钮的回调函数
audioButton.ButtonPushedFcn = @(~, ~) selectAudioFile(audioButton, fig);
% 图片文件选择按钮的回调函数
imageButton.ButtonPushedFcn = @(~, ~) selectImageFile(imageButton, fig);
% 确定按钮的回调函数
okButton.ButtonPushedFcn = @(~, ~) onOkButtonClicked(fig);

% 显示图形用户界面
fig.Visible = 'on';
end

% 音频文件选择函数
function selectAudioFile(button, fig)
[fn, pn] = uigetfile({'*.wav', 'WAV Files(*.wav)'}, '选择音频文件');
if ~isequal(fn, 0)
audio_path = fullfile(pn, fn);
button.Text = ['音频文件: ', fn];
fig.UserData.audio_path = audio_path; % 更新 fig 的 UserData 属性
end
end

% 图片文件选择函数
function selectImageFile(button, fig)
[fn, pn] = uigetfile({'*.png'; '*.jpg'; '*.jpeg'; '*.bmp'}, '选择图片文件');
if ~isequal(fn, 0)
img_path = fullfile(pn, fn);
button.Text = ['图片文件: ', fn];
fig.UserData.img_path = img_path; % 更新 fig 的 UserData 属性
% 将选择的图片保存为hidden.png
imwrite(imread(img_path), 'hidden.png');
end
end

% 确定按钮点击后的回调函数
function onOkButtonClicked(fig)
% 获取音频和图片路径
audio_path = fig.UserData.audio_path;
img_path = fig.UserData.img_path;

% 检查是否选择了音频和图片文件
if isempty(audio_path) || isempty(img_path)
uialert(fig, '请选择音频和图片文件', '错误');
return;
end

% 关闭图形用户界面
fig.Visible = 'off';

% 定义衰减因子
atten = 0.9; 
% 定义回声延迟时间D0
D0 = 8;
% 定义回声延迟时间D1
D1 = 12;
% 定义片段长度
FRAG = 1024; 

% 使用simple_param_code函数对音频进行信息嵌入,得到信息嵌入后的比特流和一些状态信息
[bits, s] = simple_param_code(D0, D1, FRAG, atten, img_path, audio_path);
fprintf('hide-no-noise mode \n');
% 使用simple_param_decode函数对有回声音频进行信息提取,验证提取信息结果正误
simple_param_decode(D0, D1, FRAG, img_path, bits, 'decoded-image-no-noise.png');

fprintf('hide-have-noise mode\n');
% 将文件hide.wav重命名为hide_no_noise.wav
movefile('hide.wav', 'hide-no-noise.wav', 'f');
% 调用passchannel函数,向hide_no_noise.wav文件添加噪声,生成hide.wav文件
% 参数10是信噪比
passchannel(10, 'hide-no-noise.wav', 'hide.wav'); 
% 对添加噪声后的音频文件进行解码,验证解码结果
simple_param_decode(D0, D1, FRAG, img_path, bits, 'decoded-image-with-noise.png');

% 对hide_no_noise.wav文件进行重采样操作
fprintf('resample-attack mode\n');
% 读取hide_no_noise.wav文件的音频数据和采样率
[m, fs] = audioread('hide-no-noise.wav');
% 保存原始采样率
s_fs = fs;
% 将音频数据转置,使其符合后续处理的格式
m = m';
% 将音频数据从采样率fs重采样到2000Hz
m = resample(m, 2000, fs);
% 将重采样后的音频数据写入hide_resamplemid.wav文件
audiowrite('hide-resampled.wav', m, 2000);
% 读取hide_resamplemid.wav文件的音频数据和采样率
[n, fs] = audioread('hide-resampled.wav');
% 将音频数据转置,使其符合后续处理的格式
n = n';
% 将音频数据从重采样后的采样率fs恢复到原始采样率s_fs
n = resample(n, s_fs, fs);
% 将恢复采样率后的音频数据写入hide.wav文件
audiowrite('hide.wav', n, s_fs);
% 对重采样后的音频文件进行解码,验证解码结果
simple_param_decode(D0, D1, FRAG, img_path, bits, 'decoded-image-resampled.png');

% 调用自定义函数compareAudioWaveforms,绘制多个音频文件的波形对比图
compareAudioWaveforms();

% 调用自定义函数compareImages,绘制多个图像的对比图
compareImages(fig);
end

% 定义函数compareAudioWaveforms,用于绘制多个音频文件的波形对比图
function compareAudioWaveforms()
% 定义包含多个音频文件名的单元格数组
allFiles = {'hide.wav', 'hide-no-noise.wav', 'hide-resampled.wav'};
% 获取音频文件的数量
numFiles = length(allFiles);
% 定义包含每个音频文件描述的单元格数组
fileDescriptions = {'有噪声的隐藏回声信息后的波形', '未添加噪声的隐藏回声信息后的波形', '重采样后的隐藏波形'};

% 创建一个新的图形窗口
figure;
% 读取audio-used.wav文件的音频数据和采样率
[convertedData, convertedSampleRate] = audioread('audio-used.wav');
% 根据audio-used.wav音频数据长度和采样率计算时间向量
convertedTime = (0:length(convertedData) - 1) / convertedSampleRate;

% 遍历每个音频文件
for i = 1:numFiles
% 读取当前音频文件的音频数据和采样率
[audioData, sampleRate] = audioread(allFiles{i});
% 根据音频数据长度和采样率计算时间向量
time = (0:length(audioData) - 1) / sampleRate;
% 在当前子图中绘制音频数据的波形
subplot(numFiles, 1, i);
plot(time, audioData, 'r'); % 其他音频用红色表示
hold on;
plot(convertedTime, convertedData, 'b'); % audio-used.wav用蓝色表示
% 设置y轴标签
ylabel('Amplitude');
% 设置x轴标签
xlabel('Time (s)');
% 设置当前子图的标题,包含文件描述和文件名
title([fileDescriptions{i}, ' - ', allFiles{i}]);
% 在波形图中添加注释
text(0.1, max(audioData), 'audio-used.wav', 'Color', 'b');
text(0.1, min(audioData), allFiles{i}, 'Color', 'r');
hold off;
end
% 设置整个图形的标题
sgtitle('回声信息隐藏提取相关音频文件的波形对比');
end

% 定义函数compareImages,用于绘制多个图像的对比图
function compareImages(fig)
% 获取用户选择的图片路径
img_path = fig.UserData.img_path;
% 将路径中的反斜杠替换为正斜杠
img_path = strrep(img_path, '\', '/');
% 定义包含多个图像文件名的单元格数组
imageFiles = {'hidden.png', 'decoded-image-no-noise.png', 'decoded-image-with-noise.png', 'decoded-image-resampled.png'};
% 获取图像文件的数量
numImages = length(imageFiles);
% 创建一个新的图形窗口
figure;
% 遍历每个图像文件
for i = 1:numImages
% 读取当前图像文件
img = imread(imageFiles{i});
% 在当前子图中显示图像
subplot(2, 2, i);
imshow(img);
% 设置当前子图的标题,包含文件名
title(strrep(imageFiles{i}, '\', '/'));
end
% 设置整个图形的标题
sgtitle('图像对比');
end
posted @   20221320冯泰瑞  阅读(10)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示