如何用Python结合LSB技术在视频中隐藏并提取两张秘密图像?
实现方案:用LSB将两张图像嵌入视频帧并还原
前置依赖
先安装所需Python库:
pip install opencv-python numpy pillow
核心逻辑
LSB(最低有效位)隐写通过修改像素RGB通道的最低1位(或多位)存储秘密数据,肉眼几乎无法察觉。整体流程为:
- 将两张秘密图像转为二进制数据流,附带尺寸信息(用于还原)
- 遍历视频帧,将秘密数据依次写入帧像素的LSB位
- 还原时反向提取LSB位,重组得到原始图像
步骤1:预处理秘密图像与视频帧
先将秘密图像转为带尺寸信息的二进制数据,同时读取拆分好的视频帧并转换为RGB格式(OpenCV默认BGR)。
import cv2 import numpy as np from PIL import Image def image_to_binary(img_path): # 读取图像并转为RGB格式 img = Image.open(img_path).convert("RGB") width, height = img.size # 用4字节存储宽度、4字节存储高度(方便还原时解析) width_bytes = width.to_bytes(4, byteorder='big') height_bytes = height.to_bytes(4, byteorder='big') # 转换图像像素为二进制流 pixel_data = img.tobytes() # 合并尺寸信息与像素数据 return width_bytes + height_bytes + pixel_data # 处理两张秘密图像 secret1_bin = image_to_binary("secret1.png") secret2_bin = image_to_binary("secret2.png") # 添加分隔符(4字节全1),方便还原时区分两张图像 separator = b'\xff\xff\xff\xff' total_secret_bin = secret1_bin + separator + secret2_bin # 读取拆分后的视频帧(假设帧存储在frames文件夹,命名为frame_0.jpg、frame_1.jpg...) frames = [] frame_idx = 0 while True: frame_path = f"frames/frame_{frame_idx}.jpg" frame = cv2.imread(frame_path) if frame is None: break # BGR转RGB frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frames.append(frame_rgb) frame_idx += 1
步骤2:LSB嵌入秘密数据
先检查视频帧总容量是否足够容纳秘密数据(每个像素3个通道可存3位数据),再逐帧逐像素替换LSB位。
def lsb_embed(frames, secret_bin): # 将二进制数据转为位列表 secret_bits = [bit for byte in secret_bin for bit in f"{byte:08b}"] secret_bit_count = len(secret_bits) # 计算总可用存储位 total_pixels = sum(frame.shape[0] * frame.shape[1] for frame in frames) total_available_bits = total_pixels * 3 if secret_bit_count > total_available_bits: raise ValueError("视频帧总容量不足,无法嵌入全部秘密数据") bit_idx = 0 # 逐帧逐像素嵌入数据 for i in range(len(frames)): frame = frames[i] height, width, _ = frame.shape for y in range(height): for x in range(width): if bit_idx >= secret_bit_count: break # 遍历RGB三个通道 for c in range(3): if bit_idx >= secret_bit_count: break # 替换当前通道的最低位 pixel_val = frame[y, x, c] new_pixel_val = (pixel_val & 0xFE) | int(secret_bits[bit_idx]) frame[y, x, c] = new_pixel_val bit_idx += 1 if bit_idx >= secret_bit_count: break if bit_idx >= secret_bit_count: break # 转回BGR格式用于后续生成视频 processed_frames = [cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) for frame in frames] return processed_frames # 执行嵌入操作 processed_frames = lsb_embed(frames, total_secret_bin)
步骤3:重新生成含秘密的视频
用OpenCV将处理后的帧合成视频,保持原视频的帧率与分辨率。
def frames_to_video(frames, output_path, fps=30): height, width, _ = frames[0].shape fourcc = cv2.VideoWriter_fourcc(*'mp4v') video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) for frame in frames: video_writer.write(frame) video_writer.release() # 生成输出视频(假设原视频帧率为30) frames_to_video(processed_frames, "stego_video.mp4", fps=30)
步骤4:提取并还原秘密图像
从视频帧中提取LSB位,重组二进制数据后拆分出两张原始图像。
def lsb_extract(frames, target_bit_count): extracted_bits = [] bit_idx = 0 for frame in frames: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) height, width, _ = frame_rgb.shape for y in range(height): for x in range(width): if bit_idx >= target_bit_count: break for c in range(3): if bit_idx >= target_bit_count: break # 提取当前通道的最低位 pixel_val = frame_rgb[y, x, c] extracted_bits.append(str(pixel_val & 1)) bit_idx += 1 if bit_idx >= target_bit_count: break if bit_idx >= target_bit_count: break # 将位列表转为二进制数据 byte_chunks = [''.join(extracted_bits[i:i+8]) for i in range(0, len(extracted_bits), 8)] secret_bin = b''.join([int(chunk, 2).to_bytes(1, byteorder='big') for chunk in byte_chunks]) return secret_bin def binary_to_image(secret_bin, output_path): # 解析图像尺寸 width = int.from_bytes(secret_bin[:4], byteorder='big') height = int.from_bytes(secret_bin[4:8], byteorder='big') # 提取像素数据并生成图像 pixel_data = secret_bin[8:] img = Image.frombytes('RGB', (width, height), pixel_data) img.save(output_path) # 读取含秘密的视频帧 stego_frames = [] cap = cv2.VideoCapture("stego_video.mp4") while cap.isOpened(): ret, frame = cap.read() if not ret: break stego_frames.append(frame) cap.release() # 提取秘密数据(这里用嵌入时的总比特数) extracted_secret_bin = lsb_extract(stego_frames, len(total_secret_bin)*8) # 拆分两张秘密图像 separator = b'\xff\xff\xff\xff' sep_idx = extracted_secret_bin.find(separator) secret1_extracted_bin = extracted_secret_bin[:sep_idx] secret2_extracted_bin = extracted_secret_bin[sep_idx+4:] # 还原并保存图像 binary_to_image(secret1_extracted_bin, "recovered_secret1.png") binary_to_image(secret2_extracted_bin, "recovered_secret2.png")
注意事项
- 建议使用PNG格式的帧,JPG的压缩会破坏LSB数据,导致还原失败
- 可选择修改多位LSB提升容量,但会增加被检测到的概率
- 实际应用中,可将秘密数据的总长度嵌入到最前端,避免还原时手动指定长度
内容的提问来源于stack exchange,提问作者Heeya
相关产品推荐
相关产品推荐

