mikeo_410


前処理(窓関数、ゼロ・パディング)

前処理の必要性は、まだ理解できていません。図を描いてみたので保存しておきます。

 1.窓関数

 窓関数を掛けることは、情報を失うことだと思うので、必要性が分かるまでは置いておこうと思います。失う情報量は40%程度にもなるように思います。

窓は、サンプルの列から、FFTの計算区間を切り出すものとして説明されていますが、ここではフレーム化したデータに対して適用する、フレーム中のサンプル数と同じ個数の数列として扱っています。

ハン窓、ハミング窓、ブラックマン窓について作図して見ました。

 窓を計算する関数:

#---------------------------------------------------------------------
# ハン窓
# L:窓の長さ
hann <- function(L){
  w <- 0.5 - 0.5 * cos( 2*pi * c( 0:(L-1) ) / (L-1) )
  return(w)
}
#---------------------------------------------------------------------
# ハミング窓
# L:窓の長さ
hamming <- function(L){
  w <- 0.54 - 0.46 * cos( 2*pi * c( 0:(L-1) ) / (L-1) )
  return(w)
}
#---------------------------------------------------------------------
# ブラックマン窓
# L:窓の長さ
blackman <- function(L){
  x <- c(0:(L-1))/(L-1)
  w <- 0.42 - 0.5 * cos(2*pi*x) + 0.08 * cos(4*pi*x)
  return(w)
}
#---------------------------------------------------------------------
# パワー(dB)
# x:PCMデータ
# pad:0を補ってFFTを計算する場合の0の個数
db_power <- function(x,pad=0){
  if(pad>0) p <- abs(fft(c(x,matrix(0,pad))))
  else      p <- abs(fft(c(x)))
  p <- p/max(p)
  p <- ifelse(p<(10^(-16)),10^(-16),p)
  db <- 20*log10(p)
  return(db)
}
#---------------------------------------------------------------------
# FFTの結果を中心がゼロになるように並べ替える。
# [1]を直流、後半を負の周波数と考える場合、左が負で、右に行くほど周波数
# が大きくなるように入れ替える。xは、長さが偶数。
fftswap <- function(x){
  r <- x
  n <- length(x)
  n2 <- n / 2
  r[(n2+1):n] <- x[1:n2]
  r[1:n2] <- x[(n2+1):n]
  return(r)
}

 

 

 100サンプルのデータに適用する場合を想定した、各窓関数を計算してみました。

 

 

#-----------------------------------------------------------------------
# 窓関数を比較します。
L <- 100 # 窓の長さ
#-----------------------------------------------------------------------
# 1から(L/2)Hz部分をプロット
plot(hann(L),type="l",ylim=c(0,1),col=2, xlab="インデクス",ylab="振幅")
par(new=T)
plot(hamming(L),type="l",ylim=c(0,1),col=3,
     axes=F,xlab="",ylab="")
par(new=T)
plot(blackman(L),type="l",ylim=c(0,1),col=4,
     axes=F,xlab="",ylab="")
abline(v=50,col="gray")
title(main="窓関数(100サンプル用に計算)")
legend(35,y=0.3,c("ハン窓","ハミング窓","ブラックマン窓"),c(2,3,4))

 

 

 

 

左図は、窓関数の計算値そのものをFFTして描画しています。すべてのサンプルが1だったときに相当し、これから窓関数が、周波数解析に与える影響を想像するのだと思います。
左図のX軸のメモリは周波数で、128サンプル中で約64回の周期が最大になります。

#-----------------------------------------------------------------------
# 窓関数を比較します。(窓関数をFFT)
L <- 128          # 計算する窓の長さ
# 比較のために計算するFFTの条件
m <- 8            # L * m サンプルでFFTを計算
pad <- L * (m-1)  # 0を補う個数
N <- L * (m/2) # FFTの計算結果の周波数の必要部分 
#-----------------------------------------------------------------------
# パワーを計算
p_rect <- db_power(matrix(1,L),pad)
p_hann <- db_power(hann(L), pad)
p_hamming <- db_power(hamming(L), pad)
p_blackman <- db_power(blackman(L), pad)
# 1からN周期部分をプロット
ymin <- min(p_hann[2:N],p_hamming[2:N],p_blackman[2:N])
freq <- seq(0, by=L/(L*m), length.out=N)
plot(freq[2:N],p_rect[2:N],type="l",ylim=c(ymin,0),col="gray",
     xlab="周波数",ylab="振幅(dB)")
