
随着数字多媒体技术的快速发展,信息安全和隐私保护变得日益重要。音频作为一种广泛使用的多媒体载体,因其特有的频率特性和人耳听觉系统的生理局限性,成为隐写术研究的重要领域。音频隐写技术通过在不影响听觉质量的前提下,将秘密信息隐藏在音频信号中,为安全通信、版权保护和数字水印提供了重要手段。
与图像隐写相比,音频隐写具有其独特的挑战和特点。人耳对音频信号的感知非常敏感,尤其是在安静环境下,微小的失真都可能被察觉。同时,音频处理过程中的压缩、传输和重采样等操作也会对隐藏信息的完整性造成挑战。因此,开发高效、安全、鲁棒的音频隐写技术成为研究热点。
本文将全面深入地介绍音频隐写技术的原理、实现方法和最新进展。我们将从基础的时域隐写方法入手,逐步深入到复杂的频域变换隐写技术,涵盖多种实用的音频隐写算法和其Python实现。通过本文的学习,读者将能够系统地掌握音频隐写技术的核心概念,实现自己的音频隐写系统,并理解如何评估和提高隐写系统的安全性和鲁棒性。
本文的主要内容包括:
让我们开始这段音频隐写技术的探索之旅!
音频隐写技术是隐写术的一个重要分支,它利用音频信号作为载体来隐藏秘密信息。要深入理解音频隐写技术,我们首先需要了解人耳的听觉特性、音频信号的表示方法以及相关的评估指标。
人耳是一个高度复杂的声音感知系统,了解其特性对于设计有效的音频隐写技术至关重要。人耳的听觉特性主要包括以下几个方面:
人耳能够感知的频率范围通常在20Hz到20kHz之间,但这种感知能力会随着年龄的增长而下降,尤其是对高频声音的感知。音频隐写技术可以利用这一特性,在人耳不敏感的频率范围内嵌入信息。
人耳的掩蔽效应是设计音频隐写技术的重要理论基础。当一个强音和一个弱音同时存在时,如果弱音的强度低于强音所产生的掩蔽阈值,那么人耳就无法感知到这个弱音。掩蔽效应可以分为时域掩蔽和频域掩蔽:
音频隐写技术可以利用这些掩蔽效应,在被掩蔽的区域嵌入信息,从而减少可感知的失真。
人耳对声音强度变化的感知存在一个最小可觉差(Just Noticeable Difference, JND)。JND表示人耳能够察觉的最小声音强度变化,通常用分贝(dB)表示。在音频隐写中,我们需要确保嵌入操作引起的信号变化不超过JND,以避免被察觉。
人耳对声音响度的感知不是线性的,而是接近对数关系。这一特性影响了我们如何评估音频隐写引起的感知失真。
音频信号在计算机中通常以数字形式表示,了解这些表示方法对于实现音频隐写技术至关重要。
数字音频通常由以下几个参数描述:
常见的音频文件格式包括:
对于隐写技术,无损格式如WAV和FLAC通常更受欢迎,因为它们不会引入额外的压缩失真。
音频处理中常用的操作包括:
这些操作对于理解音频隐写的实现和检测都很重要。
评估音频隐写技术的性能需要考虑多个方面,包括嵌入容量、不可感知性、鲁棒性和安全性。
嵌入容量通常用比特率(bits per second, bps)或相对于原始音频大小的比例来表示。较高的嵌入容量意味着可以隐藏更多的信息,但通常会增加被检测的风险和感知失真。
不可感知性评估隐写后音频的感知质量,常用的指标包括:
鲁棒性评估隐写系统抵抗各种信号处理操作和攻击的能力,如:
安全性评估隐写系统抵抗隐写分析攻击的能力,包括:
音频隐写技术经历了从简单到复杂、从基础到时域和频域变换相结合的发展过程。
早期的音频隐写技术主要基于简单的位操作,如最低有效位(LSB)替换。这些方法实现简单,但安全性较低,容易被检测。
随着研究的深入,出现了各种基于时域的改进隐写方法,如差分隐写、相位编码等。这些方法在保持较好不可感知性的同时,提供了更高的安全性。
频域隐写技术的发展标志着音频隐写进入了一个新阶段。基于傅里叶变换、离散余弦变换和小波变换的隐写方法能够更好地利用人耳的听觉特性,提供更高的嵌入容量和更好的不可感知性。
自适应隐写技术根据音频内容和人耳听觉特性动态调整嵌入策略,在保证不可感知性的前提下最大化嵌入容量。这代表了现代音频隐写技术的发展方向。
近年来,深度学习技术开始应用于音频隐写领域,通过神经网络学习最优的嵌入策略和检测方法,进一步提高了隐写系统的性能。
基于时域的音频隐写技术直接操作音频信号的时域样本值,是最简单也是最基础的音频隐写方法。尽管这些方法相对简单,但它们为更复杂的隐写技术奠定了基础。本章将详细介绍几种常见的时域音频隐写技术,并提供Python实现代码。
音频LSB(Least Significant Bit)隐写是一种直接受图像LSB隐写启发的技术,它通过修改音频样本的最低有效位来嵌入秘密信息。
LSB隐写的基本思想是:音频样本值通常使用16位或24位表示,人耳对最低几位的变化不敏感。因此,我们可以将秘密信息的每一位嵌入到音频样本的最低有效位中。
对于16位音频,每个样本有16个二进制位,最低位(LSB)对应的值为1。修改这个位的值对样本的影响很小,通常不会被人耳察觉。
下面是音频LSB隐写的Python实现代码:
import numpy as np
from scipy.io import wavfile
import os
def text_to_bits(text):
"""
将文本转换为二进制字符串
"""
result = []
for c in text:
bits = bin(ord(c))[2:].zfill(8)
result.extend([int(bit) for bit in bits])
# 添加结束标记
result.extend([0]*8)
return result
def bits_to_text(bits):
"""
将二进制位列表转换回文本
"""
text = ""
for i in range(0, len(bits), 8):
if i+8 <= len(bits):
byte = bits[i:i+8]
if sum(byte) == 0: # 遇到结束标记
break
char = chr(int(''.join(map(str, byte)), 2))
text += char
return text
def embed_lsb_audio(input_file, output_file, secret_message):
"""
使用LSB隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 检查音频长度是否足够嵌入秘密消息
required_samples = len(secret_bits)
if len(audio_data) < required_samples:
raise ValueError("音频文件长度不足以嵌入秘密消息")
# 复制音频数据以避免修改原始数据
stego_audio = audio_data.copy()
# 嵌入秘密位到音频样本的LSB
for i in range(len(secret_bits)):
# 清除当前LSB
stego_audio[i] = stego_audio[i] & 0xFFFE
# 设置新的LSB
stego_audio[i] = stego_audio[i] | secret_bits[i]
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio.astype(np.int16))
print(f"成功将秘密消息嵌入到音频中,输出文件: {output_file}")
print(f"嵌入的比特数: {len(secret_bits)}")
print(f"理论最大嵌入容量: {len(audio_data)} 比特")
def extract_lsb_audio(stego_file):
"""
从LSB隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 提取LSB位
extracted_bits = []
for sample in audio_data:
# 获取LSB
bit = sample & 1
extracted_bits.append(bit)
# 检查是否遇到结束标记(连续8个0)
if len(extracted_bits) >= 8:
last_eight = extracted_bits[-8:]
if sum(last_eight) == 0:
break
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
print(f"成功提取秘密消息: {secret_message}")
return secret_message
# 使用示例
if __name__ == "__main__":
# 嵌入秘密消息
embed_lsb_audio("original.wav", "stego.wav", "这是一条隐藏的消息!")
# 提取秘密消息
extracted_message = extract_lsb_audio("stego.wav")
print(f"提取的消息: {extracted_message}")基本的音频LSB隐写虽然实现简单,但容易受到隐写分析的攻击。以下是几种改进的方法:
随机LSB隐写使用伪随机序列来确定要修改的样本位置,增加了安全性。
def embed_random_lsb_audio(input_file, output_file, secret_message, key):
"""
使用随机LSB隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
key: 用于生成随机序列的密钥
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 检查音频长度是否足够嵌入秘密消息
required_samples = len(secret_bits)
if len(audio_data) < required_samples:
raise ValueError("音频文件长度不足以嵌入秘密消息")
# 设置随机数生成器种子
np.random.seed(key)
# 生成随机嵌入位置
embedding_positions = np.random.permutation(len(audio_data))[:len(secret_bits)]
# 复制音频数据
stego_audio = audio_data.copy()
# 嵌入秘密位
for i, pos in enumerate(embedding_positions):
# 清除当前LSB
stego_audio[pos] = stego_audio[pos] & 0xFFFE
# 设置新的LSB
stego_audio[pos] = stego_audio[pos] | secret_bits[i]
# 保存嵌入位置和嵌入长度到临时文件
np.savez("embedding_info.npz", positions=embedding_positions, length=len(secret_bits))
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio.astype(np.int16))
print(f"成功使用随机LSB隐写将秘密消息嵌入到音频中")
def extract_random_lsb_audio(stego_file, key):
"""
从随机LSB隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
key: 用于生成随机序列的密钥
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 加载嵌入信息
try:
embedding_info = np.load("embedding_info.npz")
embedding_positions = embedding_info["positions"]
length = embedding_info["length"]
except FileNotFoundError:
# 如果没有嵌入信息文件,使用密钥重新生成
np.random.seed(key)
length = int(len(audio_data) * 0.1) # 假设嵌入率为10%
embedding_positions = np.random.permutation(len(audio_data))[:length]
# 提取秘密位
extracted_bits = []
for pos in embedding_positions:
bit = audio_data[pos] & 1
extracted_bits.append(bit)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message为了提高嵌入容量,可以考虑修改多个最低有效位。但需要注意的是,修改的位数越多,引入的失真越大,越容易被人耳察觉。
def embed_multi_bit_lsb_audio(input_file, output_file, secret_message, bits_to_use=2):
"""
使用多比特LSB隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
bits_to_use: 每个样本用于嵌入的位数(默认为2)
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 计算需要的样本数量
required_samples = (len(secret_bits) + bits_to_use - 1) // bits_to_use
if len(audio_data) < required_samples:
raise ValueError("音频文件长度不足以嵌入秘密消息")
# 复制音频数据
stego_audio = audio_data.copy()
# 创建掩码以清除要修改的位
mask = ~((1 << bits_to_use) - 1)
# 嵌入秘密位
for i in range(required_samples):
start_bit = i * bits_to_use
end_bit = min(start_bit + bits_to_use, len(secret_bits))
# 获取当前段的比特
current_bits = secret_bits[start_bit:end_bit]
# 补零到指定位数
while len(current_bits) < bits_to_use:
current_bits.append(0)
# 转换为整数
bit_value = int(''.join(map(str, current_bits)), 2)
# 清除音频样本的指定位
stego_audio[i] = stego_audio[i] & mask
# 设置新的值
stego_audio[i] = stego_audio[i] | bit_value
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio.astype(np.int16))
print(f"成功使用{bits_to_use}比特LSB隐写将秘密消息嵌入到音频中")差分隐写技术利用音频样本之间的差异来嵌入信息,相比简单的LSB隐写,它能提供更好的不可感知性和安全性。
差分能量隐写根据相邻音频样本的能量差异来嵌入信息。
def embed_differential_energy(input_file, output_file, secret_message, threshold=100):
"""
使用差分能量隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
threshold: 能量差异阈值
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 检查音频长度是否足够嵌入秘密消息
required_pairs = len(secret_bits)
if len(audio_data) < required_pairs * 2:
raise ValueError("音频文件长度不足以嵌入秘密消息")
# 复制音频数据
stego_audio = audio_data.copy()
# 嵌入秘密位
for i in range(len(secret_bits)):
pos = i * 2
# 计算相邻样本的能量差异
energy_diff = abs(int(audio_data[pos]) - int(audio_data[pos+1]))
if secret_bits[i] == 1:
# 如果秘密位是1,确保能量差异大于阈值
if energy_diff <= threshold:
# 增加能量差异
if stego_audio[pos] > stego_audio[pos+1]:
stego_audio[pos] = min(32767, stego_audio[pos] + (threshold - energy_diff + 1))
else:
stego_audio[pos+1] = min(32767, stego_audio[pos+1] + (threshold - energy_diff + 1))
else:
# 如果秘密位是0,确保能量差异小于阈值
if energy_diff >= threshold:
# 减少能量差异
if stego_audio[pos] > stego_audio[pos+1]:
stego_audio[pos] = max(-32768, stego_audio[pos] - (energy_diff - threshold + 1))
else:
stego_audio[pos+1] = max(-32768, stego_audio[pos+1] - (energy_diff - threshold + 1))
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio.astype(np.int16))
print(f"成功使用差分能量隐写将秘密消息嵌入到音频中")
def extract_differential_energy(stego_file, threshold=100):
"""
从差分能量隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
threshold: 能量差异阈值
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 提取秘密位
extracted_bits = []
for i in range(0, len(audio_data) - 1, 2):
# 计算相邻样本的能量差异
energy_diff = abs(int(audio_data[i]) - int(audio_data[i+1]))
# 根据能量差异提取位
if energy_diff > threshold:
extracted_bits.append(1)
else:
extracted_bits.append(0)
# 检查是否遇到结束标记(连续8个0)
if len(extracted_bits) >= 8:
last_eight = extracted_bits[-8:]
if sum(last_eight) == 0:
break
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message相位编码隐写技术利用音频信号的相位信息来嵌入秘密数据,相比振幅修改,相位修改通常更不容易被人耳察觉。
在相位编码隐写中,我们通过调整相邻采样点的相位关系来表示秘密位。例如,我们可以定义两种不同的相位关系模式,分别对应二进制位0和1。
def embed_phase_coding(input_file, output_file, secret_message):
"""
使用相位编码隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 检查音频长度是否足够嵌入秘密消息
required_samples = len(secret_bits) * 2
if len(audio_data) < required_samples:
raise ValueError("音频文件长度不足以嵌入秘密消息")
# 复制音频数据
stego_audio = audio_data.copy()
# 嵌入秘密位
for i in range(len(secret_bits)):
pos = i * 2
# 保存原始幅度
amplitude1 = abs(stego_audio[pos])
amplitude2 = abs(stego_audio[pos+1])
if secret_bits[i] == 1:
# 相位模式1:第一个样本正,第二个样本正
stego_audio[pos] = amplitude1 if stego_audio[pos] >= 0 else -amplitude1
stego_audio[pos+1] = amplitude2 if stego_audio[pos+1] >= 0 else -amplitude2
else:
# 相位模式0:第一个样本正,第二个样本负
stego_audio[pos] = amplitude1 if stego_audio[pos] >= 0 else -amplitude1
stego_audio[pos+1] = -amplitude2 if stego_audio[pos+1] >= 0 else amplitude2
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio.astype(np.int16))
print(f"成功使用相位编码隐写将秘密消息嵌入到音频中")
def extract_phase_coding(stego_file):
"""
从相位编码隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 提取秘密位
extracted_bits = []
for i in range(0, len(audio_data) - 1, 2):
# 根据两个连续样本的符号关系提取位
if (audio_data[i] >= 0 and audio_data[i+1] >= 0) or (audio_data[i] < 0 and audio_data[i+1] < 0):
extracted_bits.append(1)
else:
extracted_bits.append(0)
# 检查是否遇到结束标记(连续8个0)
if len(extracted_bits) >= 8:
last_eight = extracted_bits[-8:]
if sum(last_eight) == 0:
break
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message不同的时域隐写技术在嵌入容量、不可感知性、鲁棒性和安全性方面各有优缺点。下表对这些技术进行了比较:
隐写技术 | 嵌入容量 | 不可感知性 | 鲁棒性 | 安全性 | 实现复杂度 |
|---|---|---|---|---|---|
基本LSB | 高 | 中 | 低 | 低 | 低 |
随机LSB | 高 | 中 | 低 | 中 | 低 |
多比特LSB | 非常高 | 低 | 低 | 低 | 低 |
差分能量 | 中 | 高 | 中 | 中 | 中 |
相位编码 | 中 | 高 | 中 | 中 | 中 |
本章介绍了几种常见的基于时域的音频隐写技术,包括LSB隐写及其改进版本、差分能量隐写和相位编码隐写。我们详细讲解了每种技术的原理,并提供了完整的Python实现代码。
时域隐写技术具有实现简单、计算效率高的优点,但也存在一些局限性,如对信号处理操作的鲁棒性较差,嵌入容量与不可感知性之间的矛盾等。为了克服这些局限性,研究人员开发了基于频域变换的隐写技术,这些技术将在接下来的章节中介绍。
基于频域的音频隐写技术利用傅里叶变换、离散余弦变换或小波变换等数学工具将音频信号转换到频域,然后在频域系数中嵌入秘密信息。这些技术通常具有更好的不可感知性和鲁棒性,因为它们利用了人耳的频域感知特性。本章将详细介绍几种常见的基于频域的音频隐写技术。
离散傅里叶变换(DFT)是一种将时域信号转换到频域的数学工具。在音频隐写中,我们可以修改频域系数来嵌入秘密信息。
DFT隐写的基本思想是:利用人耳对不同频率的敏感度不同,选择人耳不太敏感的频率区域来嵌入秘密信息。通常,中频区域(约1kHz至4kHz)是人耳最敏感的区域,而低频区域(<1kHz)和高频区域(>4kHz)则相对不敏感。
在DFT隐写中,我们通常使用短时傅里叶变换(STFT)来处理音频信号,因为STFT可以同时提供时域和频域信息。
import numpy as np
from scipy.io import wavfile
from scipy import signal
import matplotlib.pyplot as plt
def embed_dft_steganography(input_file, output_file, secret_message, block_size=2048, hop_length=512):
"""
使用离散傅里叶变换(DFT)隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: STFT块大小
hop_length: STFT跳跃长度
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 计算STFT
f, t, Zxx = signal.stft(audio_data, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 获取幅度谱和相位谱
magnitude = np.abs(Zxx)
phase = np.angle(Zxx)
# 选择嵌入区域(避开人耳最敏感的中频区域)
# 低频区域:0-1kHz
low_freq_indices = np.where(f < 1000)[0]
# 高频区域:4kHz以上
high_freq_indices = np.where(f > 4000)[0]
# 组合选择的频率区域
selected_freq_indices = np.concatenate((low_freq_indices, high_freq_indices))
# 检查是否有足够的系数用于嵌入
total_coefficients = len(selected_freq_indices) * len(t)
if total_coefficients < len(secret_bits):
raise ValueError("音频长度不足以嵌入秘密消息,请增加音频长度或减少秘密消息长度")
# 创建嵌入索引
embed_indices = []
for i in range(len(t)):
for j in selected_freq_indices:
embed_indices.append((j, i))
# 嵌入秘密位到幅度谱
for i, (j, k) in enumerate(embed_indices[:len(secret_bits)]):
# 对幅度进行微小调整来嵌入位
# 避免除以零的情况
if magnitude[j, k] > 0:
# 计算嵌入步长
step = magnitude[j, k] * 0.01 # 1%的幅度变化
# 根据秘密位调整幅度
if secret_bits[i] == 1:
magnitude[j, k] += step
else:
magnitude[j, k] -= step
# 确保幅度不为负
if magnitude[j, k] < 0:
magnitude[j, k] = 0
# 重构STFT结果
stego_Zxx = magnitude * np.exp(1j * phase)
# 进行逆STFT
_, stego_audio = signal.istft(stego_Zxx, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 确保音频数据在有效范围内
stego_audio = np.clip(stego_audio, -32768, 32767).astype(np.int16)
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用DFT隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {len(secret_bits)}")
print(f"使用的频率区域: 低频(<1kHz)和高频(>4kHz)")
def extract_dft_steganography(stego_file, bit_count, block_size=2048, hop_length=512):
"""
从DFT隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: STFT块大小
hop_length: STFT跳跃长度
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 计算STFT
f, t, Zxx = signal.stft(audio_data, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 获取幅度谱
magnitude = np.abs(Zxx)
# 选择嵌入区域
low_freq_indices = np.where(f < 1000)[0]
high_freq_indices = np.where(f > 4000)[0]
selected_freq_indices = np.concatenate((low_freq_indices, high_freq_indices))
# 创建嵌入索引
embed_indices = []
for i in range(len(t)):
for j in selected_freq_indices:
embed_indices.append((j, i))
# 提取秘密位
extracted_bits = []
for i, (j, k) in enumerate(embed_indices[:bit_count]):
# 计算相邻频率系数的平均值作为阈值
neighbors = []
# 尝试获取相邻系数
if j > 0:
neighbors.append(magnitude[j-1, k])
if j < len(f) - 1:
neighbors.append(magnitude[j+1, k])
if k > 0:
neighbors.append(magnitude[j, k-1])
if k < len(t) - 1:
neighbors.append(magnitude[j, k+1])
# 如果有邻居系数,使用其平均值作为阈值
if neighbors:
threshold = np.mean(neighbors)
# 与阈值比较,决定嵌入的位
if magnitude[j, k] > threshold:
extracted_bits.append(1)
else:
extracted_bits.append(0)
else:
# 如果没有邻居系数,默认假设为0
extracted_bits.append(0)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message离散小波变换(DWT)是一种多分辨率分析工具,它将信号分解为不同尺度的子带。在音频隐写中,DWT提供了比DFT更好的时频局部化特性。
DWT隐写的基本思想是:利用小波变换将音频信号分解为近似系数和细节系数,然后选择合适的系数进行修改以嵌入秘密信息。通常,我们选择较高尺度的细节系数,因为这些系数对应于信号的高频成分,人耳对这些成分的变化不太敏感。
import pywt
def embed_dwt_steganography(input_file, output_file, secret_message, wavelet='db4', level=3):
"""
使用离散小波变换(DWT)隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
wavelet: 小波类型
level: 分解级别
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 进行多级离散小波变换
coefficients = pywt.wavedec(audio_data, wavelet, level=level)
# 提取近似系数和细节系数
cA = coefficients[0] # 近似系数
cD = coefficients[1:] # 细节系数列表
# 选择嵌入区域 - 使用最高级别的细节系数
embedding_coefficients = cD[-1].copy()
# 检查是否有足够的系数用于嵌入
if len(embedding_coefficients) < len(secret_bits):
raise ValueError("音频长度不足以嵌入秘密消息,请增加音频长度或减少秘密消息长度")
# 嵌入秘密位
for i in range(len(secret_bits)):
# 获取当前系数值
coeff_value = embedding_coefficients[i]
# 计算嵌入步长(根据系数值动态调整)
step = abs(coeff_value) * 0.05 # 5%的相对变化
if step == 0:
step = 1 # 避免零步长
# 根据秘密位调整系数值
if secret_bits[i] == 1:
embedding_coefficients[i] += step
else:
embedding_coefficients[i] -= step
# 更新系数
cD[-1] = embedding_coefficients
coefficients[1:] = cD
# 进行逆离散小波变换
stego_audio = pywt.waverec(coefficients, wavelet)
# 确保音频长度匹配
stego_audio = stego_audio[:len(audio_data)]
# 确保音频数据在有效范围内
stego_audio = np.clip(stego_audio, -32768, 32767).astype(np.int16)
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用DWT隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {len(secret_bits)}")
print(f"使用的小波类型: {wavelet}")
print(f"分解级别: {level}")
def extract_dwt_steganography(stego_file, bit_count, wavelet='db4', level=3):
"""
从DWT隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
wavelet: 小波类型
level: 分解级别
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 进行多级离散小波变换
coefficients = pywt.wavedec(audio_data, wavelet, level=level)
# 获取最高级别的细节系数
embedding_coefficients = coefficients[1:][-1]
# 提取秘密位
extracted_bits = []
for i in range(min(bit_count, len(embedding_coefficients))):
# 获取当前系数的绝对值
coeff_abs = abs(embedding_coefficients[i])
# 计算阈值(基于相邻系数的平均值)
neighbors = []
# 尝试获取相邻系数
if i > 0:
neighbors.append(abs(embedding_coefficients[i-1]))
if i < len(embedding_coefficients) - 1:
neighbors.append(abs(embedding_coefficients[i+1]))
# 如果有邻居系数,使用其平均值作为阈值
if neighbors:
threshold = np.mean(neighbors)
# 与阈值比较,决定嵌入的位
if coeff_abs > threshold:
extracted_bits.append(1)
else:
extracted_bits.append(0)
else:
# 如果没有邻居系数,默认假设为0
extracted_bits.append(0)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message离散余弦变换(DCT)广泛应用于音频和图像处理,它将信号表示为不同频率的余弦函数之和。在音频隐写中,DCT提供了良好的能量压缩特性,可以有效地将秘密信息嵌入到不引人注意的系数中。
DCT隐写的基本思想是:将音频信号分块,对每一块进行DCT变换,然后修改特定的DCT系数来嵌入秘密信息。通常,我们选择中频DCT系数进行修改,因为这些系数对应于人耳相对不敏感的频率区域。
def perform_dct(block):
"""
对音频块执行离散余弦变换
"""
return np.fft.rfft(block, norm='ortho')
def perform_idct(dct_coeffs, length):
"""
执行逆离散余弦变换
"""
return np.fft.irfft(dct_coeffs, n=length, norm='ortho')
def embed_dct_steganography(input_file, output_file, secret_message, block_size=512):
"""
使用离散余弦变换(DCT)隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: 块大小
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 计算需要的块数
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 检查是否有足够的块用于嵌入
if num_blocks < len(secret_bits):
raise ValueError("音频长度不足以嵌入秘密消息,请增加音频长度或减少秘密消息长度")
# 复制音频数据
stego_audio = audio_data.copy()
# 嵌入秘密位
for i in range(len(secret_bits)):
# 计算当前块的起始和结束位置
start_pos = i * block_size
end_pos = min(start_pos + block_size, len(audio_data))
# 获取当前块
block = audio_data[start_pos:end_pos].astype(np.float64)
# 如果块长度不足block_size,进行零填充
if len(block) < block_size:
padded_block = np.zeros(block_size)
padded_block[:len(block)] = block
block = padded_block
# 执行DCT
dct_coeffs = perform_dct(block)
# 选择中频系数进行嵌入(避开DC系数和高频系数)
# 通常选择块大小的20%-40%之间的系数
mid_start = int(len(dct_coeffs) * 0.2)
mid_end = int(len(dct_coeffs) * 0.4)
mid_coeff_index = np.random.randint(mid_start, mid_end)
# 获取选中的系数
coeff_value = dct_coeffs[mid_coeff_index]
# 计算嵌入步长
step = np.max(np.abs(dct_coeffs)) * 0.02 # 2%的最大系数值作为步长
if step == 0:
step = 1
# 根据秘密位调整系数
if secret_bits[i] == 1:
dct_coeffs[mid_coeff_index] += step
else:
dct_coeffs[mid_coeff_index] -= step
# 执行逆DCT
idct_block = perform_idct(dct_coeffs, block_size)
# 将处理后的块放回音频数据
stego_audio[start_pos:end_pos] = idct_block[:end_pos-start_pos]
# 确保音频数据在有效范围内
stego_audio = np.clip(stego_audio, -32768, 32767).astype(np.int16)
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用DCT隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {len(secret_bits)}")
print(f"块大小: {block_size}")
def extract_dct_steganography(stego_file, bit_count, block_size=512):
"""
从DCT隐写的音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: 块大小
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 提取秘密位
extracted_bits = []
for i in range(bit_count):
# 计算当前块的起始和结束位置
start_pos = i * block_size
end_pos = min(start_pos + block_size, len(audio_data))
# 获取当前块
block = audio_data[start_pos:end_pos].astype(np.float64)
# 如果块长度不足block_size,进行零填充
if len(block) < block_size:
padded_block = np.zeros(block_size)
padded_block[:len(block)] = block
block = padded_block
# 执行DCT
dct_coeffs = perform_dct(block)
# 选择与嵌入相同的中频系数
mid_start = int(len(dct_coeffs) * 0.2)
mid_end = int(len(dct_coeffs) * 0.4)
# 计算所有中频系数的平均值作为阈值
mid_coeffs = dct_coeffs[mid_start:mid_end]
threshold = np.mean(np.abs(mid_coeffs))
# 由于在提取时不知道具体使用了哪个中频系数,
# 我们可以使用统计方法判断嵌入的位
# 计算所有中频系数相对于阈值的偏差
deviation = np.sum(np.abs(mid_coeffs) > threshold)
# 如果偏差较大,判断为1,否则为0
if deviation > len(mid_coeffs) * 0.5:
extracted_bits.append(1)
else:
extracted_bits.append(0)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message除了直接修改频域系数的幅值外,我们还可以利用频域系数之间的幅度比来嵌入信息,这种方法通常具有更好的鲁棒性。
幅度比隐写的基本思想是:选择一对频域系数,修改它们的幅度比来表示秘密位。由于这种方法修改的是相对关系而不是绝对幅值,因此对一些信号处理操作(如增益调整)具有更强的鲁棒性。
def embed_ratio_based_steganography(input_file, output_file, secret_message, block_size=1024):
"""
使用基于频域系数幅度比的隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: 块大小
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 计算需要的块数
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 检查是否有足够的块用于嵌入
if num_blocks < len(secret_bits):
raise ValueError("音频长度不足以嵌入秘密消息,请增加音频长度或减少秘密消息长度")
# 复制音频数据
stego_audio = audio_data.copy()
# 嵌入秘密位
for i in range(len(secret_bits)):
# 计算当前块的起始和结束位置
start_pos = i * block_size
end_pos = min(start_pos + block_size, len(audio_data))
# 获取当前块
block = audio_data[start_pos:end_pos].astype(np.float64)
# 执行FFT
fft_coeffs = np.fft.fft(block)
# 获取幅度谱
magnitude = np.abs(fft_coeffs)
# 选择中频区域的一对系数
# 避免低频(DC分量)和高频
mid_freq_start = int(len(magnitude) * 0.3)
mid_freq_end = int(len(magnitude) * 0.5)
# 随机选择两个不同的频率索引
while True:
idx1 = np.random.randint(mid_freq_start, mid_freq_end)
idx2 = np.random.randint(mid_freq_start, mid_freq_end)
if idx1 != idx2:
break
# 确保分母不为零
if magnitude[idx2] < 1e-10:
magnitude[idx2] = 1e-10
# 计算当前幅度比
current_ratio = magnitude[idx1] / magnitude[idx2]
# 嵌入秘密位:
# 如果秘密位为1,确保幅度比大于1
# 如果秘密位为0,确保幅度比小于1
target_ratio = 1.5 if secret_bits[i] == 1 else 0.667 # 约1/1.5
# 计算总能量
total_energy = magnitude[idx1] + magnitude[idx2]
# 根据目标比例和总能量重新分配幅值
new_magnitude1 = total_energy * target_ratio / (1 + target_ratio)
new_magnitude2 = total_energy / (1 + target_ratio)
# 应用新的幅值,但保持相位不变
fft_coeffs[idx1] = new_magnitude1 * np.exp(1j * np.angle(fft_coeffs[idx1]))
fft_coeffs[idx2] = new_magnitude2 * np.exp(1j * np.angle(fft_coeffs[idx2]))
# 执行逆FFT
ifft_block = np.fft.ifft(fft_coeffs).real
# 将处理后的块放回音频数据
stego_audio[start_pos:end_pos] = ifft_block[:end_pos-start_pos]
# 确保音频数据在有效范围内
stego_audio = np.clip(stego_audio, -32768, 32767).astype(np.int16)
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用基于幅度比的隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {len(secret_bits)}")
def extract_ratio_based_steganography(stego_file, bit_count, block_size=1024):
"""
从基于幅度比的隐写音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: 块大小
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 提取秘密位
extracted_bits = []
for i in range(bit_count):
# 计算当前块的起始和结束位置
start_pos = i * block_size
end_pos = min(start_pos + block_size, len(audio_data))
# 获取当前块
block = audio_data[start_pos:end_pos].astype(np.float64)
# 执行FFT
fft_coeffs = np.fft.fft(block)
# 获取幅度谱
magnitude = np.abs(fft_coeffs)
# 选择中频区域
mid_freq_start = int(len(magnitude) * 0.3)
mid_freq_end = int(len(magnitude) * 0.5)
# 计算所有中频系数对的幅度比
ratios = []
for j in range(mid_freq_start, mid_freq_end-1):
if magnitude[j+1] > 1e-10:
ratio = magnitude[j] / magnitude[j+1]
ratios.append(ratio)
# 统计分析幅度比分布
if ratios:
# 计算幅度比的中位数
median_ratio = np.median(ratios)
# 根据中位数判断嵌入的位
if median_ratio > 1:
extracted_bits.append(1)
else:
extracted_bits.append(0)
else:
# 如果没有有效的比例,默认假设为0
extracted_bits.append(0)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message不同的频域隐写技术在嵌入容量、不可感知性、鲁棒性和安全性方面各有特点。下表对这些技术进行了比较:
隐写技术 | 嵌入容量 | 不可感知性 | 鲁棒性 | 安全性 | 计算复杂度 |
|---|---|---|---|---|---|
DFT隐写 | 中 | 高 | 中 | 中 | 中 |
DWT隐写 | 高 | 高 | 高 | 高 | 高 |
DCT隐写 | 中 | 高 | 中 | 中 | 中 |
幅度比隐写 | 中 | 高 | 高 | 中 | 中 |
本章介绍了几种常见的基于频域的音频隐写技术,包括DFT隐写、DWT隐写、DCT隐写和基于幅度比的隐写技术。我们详细讲解了每种技术的原理,并提供了完整的Python实现代码。
相比时域隐写技术,频域隐写技术通常具有更好的不可感知性和鲁棒性,特别是DWT隐写技术在这两方面都表现出色。然而,频域隐写技术的计算复杂度通常较高,实现也相对复杂。
在实际应用中,我们需要根据具体的需求和约束条件,选择合适的隐写技术。例如,如果安全性和鲁棒性是首要考虑因素,可以选择DWT隐写技术;如果计算资源有限,可以选择DFT隐写或DCT隐写技术。
自适应音频隐写技术是一种根据音频内容特性动态调整嵌入策略的隐写方法。与传统隐写技术不同,自适应隐写技术能够分析音频信号的局部特性,并据此调整嵌入强度和位置,从而在保证不可感知性的同时提高嵌入容量。本章将详细介绍几种常见的自适应音频隐写技术。
基于人类听觉系统模型的自适应隐写技术利用心理声学模型来确定音频信号中适合嵌入秘密信息的位置和强度。这种方法的核心思想是:在人耳不太敏感的区域可以嵌入更多的信息,而在敏感区域则嵌入较少的信息或不嵌入信息。
基于人类听觉系统模型的自适应隐写技术通常包括以下步骤:
def calculate_masking_threshold(signal, sample_rate, block_size=1024, hop_length=512):
"""
计算音频信号的感知掩蔽阈值
参数:
signal: 音频信号
sample_rate: 采样率
block_size: 块大小
hop_length: 跳跃长度
返回:
每个频率点的掩蔽阈值
"""
# 对信号进行短时傅里叶变换
f, t, Zxx = signal.stft(signal, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 获取幅度谱
magnitude = np.abs(Zxx)
# 计算每个频率点的能量
energy = magnitude**2
# 初始化掩蔽阈值数组
masking_threshold = np.zeros_like(energy)
# 计算每个时间帧的掩蔽阈值
for i in range(len(t)):
# 简化的掩蔽阈值计算
# 1. 计算临界频带
# 这里使用简化的临界频带划分,实际应用中应使用更精确的模型
critical_bands = [
(0, 100), (100, 200), (200, 300), (300, 400),
(400, 510), (510, 630), (630, 770), (770, 920),
(920, 1080), (1080, 1270), (1270, 1480), (1480, 1720),
(1720, 2000), (2000, 2320), (2320, 2700), (2700, 3150),
(3150, 3700), (3700, 4400), (4400, 5300), (5300, 6400),
(6400, 7700), (7700, 9500), (9500, 12000), (12000, 15500), (15500, 24000)
]
# 2. 计算每个临界频带内的能量
band_energies = []
for start, end in critical_bands:
# 找到对应的频率索引
band_indices = np.where((f >= start) & (f < end))[0]
if len(band_indices) > 0:
band_energy = np.sum(energy[band_indices, i])
band_energies.append((band_indices, band_energy))
# 3. 确定主音和它们的掩蔽效果
for indices, energy_band in band_energies:
if energy_band > 0:
# 找到频带内能量最大的频率点作为主音
peak_idx = indices[np.argmax(energy[indices, i])]
peak_freq = f[peak_idx]
peak_level = 10 * np.log10(energy[peak_idx, i] + 1e-10)
# 计算掩蔽函数
# 简化的掩蔽函数,实际应用中应使用更精确的模型
for j in range(len(f)):
freq_diff = f[j] - peak_freq
# 主音频带内的掩蔽
if abs(freq_diff) < peak_freq * 0.1:
masking_level = peak_level - 10
# 高音频带的掩蔽
elif freq_diff > 0:
masking_level = peak_level - 20 * np.log10(1 + freq_diff/100)
# 低音频带的掩蔽
else:
masking_level = peak_level - 25 * np.log10(1 - freq_diff/100)
# 确保掩蔽电平不会过低
masking_level = max(masking_level, -100)
# 转换为线性域
masking_threshold[j, i] = max(masking_threshold[j, i], 10**(masking_level/10))
return masking_threshold
def embed_perceptual_adaptive_steganography(input_file, output_file, secret_message, block_size=1024, hop_length=512):
"""
使用基于人类听觉系统模型的自适应隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: 块大小
hop_length: 跳跃长度
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 计算感知掩蔽阈值
# 注意:实际应用中应使用更精确的心理声学模型,如ISO/IEC 11172-3 MPEG-1 Audio Layer I/II
# 这里使用简化版本进行演示
audio_float = audio_data.astype(np.float64)
f, t, Zxx = signal.stft(audio_float, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 计算幅度谱和相位谱
magnitude = np.abs(Zxx)
phase = np.angle(Zxx)
# 计算信号能量
energy = magnitude**2
# 简化的感知阈值计算
# 1. 计算每个频带的信号强度
# 2. 根据信号强度和频率位置确定可嵌入的位数量
# 计算每个频率点的信噪比估计
signal_power = energy
# 假设噪声功率是信号功率的一小部分
noise_power = np.mean(signal_power) * 0.01
snr = 10 * np.log10((signal_power + 1e-10) / (noise_power + 1e-10))
# 确定每个频点可以嵌入的位数量
# 高SNR区域可以嵌入更多位,低SNR区域嵌入更少位
bits_per_freq = np.zeros_like(snr)
bits_per_freq[snr > 40] = 2 # 高SNR区域,每个频点嵌入2位
bits_per_freq[(snr > 20) & (snr <= 40)] = 1 # 中等SNR区域,每个频点嵌入1位
bits_per_freq[(snr > 10) & (snr <= 20)] = 0.5 # 较低SNR区域,每两个频点嵌入1位
# 低SNR区域不嵌入
# 计算总嵌入容量
total_capacity = int(np.sum(bits_per_freq))
# 检查是否有足够的容量
if total_capacity < len(secret_bits):
raise ValueError(f"音频文件的嵌入容量({total_capacity}位)不足以嵌入秘密消息({len(secret_bits)}位)")
# 创建嵌入索引,按照可嵌入位数量排序,优先选择可以嵌入更多位的位置
embed_positions = []
for i in range(len(t)):
for j in range(len(f)):
if bits_per_freq[j, i] > 0:
embed_positions.append((j, i, bits_per_freq[j, i]))
# 按可嵌入位数量降序排序
embed_positions.sort(key=lambda x: x[2], reverse=True)
# 嵌入秘密位
bit_index = 0
modified = np.zeros_like(Zxx, dtype=bool)
for j, i, bits in embed_positions:
if bit_index >= len(secret_bits):
break
# 根据可嵌入位数量选择嵌入策略
if bits >= 2 and bit_index + 1 < len(secret_bits):
# 嵌入2位
value1, value2 = secret_bits[bit_index], secret_bits[bit_index+1]
# 根据两位组合修改系数
coeff_value = magnitude[j, i]
step = coeff_value * 0.02 # 2%的变化
if value1 == 0 and value2 == 0:
magnitude[j, i] = coeff_value - 1.5 * step
elif value1 == 0 and value2 == 1:
magnitude[j, i] = coeff_value - 0.5 * step
elif value1 == 1 and value2 == 0:
magnitude[j, i] = coeff_value + 0.5 * step
else: # value1 == 1 and value2 == 1
magnitude[j, i] = coeff_value + 1.5 * step
bit_index += 2
modified[j, i] = True
elif bits >= 1 and bit_index < len(secret_bits):
# 嵌入1位
value = secret_bits[bit_index]
# 修改系数
coeff_value = magnitude[j, i]
step = coeff_value * 0.015 # 1.5%的变化
if value == 1:
magnitude[j, i] = coeff_value + step
else:
magnitude[j, i] = coeff_value - step
bit_index += 1
modified[j, i] = True
elif bits == 0.5 and bit_index < len(secret_bits):
# 嵌入0.5位(每两个频点嵌入1位)
# 这里简化处理,直接嵌入1位
value = secret_bits[bit_index]
# 修改系数
coeff_value = magnitude[j, i]
step = coeff_value * 0.01 # 1%的变化
if value == 1:
magnitude[j, i] = coeff_value + step
else:
magnitude[j, i] = coeff_value - step
bit_index += 1
modified[j, i] = True
# 重构STFT结果
stego_Zxx = magnitude * np.exp(1j * phase)
# 执行逆STFT
_, stego_audio = signal.istft(stego_Zxx, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 确保音频长度匹配
stego_audio = stego_audio[:len(audio_data)]
# 确保音频数据在有效范围内
stego_audio = np.clip(stego_audio, -32768, 32767).astype(np.int16)
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用基于人类听觉系统模型的自适应隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {bit_index}")
print(f"音频文件的嵌入容量: {total_capacity}位")
def extract_perceptual_adaptive_steganography(stego_file, bit_count, block_size=1024, hop_length=512):
"""
从基于人类听觉系统模型的自适应隐写音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: 块大小
hop_length: 跳跃长度
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 执行STFT
audio_float = audio_data.astype(np.float64)
f, t, Zxx = signal.stft(audio_float, sample_rate, nperseg=block_size, noverlap=block_size-hop_length)
# 获取幅度谱
magnitude = np.abs(Zxx)
# 计算信号能量
energy = magnitude**2
# 简化的感知阈值计算,与嵌入过程相同
signal_power = energy
noise_power = np.mean(signal_power) * 0.01
snr = 10 * np.log10((signal_power + 1e-10) / (noise_power + 1e-10))
# 确定每个频点可以嵌入的位数量
bits_per_freq = np.zeros_like(snr)
bits_per_freq[snr > 40] = 2
bits_per_freq[(snr > 20) & (snr <= 40)] = 1
bits_per_freq[(snr > 10) & (snr <= 20)] = 0.5
# 创建与嵌入相同的位置列表
embed_positions = []
for i in range(len(t)):
for j in range(len(f)):
if bits_per_freq[j, i] > 0:
embed_positions.append((j, i, bits_per_freq[j, i]))
# 按可嵌入位数量降序排序
embed_positions.sort(key=lambda x: x[2], reverse=True)
# 提取秘密位
extracted_bits = []
bit_index = 0
for j, i, bits in embed_positions:
if bit_index >= bit_count:
break
# 获取当前系数值
coeff_value = magnitude[j, i]
# 计算相邻系数的平均值作为参考
neighbors = []
if j > 0:
neighbors.append(magnitude[j-1, i])
if j < len(f) - 1:
neighbors.append(magnitude[j+1, i])
if i > 0:
neighbors.append(magnitude[j, i-1])
if i < len(t) - 1:
neighbors.append(magnitude[j, i+1])
if neighbors:
threshold = np.mean(neighbors)
# 根据可嵌入位数量选择提取策略
if bits >= 2 and bit_index + 1 < bit_count:
# 提取2位
diff = coeff_value - threshold
# 根据差异大小判断两位组合
if diff < -threshold * 0.01:
# 较大的负差异
extracted_bits.extend([0, 0])
elif diff < -threshold * 0.0025:
# 较小的负差异
extracted_bits.extend([0, 1])
elif diff > threshold * 0.0025:
# 较小的正差异
extracted_bits.extend([1, 0])
elif diff > threshold * 0.01:
# 较大的正差异
extracted_bits.extend([1, 1])
else:
# 差异太小,默认值
extracted_bits.extend([0, 0])
bit_index += 2
elif bits >= 1 and bit_index < bit_count:
# 提取1位
if coeff_value > threshold:
extracted_bits.append(1)
else:
extracted_bits.append(0)
bit_index += 1
elif bits == 0.5 and bit_index < bit_count:
# 提取0.5位
if coeff_value > threshold:
extracted_bits.append(1)
else:
extracted_bits.append(0)
bit_index += 1
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message基于音频纹理分析的自适应隐写技术通过分析音频信号的局部纹理特性(如能量变化、频率分布、熵等)来确定适合嵌入的位置。这种方法的核心思想是:在纹理复杂的区域(如音乐中的和弦部分)可以嵌入更多的信息,因为这些区域的变化不易被人耳察觉。
基于音频纹理分析的自适应隐写技术通常包括以下步骤:
def calculate_texture_features(audio_block):
"""
计算音频块的纹理特征
参数:
audio_block: 音频块
返回:
纹理特征字典
"""
# 计算能量
energy = np.sum(audio_block**2) / len(audio_block)
# 计算能量熵(能量分布的熵)
# 将块分成子块,计算每个子块的能量
subblock_size = max(1, len(audio_block) // 10)
subblock_energies = []
for i in range(0, len(audio_block), subblock_size):
end = min(i + subblock_size, len(audio_block))
sub_energy = np.sum(audio_block[i:end]**2)
subblock_energies.append(sub_energy)
# 归一化子块能量
total_sub_energy = sum(subblock_energies)
if total_sub_energy > 0:
subblock_probabilities = [e / total_sub_energy for e in subblock_energies]
energy_entropy = -np.sum([p * np.log2(p + 1e-10) for p in subblock_probabilities])
else:
energy_entropy = 0
# 计算频谱熵
# 对块进行FFT
fft_coeffs = np.fft.fft(audio_block)
magnitude = np.abs(fft_coeffs[:len(fft_coeffs)//2]) # 只取频谱的一半
# 计算频谱概率
total_magnitude = np.sum(magnitude)
if total_magnitude > 0:
spectral_probabilities = magnitude / total_magnitude
spectral_entropy = -np.sum([p * np.log2(p + 1e-10) for p in spectral_probabilities])
else:
spectral_entropy = 0
# 计算零交叉率
zero_crossings = np.where(np.diff(np.sign(audio_block)))[0]
zero_crossing_rate = len(zero_crossings) / len(audio_block)
# 计算方差
variance = np.var(audio_block)
# 计算自相关峰值
correlation = np.correlate(audio_block, audio_block, mode='same')
correlation = correlation[len(correlation)//2:] # 只考虑后半部分
if len(correlation) > 1:
max_corr = np.max(correlation[1:]) # 排除零延迟相关值
autocorr_peak = max_corr / correlation[0] # 归一化
else:
autocorr_peak = 0
return {
'energy': energy,
'energy_entropy': energy_entropy,
'spectral_entropy': spectral_entropy,
'zero_crossing_rate': zero_crossing_rate,
'variance': variance,
'autocorr_peak': autocorr_peak
}
def classify_block_complexity(features):
"""
根据纹理特征对音频块的复杂性进行分类
参数:
features: 纹理特征字典
返回:
复杂性类别 (0: 简单, 1: 中等, 2: 复杂)
"""
# 使用多个特征进行综合评估
# 这里使用简单的阈值方法,实际应用中可以使用更复杂的分类算法
complexity_score = 0
# 能量熵越高,块越复杂
if features['energy_entropy'] > 3.0:
complexity_score += 1
# 频谱熵越高,频率分布越均匀,块越复杂
if features['spectral_entropy'] > 6.0:
complexity_score += 1
# 零交叉率高表示高频内容多,块可能更复杂
if features['zero_crossing_rate'] > 0.05:
complexity_score += 1
# 自相关峰值低表示信号的周期性弱,块可能更复杂
if features['autocorr_peak'] < 0.7:
complexity_score += 1
# 根据得分进行分类
if complexity_score >= 3:
return 2 # 复杂
elif complexity_score >= 1:
return 1 # 中等
else:
return 0 # 简单
def embed_texture_adaptive_steganography(input_file, output_file, secret_message, block_size=1024):
"""
使用基于音频纹理分析的自适应隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: 块大小
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 分块处理
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 复制音频数据
stego_audio = audio_data.copy()
# 计算每个块的纹理特征和复杂性类别
block_complexities = []
for i in range(num_blocks):
start = i * block_size
end = min(start + block_size, len(audio_data))
block = audio_data[start:end].astype(np.float64)
# 计算纹理特征
features = calculate_texture_features(block)
# 分类复杂性
complexity = classify_block_complexity(features)
block_complexities.append((i, complexity, start, end))
# 按复杂性降序排序,优先选择复杂的块进行嵌入
block_complexities.sort(key=lambda x: x[1], reverse=True)
# 根据块的复杂性分配嵌入容量
# 复杂块:每个样本最多可嵌入1位LSB
# 中等块:每两个样本可嵌入1位LSB
# 简单块:每四个样本可嵌入1位LSB
embedding_capacity = 0
for _, complexity, start, end in block_complexities:
block_length = end - start
if complexity == 2: # 复杂
embedding_capacity += block_length // 1
elif complexity == 1: # 中等
embedding_capacity += block_length // 2
else: # 简单
embedding_capacity += block_length // 4
# 检查是否有足够的容量
if embedding_capacity < len(secret_bits):
raise ValueError(f"音频文件的嵌入容量({embedding_capacity}位)不足以嵌入秘密消息({len(secret_bits)}位)")
# 嵌入秘密消息
bit_index = 0
for block_idx, complexity, start, end in block_complexities:
if bit_index >= len(secret_bits):
break
# 获取当前块
block = stego_audio[start:end].astype(np.int16)
# 根据块的复杂性选择嵌入策略
if complexity == 2: # 复杂
# 每个样本嵌入1位
for i in range(len(block)):
if bit_index >= len(secret_bits):
break
# 清除LSB
block[i] = block[i] & ~1
# 设置新的LSB
block[i] = block[i] | secret_bits[bit_index]
bit_index += 1
elif complexity == 1: # 中等
# 每两个样本嵌入1位
for i in range(0, len(block), 2):
if bit_index >= len(secret_bits):
break
# 清除LSB
block[i] = block[i] & ~1
# 设置新的LSB
block[i] = block[i] | secret_bits[bit_index]
bit_index += 1
else: # 简单
# 每四个样本嵌入1位
for i in range(0, len(block), 4):
if bit_index >= len(secret_bits):
break
# 清除LSB
block[i] = block[i] & ~1
# 设置新的LSB
block[i] = block[i] | secret_bits[bit_index]
bit_index += 1
# 将修改后的块放回音频数据
stego_audio[start:end] = block
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用基于音频纹理分析的自适应隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {bit_index}")
print(f"音频文件的嵌入容量: {embedding_capacity}位")
def extract_texture_adaptive_steganography(stego_file, bit_count, block_size=1024):
"""
从基于音频纹理分析的自适应隐写音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: 块大小
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 分块处理
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 计算每个块的纹理特征和复杂性类别(与嵌入过程相同)
block_complexities = []
for i in range(num_blocks):
start = i * block_size
end = min(start + block_size, len(audio_data))
block = audio_data[start:end].astype(np.float64)
# 计算纹理特征
features = calculate_texture_features(block)
# 分类复杂性
complexity = classify_block_complexity(features)
block_complexities.append((i, complexity, start, end))
# 按复杂性降序排序
block_complexities.sort(key=lambda x: x[1], reverse=True)
# 提取秘密消息
extracted_bits = []
for _, complexity, start, end in block_complexities:
if len(extracted_bits) >= bit_count:
break
# 获取当前块
block = audio_data[start:end].astype(np.int16)
# 根据块的复杂性选择提取策略
if complexity == 2: # 复杂
# 每个样本提取1位
for i in range(len(block)):
if len(extracted_bits) >= bit_count:
break
# 提取LSB
bit = block[i] & 1
extracted_bits.append(bit)
elif complexity == 1: # 中等
# 每两个样本提取1位
for i in range(0, len(block), 2):
if len(extracted_bits) >= bit_count:
break
# 提取LSB
bit = block[i] & 1
extracted_bits.append(bit)
else: # 简单
# 每四个样本提取1位
for i in range(0, len(block), 4):
if len(extracted_bits) >= bit_count:
break
# 提取LSB
bit = block[i] & 1
extracted_bits.append(bit)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message基于机器学习的自适应隐写技术利用机器学习算法来分析音频信号,预测每个位置的嵌入容量,并据此优化嵌入策略。这种方法可以自动学习音频信号的特性与嵌入容量之间的关系,从而提高隐写的性能。
基于机器学习的自适应隐写技术通常包括以下步骤:
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import StandardScaler
def extract_features_for_ml(audio_block, sample_rate):
"""
为机器学习模型提取音频特征
参数:
audio_block: 音频块
sample_rate: 采样率
返回:
特征向量
"""
# 基本统计特征
mean_val = np.mean(audio_block)
std_val = np.std(audio_block)
min_val = np.min(audio_block)
max_val = np.max(audio_block)
# 能量特征
energy = np.sum(audio_block**2) / len(audio_block)
rms = np.sqrt(energy)
# 频谱特征
fft_coeffs = np.fft.fft(audio_block)
magnitude = np.abs(fft_coeffs[:len(fft_coeffs)//2])
# 频域能量
spectral_energy = np.sum(magnitude**2) / len(magnitude)
# 频谱中心
if np.sum(magnitude) > 0:
freq_axis = np.fft.fftfreq(len(audio_block), 1/sample_rate)[:len(audio_block)//2]
spectral_centroid = np.sum(freq_axis * magnitude) / np.sum(magnitude)
else:
spectral_centroid = 0
# 频谱带宽
if np.sum(magnitude) > 0 and spectral_centroid > 0:
spectral_bandwidth = np.sqrt(np.sum(((freq_axis - spectral_centroid)**2) * magnitude) / np.sum(magnitude))
else:
spectral_bandwidth = 0
# 零交叉率
zero_crossings = np.where(np.diff(np.sign(audio_block)))[0]
zero_crossing_rate = len(zero_crossings) / len(audio_block)
# 自相关特征
correlation = np.correlate(audio_block, audio_block, mode='same')
correlation = correlation[len(correlation)//2:]
if len(correlation) > 1:
# 自相关衰减率(前10%样本的平均值)
decay_end = max(1, int(len(correlation) * 0.1))
autocorr_decay = np.mean(correlation[1:decay_end]) / correlation[0]
# 自相关峰值位置
if np.max(correlation[1:]) > 0:
peak_pos = np.argmax(correlation[1:]) + 1
normalized_peak_pos = peak_pos / len(correlation)
else:
normalized_peak_pos = 0
else:
autocorr_decay = 0
normalized_peak_pos = 0
# 梅尔频率倒谱系数(MFCC)的简化版本
# 这里使用简化的频谱子带能量作为特征
num_subbands = 8
subband_energies = []
band_edges = np.linspace(0, len(magnitude), num_subbands + 1, dtype=int)
for i in range(num_subbands):
start, end = band_edges[i], band_edges[i+1]
if start < len(magnitude):
end = min(end, len(magnitude))
sub_energy = np.sum(magnitude[start:end])
subband_energies.append(sub_energy)
else:
subband_energies.append(0)
# 归一化子带能量
total_sub_energy = sum(subband_energies)
if total_sub_energy > 0:
subband_energies = [e / total_sub_energy for e in subband_energies]
# 组合特征
features = [
mean_val, std_val, min_val, max_val, energy, rms,
spectral_energy, spectral_centroid, spectral_bandwidth, zero_crossing_rate,
autocorr_decay, normalized_peak_pos
] + subband_energies
return np.array(features)
def train_capacity_prediction_model():
"""
训练嵌入容量预测模型
注意:这里使用简化的模拟数据进行演示,实际应用中应使用真实的训练数据
返回:
训练好的模型和特征缩放器
"""
# 生成模拟训练数据
# 在实际应用中,应该使用带有真实嵌入容量标签的数据
np.random.seed(42)
num_samples = 1000
# 生成特征
X = []
for _ in range(num_samples):
# 生成随机音频块
block_size = np.random.randint(512, 2048)
audio_block = np.random.normal(0, 1, block_size)
# 模拟不同类型的音频内容
# 0: 静音, 1: 噪声, 2: 音调, 3: 复杂音频
audio_type = np.random.randint(0, 4)
if audio_type == 0: # 静音
audio_block *= 0.01
elif audio_type == 1: # 噪声
audio_block = np.random.uniform(-1, 1, block_size)
elif audio_type == 2: # 音调
freq = np.random.uniform(100, 2000)
t = np.linspace(0, block_size/44100, block_size)
audio_block = np.sin(2 * np.pi * freq * t)
else: # 复杂音频
# 混合多个音调
audio_block = np.zeros(block_size)
num_tones = np.random.randint(2, 6)
for _ in range(num_tones):
freq = np.random.uniform(100, 4000)
amp = np.random.uniform(0.1, 0.5)
phase = np.random.uniform(0, 2*np.pi)
t = np.linspace(0, block_size/44100, block_size)
audio_block += amp * np.sin(2 * np.pi * freq * t + phase)
# 添加噪声
audio_block += np.random.normal(0, 0.1, block_size)
# 提取特征
features = extract_features_for_ml(audio_block, 44100)
X.append(features)
X = np.array(X)
# 生成标签(嵌入容量)
# 基于音频类型设置嵌入容量
y = []
for i in range(num_samples):
audio_type = i % 4
base_capacity = 0.0
if audio_type == 0: # 静音
base_capacity = 0.05 # 5%的容量
elif audio_type == 1: # 噪声
base_capacity = 0.3 # 30%的容量
elif audio_type == 2: # 音调
base_capacity = 0.1 # 10%的容量
else: # 复杂音频
base_capacity = 0.4 # 40%的容量
# 添加一些随机性
capacity = base_capacity + np.random.normal(0, 0.02)
capacity = max(0.01, min(0.5, capacity)) # 确保容量在合理范围内
y.append(capacity)
y = np.array(y)
# 特征标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 训练决策树回归模型
model = DecisionTreeRegressor(max_depth=10, random_state=42)
model.fit(X_scaled, y)
return model, scaler
def embed_ml_adaptive_steganography(input_file, output_file, secret_message, block_size=1024):
"""
使用基于机器学习的自适应隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: 块大小
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 分块处理
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 复制音频数据
stego_audio = audio_data.copy()
# 训练嵌入容量预测模型
# 注意:在实际应用中,应该使用预先训练好的模型
model, scaler = train_capacity_prediction_model()
# 预测每个块的嵌入容量
block_capacities = []
for i in range(num_blocks):
start = i * block_size
end = min(start + block_size, len(audio_data))
block = audio_data[start:end].astype(np.float64)
# 提取特征
features = extract_features_for_ml(block, sample_rate)
features = features.reshape(1, -1)
# 标准化特征
features_scaled = scaler.transform(features)
# 预测嵌入容量
predicted_capacity = model.predict(features_scaled)[0]
# 计算实际可嵌入的比特数
bits_count = int(len(block) * predicted_capacity)
block_capacities.append((i, start, end, bits_count))
# 按可嵌入比特数降序排序
block_capacities.sort(key=lambda x: x[3], reverse=True)
# 计算总嵌入容量
total_capacity = sum([bits for _, _, _, bits in block_capacities])
# 检查是否有足够的容量
if total_capacity < len(secret_bits):
raise ValueError(f"音频文件的嵌入容量({total_capacity}位)不足以嵌入秘密消息({len(secret_bits)}位)")
# 嵌入秘密消息
bit_index = 0
for _, start, end, bits_count in block_capacities:
if bit_index >= len(secret_bits):
break
# 获取当前块
block = stego_audio[start:end].astype(np.int16)
# 计算实际可以嵌入的位数
actual_bits = min(bits_count, len(secret_bits) - bit_index)
# 计算嵌入步长
step = max(1, len(block) // actual_bits)
# 嵌入位
for i in range(0, len(block), step):
if bit_index >= len(secret_bits):
break
# 清除LSB
block[i] = block[i] & ~1
# 设置新的LSB
block[i] = block[i] | secret_bits[bit_index]
bit_index += 1
# 将修改后的块放回音频数据
stego_audio[start:end] = block
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用基于机器学习的自适应隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {bit_index}")
print(f"音频文件的嵌入容量: {total_capacity}位")
def extract_ml_adaptive_steganography(stego_file, bit_count, block_size=1024):
"""
从基于机器学习的自适应隐写音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: 块大小
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 分块处理
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 训练嵌入容量预测模型(与嵌入过程相同)
model, scaler = train_capacity_prediction_model()
# 预测每个块的嵌入容量
block_capacities = []
for i in range(num_blocks):
start = i * block_size
end = min(start + block_size, len(audio_data))
block = audio_data[start:end].astype(np.float64)
# 提取特征
features = extract_features_for_ml(block, sample_rate)
features = features.reshape(1, -1)
# 标准化特征
features_scaled = scaler.transform(features)
# 预测嵌入容量
predicted_capacity = model.predict(features_scaled)[0]
# 计算实际可嵌入的比特数
bits_count = int(len(block) * predicted_capacity)
block_capacities.append((i, start, end, bits_count))
# 按可嵌入比特数降序排序
block_capacities.sort(key=lambda x: x[3], reverse=True)
# 提取秘密消息
extracted_bits = []
for _, start, end, bits_count in block_capacities:
if len(extracted_bits) >= bit_count:
break
# 获取当前块
block = audio_data[start:end].astype(np.int16)
# 计算提取步长
step = max(1, len(block) // bits_count)
# 提取位
for i in range(0, len(block), step):
if len(extracted_bits) >= bit_count:
break
# 提取LSB
bit = block[i] & 1
extracted_bits.append(bit)
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message智能阈值自适应隐写技术通过动态调整嵌入阈值,在保证不可感知性的同时最大化嵌入容量。这种方法的核心思想是:根据音频信号的局部特性,自动调整嵌入强度,使得在不同区域的嵌入效果都能达到最佳。
智能阈值自适应隐写技术通常包括以下步骤:
def calculate_adaptive_threshold(audio_block, target_snr_db=40):
"""
计算自适应嵌入阈值
参数:
audio_block: 音频块
target_snr_db: 目标信噪比(分贝)
返回:
嵌入阈值
"""
# 计算块能量
block_energy = np.sum(audio_block**2) / len(audio_block)
# 计算目标噪声能量
target_noise_energy = block_energy / (10**(target_snr_db / 10))
# 计算噪声标准差
noise_std = np.sqrt(target_noise_energy)
# 将噪声标准差转换为阈值
threshold = noise_std * 2 # 2倍标准差作为阈值
return threshold
def embed_adaptive_threshold_steganography(input_file, output_file, secret_message, block_size=1024, initial_snr=40, feedback_factor=0.1):
"""
使用智能阈值自适应隐写技术将秘密消息嵌入到音频文件中
参数:
input_file: 输入音频文件路径
output_file: 输出音频文件路径
secret_message: 要隐藏的秘密消息
block_size: 块大小
initial_snr: 初始信噪比(分贝)
feedback_factor: 反馈调整因子
"""
# 读取音频文件
sample_rate, audio_data = wavfile.read(input_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 转换秘密消息为二进制位
secret_bits = text_to_bits(secret_message)
# 分块处理
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 复制音频数据
stego_audio = audio_data.copy()
# 初始化嵌入位置和计数器
bit_index = 0
current_snr = initial_snr
# 嵌入秘密消息
for i in range(num_blocks):
if bit_index >= len(secret_bits):
break
# 计算当前块的起始和结束位置
start = i * block_size
end = min(start + block_size, len(audio_data))
# 获取当前块
block = stego_audio[start:end].astype(np.float64)
# 计算当前块的自适应阈值
threshold = calculate_adaptive_threshold(block, current_snr)
# 计算可以嵌入的位数量
# 基于阈值和块长度估计嵌入容量
max_bits = int(len(block) * min(0.5, 10 / (current_snr + 10))) # 经验公式
bits_to_embed = min(max_bits, len(secret_bits) - bit_index)
if bits_to_embed > 0:
# 计算嵌入步长
step = max(1, len(block) // bits_to_embed)
# 嵌入位
for j in range(0, len(block), step):
if bit_index >= len(secret_bits):
break
# 获取当前样本值
sample = block[j]
# 根据阈值和秘密位调整样本值
delta = threshold * 0.5 # 使用阈值的一半作为调整量
if secret_bits[bit_index] == 1:
# 向上调整样本值
block[j] = sample + delta
else:
# 向下调整样本值
block[j] = sample - delta
# 计算调整后的误差
error = abs(block[j] - sample)
# 通过反馈机制调整下一个块的阈值
if error > threshold:
# 误差过大,增加SNR,减小后续阈值
current_snr += feedback_factor
elif error < threshold * 0.5:
# 误差过小,可以减小SNR,增加后续阈值
current_snr -= feedback_factor * 0.5
# 确保SNR在合理范围内
current_snr = max(20, min(60, current_snr))
bit_index += 1
# 将修改后的块放回音频数据
stego_audio[start:end] = np.clip(block, -32768, 32767).astype(np.int16)
# 保存嵌入后的音频
wavfile.write(output_file, sample_rate, stego_audio)
print(f"成功使用智能阈值自适应隐写将秘密消息嵌入到音频中")
print(f"嵌入的比特数: {bit_index}")
print(f"最终的SNR设置: {current_snr:.2f} dB")
def extract_adaptive_threshold_steganography(stego_file, bit_count, block_size=1024, initial_snr=40, feedback_factor=0.1):
"""
从智能阈值自适应隐写音频文件中提取秘密消息
参数:
stego_file: 隐写音频文件路径
bit_count: 要提取的比特数量
block_size: 块大小
initial_snr: 初始信噪比(分贝)
feedback_factor: 反馈调整因子
返回:
提取的秘密消息
"""
# 读取隐写音频文件
sample_rate, audio_data = wavfile.read(stego_file)
# 确保音频是16位整数
if audio_data.dtype != np.int16:
audio_data = audio_data.astype(np.int16)
# 如果音频是立体声,转换为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data[:, 0]
# 分块处理
num_blocks = int(np.ceil(len(audio_data) / block_size))
# 初始化提取位置和计数器
extracted_bits = []
current_snr = initial_snr
# 提取秘密消息
for i in range(num_blocks):
if len(extracted_bits) >= bit_count:
break
# 计算当前块的起始和结束位置
start = i * block_size
end = min(start + block_size, len(audio_data))
# 获取当前块
block = audio_data[start:end].astype(np.float64)
# 计算当前块的自适应阈值(与嵌入过程相同)
threshold = calculate_adaptive_threshold(block, current_snr)
# 估计嵌入步长
max_bits = int(len(block) * min(0.5, 10 / (current_snr + 10)))
step = max(1, len(block) // max_bits)
# 提取位
for j in range(0, len(block), step):
if len(extracted_bits) >= bit_count:
break
# 获取当前样本值
sample = block[j]
# 获取相邻样本作为参考
ref_samples = []
if j > 0:
ref_samples.append(block[j-1])
if j < len(block) - 1:
ref_samples.append(block[j+1])
# 计算参考值
if ref_samples:
reference = np.mean(ref_samples)
else:
reference = 0
# 根据样本值与参考值的差异判断嵌入的位
diff = sample - reference
if abs(diff) > threshold * 0.2: # 如果差异足够大
if diff > 0:
extracted_bits.append(1)
else:
extracted_bits.append(0)
# 更新SNR(模拟嵌入时的反馈过程)
if abs(diff) > threshold:
current_snr += feedback_factor
elif abs(diff) < threshold * 0.5:
current_snr -= feedback_factor * 0.5
# 确保SNR在合理范围内
current_snr = max(20, min(60, current_snr))
# 转换位为文本
secret_message = bits_to_text(extracted_bits)
return secret_message不同的自适应隐写技术在嵌入容量、不可感知性、鲁棒性和安全性方面各有特点。下表对这些技术进行了比较:
隐写技术 | 嵌入容量 | 不可感知性 | 鲁棒性 | 安全性 | 计算复杂度 | 自适应能力 |
|---|---|---|---|---|---|---|
基于人类听觉系统模型 | 高 | 高 | 中 | 高 | 高 | 高 |
基于音频纹理分析 | 高 | 中 | 中 | 中 | 中 | 中 |
基于机器学习 | 高 | 高 | 高 | 高 | 高 | 高 |
智能阈值自适应 | 高 | 中 | 中 | 中 | 中 | 高 |
本章介绍了几种常见的自适应音频隐写技术,包括基于人类听觉系统模型的自适应隐写、基于音频纹理分析的自适应隐写、基于机器学习的自适应隐写和智能阈值自适应隐写技术。我们详细讲解了每种技术的原理,并提供了完整的Python实现代码。
自适应隐写技术通过根据音频内容特性动态调整嵌入策略,能够在保证不可感知性的同时提高嵌入容量,相比传统隐写技术具有明显的优势。不同的自适应技术各有特点,适用于不同的应用场景: