見出し画像

音声スペクトログラム分析による類似音声検出システム in Python


概要

このツールは、2つのPythonスクリプトで構成されています。

  1. spectrogram-based_audio_similarity_analyzer.py
    オーディオファイルのスペクトログラムを基にした類似性分析を行います。

  2. wav_volume_based_trimmer_dir.py
    WAVファイルを音量に基づいてトリミングします。

用語説明

  • 参照ファイル: 比較の基準となる既知のオーディオファイル群。これらは reference_wav_files/original ディレクトリに配置します。

  • 対象ファイル: 参照ファイルと比較される、分析対象のオーディオファイル群。これらは target_wav_files/original ディレクトリに配置します。

機能

  • WAVファイルのスペクトログラムを生成し、特徴量として抽出

  • 複数のオーディオファイル間の類似性を比較

  • 音量に基づいてWAVファイルをトリミング

  • ディレクトリ単位でのバッチ処理

必要なライブラリ

  • numpy

  • librosa

  • scipy

  • os

使い方

  • 必要なライブラリをインストールします。

pip install numpy librosa scipy
  • 以下のディレクトリ構造を作成します。

.
├── reference_wav_files
│   ├── original  # 参照ファイルを配置するディレクトリ
│   └── trimmed  # トリミングされたファイルが保存されるディレクトリ
├── target_wav_files
│   ├── original  # 対象ファイルを配置するディレクトリ
│   └── trimmed  # トリミングされたファイルが保存されるディレクトリ
├── spectrogram-based_audio_similarity_analyzer.py
└── wav_volume_based_trimmer_dir.py
  • reference_wav_files/original と target_wav_files/original に分析したいWAVファイルを配置します。

  • spectrogram-based_audio_similarity_analyzer.py を実行します:

python spectrogram-based_audio_similarity_analyzer.py

スクリプトの詳細

1. spectrogram-based_audio_similarity_analyzer.py

このスクリプトは、オーディオファイルの類似性分析の中心的な役割を果たします。

import numpy as np
import librosa
import base64
from scipy.spatial.distance import cosine
import os
import wav_volume_based_trimmer_dir

def create_spectrogram_features(audio_file, n_mels=128, n_fft=2048, hop_length=512):
    y, sr = librosa.load(audio_file, offset=0, duration=1)
    spectrogram = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels, n_fft=n_fft, hop_length=hop_length)
    spectrogram_db = librosa.power_to_db(spectrogram, ref=np.max)
    return spectrogram_db

def features_to_feature_string(features):
    flattened = features.flatten()
    normalized = ((flattened - flattened.min()) / (flattened.max() - flattened.min()) * 255).astype(np.uint8)
    byte_data = normalized.tobytes()
    base64_str = base64.b64encode(byte_data).decode('utf-8')
    return base64_str

def audio_to_spectrogram_feature_string(audio_file):
    spectrogram = create_spectrogram_features(audio_file)
    feature_string = features_to_feature_string(spectrogram)
    return feature_string

def feature_string_to_features(feature_string):
    byte_data = base64.b64decode(feature_string)
    normalized = np.frombuffer(byte_data, dtype=np.uint8)
    features = normalized.astype(float) / 255
    return features

def compare_audio_features(feature_string1, feature_string2, threshold=0.1):
    features1 = feature_string_to_features(feature_string1)
    features2 = feature_string_to_features(feature_string2)
    similarity = 1 - cosine(features1, features2)
    return similarity >= 1 - threshold, similarity

def compare_with_reference_set(reference_files, target_file, threshold=0.1):
    target_feature = audio_to_spectrogram_feature_string(target_file)
    
    max_similarity = -1
    most_similar_file = None
    
    for ref_file in reference_files:
        ref_feature = audio_to_spectrogram_feature_string(ref_file)
        is_similar, similarity = compare_audio_features(ref_feature, target_feature, threshold)
        
        if similarity > max_similarity:
            max_similarity = similarity
            most_similar_file = ref_file
    
    is_similar = max_similarity >= (1 - threshold)
    return most_similar_file, max_similarity, is_similar

def batch_compare_with_reference_set(reference_dir, target_dir, threshold=0.1):
    reference_files = [os.path.join(reference_dir, f) for f in os.listdir(reference_dir) if f.endswith('.wav')]
    target_files = [os.path.join(target_dir, f) for f in os.listdir(target_dir) if f.endswith('.wav')]
    
    results = []
    
    for target_file in target_files:
        most_similar, similarity, is_similar = compare_with_reference_set(reference_files, target_file, threshold)
        results.append({
            'target_file': target_file,
            'most_similar_reference': most_similar,
            'similarity': similarity,
            'is_similar': is_similar
        })
    
    return results