par(new=T)
plot(freq[2:N],p_hann[2:N],type="l",ylim=c(ymin,0),col=2,
     axes=F,xlab="",ylab="")
par(new=T)
plot(freq[2:N],p_hamming[2:N],type="l",ylim=c(ymin,0),col=3,
     axes=F,xlab="",ylab="")
par(new=T)
plot(freq[2:N],p_blackman[2:N],type="l",ylim=c(ymin,0),col=4,
     axes=F,xlab="",ylab="")
title(main=sprintf("窓関数(%dサンプル、%dbyte/FFT)",L,L*m))
legend(35,y=0.3,c("矩形窓","ハン窓","ハミング窓","ブラックマン窓"),
                c("gray",2,3,4))

 

 

 

 

 

 

 

通常は、上の図のようにゼロ・パディグして作図されるようです。左図は、前述のスクリプトのm<-8をm<-1として、パディング無しで描いたものです。
上の図の縦縞模様は、パディングすることで加えられた周期性によるものだと思います。

矩形窓の分が-320dBなのは、パワーを計算するときに小さい値を10の-16乗に置き換えているためです。(正しくはどうするのか分かりませんがゼロ以下の対数を計算しないように。)
また、[2]から描画しており、直流分を示す[1]が書かれていません。[1]は0が入っています。

 

 

 

 

 

 

 

 

 

 

 

 

左図は、前の2つの図と同じことを、FFTの結果の全体について作図したものです。
前の図は、今回の図の右半分になります。(パディングした0の数が違いますが。)

FFTの結果の並びを入れ替えています。FFTの結果の後半を負の周波数、[1]を周波数ゼロと考え、中心がゼロで、周波数の順に並ぶようにしてあります。

 

#-----------------------------------------------------------------------
# 窓関数を比較します。(全体を中心ゼロで)
L <- 128          # 計算する窓の長さ
# 比較のために計算するFFTの条件
m <- 2            # L * m サンプルでFFTを計算
pad <- L * (m-1)  # 0を補う個数
N <- L * (m/2)    # FFTの計算結果の周波数の必要部分
#-----------------------------------------------------------------------
# パワーを計算
p_rect <- fftswap(db_power(matrix(1,L),pad))
p_hann <- fftswap(db_power(hann(L), pad))
p_hamming <- fftswap(db_power(hamming(L), pad))
p_blackman <- fftswap(db_power(blackman(L), pad))
# プロット
ymin <- -120
freq <- seq(-L/(L*m)*N, by=L/(L*m), length.out=N*2)
plot(freq,p_rect,type="l",ylim=c(ymin,0),col=1,
     xlab="周波数",ylab="振幅(dB)")
par(new=T)
plot(freq,p_hann,type="l",ylim=c(ymin,0),col=2,
     axes=F,xlab="",ylab="")
par(new=T)
plot(freq,p_hamming,type="l",ylim=c(ymin,0),col=3,
     axes=F,xlab="",ylab="")
par(new=T)
plot(freq,p_blackman,type="l",ylim=c(ymin,0),col=4,
     axes=F,xlab="",ylab="")
title(main=sprintf("窓関数(%dサンプル、%dbyte/FFT)",L,L*m))
legend(25,y=0.3,c("矩形窓","ハン窓","ハミング窓","ブラックマン窓"),
                c(1,2,3,4))

 

 

 

左図は、パディングなしで計算したものです。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

周波数の分かっているものに窓関数を掛けて結果を比べれば良いのではと、ホワイトノイズ(正規乱数)で作図を試みていますが上手い図ができません。

 

そこで、すべての周波数が均一にある状態の、FFTの結果を作って、FFTの逆変換したものをPCMデータと見なせば、窓の効果が調べられるのではと考えました。

