任意の画像をスペクトログラムとして読み込み音声を生成する(i2s2w)

概要

指定した画像をスペクトログラムとして持つような音声を作る.

デモ

右肩上がりの直線を与えると

時間に比例して周波数の高くなるTSP信号(もどき)の音声を返す.

文字も埋め込める.

写真などの濃淡のはっきりしていない画像では,広い周波数域に同程度のパワーを持つためノイズっぽくなってしまう.

@i2s2wで動かしていているのでお試しください. ただTwitter bot 作成の知見が薄いので稼働が不安定かも知れません.

スペクトログラムについてざっくりと

音声信号の特徴を知りたいときはフーリエ変換を行ってどのように周波数が含まれているか調べる.

f:id:gifunogi:20170323160117p:plain

しかし,発話音声や音楽信号などの時間的な変化の大きい信号では全体の周波数的特徴が分かってもそのときどきの実用的な特徴がわからない.

f:id:gifunogi:20170323160106p:plain

このとき,時間方向に細かく分割した周波数分析(短時間フーリエ変換)を行ったスペクトログラムの表示から特徴を知ることが多い.

f:id:gifunogi:20170323225107p:plain

スペクトログラムは縦軸を周波数,横軸を時間,色でパワーを示す擬似的な3次元のグラフと見ることができる.

スペクトログラムから音声への逆変換

スペクトログラムを時間方向に分割し,逆フーリエ変換を行えばその時間における音声波形を復号することができる. (これは半分嘘で,一般的にスペクトログラム表示では位相情報が失われるため完全な復号はできない.) (実際に完全な復号をやっている研究もあるらしいのですが資料を読んでいません.)

つまり,任意の画像を読み込み,1列1列をフーリエ変換結果として考えて逆フーリエ変換を行えば,その画像をスペクトログラムとしてもつ音声波形を生成できる.

ソースコード(Python3)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
import numpy as np
import scipy.io.wavfile as scw
from PIL import Image
from scipy.fftpack import ifft

img_path = "spectrogram_image.png"   # 読み込む画像の名前

# 読み込んだ画像のリサイズ設定
# 生成される音声の信号時間は height * width * 2 / fs [sec]
height = 1024      # 大きいほど時間分解能が高く
width = 64         # 大きいほど周波数分解能が高く

raw_input_img = Image.open(img_path).convert("L") # グレースケールで読み込み
input_img = raw_input_img.resize((height, width))
img_data = np.array(input_img, dtype="uint8")
img_data = np.rot90(img_data, 3)                   # 画像の回転(スペクトログラム表示のときの方向調整)
img_data = 255 - img_data                          # 黒がパワー大,白がパワー小となるようにネガポジ反転

# 時間方向で画像を分割し,1行を短時間周波数結果と見る
# 1行ごとに逆フーリエ変換(ifft)を行うことで波形を生成できる
wav_data = np.zeros((height, width * 2))
for i, spectral in enumerate(img_data):
    wav_data[i] = ifft(spectral, n=width*2).real

wav_data = np.reshape(wav_data, height * width * 2)        # 1行の行列データに整形
wav_data = (2 ** 15 * wav_data) // max(abs(wav_data))   # 最大が 2**15 になるように正規化
wav_data = np.array(wav_data, dtype="int16")         # data type を int16に(wav書き出しのため)

t = str(time.time()).replace(".", "")
wav_path = "./out{0}.wav".format(t)       # 保存するwav音声の名前
fs = 16000                             # サンプリング周波数
scw.write(wav_path, fs, wav_data)       # wav書き出し

モチベーション

これから3年間音響の勉強をしなければならないらしい.