if __name__ == "__main__":
    reference_dir = "./reference_wav_files"
    target_dir = "./target_wav_files"

    wav_volume_based_trimmer_dir.main(reference_dir)
    wav_volume_based_trimmer_dir.main(target_dir)
    
    trimmed_reference_dir = f"{reference_dir}/trimmed"
    trimmed_target_dir = f"{target_dir}/trimmed"
    
    results = batch_compare_with_reference_set(trimmed_reference_dir, trimmed_target_dir)
    
    if not results:
        print("---")
        print("比較が行われませんでした。両方のディレクトリに有効な.wavファイルがあるか確認してください。")
    else:
        for result in results:
            print("---")
            print(f"対象ファイル: {result['target_file']}")
            print(f"最も類似した参照ファイル: {result['most_similar_reference']}")
            print(f"類似度: {result['similarity']:.4f}")
            print(f"類似判定: {'類似' if result['is_similar'] else '非類似'}")

2. wav_volume_based_trimmer_dir.py

このスクリプトは、WAVファイルを音量に基づいてトリミングする機能を提供します。

import os
import wave
import numpy as np
from scipy.io import wavfile

def trim_audio(input_file, output_file, threshold=0.1, duration=1.0):
    # WAVファイルを読み込む
    try:
        rate, data = wavfile.read(input_file)
    except FileNotFoundError:
        print(f"エラー: 入力ファイル '{input_file}' が見つかりません。")
        return
    except:
        print(f"エラー: 入力ファイル '{input_file}' の読み込み中に問題が発生しました。")
        return

    # データを float32 型に変換し、正規化する
    data = data.astype(np.float32) / np.iinfo(data.dtype).max
    
    # ステレオの場合は、チャンネルの平均を取る
    if len(data.shape) > 1:
        data = np.mean(data, axis=1)
    
    # 閾値を超える最初のインデックスを見つける
    threshold_indices = np.where(np.abs(data) > threshold)[0]
    
    if len(threshold_indices) == 0:
        print(f"警告: 閾値 {threshold} を超える音量が見つかりませんでした。全体の最大音量: {np.max(np.abs(data))}")
        return
    
    threshold_index = threshold_indices[0]
    
    # 切り出し開始位置から指定された長さ分のデータを抽出
    end_index = min(threshold_index + int(rate * duration), len(data))
    trimmed_data = data[threshold_index:end_index]
    
    # 新しいWAVファイルとして保存
    trimmed_data = (trimmed_data * 32767).astype(np.int16)
    try:
        wavfile.write(output_file, rate, trimmed_data)
        print(f"音声を '{input_file}' から '{output_file}' に切り出しました。")
        print(f"切り出し開始位置: {threshold_index / rate:.2f} 秒")
    except:
        print(f"エラー: 出力ファイル '{output_file}' の書き込み中に問題が発生しました。")

def process_directory(input_dir, output_dir, threshold=0.1, duration=1.0):
    # 出力ディレクトリが存在しない場合は作成
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # 入力ディレクトリ内のすべてのWAVファイルを処理
    for filename in os.listdir(input_dir):
        if filename.lower().endswith('.wav'):
            input_path = os.path.join(input_dir, filename)
            output_filename = f"trimmed_{filename}"
            output_path = os.path.join(output_dir, output_filename)
            
            print(f"処理中: {filename}")
            trim_audio(input_path, output_path, threshold, duration)

def main(dir):
    input_directory = f"{dir}/original"
    output_directory = f"{dir}/trimmed"
    process_directory(input_directory, output_directory, threshold=0.1, duration=1.0)

処理の流れ

  1. wav_volume_based_trimmer_dir.py が呼び出され、参照ファイルと対象ファイルがトリミングされます。

  2. トリミングされたファイルは trimmed サブディレクトリに保存されます。

  3. トリミングされたファイルを使用して類似性分析が行われます。

  4. 各対象ファイルについて、最も類似した参照ファイル、類似度、類似判定結果が出力されます。

出力例

---
対象ファイル: ./target_wav_files/trimmed/trimmed_target1.wav
最も類似した参照ファイル: ./reference_wav_files/trimmed/trimmed_ref2.wav
類似度: 0.9876
類似判定: 類似

