ASCII倶楽部

このページの本文へ

週替わりギークス 第284回

イケボからイケメンを生成する研究

2023年08月26日 07時00分更新

文● 高桑蘭佳(らんらん) 編集● ASCII

  • この記事をはてなブックマークに追加
  • 本文印刷

 メンヘラテクノロジーの高桑蘭佳です。少し前からこの連載でも話題に出ていますが、最近メンヘラテクノロジーでは「DIALS2」という新しいサービスの開発を頑張っています。

 イケメンキャストと通話ができるアプリなのですが、世界観を統一するためイケメンアイコンは画像生成AI「Stable Diffusion」を使っています。いまはα版をクローズドにトライアル運用している状態なので、活動してくれることになったキャストの方は大量にあるイケメンアイコンの中から自分の雰囲気に合うものを選択してもらっています。

 しかし、この「自分の声の印象に合った顔を選ぶ」というのが意外と難しい……。漫画がアニメ化したときどの声優さんが担当するかでイメージと合っていた、違っていたという議論になるように、なんでもいいというわけではありません。

 調べてみると、2019年にMITコンピュータ科学・人工知能研究所が発表した論文で、声と顔の相関関係を学習し、話者の身体的特徴を捉えた画像を生成する「Speech2Face」というモデルが開発されていました。イケメンアイコンの場合、実在する人物ではないので少し違いますが、声の雰囲気にあったイケメンアイコンが生成できたらいいな〜と思いました。とはいえ、学部時代のフーリエ変換の授業が苦痛すぎたことをきっかけに、これまであまり音声の領域には触れてきませんでした。

 なので、今回は基本的な音声データの扱い方から勉強して、将来的にイケボからイケメンアイコン生成ができることを目標に、いろいろ試してみようと思います。

声をいくつかのカテゴリに分類する基準を設定

 まずはPythonで音声分析に使えそうなライブラリを調べてみると、候補としてLibROSAが挙がりました。LibROSAは音楽およびオーディオ分析用のPythonパッケージで、音声の特徴量抽出などができるようです。

 MFCCやピッチ、スペクトル対比、RMSE……あたりが人間の音声の分析に使えそう?な雰囲気がありましたが、いずれも理解が及ばなすぎて心折れそう。今回は声からなにかしらのイケメンアイコンをとりあえず生成することに重きをおきます。なので、このあたりの正確性については目を瞑っていただいて、ピッチ(声の高さ)とスペクトル対比(声の明瞭さ?)をカテゴライズするために使用したいと思います。解釈があっているのかはさておき、ピッチ、スペクトル対比の高低を基準に以下の4つのカテゴリに分類します。

  1. 高めで、ハスキーな声:穏やかな雰囲気
  2. 高めで、クリアな声:明るい雰囲気
  3. 低めで、クリアな声:力強い雰囲気
  4. 低めで、ハスキーな声:落ち着いた雰囲気

 次に、ピッチとスペクトル対比の高い・低いの閾値をどこに設定するか考えます。今回はイケメンアイコンなので、日本人男性の声の平均的なラインがどのあたりなのか知りたい。調べてみると、「Common Voice」という音声データのデータセットの収集・公開をしているプロジェクトがありました。日本人男性の音声データもたくさん公開されていたので、今回は簡易的に100件程度ランダムに取得し、ピッチとスペクトル対比の平均値を算出しました。


import os
import librosa
import numpy as np
import pandas as pd

def compute_features(audio_path):
    # 音声ファイルから主要なピッチと平均スペクトラル対比を計算
    y, sr = librosa.load(audio_path, sr=None)
    
    # YINアルゴリズムを使用してピッチを検出
    pitches = librosa.yin(y, fmin=80, fmax=400)
    
    # NaN値や無限大の値を除去するためのフィルタリング
    valid_pitches = pitches[~np.isnan(pitches)]
    
    main_pitch = np.mean(valid_pitches) if len(valid_pitches) > 0 else 0
    
    spectral_contrasts = librosa.feature.spectral_contrast(y=y, sr=sr)
    avg_spectral_contrast = np.mean(spectral_contrasts)
    return main_pitch, avg_spectral_contrast

def calculate_means(csv_path, base_directory=''):
    # CSVファイルにリストされた音声ファイルから、ピッチとスペクトラル対比の平均値を計算    data_frame = pd.read_csv(csv_path)
    all_pitches = []
    all_spectral_contrasts = []
    for audio_path in data_frame['path']:
        full_audio_path = os.path.join(base_directory, audio_path)
        pitch, spectral_contrast = compute_features(full_audio_path)
        all_pitches.append(pitch)
        all_spectral_contrasts.append(spectral_contrast)
    average_pitch = np.mean(all_pitches)
    average_contrast = np.mean(all_spectral_contrasts)
    return average_pitch, average_contrast

