You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Python3的MP4视频Fourier domain steganography技术咨询

视频隐写技术问题及代码分析

背景

受GitHub上一个用Rust实现的将.zip文件嵌入视频实现“文件存储”的项目启发,我尝试用Python3结合傅里叶域隐写术将.tar.gz等任意文件嵌入MP4视频。已编写encode_video函数,代码运行无报错,但无法确认是否成功将文件字节嵌入视频帧,且未实现提取嵌入文件的函数。同时不确定generate_video生成的视频是否会引发问题。现咨询以下问题:

  1. encode_video函数是否能正常完成文件嵌入功能?
  2. 如何编写从视频中提取嵌入文件的函数?
  3. 傅里叶域隐写术是否适用于该场景?若不适用,求可行的替代隐写方案及实现建议。

当前代码

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等常用视频格式为有损压缩,会量化和丢弃频域系数,导致嵌入数据严重损坏。
  • 实现复杂度高:需精确处理复数域数据,对数值偏移控制要求严格,否则会导致视觉失真或数据丢失。
  • 容量有限:傅里叶域中可安全修改的系数数量少,无法存储大文件。

可行替代方案及建议

  1. 最低有效位(LSB)隐写

    • 原理:修改像素RGB通道的最低1-2位二进制值,人类视觉无法察觉。
    • 优势:实现简单,兼容性好,容量较大(约为视频总像素数的1/8)。
    • 注意:使用无损视频编码(如FFV1、PNG序列),避免压缩破坏LSB数据。
  2. 帧间冗余隐写

    • 原理:利用视频帧间的预测误差(如H.264残差数据)嵌入信息,不影响视觉质量。
    • 优势:适合压缩视频,隐蔽性强。
    • 实现:可借助FFmpeg相关库直接操作视频编码的原始数据。
  3. 噪声帧存储

    • 原理:将文件字节转换为灰度值生成噪声帧,混合到视频中,提取时解析指定帧的像素值。
    • 优势:实现最简单,容量大,适合大文件存储。
    • 注意:需标记数据帧位置,避免与正常帧混淆。

内容的提问来源于stack exchange,提问作者Ori

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 23:45:41