第6回 ピッチ・エネルギー・ゼロ交差などの前処理¶
この回の資料
- 📊 スライドを開く(HTML)
- 📄 スライド(PDF)
- 形式:オンライン同期回(Zoom・録画を後日公開)
本文の項目番号とHTMLスライドのページ番号は異なる。
本日の到達目標¶
- ZCR・RMS・ピッチ(F0)の定義と計算方法を説明できる
- これらの特徴量による有声・無声・低振幅区間の推定方法と、その限界を説明できる
- Pythonで波形と3特徴量(ZCR・RMS・F0)を同時可視化できる
- 音声活動検出(VAD)の基本的な仕組みを説明できる
- 発展:ZCR・RMS・自己相関がそれぞれ何を測っているかを、式から自分で導ける
進化の視点:声の高さは、体の情報をどこまで運ぶか¶
基本周波数(F0)は、声帯の長さ・振動に関わる質量・張力などの影響を受け、発話中にも変わる。ただし、低い声なら大きい体だと、個人の体格をそのまま逆算できるわけではない。
成人男女の研究を統合したメタ分析では、同性内での体格との関連はF0よりもフォルマントに基づく声道長の指標の方が強かった。群の平均差と、群内の個人差を分ける必要がある(Pisanski et al., 2014)。
技術への接続:ピッチ抽出で得るのはF0の推定値であり、年齢・性別・体格そのものではない。属性推定に利用するには、別途、対象集団のデータで妥当性や誤差を評価する必要がある。音声の利用目的については第13回で扱う。
問い:F0と体格の関連を検証する際、年齢や性別などの群間差と、同じ群内での関連をどう分けて調べるか。
項目1: フレーム分割と窓掛けの復習¶
第4回・第5回の復習
多くの音声特徴量は、短い区間(フレーム)ごとに計算する。ただし、フレーム長や窓関数は特徴量ごとに選ぶ。本日のRMS・ZCRは、波形の各フレームから直接求める。
フレーム分割の例(22,050 Hz、フレーム長1,024、ホップ512)
| フレーム | サンプル位置 | 前フレームとの重なり |
|---|---|---|
| 1 | 0〜1,023 | — |
| 2 | 512〜1,535 | 50% |
| 3 | 1,024〜2,047 | 50% |
主要パラメータ(再確認)
| パラメータ | 例 | 備考 |
|---|---|---|
| フレーム長 | 25 msなら16 kHzで400サンプル | 時間長=サンプル数÷サンプリング周波数 |
| ホップサイズ | 10 msなら16 kHzで160サンプル | 25 ms窓に対して60%の重なり |
| 窓関数 | スペクトル分析ではHann窓など | スペクトル漏れを軽減する。完全に防ぐわけではない |
本日の実装では、低いF0の周期を複数含めるため、22,050 Hzでフレーム長2,048(約92.9 ms)、ホップ512(約23.2 ms)を用いる。RMS・ZCR・F0に同じ時間軸を使うための演習設定であり、常に最適な長さという意味ではない。
短い区間では音声の性質をおおむね一定と近似し、変化を追跡する。窓を長くすると周期の情報を得やすい一方、短い変化を捉えにくくなる。RMSの計算自体に定常性の仮定は必須ではない(RMS公式ドキュメント)。
項目2: ゼロ交差率(ZCR)の定義と計算式¶
ゼロ交差とは
隣り合うサンプルの符号が変化することを「ゼロ交差」と呼ぶ。例えば符号列「+、+、−、−、+」には2回の交差がある。
ZCRの定義式
ここでは、フレーム内の符号変化の回数をサンプル数で割る、librosaに合わせた定義を用いる:
- \(N\):フレーム内のサンプル数(サンプル番号は0〜\(N-1\))
- \(s(x)\):\(x\geq0\)なら+1、\(x<0\)なら−1とする2値の符号関数
- 0を正側に含める規約を採用する。librosaには微小値を0とみなす閾値もある
ZCRの単位
- ZCRは無次元。上の例なら \(2/5=0.4\)
- 1秒あたりの交差回数への換算は \(\text{ZCR}\times f_s\)。F0そのものではない
- 分母を隣接対の数\(N-1\)にする別の定義もあるため、比較時は規約をそろえる
ZCRの直感的意味
| 傾向 | 読み取れることと限界 |
|---|---|
| 高い | 符号が頻繁に反転する。無声摩擦音や広帯域雑音などで見られる |
| 低い | 符号の反転が少ない。有声母音で見られるが、有声であることの証明ではない |
| 完全なゼロ信号 | この規約ではZCR=0。小さな雑音を含む非発話区間とは区別する |
ZCRはサンプリング周波数や雑音にも依存し、共通の閾値だけで音の種類を確定できない(公式ドキュメント)。
導出①:なぜこの式で「交差の回数」が数えられるのか
式の中身は \(|s(x[n]) - s(x[n-1])|\) である。ここで定義した符号関数は +1 か −1 しか取らないので、隣り合う2サンプルの符号の組み合わせは4通りしかない。
| 前のサンプル | 後のサンプル | 差 | 絶対値 |
|---|---|---|---|
| +1 | +1 | 0 | 0 |
| −1 | −1 | 0 | 0 |
| +1 | −1 | +2 | 2 |
| −1 | +1 | −2 | 2 |
符号が変わらなければ 0、変われば 2 になる。だから総和は交差した回数のちょうど2倍である。先頭の \(1/(2N)\) にある 2 は、この 2 を打ち消すためにある。残る \(1/N\) が「1サンプルあたり何回交差したか」の率にしている。
なぜ周波数の粗い指標になるのか:正弦波 \(\sin(2\pi f t)\) は1周期に2回ゼロを横切る。1秒間に \(f\) 周期あるので、1秒あたりの交差回数は \(2f\) である。したがって
となる(有限長フレームでは端の影響がある)。同じサンプリング周波数なら、純音の周波数が高いほど交差が増える。複合音では成分の振幅や位相にも左右されるため、同じ関係をそのまま当てはめない。
ZCR はピッチの代わりにはならない
上の対応は純音のときの話である。倍音や雑音が重なると交差の回数が変わり、基本周波数との単純な対応は崩れる。ZCR が答えられるのは「速い成分が多いか少ないか」までで、何 Hz かは答えられない。ピッチを知りたいならスライド6の自己相関を使う。
使った数学:符号関数の場合分けと、正弦波の周期とゼロ点の関係(三角関数)。
項目3: RMSとエネルギーの関係¶
RMSとは(Root Mean Square:二乗平均平方根)
信号の振幅の大きさを要約する指標です。RMS自体はエネルギーではなく、同じフレーム長なら、RMSの二乗がフレームの二乗和(信号エネルギー)に比例します。
- \(x_{m}[n]\):フレーム \(m\) の \(n\) 番目のサンプル
- 大きな音 → 各サンプルの絶対値が大きい → RMSが高い
なぜ「二乗」するか
- 音声波形はプラスとマイナスを繰り返すため、単純平均すると0に近くなる
- 二乗することで符号を消し、平均二乗振幅を計算できる
- その後平方根を取ることで元の単位(振幅)に戻す
デシベル(dB)表現
ここで\(A_{\mathrm{ref}}>0\)は基準振幅、\(\epsilon>0\)は対数を計算するための下限値。本日のコードは\(A_{\mathrm{ref}}=1\)を使う。録音サンプル値に対するdBであり、校正された音圧レベル(dB SPL)ではない(公式ドキュメント)。
RMSと低振幅区間の検出
- 無音区間:RMSがほぼ0(または非常に低いdB値)
- 発話でも弱い子音は低くなり、非発話の大きな雑音は高くなる
- 閾値を設けて区間の候補を得られるが、RMSだけで発話の有無は確定できない
導出②:なぜ二乗して、平均して、平方根を取るのか
波形をそのまま平均すると
になる。音声は空気圧の平均からのずれであり、正と負がほぼ同じだけ現れるからである。大きな音でも小さな音でも0に近くなり、指標として使えない。
二乗すれば符号が消える。
これは平均二乗である。離散信号の二乗和を\(E=\sum_n x[n]^2\)と定義すると、\(E=N\,\mathrm{RMS}^2\)となる。実際の音の物理的エネルギーとの対応には、録音系の校正なども必要である。ただし単位が振幅の二乗になっているので、波形と並べて読めない。最後に平方根を取ると単位が振幅に戻る。これが RMS である。
絶対値ではいけないのか:\(\frac{1}{N}\sum |x[n]|\) でも符号は消える。実際この量も使われる。二乗は信号エネルギーと結びつき、大きい値をより強く反映する。分散との共通点は二乗を平均することである。ただし、分散は平均との差を二乗し、RMSはサンプル値自体を二乗する。
フレームごとに計算する理由:RMSは非定常な信号や録音全体にも定義できる。ここで短く区切るのは、振幅の時間変化を追うためである。窓を長くすれば値は滑らかになるが、短い変化を見落とす。
使った数学:総和の性質と、平均・二乗平均の定義(統計学Ⅰ第3回)。
項目4: ZCRとRMSによる区間候補の分類¶
3種類の区間を推定する考え方
ZCRとRMSによる簡単な規則を試す。ただし、出力は正解ラベルではなく候補である。
| 出力する候補 | RMS | ZCR | 誤りやすい例 |
|---|---|---|---|
| 低振幅 | 低 | どちらでも | 弱い発話を含むことがある |
| 有声らしい | 閾値以上 | 低 | 周期的な機械音も含み得る |
| 無声らしい | 閾値以上 | 高 | 非発話の雑音も含み得る |
判定アルゴリズム(演習用の規則)
各フレームについて:
RMSのdB値が閾値未満 → 低振幅
それ以外でZCRが閾値未満 → 有声らしい候補
それ以外 → 無声らしい候補
本日のコードの閾値(ZCR=0.1、基準振幅1に対して−30 dB)は動作を試すための設定例。録音レベル・サンプリング周波数・話者・雑音条件によって調整し、正解ラベルと比較して誤りを確かめる。
通常の囁き声は声帯の周期振動が弱い/ないため、F0が安定して得られない場合がある。しかし囁き声も発話であり、無声・非発話・無音は同じ概念ではない。
項目5: ピッチ(基本周波数F0)の概念¶
ピッチとは
ピッチ(pitch)は知覚される音の高さ。通常の有声音では、声帯振動の繰り返しの周波数である基本周波数F0が主な手がかりになる。知覚量と物理量は区別する。
- \(T_0\):基本周期(1回の振動にかかる時間)
- \(F_0\) が大きい → 高い声
- \(F_0\) が小さい → 低い声
F0の探索範囲
本日の演習では50〜500 Hzを探索する。これは演習用の設定であり、人間の声の普遍的な範囲ではない。歌声や高い声などは範囲外になり得る。範囲設定は誤推定にも影響するので、音声を聴き、グラフを確認する。
F0の分布には重なりと個人差がある。F0だけから性別・年齢・話している言語を確定することはできない。
フォルマントとの違い(重要!)
| 基本周波数F0(ピッチの主な手がかり) | フォルマント(F1, F2, ...) | |
|---|---|---|
| 物理的実体 | 声帯の振動周波数 | 声道の共鳴周波数 |
| 変化の要因 | 声帯の張り(感情・イントネーション) | 口・舌・唇の形 |
| 音声認識での役割 | 韻律(イントネーション)解析 | 音素識別(母音の区別) |
| 話者識別での役割 | 話者の声域 | 声道の形状 |
項目6: 自己相関関数によるピッチ推定¶
なぜ自己相関関数(ACF)か
有声音は周期的な波形を持ちます。自己相関関数はその周期性を検出するのに適しています。
自己相関関数の定義
- \(\tau\):サンプル単位のラグ。対応する時間差は\(\tau/f_s\)秒
- \(R_x[0]=\sum_n x[n]^2\):フレームの二乗和(信号エネルギー)
- 重なるサンプルだけを足す有限長の定義。ラグが大きいほど積の項数が減る
ピッチ推定の手順(基本形)
- 低いF0の周期を複数含む長さでフレームを切り出す
- 自己相関を計算し、必要に応じて振幅や窓による影響を正規化する
- 想定したF0範囲に対応する正のラグで、周期を示すピークを探す
- 候補ラグ\(\tau_0\)に対し\(F_0\approx f_s/\tau_0\)と推定する
例えば70〜500 Hzを探索するなら、整数ラグは\(\lceil f_s/500\rceil\)から\(\lfloor f_s/70\rfloor\)までとする。22,050 Hzでは45〜315サンプルである。単に全域の最大値を取ると、ラグ0や短いラグを選びやすい。
導出③:なぜ自己相関のピークが周期の手がかりになるのか
サンプル単位の基本周期を\(P\)とする。理想的な周期信号なら\(x[n+P]=x[n]\)なので、重なり部分では
となる。各積が正にそろうため、周期付近にピークが現れやすい。ただし有限長の上式では項数が減るので、一般に\(R_x[P]=R_x[0]\)ではない。周期から外れたラグでは山と谷の対応がずれ、積が打ち消し合いやすい。
ラグ\(P\)サンプルが1周期なら、秒単位の周期\(T_0\)との関係は
である。\(f_s=22{,}050\) Hz、\(P=147\)なら、\(T_0\approx6.67\) ms、\(F_0=150\) Hzになる。実際の周期は整数サンプルに一致しないこともあり、補間が利用される。
オクターブ誤りが起きる仕組み
周期の整数倍でも波形は似るので、\(2P\)にもピークが現れ得る。これを基本周期として選ぶとF0を半分、すなわち1オクターブ低く推定する。必ず誤るという意味ではない。短いラグや倍音の影響による誤りもあり、推定法はこれらを減らす工夫を含む。
使った数学:周期関数、有限和、サンプル数から時間への単位換算。
項目7: YINアルゴリズムの概要¶
YINアルゴリズムとは
de Cheveigné & Kawahara (2002) が提案した基本周波数推定アルゴリズム。
librosaの librosa.yin() として実装されています。
YINの主要なステップ
-
差分関数(d)の計算 $\(d[\tau] = \sum_{n} \left(x[n] - x[n+\tau]\right)^2\)$ 自己相関の代わりに「信号とそのτ遅延版との差」を使う。
-
累積平均正規化差分関数(CMND) $\(d'[\tau] = \begin{cases} 1 & \text{if } \tau = 0 \\ d[\tau] / \left(\frac{1}{\tau}\sum_{j=1}^{\tau} d[j]\right) & \text{otherwise} \end{cases}\)$ 短いラグ付近の自明な小さい差を避け、閾値で周期候補を選びやすくするための正規化。
-
閾値による極小値の探索 \(d'[\tau]\) が閾値(librosaの既定値0.1)を下回る最初の局所最小を周期候補とする。該当する谷がないときも、
librosa.yin()は最小値の位置からF0候補を返す。 -
放物線補間 整数のτを小数点精度に補間して精度向上。
YINとpYINの出力の違い
librosa.yin()は各フレームのF0候補を返す。有声フラグは返さず、fmax付近の値も無声を示す特別な値ではないlibrosa.pyin()は確率的なYINの拡張で、F0・有声フラグ・有声確率を返す。既定では無声と推定した区間のF0はNaNになる- 有声推定も誤り得る。非発話の周期音はF0を持ち得るので、F0があることと発話であることは別
以下の実習では、無声区間も可視化するためpYINを使う(YIN/pYIN公式ドキュメント)。
項目8: Python実習(フル実装)¶
環境設定
# ===== セル1:ライブラリのインストールとインポート =====
!pip install "librosa==0.11.0" soundfile matplotlib numpy -q
import librosa
import librosa.display
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from IPython.display import Audio, display
# ===== セル2:音声読み込みと基本確認 =====
# 音声ファイルの読み込み
# LibriSpeechの英語発話サンプル。自分の録音ファイルにも変更できる
audio_path = librosa.example('libri1')
y, sr = librosa.load(audio_path, sr=22050, duration=10.0)
print(f"サンプリングレート: {sr} Hz")
print(f"音声の長さ: {len(y)/sr:.2f} 秒")
print(f"サンプル数: {len(y)}")
# 音声を再生
display(Audio(y, rate=sr))
# ===== セル3:ZCRの計算と可視化 =====
hop_length = 512 # 約23.2 ms(sr=22050)
frame_length = 2048 # 約92.9 ms。低いF0の周期を複数含める
# ZCRの計算
zcr = librosa.feature.zero_crossing_rate(y,
frame_length=frame_length,
hop_length=hop_length, zero_pos=True)
# zcr の形状:(1, フレーム数)
zcr = zcr[0] # 次元を削減 → (フレーム数,)
# 時間軸の作成
frames = np.arange(len(zcr))
times = librosa.frames_to_time(frames, sr=sr, hop_length=hop_length)
print(f"ZCRの形状: {zcr.shape}")
print(f"ZCR統計: min={zcr.min():.4f}, max={zcr.max():.4f}, "
f"mean={zcr.mean():.4f}")
# ZCRを可視化
fig, axes = plt.subplots(2, 1, figsize=(12, 6), sharex=True)
# 波形
librosa.display.waveshow(y, sr=sr, ax=axes[0], alpha=0.6)
axes[0].set_title("音声波形", fontsize=12)
axes[0].set_ylabel("振幅")
# ZCR
axes[1].plot(times, zcr, color='darkorange', linewidth=1.2)
axes[1].axhline(y=0.1, color='red', linestyle='--', linewidth=1, label='閾値 0.1')
axes[1].set_xlabel("時間 (秒)", fontsize=11)
axes[1].set_ylabel("ZCR")
axes[1].set_title("ゼロ交差率(ZCR)", fontsize=12)
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ===== セル4:RMS(振幅の指標)の計算と可視化 =====
# RMSの計算
rms = librosa.feature.rms(y=y, frame_length=frame_length, hop_length=hop_length)
rms = rms[0] # 次元を削減 → (フレーム数,)
print(f"RMSの形状: {rms.shape}")
print(f"RMS統計: min={rms.min():.6f}, max={rms.max():.6f}, "
f"mean={rms.mean():.6f}")
# 基準振幅1.0に対するdBに変換(dB SPLではない)
rms_db = librosa.amplitude_to_db(rms, ref=1.0, top_db=None)
# 可視化
fig, axes = plt.subplots(2, 1, figsize=(12, 6), sharex=True)
# 波形
librosa.display.waveshow(y, sr=sr, ax=axes[0], alpha=0.6)
axes[0].set_title("音声波形", fontsize=12)
# RMS
axes[1].plot(times, rms_db, color='steelblue', linewidth=1.5, label='RMS (dB)')
axes[1].axhline(y=-30, color='red', linestyle='--', linewidth=1, label='閾値 -30 dB')
axes[1].fill_between(times, rms_db, -100, alpha=0.3, color='steelblue')
axes[1].set_xlabel("時間 (秒)", fontsize=11)
axes[1].set_ylabel("RMS (dB)")
axes[1].set_title("RMSレベル(基準振幅1のdB)", fontsize=12)
axes[1].set_ylim(-100, 5)
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ===== セル5:pYINでF0と有声フラグを推定 =====
fmin = 50 # 演習用の探索下限。音声に応じて調整する
fmax = 500 # 演習用の探索上限
f0, voiced_flag, voiced_prob = librosa.pyin(
y, fmin=fmin, fmax=fmax, sr=sr,
hop_length=hop_length, frame_length=frame_length)
f0_voiced = np.where(voiced_flag, f0, np.nan)
print(f"F0の形状: {f0.shape}")
valid = np.sum(voiced_flag)
total = len(voiced_flag)
print(f"有声と推定したフレーム数: {valid}/{total} ({100*valid/total:.1f}%)")
fig, axes = plt.subplots(2, 1, figsize=(12, 6), sharex=True)
librosa.display.waveshow(y, sr=sr, ax=axes[0], alpha=0.6)
axes[0].set_title("音声波形", fontsize=12)
axes[1].plot(times, f0_voiced, color='crimson', linewidth=1.5,
label='F0(pYINによる推定)')
axes[1].set_xlabel("時間 (秒)", fontsize=11)
axes[1].set_ylabel("基本周波数 (Hz)")
axes[1].set_title("基本周波数F0:無声と推定した区間はNaN", fontsize=12)
axes[1].set_ylim(0, fmax + 50)
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# ===== セル6:複合可視化(波形 + ZCR + RMS + F0 を同時プロット)=====
fig = plt.figure(figsize=(14, 12))
gs = gridspec.GridSpec(4, 1, hspace=0.45)
# ──────────────── 上段:波形 ────────────────
ax_wave = fig.add_subplot(gs[0])
librosa.display.waveshow(y, sr=sr, ax=ax_wave, alpha=0.7, color='gray')
ax_wave.set_title("① 音声波形(時間領域)", fontsize=12, fontweight='bold')
ax_wave.set_ylabel("振幅")
ax_wave.set_xlabel("")
ax_wave.tick_params(labelbottom=False)
# ──────────────── 2段目:ZCR ────────────────
ax_zcr = fig.add_subplot(gs[1], sharex=ax_wave)
ax_zcr.plot(times, zcr, color='darkorange', linewidth=1.2, label='ZCR')
ax_zcr.axhline(y=0.1, color='red', linestyle='--', linewidth=1,
label='ZCRの設定例 (0.1)', alpha=0.8)
ax_zcr.fill_between(times, zcr, 0, alpha=0.25, color='darkorange')
ax_zcr.set_title("② ゼロ交差率(ZCR)", fontsize=12, fontweight='bold')
ax_zcr.set_ylabel("ZCR")
ax_zcr.legend(loc='upper right', fontsize=9)
ax_zcr.grid(True, alpha=0.3)
ax_zcr.tick_params(labelbottom=False)
# ──────────────── 3段目:RMS ────────────────
ax_rms = fig.add_subplot(gs[2], sharex=ax_wave)
ax_rms.plot(times, rms_db, color='steelblue', linewidth=1.5, label='RMS (dB)')
ax_rms.axhline(y=-30, color='red', linestyle='--', linewidth=1,
label='低振幅の設定例 (-30 dB)', alpha=0.8)
ax_rms.fill_between(times, rms_db, -100, alpha=0.25, color='steelblue')
ax_rms.set_title("③ RMSレベル(基準振幅1のdB)", fontsize=12, fontweight='bold')
ax_rms.set_ylabel("dB")
ax_rms.set_ylim(-100, 5)
ax_rms.legend(loc='upper right', fontsize=9)
ax_rms.grid(True, alpha=0.3)
ax_rms.tick_params(labelbottom=False)
# ──────────────── 下段:F0 ────────────────
ax_f0 = fig.add_subplot(gs[3], sharex=ax_wave)
ax_f0.plot(times, f0_voiced, color='crimson', linewidth=1.8,
label='F0 (pYIN)', marker='.', markersize=2)
ax_f0.set_title("④ ピッチ(基本周波数 F0)", fontsize=12, fontweight='bold')
ax_f0.set_ylabel("周波数 (Hz)")
ax_f0.set_xlabel("時間 (秒)", fontsize=11)
ax_f0.set_ylim(0, fmax + 50)
ax_f0.legend(loc='upper right', fontsize=9)
ax_f0.grid(True, alpha=0.3)
fig.suptitle("音声特徴量の複合可視化(波形 / ZCR / RMS / F0)",
fontsize=14, fontweight='bold', y=1.01)
plt.savefig("features_overview.png", dpi=150, bbox_inches='tight')
plt.show()
print("グラフを features_overview.png に保存しました")
# ===== セル7:ZCRとRMSを使った区間候補の分類 =====
def rule_based_candidates(zcr, rms_db, zcr_threshold=0.1, rms_threshold=-30):
"""
ZCRとRMSによる教材用の区間候補分類(発話の正解判定ではない)
Returns:
labels: 各フレームのラベル
'low_level' = 低振幅
'voiced_like' = 有声らしい候補
'unvoiced_like' = 無声らしい候補
"""
labels = []
for z, r in zip(zcr, rms_db):
if r < rms_threshold:
labels.append('low_level')
elif z < zcr_threshold:
labels.append('voiced_like')
else:
labels.append('unvoiced_like')
return np.array(labels)
labels = rule_based_candidates(zcr, rms_db,
zcr_threshold=0.1,
rms_threshold=-30)
# 結果の集計
from collections import Counter
count = Counter(labels)
total = len(labels)
print("=== 演習用の区間候補 ===")
for label, cnt in count.items():
print(f" {label:10s}: {cnt:4d} フレーム ({100*cnt/total:.1f}%)")
# 可視化(ラベルを色で塗り分け)
label_colors = {'low_level': 'lightgray', 'voiced_like': 'lightgreen', 'unvoiced_like': 'lightsalmon'}
fig, ax = plt.subplots(figsize=(12, 3))
librosa.display.waveshow(y, sr=sr, ax=ax, alpha=0.6, color='gray')
for i, label in enumerate(labels):
t_start = max(0, times[i] - hop_length / (2 * sr))
t_end = min(len(y) / sr, times[i] + hop_length / (2 * sr))
ax.axvspan(t_start, t_end,
alpha=0.4,
color=label_colors[label],
linewidth=0)
# 凡例
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor='lightgreen', alpha=0.6, label='有声らしい候補'),
Patch(facecolor='lightsalmon', alpha=0.6, label='無声らしい候補'),
Patch(facecolor='lightgray', alpha=0.6, label='低振幅')]
ax.legend(handles=legend_elements, loc='upper right')
ax.set_title("ZCR + RMS による区間候補(正解ラベルと比較する)", fontsize=12)
ax.set_xlabel("時間 (秒)")
ax.set_ylabel("振幅")
plt.tight_layout()
plt.show()
項目9: 音声活動検出(VAD)の基礎¶
VAD(Voice Activity Detection)とは
音声区間と非音声区間を自動的に識別する技術です。
音声認識・処理システムにおけるVADの役割
入力音声ストリーム(連続的に入ってくる)
│
▼ VAD
├─ 音声区間 → 音声認識エンジンへ送る
└─ 非音声区間 → スキップ(処理しない)
VADが重要な理由
- 計算コスト削減:非発話区間の後段処理を省ける
- 認識の支援:非発話の雑音に対する不要な出力を減らせる場合がある。一方、発話を取り逃すと認識も欠落する
- 区間の制御:端末内で保存・送信する区間を選ぶ用途に使える。ただし、VADだけで収集や保存が制御されるわけではなく、システム全体の実装による
VADのアプローチ
| アプローチ | 特徴 | 代表例 |
|---|---|---|
| 単純な規則 | エネルギー等の閾値から候補を得る | 本日の分類は原理を試す簡略例 |
| 統計的 | 周波数帯域の特徴をGMMで比較するなど | WebRTC VAD |
| 深層学習 | 学習した音声区間の判別モデル | Silero VAD、pyannoteなど |
WebRTC VAD(実用的なライブラリ)
# pip install webrtcvad
import webrtcvad
# aggressiveness: 0(穏やか)〜 3(厳格)
vad = webrtcvad.Vad(2)
WebRTC VADは16-bitモノラルPCM、8/16/32/48 kHz、10/20/30 msのフレームを受け取る。上の22,050 Hzの浮動小数点波形をそのまま渡せない。上のコードは初期化のみ(Pythonラッパー公式README、WebRTCのGMM実装)。
項目10: 演習—特徴量からの推理¶
演習テーマ
「どんな音が候補になるか。特徴量だけでは何が決まらないかを考えよう」
以下は22,050 Hzでの模式例。dBは振幅1を基準とし、F0のNaNはpYINが無声と推定したことを表す。音が存在しないという意味ではない。
演習の進め方
- 以下の3つの特徴量パターンを見て、どんな音声か推測する
- 自分のペースで考えてから、チャットや共有ドキュメントに回答を書き込む
- 余裕があればクラス全体で共有する(Zoom参加またはメール提出でも可)
ケース1:特徴量パターン
ZCR:全体的に低い(0.05〜0.08)が、時々0.3以上に急上昇
RMS:高いゾーンと低いゾーンが規則的に交互に現れる
F0 :有声区間では130〜180Hz程度で安定
ヒント:RMSの高低の繰り返し、低めのZCRは何を示す?
ケース2:特徴量パターン
ZCR:常に高い(0.2〜0.35)
RMS:非常に低い(-50dB 以下)
F0 :ほとんど NaN(未検出)
ヒント:エネルギーが低く、ゼロ交差が多い…
ケース3:特徴量パターン
ZCR:低い区間と高い区間が混在
RMS:高い値が持続(-10〜-15dB 程度)
F0 :200〜280Hz の有声区間と、NaN 区間が交互
ヒント:F0が200〜280Hz程度、有声と無声が混在…
解答例(授業終了後に公開)
| ケース | 考えられる候補 | この情報だけでは決まらないこと |
|---|---|---|
| ケース1 | 周期的な音と摩擦的な音を含む発話など | 性別・言語・文章の読み上げかどうか |
| ケース2 | 小さな広帯域雑音、弱い無声子音など | 音源がファンかどうか、発話が含まれないか |
| ケース3 | 周期的な区間と非周期的な区間を含む発話など | 性別・年齢・言語 |
各候補を区別するには、元の音声、スペクトログラム、周期性の推定結果などを照合する。特徴量だけで一つの状況に確定しない。
項目11: まとめと次回予告¶
本日のまとめ
| 特徴量 | 定義・推定 | 読み取れること |
|---|---|---|
| ZCR | 符号変化の回数÷サンプル数 | 符号の反転の頻度。単独では有声性を確定しない |
| RMS | 二乗平均平方根 | 録音信号の振幅。RMSの二乗は平均二乗振幅 |
| F0 | 周期の逆数。実習ではpYINで推定 | 有声区間の周期性。推定誤りや範囲外に注意 |
| フォルマント | 声道の共鳴に対応するスペクトル包絡の山 | 母音の音色など。F0の倍音の山とは区別する |
RMSが低い区間にも発話が、RMSが高い区間にも雑音があり得る。ZCRによる区間候補、有声性推定、VADの発話区間推定は目的が異なる。
librosaで使った主な関数
librosa.feature.zero_crossing_rate() # ZCR
librosa.feature.rms() # RMS
librosa.pyin() # F0・有声フラグ・有声確率
librosa.amplitude_to_db() # 基準振幅に対するdB
次回(第7回)予告
HMM・GMMによる系列モデルの基礎を学び、自己教師あり表現学習との違いにつなげます。
予習課題
- 「HMM(隠れマルコフモデル)」を調べ、「状態」「遷移確率」「出力確率」の3概念を理解しておく
- 今日の複合可視化コードを自分の声(録音)に適用してみる
参考文献・資料¶
- de Cheveigné, A. & Kawahara, H. (2002). YIN, a fundamental frequency estimator for speech and music. JASA, 111(4).
- Rabiner, L.R. & Juang, B.H. (1993). Fundamentals of Speech Recognition. Prentice Hall.
- librosa.feature.zero_crossing_rate: https://librosa.org/doc/main/generated/librosa.feature.zero_crossing_rate.html
- librosa.feature.rms: https://librosa.org/doc/main/generated/librosa.feature.rms.html
-
librosa.yin: https://librosa.org/doc/main/generated/librosa.yin.html
- librosa.amplitude_to_db(公式)
- Pisanski et al. (2014): Vocal indicators of body size in men and women