音乐的正统在于看 FFT 听音乐!
这道题本质上就是把频谱图转换为声波。
我本来以为这道题还要手撕 FFT 的,结果发现完全不用,首先给了代码,再者[见下文],于是这道题就完全是一道工程题,只要会调库就可以了。
看源码,源码大致可以分成两个部分:正向 FFT,和 gif 生成。
我们就反过来“逆向”每一步就行了。
首先是读 gif,这个没啥好说的,只是细节比较多,直接上代码(趴):
image = Image.open('flag.gif')
db_frames = []
for i, gif_frame in enumerate(ImageSequence.Iterator(image)):
# 将 gif_frame 转为 [x, y, 3] 的三维数组
frame = np.asarray(
gif_frame.convert('RGB').getdata(),
dtype=np.uint8
).reshape(gif_frame.size[1], gif_frame.size[0], 3)
dbs = [] # 该 frame 中,频率对应的 dB
for freq in range(0, num_freqs):
for i, threshold in enumerate(list(range(min_db, max_db + 1, quantize))[::-1], start=1):
x = threshold - min_db
y = (freq * 2 + 1) * 2
color = frame[x][y]
if (color == white_pixel).all():
dbs.append(i - 1)
break
db_frames.append(dbs)
随后就是要逆向这个 melspectrogram
。看源代码:
spectrogram = (
numpy.around(
librosa.power_to_db(
librosa.feature.melspectrogram(
...
)
) / quantize
) * quantize
)
最外层,/ quantize
, numpy.around
, * quantize
就是将 dB 取整。
然后再里面一层,就是 librosa.power_to_db
,看这个命名……盲猜一下……大概有个函数……可以把它反过来,还真有:librosa.db_to_power
。
再里面一层,就是 FFT 大头,librosa.feature.melspectrogram
,本来呢,我还做好准备要手撕 FFT 的,然后一查文档,发现好家伙,这函数也有逆函数 librosa.feature.inverse.mel_to_audio
,这就省事了。
接下来的事就是把上面的过程反过来套一遍就可以了:
db_frames = [ ... ] # 上面的 db_frames
frames = np.array(db_frames).transpose(1, 0) # 交换两维,我也不知道为啥要这样,反正接口要对上就对了
frames += min_db
# 如果考虑上面的对 2 取整的话,实际上还要加上期望值 1 才对?
y = librosa.feature.inverse.mel_to_audio(
librosa.db_to_power(frames),
22050, # sample_rate
n_fft=fft_window_size,
hop_length=frame_step_size,
window=window_function_type
)
sf.write('origin.wav', y, 22050)
至此,我们就把 gif 逆向成了 origin.wav
。
音频是「The flag is, F-L-A-G, 634,971,243,582」。