diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8dd753 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.wav diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3f2609 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# DeepFilterNet3 WAV ノイズ抑制ツール + +WAV 音声に [DeepFilterNet3](https://github.com/Rikorose/DeepFilterNet) を適用し、背景ノイズを抑制するコマンドラインツールです。 + +入力音声は DeepFilterNet3 の処理サンプリング周波数(通常 48 kHz)へ自動変換されます。処理後は、既定では入力と同じサンプリング周波数の PCM16 WAV として保存されます。 + +## 開発環境の構築 + +### 必要なもの + +- NVIDIA GPU +- CUDA 12.8 に対応する NVIDIA ドライバー +- Python 3.11(64-bit) +- `pip` +- SoundFile(WAV の読み書きに使用) +- モデルと Python パッケージを取得するためのインターネット接続 + +このスクリプトは DeepFilterNet 0.5.6、PyTorch 2.11.0、TorchAudio 2.11.0、CUDA 12.8 の組み合わせを基準にしています。DeepFilterNet は利用可能な CUDA デバイスを自動的に選択します。 + +### Windows環境(PowerShell) + +NVIDIA ドライバーをインストールし、PowerShellでGPUが認識されていることを確認します。 + +```powershell +nvidia-smi +``` + +Python 3.11をインストールしてから、プロジェクト用の仮想環境を作成します。 + +```powershell +cd D:\usr\prog\deepfilternet3_enhance +py -3.11 -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +``` + +PowerShell の実行ポリシーにより仮想環境を有効化できない場合は、その PowerShell セッションに限って次を実行してから、もう一度有効化してください。 + +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +.\.venv\Scripts\Activate.ps1 +``` + +CUDA 12.8対応のPyTorchと、アプリケーションの依存パッケージをインストールします。 + +```powershell +python -m pip install torch==2.11.0 torchaudio==2.11.0 --index-url https://download.pytorch.org/whl/cu128 +python -m pip install deepfilternet==0.5.6 soundfile +``` + +### Linux環境 + +NVIDIA ドライバーをインストールし、GPUが認識されていることを確認します。 + +```bash +nvidia-smi +``` + +Python 3.11と仮想環境機能を導入します。Ubuntu/Debian系では、次のようにインストールできます。 + +```bash +sudo apt update +sudo apt install python3.11 python3.11-venv python3-pip +``` + +使用しているディストリビューションにPython 3.11のパッケージがない場合は、そのディストリビューションの手順に従ってPython 3.11を導入してください。 + +プロジェクト用の仮想環境を作成します。 + +```bash +cd /path/to/deepfilternet3_enhance +python3.11 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +``` + +CUDA 12.8対応のPyTorchと、アプリケーションの依存パッケージをインストールします。 + +```bash +python -m pip install torch==2.11.0 torchaudio==2.11.0 --index-url https://download.pytorch.org/whl/cu128 +python -m pip install deepfilternet==0.5.6 soundfile +``` + +### CUDAの動作確認 + +Windows、Linuxともに、仮想環境を有効にした状態で次を実行します。 + +```powershell +python -c "import torch; print('PyTorch:', torch.__version__); print('CUDA runtime:', torch.version.cuda); print('CUDA available:', torch.cuda.is_available()); print('GPU:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'not available')" +``` + +正常に認識されている場合は、`CUDA available: True` と GPU 名が表示されます。DeepFilterNet 0.5.6 は利用可能な CUDA デバイスを自動的に選択するため、このツールに GPU 用オプションを指定する必要はありません。実行ログにも `Running on device cuda` と表示されます。 + +## 実行方法 + +基本構文は次のとおりです。 + +```text +python deepfilternet3_enhance.py INPUT_WAV OUTPUT_WAV [オプション] +``` + +例: + +```powershell +python deepfilternet3_enhance.py sample1a305s.wav enhanced.wav +``` + +出力先の親ディレクトリが存在しない場合は自動的に作成されます。利用可能な引数は `--help` でも確認できます。 + +```powershell +python deepfilternet3_enhance.py --help +``` + +### コマンドオプション + +| 引数 | 必須 | 説明 | +| --- | --- | --- | +| `INPUT_WAV` | はい | 入力 WAV ファイル。拡張子は `.wav` である必要があります。 | +| `OUTPUT_WAV` | はい | 出力 WAV ファイル。拡張子は `.wav` である必要があります。 | +| `--post-filter` | いいえ | ポストフィルタを有効にし、ノイズ抑制をやや強くします。 | +| `--atten-lim-db DB` | いいえ | 最大抑制量を dB で制限します。`0` より大きい値を指定してください。未指定時は制限しません。 | +| `--mono` | いいえ | ステレオなどの多チャンネル入力を、チャンネル平均によりモノラル化してから処理します。 | +| `--model-rate-output` | いいえ | 入力の周波数へ戻さず、モデルのサンプリング周波数(通常 48 kHz)で出力します。 | +| `-h`, `--help` | いいえ | ヘルプを表示して終了します。 | + +### 実行例 + +ポストフィルタを有効にして、抑制を強める場合: + +```powershell +python deepfilternet3_enhance.py input.wav output_pf.wav --post-filter +``` + +最大抑制量を 12 dB に制限し、音声の変質を抑えたい場合: + +```powershell +python deepfilternet3_enhance.py input.wav output_lim12.wav --atten-lim-db 12 +``` + +ステレオ入力をモノラル化する場合: + +```powershell +python deepfilternet3_enhance.py input.wav output_mono.wav --mono +``` + +モデルのサンプリング周波数で保存する場合: + +```powershell +python deepfilternet3_enhance.py input.wav output_48k.wav --model-rate-output +``` + +オプションは組み合わせて指定できます。 + +```powershell +python deepfilternet3_enhance.py input.wav output.wav --post-filter --atten-lim-db 18 --mono +``` + +## 実行結果の表示 + +実行中は、入力チャンネル数、サンプリング周波数、音声の長さ、処理時間などが表示されます。 + +- `Real-time factor` は「処理時間 ÷ 音声時間」です。`1` 未満なら、音声の再生時間より短い時間で処理できたことを示します。 +- `処理後ピーク値` が `1.0` を超えた場合は、PCM16 の範囲に収めるためにクリッピングされ、警告が標準エラー出力へ表示されます。 +- 正常終了時の終了コードは `0`、エラー発生時は `1` です。 + +## 使用上の注意 + +- 初回実行時は DeepFilterNet3 の学習済みモデルが取得されることがあり、インターネット接続と保存領域が必要です。2 回目以降は通常、キャッシュされたモデルが使用されます。 +- 対応する入出力形式は WAV のみです。MP3、M4A、FLAC などは、事前に WAV へ変換してください。 +- 既定の出力形式は PCM16 です。入力が 24-bit PCM や浮動小数点 WAV でも、そのビット深度は維持されません。 +- WAV の読み書きには SoundFile を使用します。DeepFilterNet 0.5.6 が内部で参照する旧 TorchAudio 音声 I/O API は使用しません。 +- 既定では、処理後の音声を入力 WAV のサンプリング周波数へ戻します。余分な再変換を避けたい場合は `--model-rate-output` を指定してください。 +- `--post-filter` はノイズを強く抑えられる一方、声の質感や残響も変化しやすくなります。結果を試聴して利用を判断してください。 +- `--atten-lim-db` の値を小さくすると抑制が控えめになり、原音の変質を抑えやすくなります。ただし、背景ノイズは多く残ります。 +- `--mono` を指定しない場合、入力のチャンネル数は維持されます。モノラル化するとステレオ定位の情報は失われます。 +- 長い音声や多チャンネル音声は、処理時間とメモリ使用量が増えます。元ファイルを残し、まず短い区間で設定を確認することを推奨します。 +- 出力先に既存ファイルを指定すると上書きされる可能性があるため、重要なファイルとは別のパスを指定してください。 +- ノイズの種類や録音状態によっては、声の欠落、金属的な音、残留ノイズなどが発生します。本番利用前に必ず出力を試聴してください。 + +## トラブルシューティング + +### `ModuleNotFoundError: No module named 'df'` + +仮想環境を有効にし、DeepFilterNet をインストールしてください。 + +```powershell +.\.venv\Scripts\Activate.ps1 +python -m pip install deepfilternet==0.5.6 soundfile +``` + +### `ModuleNotFoundError: No module named 'torchaudio.backend'` + +DeepFilterNet 0.5.6 と新しい TorchAudio の組み合わせで発生する互換性エラーです。このスクリプトは旧 API の import に対する互換処理を内蔵し、実際の WAV 入出力には SoundFile を使用します。最新版のスクリプトを使用し、SoundFile が未導入の場合はインストールしてください。 + +```powershell +python -m pip install soundfile +``` + +### 入力ファイルが見つからない + +現在のディレクトリとファイル名を確認するか、引用符付きの絶対パスを指定してください。 + +```powershell +python deepfilternet3_enhance.py "D:\audio files\input.wav" "D:\audio files\output.wav" +``` + +### 処理が遅い、またはメモリ不足になる + +まず短い WAV で動作を確認してください。必要に応じて音声を複数ファイルへ分割して処理します。分割する場合は、つなぎ目で音が不自然にならないよう、前後に余裕を持たせてください。 + +### `CUDA available: False` と表示される + +次の点を順番に確認してください。 + +- `nvidia-smi` で NVIDIA GPU とドライバーが認識されているか +- CUDA 対応版の PyTorch をインストールした仮想環境が有効になっているか +- `python -m pip show torch` が、意図した仮想環境のパッケージを示しているか +- `torch.version.cuda` が `None` になっていないか。`None` の場合はCUDA対応ビルドがインストールされていません。 +- NVIDIA ドライバーが、選択した PyTorch CUDA ビルドに対応しているか + +CUDA対応ビルドが入っていない場合は、前述のWindowsまたはLinuxのセットアップ手順に従って `torch` と `torchaudio` を入れ直してください。 diff --git a/deepfilternet3_enhance.py b/deepfilternet3_enhance.py new file mode 100644 index 0000000..3dba690 --- /dev/null +++ b/deepfilternet3_enhance.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +""" +WAV音声をDeepFilterNet3で強調(ノイズ抑制)するスクリプト。 + +使用例: + python deepfilternet3_enhance.py input.wav output.wav + + # より強い抑制を試す(ポストフィルタ有効) + python deepfilternet3_enhance.py input.wav output_pf.wav --post-filter + + # 最大抑制量を12 dBに制限し、音声の変質を抑える + python deepfilternet3_enhance.py input.wav output_lim12.wav --atten-lim-db 12 + + # ステレオ等をモノラル化して処理 + python deepfilternet3_enhance.py input.wav output_mono.wav --mono +""" + +from __future__ import annotations + +import argparse +import sys +import time +import types +from pathlib import Path + +import soundfile as sf +import torch +import torchaudio +from torchaudio.functional import resample as torchaudio_resample + + +def install_deepfilternet_torchaudio_compat() -> None: + """DeepFilterNet 0.5.6の旧AudioMetaData importを現行TorchAudio上で通す。""" + module_name = "torchaudio.backend.common" + if module_name in sys.modules: + return + + try: + __import__(module_name) + return + except ModuleNotFoundError: + # df.ioはimport時の型注釈にだけAudioMetaDataを必要とします。 + # 実際の入出力は、このスクリプトのSoundFile実装を使用します。 + common_module = types.ModuleType(module_name) + common_module.AudioMetaData = object + backend_module = types.ModuleType("torchaudio.backend") + backend_module.common = common_module + sys.modules["torchaudio.backend"] = backend_module + sys.modules[module_name] = common_module + if not hasattr(torchaudio, "backend"): + torchaudio.backend = backend_module + + +install_deepfilternet_torchaudio_compat() + +from df.enhance import enhance, init_df + + +MODEL_NAME = "DeepFilterNet3" + + +def resample_audio(audio: torch.Tensor, original_sr: int, target_sr: int) -> torch.Tensor: + """高品質なsinc補間で音声をリサンプリングする。""" + if original_sr == target_sr: + return audio + return torchaudio_resample( + audio, + original_sr, + target_sr, + resampling_method="sinc_interp_kaiser", + lowpass_filter_width=64, + rolloff=0.9475937167399596, + beta=14.769656459379492, + ) + + +def load_wav(input_path: Path, target_sr: int) -> tuple[torch.Tensor, int]: + """SoundFileでWAVをfloat32の[C, T]テンソルとして読み込む。""" + samples, original_sr = sf.read( + str(input_path), + dtype="float32", + always_2d=True, + ) + audio = torch.from_numpy(samples.T.copy()) + audio = resample_audio(audio, int(original_sr), target_sr) + return audio.contiguous(), int(original_sr) + + +def save_wav(output_path: Path, audio: torch.Tensor, sample_rate: int) -> None: + """SoundFileで[C, T]テンソルをPCM16 WAVとして保存する。""" + samples = audio.detach().cpu().transpose(0, 1).contiguous().numpy() + sf.write( + str(output_path), + samples, + sample_rate, + format="WAV", + subtype="PCM_16", + ) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="DeepFilterNet3を用いてWAV音声の背景ノイズを抑制します。" + ) + parser.add_argument("input_wav", type=Path, help="入力WAVファイル") + parser.add_argument("output_wav", type=Path, help="出力WAVファイル") + parser.add_argument( + "--post-filter", + action="store_true", + help="ポストフィルタを有効化し、ノイズ抑制をやや強くします。", + ) + parser.add_argument( + "--atten-lim-db", + type=float, + default=None, + metavar="DB", + help=( + "最大ノイズ抑制量をdBで制限します。" + "例: 12を指定すると最大12 dBの抑制となります。" + "未指定時は制限しません。" + ), + ) + parser.add_argument( + "--mono", + action="store_true", + help="多チャンネル入力を平均してモノラル化してから処理します。", + ) + parser.add_argument( + "--model-rate-output", + action="store_true", + help=( + "出力をDeepFilterNet3のサンプリング周波数(通常48 kHz)で保存します。" + "未指定時は入力WAVのサンプリング周波数へ戻します。" + ), + ) + return parser.parse_args() + + +def initialize_deepfilternet3(post_filter: bool): + """ + DeepFilterNet 0.5.6(戻り値3個)と、mainブランチのAPI(戻り値4個)の + どちらでも動くよう、先頭2個だけを使用します。 + """ + init_result = init_df( + model_base_dir=MODEL_NAME, + post_filter=post_filter, + log_level="INFO", + log_file=None, + ) + model = init_result[0] + df_state = init_result[1] + return model, df_state + + +def enhance_wav( + input_path: Path, + output_path: Path, + *, + post_filter: bool, + atten_lim_db: float | None, + mono: bool, + model_rate_output: bool, +) -> None: + if not input_path.is_file(): + raise FileNotFoundError(f"入力ファイルが見つかりません: {input_path}") + if input_path.suffix.lower() != ".wav": + raise ValueError(f"入力はWAVファイルを指定してください: {input_path}") + if output_path.suffix.lower() != ".wav": + raise ValueError(f"出力拡張子は.wavにしてください: {output_path}") + if atten_lim_db is not None and atten_lim_db <= 0: + raise ValueError("--atten-lim-db は0より大きい値を指定してください。") + + output_path.parent.mkdir(parents=True, exist_ok=True) + + print(f"モデルを初期化しています: {MODEL_NAME}") + model, df_state = initialize_deepfilternet3(post_filter) + model_sr = int(df_state.sr()) + + print(f"入力WAVを読み込んでいます: {input_path}") + # DeepFilterNet3の処理周波数へ自動リサンプリングされます。 + audio, original_sr = load_wav(input_path, model_sr) + + if mono and audio.shape[0] > 1: + audio = audio.mean(dim=0, keepdim=True) + + channels = int(audio.shape[0]) + duration_sec = audio.shape[-1] / model_sr + print( + f"入力: {channels} ch, 元SR={original_sr} Hz, " + f"処理SR={model_sr} Hz, 長さ={duration_sec:.1f} 秒" + ) + + start = time.perf_counter() + enhanced = enhance( + model, + df_state, + audio, + pad=True, # STFT/ISTFTおよびモデルの遅延を補償 + atten_lim_db=atten_lim_db, + ) + elapsed = time.perf_counter() - start + rtf = elapsed / duration_sec if duration_sec > 0 else float("nan") + + enhanced = enhanced.detach().cpu() + + output_sr = model_sr if model_rate_output else original_sr + if output_sr != model_sr: + enhanced = resample_audio(enhanced, model_sr, output_sr) + + # PCM16保存時の整数オーバーフローを防止します。 + peak_before = float(enhanced.abs().max()) if enhanced.numel() else 0.0 + clip_limit = 1.0 - (1.0 / 32768.0) + enhanced = enhanced.clamp(min=-1.0, max=clip_limit) + + save_wav(output_path, enhanced, output_sr) + + print(f"出力: {output_path}") + print(f"処理時間: {elapsed:.2f} 秒") + print(f"Real-time factor: {rtf:.3f}(1未満なら実時間より高速)") + print(f"処理後ピーク値: {peak_before:.4f}") + if peak_before > 1.0: + print( + "注意: 処理後信号のピークが1.0を超えたため、" + "PCM16保存時にクリッピングしました。", + file=sys.stderr, + ) + + +def main() -> int: + args = parse_args() + try: + enhance_wav( + args.input_wav, + args.output_wav, + post_filter=args.post_filter, + atten_lim_db=args.atten_lim_db, + mono=args.mono, + model_rate_output=args.model_rate_output, + ) + return 0 + except Exception as exc: + print(f"エラー: {exc}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main())