#-----------------------------------------------------------------------
# 窓関数を比較します。
L <- 128          # 計算する窓の長さ
# 比較のために計算するFFTの条件
pad <- L * (m-1)  # 0を補う個数
N <- L * (m/2)    # FFTの計算結果の周波数の必要部分 
#-----------------------------------------------------------------------
# PCMデータとして均一に周波数成分のある状態をFFTの逆変換で作る
d0 <- matrix(complex(re=0,im=0),L)  
d0[2:L/2] <- complex(re=1/L)
d0[(L/2+2):L] <- Conj(complex(re=1/L))
rn <-Re(fft(d0,inv=T))
#rn[1] <- -rn[L]
#-----------------------------------------------------------------------
# FFTの入力となる窓掛けしたPCMデータを作成
w_org <- rn                    # オリジナルのFFT入力値
w_hann <- rn * hann(L)         # hann窓のFFT入力値
w_hamming <- rn * hamming(L)   # hamming窓のFFT入力値
w_blackman <- rn * blackman(L) # blackman窓のFFT入力値
# パワーを計算
p_org <- db_power(w_org)
p_hann <- db_power(w_hann)
p_hamming <- db_power(w_hamming)
p_blackman <- db_power(w_blackman)
# 1から(L/2)Hz部分をプロット
ymin <- min(p_org[2:N],p_han[2:N],p_hamming[2:N],p_blackman[2:N])
freq <- seq(0, by=L/(L*m), length.out=N)
plot(freq[2:N],p_org[2:N],type="l",ylim=c(ymin,0),
     xlab="周波数",ylab="振幅(dB)")
par(new=T)
plot(freq[2:N],p_han[2:N],type="l",ylim=c(ymin,0),col=2,
     axes=F,xlab="",ylab="")
par(new=T)
plot(freq[2:N],p_hamming[2:N],type="l",ylim=c(ymin,0),col=3,lwd=2,
     axes=F,xlab="",ylab="")
par(new=T)
plot(freq[2:N],p_blackman[2:N],type="l",ylim=c(ymin,0),col=4,
     axes=F,xlab="",ylab="")
title(main=sprintf("窓関数の影響(%dサンプル、%dbyte/FFT)",L,L*m))
legend(5,y=-50,c("矩形窓","ハン窓","ハミング窓","ブラックマン窓"),
                c(1,2,3,4))

 

 

 

 

 

2.ゼロ・パディング

今のところ、ゼロパディングの目的として理解しているのは次の2つです。

  1.  サンプル数が2の整数乗だとFFTの計算が高速なので、サンプル数が2の整数乗になるように後半にゼロを補う。
  2. FFTを計算するサンプル数を増やすと見かけ上の分解能が高くなる。FFTの計算結果の個数はサンプル数と同じで、間隔は、サンプリング周波数 / サンプル数となるので分解能が高くなる。

音の周波数測定」では、1.48秒分のFFTを計算して1Hz以上の分解能を実現しようとしましたが、ここでは同じ条件で、ゼロ詰めの効果を見て見ようと思います。周波数は、11025Hzで試します。(周波数は、測定可能な周波数の上限が変わるだけで、分解能には影響しないと考えデータ量の少ない状態で試しました。)
Cと刻んである音叉(おそらく、C5=523.25Hz)を2秒間録音します。そのうち、最初の4000サンプル目から、1.48秒分のサンプルを切り出して使います。
このサンプル s1 と、s1の先頭の1/8の s2を使って、パワースペクトルを計算します。s2を計算するときには、ゼロ・パディングして同じ長さにします。結果のパワースペクトルも同じ個数になります。パワースペクトルの計算は、このページの先頭農法に書いた、「窓を計算する関数:」のスクリプトを使っています。
1/8のサンプルからでも同じ結果(パワーが最大の周波数)が得られることが分かりました。
  

