サンプリング定理とは
原信号に含まれる最大周波数成分を$f$とすると、その2倍の周波数$2f$でサンプリングしたデータは原信号を完全に復元できる
といった内容がよく書かれている。

ただ、サンプリング定理を検索してみると、「誤解」とか「間違った理解」といった、上記の内容を表面的に理解しただけではだめだと、という趣旨の内容が見つかる。

例えば、こちらには「上記がいっていることは、あくまでも周波数成分情報の復元についてであり、波形の復元を保証するものではない」といっている。

その他にもサンプリング定理の理解について解説されているサイトがいくつかある。(※1)(※2)

これらのサイトをよんで自分の理解したところは、
  • 「理論上」は元波形を再現できる。
  • 再現には、理想的なLPF(矩形)が必要であるが、現実的には作成は困難。
  • 仮に、理想的なLPFができたとしも、ある1点のデータを再現するのにその前後の無限のデータが必要。(たたみこみ処理を行うため)
  • たたみこみはインパルスに対して行うが、現実にはインパルスの作成は困難。

ためしにサンプリングした点とsinc関数を乗じて加算してどれくらい元の波形が再現できるかやってみる。

周波数を2[Hz]の正弦波をサンプリングレートを5[Hz]でサンプリングしてみる。
サンプリングした点 サンプリングした点

サンプリングした点にインタポレーションし、1kHzにアップサンプリングして、±10周期分のsinc関数をたたみこんでみる。
sinc関数。
sinc関数

サンプリングした点とsinc関数を乗じたものの重ね合わせを表示してみる。
sinc関数重ね合わせ

上記の関数の和をとって表示してみる。
sinc関数重ね合わせ_加算

ほぼほぼ再現できているが振幅が一定ではない。

オリジナルの正弦波を重ね合わせてみると以下のようになる。 sinc関数重ね合わせ_加算_オリジナル
ところどころ振幅が小さいところがあるのがわかる。

# -*- coding: utf-8 -*-
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
from scipy.io.wavfile import read as audioread


#サンプリングレート
sample_rate = 5
#周波数
f=2
#信号長
sig_len_s = 100
sig_len_sa = sig_len_s * sample_rate
#サンプリング点
sig_s=np.cos(2*np.pi*f*np.arange(sig_len_sa)/sample_rate)
#描画用に正弦波のデータを生成
up_rate = 200
up_sample_rate = up_rate * sample_rate
sig_len_sa = sig_len_s * up_sample_rate
sig=np.cos(2*np.pi*f*np.arange(sig_len_sa)/up_sample_rate)
#描画用にサンプリング点をリピート
x_list=[]
sig_s_u = np.zeros(sig_len_sa, 'float')
for idx, d in enumerate(sig_s):
    x_list.append(idx*up_rate)
    sig_s_u[idx*up_rate] = sig_s[idx]
#正弦波とサンプル点を描画
plt.scatter(x_list, sig_s, color='r', marker='o')
plt.plot(range(len(sig)), sig)
plt.show()
#sinc関数をたたみこんでみる
r = 10*up_rate
sinc_x = np.sinc(np.arange(-r,r+1)/up_rate)
a = 50
cov_s = np.zeros(up_sample_rate*a+2*r, 'float')
for idx in range(a):
    d = sig_s[idx]
    s = idx*up_rate
    tmp = cov_s[s:s+len(sinc_x)]
    cov_s[s:s+len(sinc_x)] = tmp + d*sinc_x
    plt.plot(np.arange(s,s+len(sinc_x) ), d*sinc_x)
plt.show()
#オリジナルの正弦波と重ね合わせて表示
d_len = a*len(sinc_x)
plt.plot(cov_s[r:r+d_len], label='conv sinc')
plt.plot(sig[:d_len], label='original')
plt.legend()
plt.show()