1.傅里叶转换的基础概念
时域与频域的处理:
时域:信号随时间变化的样子(比如心跳波形、声音的振动)。
频域:信号由哪些频率的波组成(比如低音、中音、高音的比例)
核心思想:
任何信号(比如声音、图片)都可以拆解成一堆不同频率的“波”的组合。
数学本质: 傅里叶变换的公式本质是用 正弦波和余弦波的叠加 来拟合原始信号。 (公式可以简单理解为:信号 = 波1 + 波2 + 波3 + …)
2.什么是幅度谱和相位谱
首先我们要了解图片,图片由像素组成,与幅度谱和相位谱有关的傅里叶变换,就是把“直接看图”转成“分析波”,而:
- 幅度谱:这些波的“强弱”(比如低频波强=图片整体偏亮);
低频:图像平滑区域(如天空、人脸)。
高频:图像边缘和细节(如文字、纹理)
- 相位谱:这些波的“怎么拼成图”(决定形状和结构)。
相位决定图像的轮廓和形状,即使幅度谱相同,相位不同,图像也会完全不同。
3.例题
SQCTF:FFT IFFT
首先查看题目代码分析加密过程
import os
import cv2
import struct
import numpy as np
def mapping(data, down=0, up=255, tp=np.uint8):
data_max = data.max()
data_min = data.min()
interval = data_max - data_min
new_interval = up - down
new_data = (data - data_min) * new_interval / interval + down
new_data = new_data.astype(tp)
return new_data
def fft(img):
fft = np.fft.fft2(img)
fft = np.fft.fftshift(fft)
m = np.log(np.abs(fft))
p = np.angle(fft)
return m, p
if __name__ == '__main__':
os.mkdir('m')
os.mkdir('p')
os.mkdir('frame')
os.system('ffmpeg -i secret.mp4 frame/%03d.png') #视频拆分多张照片
files = os.listdir('frame')
r_file = open('r', 'wb')
for file in files:
img = cv2.imread(f'frame/{file}', cv2.IMREAD_GRAYSCALE) #读取所有帧
m, p = fft(img)
r_file.write(struct.pack('!ff', m.min(), m.max()))
new_img1 = mapping(m) #绘制
new_img2 = mapping(p) #绘制
cv2.imwrite(f'm/{file}', new_img1)
cv2.imwrite(f'p/{file}', new_img2)
r_file.close()
os.system('ffmpeg -i m/%03d.png -r 25 -vcodec png 1.mkv')
os.system('ffmpeg -i p/%03d.png -r 25 -vcodec png 2.mkv')
这段代码的主要功能是对视频文件进行傅里叶变换处理,并生成相关的图像和视频文件,具体功能如下:
- 视频帧提取:
- 使用
ffmpeg将输入的secret.mp4视频文件拆分成一系列帧图像,保存到frame文件夹中
- 使用
- 傅里叶变换处理:
- 对每一帧图像进行灰度处理
- 执行二维傅里叶变换,并将频谱移到中心
- 计算傅里叶变换的幅度谱(
m)和相位谱(p)
- 幅度谱通过取对数进行处理(
np.log(np.abs(fft)))
- 对每一帧图像进行灰度处理
- 数据映射与保存:
- 将幅度谱和相位谱的值映射到 0-255 范围(便于可视化)
- 将映射后的幅度谱保存到
m文件夹,相位谱保存到p文件夹
- 将映射后的幅度谱保存到
- 将幅度谱和相位谱的值映射到 0-255 范围(便于可视化)
- 生成统计数据:
- 创建
r文件,以二进制形式存储每帧幅度谱的最小值和最大值
- 创建
- 生成输出视频:
- 使用
ffmpeg将m文件夹中的幅度谱图像合成为1.mkv视频- 将
p文件夹中的相位谱图像合成为2.mkv视频
- 将
- 使用
fft = np.fft.fft2(img) # 对图像做二维傅里叶变换
fft = np.fft.fftshift(fft) # 将零频(低频)移到中心
m = np.log(np.abs(fft)) # 提取幅度谱,用对数压缩动态范围
p = np.angle(fft) # 提取相位谱
那么我们可以用脚本恢复一下
import os
import cv2
import struct
import numpy as np
import shutil
def inverse_mapping(data, orig_min, orig_max, tp=np.float64):
"""将0-255范围的图像映射回原始数据范围"""data = data.astype(np.float64)
new_data = data * (orig_max - orig_min) / 255 + orig_min
return new_data.astype(tp)
def ifft(m, p):
"""从幅度谱和相位谱进行逆傅里叶变换"""# 组合幅度和相位为复数
fft_complex = np.exp(m) * np.exp(1j * p) # 幅度谱需要指数还原(原处理用了log)
# 逆移位和逆傅里叶变换
fft_shift = np.fft.ifftshift(fft_complex)
img = np.fft.ifft2(fft_shift)
# 取实部并转换为灰度值范围
img = np.abs(img)
return img
def decrypt():
# 创建输出目录
os.makedirs('recovered_frame', exist_ok=True)
# 读取幅度谱的min/max数据
with open('r', 'rb') as f:
data = f.read()
# 解析二进制数据(每个帧对应两个float值:min和max)
frame_count = len(data) // 8 # 每个float占4字节,每个帧2个float
min_max_list = [struct.unpack('!ff', data[i * 8:(i + 1) * 8]) for i in range(frame_count)]
# 获取并排序所有帧文件
m_files = sorted(os.listdir('m'), key=lambda x: int(x.split('.')[0]))
p_files = sorted(os.listdir('p'), key=lambda x: int(x.split('.')[0]))
# 逐帧恢复
for i, (m_file, p_file) in enumerate(zip(m_files, p_files)):
# 读取幅度谱和相位谱图像
m_img = cv2.imread(f'm/{m_file}', cv2.IMREAD_GRAYSCALE)
p_img = cv2.imread(f'p/{p_file}', cv2.IMREAD_GRAYSCALE)
# 获取当前帧的幅度谱原始min和max
m_min, m_max = min_max_list[i]
# 将图像映射回原始傅里叶变换的幅度和相位
m = inverse_mapping(m_img, m_min, m_max)
p = inverse_mapping(p_img, -np.pi, np.pi) # 相位范围是[-π, π]
# 逆傅里叶变换恢复图像
recovered_img = ifft(m, p)
# 映射到0-255灰度范围并保存
recovered_img = ((recovered_img - recovered_img.min()) /
(recovered_img.max() - recovered_img.min()) * 255).astype(np.uint8)
cv2.imwrite(f'recovered_frame/{m_file}', recovered_img)
print(f'已恢复帧 {i + 1}/{len(m_files)}')
# 用ffmpeg将恢复的帧合成为视频
os.system('ffmpeg -i recovered_frame/%03d.png -r 25 -vcodec libx264 recovered.mp4')
print('解密完成,输出视频:recovered.mp4')
if __name__ == '__main__':
# 清理之前的恢复结果(可选)
if os.path.exists('recovered_frame'):
shutil.rmtree('recovered_frame')
decrypt()
解密成功:

SQCTF{HELLO}