pcm <- wav16record() # デフォルトは11025Hzで2秒間の録音です。
wav16write("c.wav",pcm) # とりあえず、保存します。
windows(4,4)
plot(pcm,type="l",xlab="時間",ylab="振幅") # こんな波形です。
# 定数を計算しておきます。-------------------------------------
len  <- 11025 * 1.48    # 1.48秒に相当するサンプル数。
len8 <- round(len / 8)  # 比較用に1/8から算出します。
d <- 11025 /len         # 周波数分解能(FFTの結果の間隔)
paste("サンプル数:",len,"比較サンプル数:",len8,"分解能:",round(d,3))
#--------------------------------------------------------------
s1 <- pcm[4000:(4000+len-1)] # 1.4秒分のサンプルをs1に
s2 <- s1[1:len8]             # 比較用に1/8(175ms分)をs2に
p1 <- db_power(s1)           # パワーを計算
p2 <- db_power(s2, len-len8) # ゼロ詰めしたもののパワーを計算
# 作図(周波数) -------------------------------------------------
b <- round(460 / d) # 460Hzから描画
e <- round(600 / d) # 600Hzまで描画
freq <- seq( d * b, by=d, length.out=(e - b + 1) ) # 周波数の軸用
windows(4,4)
plot(freq, p1[b:e], type="l", xlab="周波数(Hz)",ylab="パワー(dB)")
par(new=T)
plot(freq, p2[b:e], type="l",col=2, axes=F, xlab="", ylab="")
title(main="ゼロ詰めの影響(1.4秒と175ms)")
legend(535,y=-2,c("全サンプル","1/8サンプル"),c(1,2))
# 作図(詳細位置) -----------------------------------------------
max_index <- which.max(p1[2:(length(p1)/2)])
b <- max_index - 10 # 最大値の前後
e <- max_index + 10 # 10個を描画
freq <- seq( d * b, by=d, length.out=(e - b + 1) ) # 周波数の軸用
windows(4,4)
plot(freq, p1[b:e], type="b", lwd=2, xlab="周波数(Hz)",ylab="パワー(dB)")
par(new=T)
plot(freq, p2[b:e], type="b",col=2, axes=F, xlab="", ylab="")
title(main="ゼロ詰めの影響(1.4秒と175ms)詳細")
legend(526,y=-1,c("全","1/8"),c(1,2))
# 分解能の比較 -----------------------------------------------
paste("ゼロ詰めなしで計算した周波数(Hz):",max_index * d)
# ゼロ詰めで8倍に
p3 <- db_power(s1, length(s1)*7)
max_index2 <- which.max(p3[2:(length(p3)/2)])
max_index2
d2 <- 11025 / length(p3)
paste("ゼロ詰めして計算した周波数(Hz):",max_index2 * d2)

 次に、ゼロ・パディングで分解能が上がるのかを見たいと思います。
前述のスクリプトの最後の部分を実行すると、以下の表示になります。

> # 分解能の比較 -----------------------------------------------
> paste("ゼロ詰めなしで計算した周波数(Hz):",max_index * d)

[1] "ゼロ詰めなしで計算した周波数(Hz): 522.972972972973"
> # ゼロ詰めで8倍に
> p3 <- db_power(s1, length(s1)*7)
> max_index2 <- which.max(p3[2:(length(p3)/2)])
> max_index2

[1] 6191
> d2 <- 11025 / length(p3)
> paste("ゼロ詰めして計算した周波数(Hz):",max_index2 * d2)

[1] "ゼロ詰めして計算した周波数(Hz): 522.888513513514"
>

 どちらが真実に近いのかは、結局分かりません。

そこで、純粋に計算の問題として、サンプルを作って、周波数の検出能力を比べて見ました。

  

> # サンプリングレート1000Hzで、10秒間サンプリングする。サンプル数は10000。
> # 分解能は、0.1Hz
> # サンプルは100, 100.1, 100.2, ... , 101Hz。
> s <- matrix(0,11,10000)
> for(i in 1:11){
+   # 1秒(1000サンプル)で、100Hzは100回転。0.1Hzは0.1回転。
+   # それぞれ、2π/(1000/100)、2π/(1000/0.1)ずつ進む。
+   s[i,] <- sin(seq(0, by=2*pi*(1/10 + (i-1)*1/10000), length.out=10000))
+ }
> # 全体から
> d <- 1/10 # サンプリング時間が10秒なので分解能は0.1Hz
> for(i in 1:11){
+   p <- db_power(s[i,])
+   k <- which.max(p[2:(length(p)/2)])
+   print(paste("INDEX:",k, " /", k * d,"Hz"))
+ }