if __name__ == "__main__":
    # サンプルのCSVファイルとベースディレクトリを設定
    csv_file_path = '(サンプルのCSVファイル)'
    base_directory = '(音声ファイルのベースディレクトリ)'
    
    # ピッチとスペクトラル対比の平均値を計算
    avg_pitch, avg_contrast = calculate_means(csv_file_path, base_directory)
    
    # 結果を表示
    print(f"Average Pitch: {avg_pitch}")
    print(f"Average Contrast: {avg_contrast}")

 実行結果は以下となり、たぶんそれっぽい感じになりました!

Average Pitch: 152.73590839549504
Average Contrast: 22.161220504432073

声がどのカテゴリに該当するかを算出

 この値を用いて、イケメンを生成したい声がどのカテゴリに該当するかを算出します。


import os
import librosa
import numpy as np
import pandas as pd

def compute_features(audio_path):
    # 音声ファイルから主要なピッチと平均スペクトラル対比を計算
    y, sr = librosa.load(audio_path, sr=None)
    
    # YINアルゴリズムを使用してピッチを検出
    pitches = librosa.yin(y, fmin=80, fmax=400)
    
    # NaN値や無限大の値を除去するためのフィルタリング
    valid_pitches = pitches[~np.isnan(pitches)]
    
    main_pitch = np.mean(valid_pitches) if len(valid_pitches) > 0 else 0
    
    spectral_contrasts = librosa.feature.spectral_contrast(y=y, sr=sr)
    avg_spectral_contrast = np.mean(spectral_contrasts)
    return main_pitch, avg_spectral_contrast

def categorize_audio_by_average(audio_path, average_pitch, average_contrast):
    # 平均値をもとに、音声のピッチとスペクトラル対比のカテゴリ化
    
    # 音声ファイルの特徴を計算
    pitch, spectral_contrast = compute_features(audio_path)
    
    # 平均ピッチをもとにカテゴリ化
    if pitch <= average_pitch:
        pitch_category = "low"
    else:
        pitch_category = "high"
        
    # 平均スペクトラルコントラストをもとにカテゴリ化
    if spectral_contrast <= average_contrast:
        contrast_category = "high"
    else:
        contrast_category = "low"
        
    return pitch_category, contrast_category

def main():
    # 生成したい音声ファイルのパス
    generate_audio_path = '(生成したい音声ファイルのパス)'
    
    # ピッチとスペクトラル対比の平均値
    avg_pitch = 152.73590839549504
    avg_contrast = 22.161220504432073
    
    # 音声ファイルをカテゴリ化
    pitch_category, contrast_category = categorize_audio_by_average(sample_audio_path, avg_pitch, avg_contrast)
    
    # 結果を表示
    print(f"Pitch Category: {pitch_category}")
    print(f"Contrast Category: {contrast_category}")

if __name__ == "__main__":
    main()

 各カテゴリにどんな音声ファイルが分類されるのかを調べてみた結果はGoogleドライブにアップロードしてあるので、聴き比べてみてください。

次回、音声の分析結果からイケメンアイコンを生成する方法を考える

 今回はここまでで音声のカテゴリに分類する方法を検討してみました。ピッチ、スペクトル対比の高低を基準に4つのカテゴリに分類をしたので、めちゃくちゃ安直にStable Diffusionで以下のプロンプトからイケメンを生成してみました。

  1. anime style, 2d, 1 male, solo, ikemen, (high-voice), (husky-voice)
  2. anime style, 2d, 1 male, solo, ikemen, (high-voice), (clear-voice)
  3. anime style, 2d, 1 male, solo, ikemen, (low-voice), (husky-voice)
  4. anime style, 2d, 1 male, solo, ikemen, (low-voice), (clear-voice)

 それぞれ数枚生成してみた結果が以下です。

 違いがあるような、ないような……声が低いとやや筋肉感強めになっている気もするし、クリアな声の方が顔が若干きりっとしているようなしていないような……?と、まだまだイケメン生成までの道のりは長いですが、次回は音声の分析結果からいい感じのプロンプトづくりに挑戦してみる予定です。

高桑蘭佳(たかくわらんか)

 1994年生まれ。石川県出身。東京工業大学大学院環境社会理工学院研究生。2018年8月にメンヘラテクノロジーを設立。彼氏を束縛したくて起業した大学院生として「アウト×デラックス」(フジテレビ系列)や、「指原莉乃&ブラマヨの恋するサイテー男総選挙」(AbemaTV)などに出演。

カテゴリートップへ

この連載の記事

週間ランキングTOP5

ASCII倶楽部会員によく見られてる記事はコレだ!

ASCII倶楽部の新着記事

会員専用動画の紹介も!