カスタマイズ

このツールには、パフォーマンスと結果を微調整するためのいくつかのパラメータがあります。主要なカスタマイズ可能なパラメータとその影響について説明します。

1. 類似性判定のしきい値 (threshold)

compare_audio_features 関数内の threshold パラメータ:

def compare_audio_features(feature_string1, feature_string2, threshold=0.1):
  • 目的: このパラメータは、二つのオーディオファイルが「類似している」と判断される最小の類似度を決定します。

  • 調整方法:

    • 値を小さくする(例:0.05)と、より厳密な類似性判定になります。

    • 値を大きくする(例:0.2)と、より寛容な類似性判定になります。

  • 影響: しきい値を調整することで、偽陽性(似ていないのに似ていると判断される)と偽陰性(似ているのに似ていないと判断される)のバランスを取ることができます。

2. スペクトログラム生成パラメータ

create_spectrogram_features 関数内のパラメータ:

def create_spectrogram_features(audio_file, n_mels=128, n_fft=2048, hop_length=512):
  • n_mels: メルフィルタバンクの数

    • 目的: スペクトログラムの周波数分解能を制御します。

    • 調整方法: 値を増やす(例:256)とより詳細な周波数情報が得られ、減らす(例:64)と粗い周波数情報になります。

    • 影響: 高い値はより詳細な分析を可能にしますが、計算コストが増加します。

  • n_fft: FFTウィンドウのサイズ

    • 目的: 時間-周波数分解能のトレードオフを制御します。

    • 調整方法: 値を増やす(例:4096)と周波数分解能が向上し、減らす(例:1024)と時間分解能が向上します。

    • 影響:

      • 大きな値は周波数の詳細な違いを捉えるのに適しています。例えば、異なる話者の声の特徴、環境音の微妙な違い、または音響的に複雑な信号の分析に有用です。

      • 一方、小さな値は時間的な変化をより正確に捉えるのに適しています。これは急激な音の変化、短い音声コマンド、または音の開始・終了時点の正確な検出などに有用です。

    • 用途例:

      • 話者識別: 大きな値を使用して、個々の話者の声の特徴をより細かく分析。

      • 環境音分類: 中程度の値を使用して、様々な環境音の特徴を均等に捉える。

      • 音声コマンド認識: 小さな値を使用して、短い音声コマンドの時間的特徴を正確に捉える。

  • hop_length: フレーム間の進み幅

    • 目的: スペクトログラムの時間方向の分解能を制御します。

    • 調整方法: 値を小さくする(例:256)とより細かい時間分解能が得られ、大きくする(例:1024)と粗い時間分解能になります。

    • 影響: 小さい値はより詳細な時間情報を捉えますが、計算コストと特徴量のサイズが増加します。

3. 音声トリミングパラメータ

trim_audio 関数内のパラメータ:

def trim_audio(input_file, output_file, threshold=0.1, duration=1.0):
  • threshold: トリミング開始点を決定する音量のしきい値

    • 目的: 無音部分を除去する際の感度を制御します。

    • 調整方法: 値を小さくする(例:0.05)とより小さい音でもトリミング開始点と判断し、大きくする(例:0.2)とより大きな音のみをトリミング開始点と判断します。

    • 影響: 背景ノイズの多い音声では大きめの値を、静かな環境の音声では小さめの値を選択すると良いでしょう。

  • duration: トリミングする音声の長さ(秒)

    • 目的: 分析に使用する音声の長さを決定します。

    • 調整方法: 値を増やす(例:2.0)とより長い音声を分析し、減らす(例:0.5)と短い音声を分析します。

    • 影響: 長い値はより多くの情報を含みますが、計算コストが増加します。短い音声の特徴を捉えたい場合は小さな値を選択します。

これらのパラメータを調整することで、様々な種類のオーディオデータや分析目的に合わせてツールの動作を最適化できます。最適な値は使用するオーディオデータの性質や求める結果の精度によって異なるため、実験的に調整することをおすすめします。

注意事項

  • 入力ファイルは WAV 形式である必要があります。

  • 処理時間はファイルサイズと数に応じて変動します。

  • 十分なディスク容量があることを確認してください。トリミングされたファイルが追加で生成されます。

このツールを使用することで、大量のオーディオファイルの中から類似したものを効率的に見つけ出すことができます。

いいなと思ったら応援しよう!