基于Python3的MP4视频Fourier domain steganography技术咨询
视频隐写技术问题及代码分析
背景
受GitHub上一个用Rust实现的将.zip文件嵌入视频实现“文件存储”的项目启发,我尝试用Python3结合傅里叶域隐写术将.tar.gz等任意文件嵌入MP4视频。已编写encode_video函数,代码运行无报错,但无法确认是否成功将文件字节嵌入视频帧,且未实现提取嵌入文件的函数。同时不确定generate_video生成的视频是否会引发问题。现咨询以下问题:
encode_video函数是否能正常完成文件嵌入功能?- 如何编写从视频中提取嵌入文件的函数?
- 傅里叶域隐写术是否适用于该场景?若不适用,求可行的替代隐写方案及实现建议。
当前代码
import cv2 import numpy as np from scipy.fft import fft2, ifft2 import secrets import string import os import tqdm def clear(): os.system('clear||cls') # 生成随机文件名 def generate_filename(): alphabet = string.ascii_letters + string.digits filename = ''.join(secrets.choice(alphabet) for i in range(12)) + ".mp4" return filename # 生成随机噪声视频 def generate_video(): width = 640 height = 480 fps = 30 duration = 30 filename = generate_filename() fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(filename, fourcc, fps, (width, height)) for i in range(duration * fps): noise = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8) out.write(noise) out.release() return filename def encode_video(file_name): with open(file_name, 'rb') as rb: file_bytes = rb.read() video_path = generate_video() cap = cv2.VideoCapture(video_path) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv2.CAP_PROP_FPS)) num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) bytes_per_frame = len(file_bytes) // num_frames progress_bar = tqdm.tqdm(total=num_frames, desc="Embedding Data...") for i in range(num_frames): ret, frame = cap.read() if ret: gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) fft = np.fft.fft2(gray) fft_flat = fft.flatten() start_index = i * bytes_per_frame end_index = start_index + bytes_per_frame bytes_to_hide = np.frombuffer(file_bytes[start_index:end_index], dtype=np.uint8) fft_flat[start_index:end_index] += bytes_to_hide fft_modified = fft_flat.reshape(height, width) gray_modified = np.fft.ifft2(fft_modified).real frame_modified = cv2.cvtColor(gray_modified.astype(np.uint8), cv2.COLOR_GRAY2BGR) if i == 0: output_path = 'special_vid.mp4' fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height), True) out.write(frame_modified) progress_bar.update(1) progress_bar.close() cap.release() out.release() cv2.destroyAllWindows() os.remove(video_path) os.rename(output_path, video_path) clear() print("Data successfully embedded.")
问题解答
1. encode_video函数是否能正常完成文件嵌入功能?
当前函数存在多个关键缺陷,无法可靠完成嵌入:
- 傅里叶系数处理错误:傅里叶变换的结果是复数数组,直接将uint8字节值加到复数上会忽略虚部,逆变换后数据精度丢失严重。
- 数据截断:
bytes_per_frame = len(file_bytes) // num_frames会丢弃无法整除的剩余字节,导致文件数据不完整。 - 有损压缩干扰:使用MP4V编码(有损压缩)会修改傅里叶域系数,直接破坏嵌入的数据。
- 类型转换溢出:逆傅里叶变换后的浮点数直接强制转为uint8,会导致数值溢出或截断,进一步损坏数据。
2. 如何编写从视频中提取嵌入文件的函数?
提取函数需要对应嵌入逻辑的逆操作,同时修正原函数的缺陷。以下是修正后的嵌入和提取函数:
修正后的嵌入函数(关键调整)
def encode_video(file_name): with open(file_name, 'rb') as rb: file_bytes = rb.read() # 追加文件长度信息,提取时知道要读取的总字节数 file_len = len(file_bytes) file_bytes = file_len.to_bytes(4, byteorder='big') + file_bytes video_path = generate_video() cap = cv2.VideoCapture(video_path) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv2.CAP_PROP_FPS)) num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) total_bytes = len(file_bytes) bytes_per_frame = (total_bytes + num_frames - 1) // num_frames # 向上取整,避免丢失数据 # 使用无损编码避免压缩破坏数据 output_path = 'special_vid.mp4' fourcc = cv2.VideoWriter_fourcc(*'FFV1') out = cv2.VideoWriter(output_path, fourcc, fps, (width, height), True) progress_bar = tqdm.tqdm(total=num_frames, desc="Embedding Data...") byte_idx = 0 for i in range(num_frames): ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) fft = np.fft.fft2(gray) # 操作傅里叶系数的实部,选择高频区域减少视觉影响 fft_real = fft.real fft_flat = fft_real.flatten() current_bytes = min(bytes_per_frame, total_bytes - byte_idx) if current_bytes <= 0: out.write(frame) progress_bar.update(1) continue bytes_to_hide = np.frombuffer(file_bytes[byte_idx:byte_idx+current_bytes], dtype=np.int16) # 将字节值偏移到0附近,避免过度修改系数 fft_flat[:current_bytes] += bytes_to_hide - 128 fft_real_modified = fft_flat.reshape(height, width) fft_modified = np.complex64(fft_real_modified + 1j * fft.imag) gray_modified = np.fft.ifft2(fft_modified).real # 限制数值范围,避免溢出 gray_modified = np.clip(gray_modified, 0, 255).astype(np.uint8) frame_modified = cv2.cvtColor(gray_modified, cv2.COLOR_GRAY2BGR) out.write(frame_modified) byte_idx += current_bytes progress_bar.update(1) progress_bar.close() cap.release() out.release() cv2.destroyAllWindows() os.remove(video_path) os.rename(output_path, video_path) clear() print("Data successfully embedded.")
对应的提取函数
def decode_video(video_path, output_file): cap = cv2.VideoCapture(video_path) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) extracted_bytes = bytearray() progress_bar = tqdm.tqdm(total=num_frames, desc="Extracting Data...") file_len = None total_needed = 4 # 先读取存储文件长度的4字节 for i in range(num_frames): ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) fft = np.fft.fft2(gray) fft_real = fft.real fft_flat = fft_real.flatten() if file_len is None: # 提取文件长度信息 len_bytes = fft_flat[:total_needed].astype(np.int16) + 128 extracted_bytes.extend(len_bytes.astype(np.uint8)) if len(extracted_bytes) >= 4: file_len = int.from_bytes(extracted_bytes[:4], byteorder='big') total_needed = file_len extracted_bytes = extracted_bytes[4:] else: # 提取剩余文件数据 current_extract = min(total_needed - len(extracted_bytes), len(fft_flat)) if current_extract <= 0: break byte_vals = fft_flat[:current_extract].astype(np.int16) + 128 extracted_bytes.extend(byte_vals.astype(np.uint8)) progress_bar.update(1) if file_len is not None and len(extracted_bytes) >= file_len: break progress_bar.close() cap.release() cv2.destroyAllWindows() # 写入提取的文件 with open(output_file, 'wb') as wb: wb.write(extracted_bytes[:file_len]) print("Data extracted successfully.")
3. 傅里叶域隐写术是否适用于该场景?替代方案建议
傅里叶域隐写的适用性
傅里叶域隐写不适用于该场景,原因如下:
- 压缩兼容性差:MP4等常用视频格式为有损压缩,会量化和丢弃频域系数,导致嵌入数据严重损坏。
- 实现复杂度高:需精确处理复数域数据,对数值偏移控制要求严格,否则会导致视觉失真或数据丢失。
- 容量有限:傅里叶域中可安全修改的系数数量少,无法存储大文件。
可行替代方案及建议
最低有效位(LSB)隐写
- 原理:修改像素RGB通道的最低1-2位二进制值,人类视觉无法察觉。
- 优势:实现简单,兼容性好,容量较大(约为视频总像素数的1/8)。
- 注意:使用无损视频编码(如FFV1、PNG序列),避免压缩破坏LSB数据。
帧间冗余隐写
- 原理:利用视频帧间的预测误差(如H.264残差数据)嵌入信息,不影响视觉质量。
- 优势:适合压缩视频,隐蔽性强。
- 实现:可借助FFmpeg相关库直接操作视频编码的原始数据。
噪声帧存储
- 原理:将文件字节转换为灰度值生成噪声帧,混合到视频中,提取时解析指定帧的像素值。
- 优势:实现最简单,容量大,适合大文件存储。
- 注意:需标记数据帧位置,避免与正常帧混淆。
内容的提问来源于stack exchange,提问作者Ori
相关产品推荐
相关产品推荐