[1] "INDEX: 1000  / 100 Hz"
[1] "INDEX: 1001  / 100.1 Hz"
[1] "INDEX: 1002  / 100.2 Hz"
[1] "INDEX: 1003  / 100.3 Hz"
[1] "INDEX: 1004  / 100.4 Hz"
[1] "INDEX: 1005  / 100.5 Hz"
[1] "INDEX: 1006  / 100.6 Hz"
[1] "INDEX: 1007  / 100.7 Hz"
[1] "INDEX: 1008  / 100.8 Hz"
[1] "INDEX: 1009  / 100.9 Hz"
[1] "INDEX: 1010  / 101 Hz"
> # 2000サンプルから
> d <- 1/2  # サンプリング時間が2秒なので分解能は0.5Hz
> for(i in 1:11){
+   p <- db_power(s[i,1:2000])
+   k <- which.max(p[2:(length(p)/2)])
+   print(paste("INDEX:",k, " /", k * d,"Hz"))
+ }

[1] "INDEX: 200  / 100 Hz"
[1] "INDEX: 200  / 100 Hz"
[1] "INDEX: 200  / 100 Hz"
[1] "INDEX: 201  / 100.5 Hz"
[1] "INDEX: 201  / 100.5 Hz"
[1] "INDEX: 201  / 100.5 Hz"
[1] "INDEX: 201  / 100.5 Hz"
[1] "INDEX: 201  / 100.5 Hz"
[1] "INDEX: 202  / 101 Hz"
[1] "INDEX: 202  / 101 Hz"
[1] "INDEX: 202  / 101 Hz"
> # 2000サンプルにゼロ詰めをして10,000サンプルに
> d <- 1/10 # サンプリング時間が10秒なので分解能は0.1Hz
> for(i in 1:11){
+   p <- db_power(s[i,1:2000],8000)
+   k <- which.max(p[2:(length(p)/2)])
+   print(paste("INDEX:",k, " /", k * d,"Hz"))
+ }

[1] "INDEX: 1000  / 100 Hz"
[1] "INDEX: 1001  / 100.1 Hz"
[1] "INDEX: 1002  / 100.2 Hz"
[1] "INDEX: 1003  / 100.3 Hz"
[1] "INDEX: 1004  / 100.4 Hz"
[1] "INDEX: 1005  / 100.5 Hz"
[1] "INDEX: 1006  / 100.6 Hz"
[1] "INDEX: 1007  / 100.7 Hz"
[1] "INDEX: 1008  / 100.8 Hz"
[1] "INDEX: 1009  / 100.9 Hz"
[1] "INDEX: 1010  / 101 Hz"
>

サンプリングレートが1000Hz、サンプリング時間が10秒のデータを作ります。
サンプル数は、10,000個になります。
100Hzから101Hzまで、0.1Hz刻みのサイン波をサンプリングした状態を、s に作りました。

1.まず、全サンプルからパワーを計算した場合です。
  サンプリング時間が10秒なので、分解能が0.1Hzとなり、正しく周波数を検出しています。

2.次は、2000サンプルの場合です。
  サンプリング時間が2秒なので、分解能は、0.5Hzになり、3段階の値になりました。

3.同じ2000サンプルですが、ゼロを詰め10秒分のサンプル数と同じにします。
  見かけ上の分解能は、0.1Hzです。
  結果は、正しく周波数を検出しています。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

> # 800サンプルにゼロ詰めをして10,000サンプルに
> d <- 1/10 # サンプリング時間が10秒なので分解能は0.1Hz
> for(i in 1:11){
+   p <- db_power(s[i,1:800],9200)
+   k <- which.max(p[2:(length(p)/2)])
+   print(paste("INDEX:",k, " /", k * d,"Hz"))
+ }
[1] "INDEX: 1000  / 100 Hz"
[1] "INDEX: 1001  / 100.1 Hz"
[1] "INDEX: 1002  / 100.2 Hz"
[1] "INDEX: 1003  / 100.3 Hz"
[1] "INDEX: 1004  / 100.4 Hz"
[1] "INDEX: 1005  / 100.5 Hz"
[1] "INDEX: 1006  / 100.6 Hz"
[1] "INDEX: 1007  / 100.7 Hz"
[1] "INDEX: 1008  / 100.8 Hz"
[1] "INDEX: 1009  / 100.9 Hz"
[1] "INDEX: 1010  / 101 Hz"
>

 

さらに欲を出して、800サンプル(800ms)分で計算して見ました。0.1Hzの精度があります。
これで、AudioFCプログラムを修正して見ます。

 

 

 

 


千葉 不動産 川崎 不動産 石川県 不動産 Web制作 京都 不動産査定