自己教師あり音声特徴量に対する線形回帰のみで音声変換を行うLinearVCを動かす

初めに

LinearVC は、線形回帰のみで音声変換(Voice Conversion)を行う手法です。 Interspeech 2025 に採択された論文の実装になります。

github.com

arxiv.org

処理パイプラインは以下の3ステップで構成されています。

  1. WavLM Large(レイヤー6)で自己教師あり特徴量を抽出
  2. ソース話者とターゲット話者の特徴量から線形回帰で射影行列を学習
  3. 特徴量を変換し HiFiGAN で波形を生成

開発環境

項目 詳細
OS Windows 11
CPU AMD Ryzen 9 7950X
GPU NVIDIA GeForce RTX 4090
メモリ 128GB
Python 3.13.8
CUDA 12.6
uv 0.6.x

環境構築

fork したリポジトリを clone します。

github.com

git clone https://github.com/ayutaz/linearvc.git
cd linearvc

pyproject.toml は以下の内容です。 PyTorch を CUDA 12.6 対応版でインストールするため、explicit = true で専用のインデックスを指定しています。

[project]
name = "linearvc"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.13"
dependencies = [
    "celer>=0.7.4",
    "ipython>=9.10.0",
    "jupyter>=1.1.1",
    "numpy>=2.4.2",
    "torch>=2.10.0",
    "torchaudio>=2.10.0",
    "tqdm>=4.67.3",
]

[[tool.uv.index]]
name = "pytorch-cu126"
url = "https://download.pytorch.org/whl/cu126"
explicit = true

[tool.uv.sources]
torch = [{ index = "pytorch-cu126" }]
torchaudio = [{ index = "pytorch-cu126" }]

依存パッケージをインストールします。

uv sync

データの準備

音声変換には、ソース話者(変換元)とターゲット話者(変換先)の音声データが必要です。 今回はソース話者に LibriSpeech dev-clean の話者 1272(英語男性)、ターゲット話者につくよみちゃんコーパス Vol.1(日本語女性)を使用します。

LibriSpeech dev-clean のダウンロード

mkdir -p data
cd data
wget https://www.openslr.org/resources/12/dev-clean.tar.gz
tar xzf dev-clean.tar.gz
cd ..

展開すると以下のようなディレクトリ構成になります。

data/LibriSpeech/dev-clean/
├── 1272/          # 話者ID
│   ├── 128104/    # チャプターID
│   │   ├── 1272-128104-0000.flac
│   │   ├── 1272-128104-0001.flac
│   │   └── ...
│   └── 135031/
│       └── ...
└── ...

サブセットの作成

linearvc.py--extension オプションはソースとターゲット両方に適用されるため、拡張子を .wav に統一します。 LibriSpeech の .flac.wav に変換し、それぞれ数ファイルずつのサブセットを作成します。

import torchaudio
from pathlib import Path

src_dir = Path("data/LibriSpeech/dev-clean/1272")
wav_dir = Path("data/subset_libri1272")
wav_dir.mkdir(exist_ok=True)

for flac_file in sorted(src_dir.rglob("*.flac"))[:3]:
    wav, sr = torchaudio.load(flac_file)
    out_path = wav_dir / flac_file.name.replace(".flac", ".wav")
    torchaudio.save(str(out_path), wav, sr)

つくよみちゃんコーパスも同様に数ファイルコピーします。

mkdir -p data/subset_tsukuyomi
cp path/to/つくよみちゃんコーパス/02\ WAV(+12dB増幅)/VOICEACTRESS100_00{1,2,3}.wav data/subset_tsukuyomi/

実行

linearvc.py を実行して音声変換を行います。 ソース話者のディレクトリ、ターゲット話者のディレクトリ、変換したい入力音声ファイル、出力ファイル名を指定します。

パターン1: LibriSpeech 1272 → つくよみちゃん(英語男性 → 日本語女性)

uv run python linearvc.py \
    data/subset_libri1272 \
    data/subset_tsukuyomi \
    data/subset_libri1272/1272-128104-0000.wav \
    output.wav

実行すると以下のようなログが出力されます。

Reading from: data\subset_libri1272
Reading from: data\subset_tsukuyomi
Reading: data\subset_libri1272\1272-128104-0000.wav
Source features:
100%|██████████| 3/3 [00:00<00:00, 14.13it/s]
Target features:
100%|██████████| 3/3 [00:00<00:00, 21.18it/s]
Writing: output.wav

パターン2: つくよみちゃん → LibriSpeech 1272(日本語女性 → 英語男性)

uv run python linearvc.py \
    data/subset_tsukuyomi \
    data/subset_libri1272 \
    data/subset_tsukuyomi/VOICEACTRESS100_001.wav \
    output_tsukuyomi2libri.wav
Reading from: data\subset_tsukuyomi
Reading from: data\subset_libri1272
Reading: data\subset_tsukuyomi\VOICEACTRESS100_001.wav
Source features:
100%|██████████| 3/3 [00:00<00:00, 20.83it/s]
Target features:
100%|██████████| 3/3 [00:00<00:00, 36.68it/s]
Writing: output_tsukuyomi2libri.wav

パターン3: LibriSpeech 1272 → LibriSpeech 1462(英語男性 → 英語女性)

uv run python linearvc.py \
    data/subset_libri1272 \
    data/subset_libri1462 \
    data/subset_libri1272/1272-128104-0000.wav \
    output_libri2libri.wav
Reading from: data\subset_libri1272
Reading from: data\subset_libri1462
Reading: data\subset_libri1272\1272-128104-0000.wav
Source features:
100%|██████████| 3/3 [00:00<00:00, 20.81it/s]
Target features:
100%|██████████| 3/3 [00:00<00:00, 20.43it/s]
Writing: output_libri2libri.wav

各パターンとも output.wav が生成されていれば成功です。

精度はあまりよくはなさそうです

参考

Roboflow の trackers ライブラリで YOLO + ByteTrack による動画マルチオブジェクト追跡をする

初めに

trackers は、Roboflow が提供するマルチオブジェクト追跡(MOTアルゴリズムクリーンルーム実装ライブラリです。Apache 2.0 ライセンスで公開されており、任意の検出モデルと組み合わせて使えます。

今回は YOLO で物体を検出し、ByteTrack で追跡、supervision で可視化する一連のパイプラインを動かします。ByteTrack は二段階アソシエーション(高信頼度 → 低信頼度の順でマッチング)を行うアルゴリズムで、低信頼度の検出も活用することでオクルージョンに強い追跡が可能です。

結果

入力

出力

開発環境

項目 バージョン
OS Windows 11
Python 3.13
パッケージマネージャ uv
trackers 2.2.0rc0
YOLO モデル yolo11m.pt (Ultralytics)

環境構築

リポジトリをクローンして依存パッケージをインストールします。

git clone https://github.com/roboflow/trackers.git
cd trackers
uv sync

trackers は ultralyticsopencv-python を依存に含んでいるため、追加インストールは不要です。

テスト用動画のダウンロード

supervision ライブラリの download_assets を使うとサンプル動画を簡単に取得できます。

from supervision.assets import download_assets, VideoAssets

video_path = download_assets(VideoAssets.PEOPLE_WALKING)

実行すると people-walking.mp4 がカレントディレクトリにダウンロードされます。

デモスクリプトの解説

スクリプトexamples/demo_bytetrack.py にあります。

引数

引数 デフォルト 説明
--source (必須) 入力動画のパス
--output output.mp4 出力動画のパス
--model yolo11m.pt YOLO モデル名またはパス

検出

YOLO で各フレームの物体を検出し、sv.Detections.from_ultralytics() で supervision の形式に変換します。

results = model(frame, verbose=False)[0]
detections = sv.Detections.from_ultralytics(results)

追跡

ByteTrackTracker().update(detections) を呼ぶだけでトラッカーID が付与されます。tracker_id-1 の場合は、まだ連続フレーム数の閾値(デフォルト2フレーム)に達していない未成熟なトラックです。

tracker = ByteTrackTracker()
detections = tracker.update(detections)

可視化

sv.BoxAnnotatorsv.LabelAnnotatorバウンディングボックスとラベルを描画します。

box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()

annotated = box_annotator.annotate(scene=frame.copy(), detections=detections)
annotated = label_annotator.annotate(
    scene=annotated, detections=detections, labels=labels
)

ソースコード全文

import argparse
import time

import cv2
import supervision as sv
from ultralytics import YOLO

from trackers import ByteTrackTracker


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="YOLO + ByteTrack object tracking demo"
    )
    parser.add_argument("--source", type=str, required=True)
    parser.add_argument("--output", type=str, default="output.mp4")
    parser.add_argument("--model", type=str, default="yolo11m.pt")
    return parser.parse_args()


def main() -> None:
    args = parse_args()

    model = YOLO(args.model)
    tracker = ByteTrackTracker()
    box_annotator = sv.BoxAnnotator()
    label_annotator = sv.LabelAnnotator()

    cap = cv2.VideoCapture(args.source)
    if not cap.isOpened():
        print(f"Error: cannot open video '{args.source}'")
        return

    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    writer = cv2.VideoWriter(args.output, fourcc, fps, (width, height))

    frame_idx = 0
    prev_time = time.time()

    print(f"Processing '{args.source}' ({total_frames} frames, {fps:.1f} FPS)")
    print(f"Output: '{args.output}'")
    print("Press 'q' to stop early.\n")

    while True:
        ret, frame = cap.read()
        if not ret:
            break

        results = model(frame, verbose=False)[0]
        detections = sv.Detections.from_ultralytics(results)
        detections = tracker.update(detections)

        tracker_ids = (
            detections.tracker_id
            if detections.tracker_id is not None
            else []
        )
        labels = [
            f"#{int(tid)}" if tid >= 0 else "?"
            for tid in tracker_ids
        ]

        annotated = box_annotator.annotate(
            scene=frame.copy(), detections=detections
        )
        annotated = label_annotator.annotate(
            scene=annotated, detections=detections, labels=labels
        )

        now = time.time()
        processing_fps = 1.0 / max(now - prev_time, 1e-9)
        prev_time = now

        frame_idx += 1
        n_tracks = (
            int((detections.tracker_id >= 0).sum())
            if detections.tracker_id is not None
            else 0
        )
        print(
            f"\rFrame {frame_idx}/{total_frames}"
            f"  FPS: {processing_fps:.1f}"
            f"  Tracks: {n_tracks}",
            end="", flush=True,
        )

        writer.write(annotated)
        cv2.imshow("ByteTrack Demo", annotated)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            print("\nStopped by user.")
            break

    cap.release()
    writer.release()
    cv2.destroyAllWindows()
    print(f"\n\nDone. Output saved to '{args.output}'")


if __name__ == "__main__":
    main()

実行

以下のコマンドで実行します。

uv run python examples/demo_bytetrack.py --source people-walking.mp4

ターミナルに処理状況が表示されます。

Processing 'people-walking.mp4' (210 frames, 30.0 FPS)
Output: 'output.mp4'
Press 'q' to stop early.

Frame 210/210  FPS: 15.2  Tracks: 7

ウィンドウが開いてリアルタイムで追跡結果が表示されます。途中で止めたい場合は q キーを押してください。完了すると output.mp4 が出力されます。

JAXA Earth APIとGradioで衛星データの取得・可視化アプリを作る

初めに

JAXA Earth API は、JAXA保有するCOG/STAC形式の地球観測衛星データにPythonからアクセスできるライブラリです。降水量、標高、植生指数、海面水温など多様な衛星データをプログラムから取得・処理・可視化できます。

今回、このAPIを使って衛星データを手軽に閲覧できるGradioアプリを作りました。以下の5つのタブで構成されています。

  1. コレクション検索 - 利用可能なデータセットの検索
  2. 衛星画像ビューア - 衛星画像の取得・表示
  3. 時系列解析 - 複数日の統計処理
  4. 差分解析 - 2時期のデータ比較
  5. マスキング - マスクデータによるフィルタリング

GitHubリポジトリは以下で公開しています。

github.com

開発環境

項目 バージョン
OS Windows 11
Python 3.13.8
uv 0.9.2
jaxa-earth 0.1.5
Gradio 6.5.1

環境構築

pyproject.tomlJAXA のパッケージインデックスを設定します。

[project]
name = "jaxa-earth-data"
version = "0.1.0"
description = "JAXA Earth Data Gradio Viewer"
requires-python = ">=3.13"
dependencies = [
    "gradio",
    "matplotlib",
    "pandas",
    "numpy",
    "jaxa-earth",
]

[tool.uv]
[[tool.uv.index]]
name = "jaxa"
url = "https://data.earth.jaxa.jp/api/python/repository/"

[tool.uv.sources]
jaxa-earth = { index = "jaxa" }

jaxa-earthPyPIではなくJAXA独自のリポジトリで配布されているため、[tool.uv] でインデックスを追加し、[tool.uv.sources] でパッケージの取得元を指定しています。

インストールは以下のコマンドで完了します。

uv sync

JAXA Earth API の基本的な使い方

JAXA Earth APIImageCollectionListImageCollectionImageProcess のパイプラインでデータを取得・処理します。

from jaxa.earth import je

# 1. コレクション検索
icl = je.ImageCollectionList()
collections, bands = icl.filter_name(keywords=["GSMaP"])

# 2. データ取得(メソッドチェーン順序は固定)
ic = je.ImageCollection(
    collection="JAXA.EORC_GSMaP_standard.Gauge.00Z-23Z.v6_daily"
)
ic.filter_date(dlim=["2021-07-01T00:00:00", "2021-07-03T00:00:00"])
ic.filter_resolution(ppu=4)
ic.filter_bounds(bbox=[130.0, 30.0, 145.0, 45.0])
ic.select(band="PRECIP")
ic.get_images()

# 3. 画像処理・可視化
ip = je.ImageProcess(data=ic)
ip.show_images(cmap="turbo", clim=[0, 20])

注意: ImageCollection のメソッド呼び出し順序は filter_datefilter_resolutionfilter_boundsselectget_images で固定です。順序を変えるとエラーになります。

主なパラメータは以下の通りです。

  • collection: データセットID(例: JAXA.EORC_GSMaP_standard.Gauge.00Z-23Z.v6_daily
  • ppu (Pixels Per Unit): 解像度を指定(値が大きいほど高解像度)
  • bbox: 取得範囲を [min_lon, min_lat, max_lon, max_lat] で指定
  • cmap: カラーマップ(turbo / ndvi / spectral

機能紹介

コレクション検索

キーワードを入力して利用可能なデータセットを検索できます。空欄で検索すると全コレクションを一覧表示します。

衛星画像ビューア

20種類以上のプリセットデータセットから選択し、衛星画像を取得・表示します。Collection ID、バンド、日時範囲、解像度、取得範囲などのパラメータを自由に変更可能です。

GSMaPの日次降水量データを取得した例です。日本周辺の降水量分布が可視化されています。

時系列解析

複数日のデータを取得し、平均・最大・最小・標準偏差・中央値の統計処理を実行します。統計画像と空間統計グラフを同時に表示し、統計データテーブルも確認できます。

降水量データの時系列平均を算出した例です。統計画像・空間統計グラフ・データテーブルが表示されます。

差分解析

2つの期間のデータを比較し、差分を可視化します。例えば、2020年と2021年の降水量の差分を確認する、といった使い方ができます。

2020年と2021年の夏季降水量(月次GSMaP)を比較した例です。期間1(参照)・期間2(比較)・差分の3つの画像が並んで表示されます。

マスキング

マスクデータを使ってデータをフィルタリングします。例えば、標高データにマスクを適用して特定の領域のみを表示できます。マスク手法は values_equal / range / bits_equal から選択可能です。

AW3D30標高データにマスクを適用した例です。マスク前後の画像を並べて比較できます。

実行

以下のコマンドでアプリを起動します。

uv run python app.py

ブラウザで http://localhost:7861 にアクセスするとアプリが表示されます。

参考

MaAI(間合い)は、リアルタイム・軽量な非言語行動生成ソフトウェア「MaAI」を使って、日本語音声ファイルからターンテイキング予測・相槌予測・うなずき予測を行う

初めに

MaAIはリアルタイム・連続的な非言語行動生成ソフトウェアです。音声対話システムやロボット向けに、ターンテイキング予測・相槌予測・うなずき予測・VADされている.

github.com

開発環境

環境構築

リポジトリのクローンします。

git clone https://github.com/MaAI-Kyoto/MaAI.git
cd MaAI

依存関係のインストールします。まずは先に pyproject.toml を以下のように作成をします

[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "maai"
dynamic = ["version"]
description = "Real-time and Continuous Non-Linguistic Behavior (Maai) Generation Software"
authors = [
  { name = "MaAI team", email = "inoue@sap.ist.i.kyoto-u.ac.jp" }
]
license = { file = "LICENSE" }

requires-python = ">=3.10"
dependencies = [
    "torch>=2.2.0",
    "numpy",
    "einops>=0.7.0",
    "soundfile",
    "pygame",
    "pydub",
    "pyaudio",
    "matplotlib",
    "seaborn",
    "fastapi>=0.111.0",
    "huggingface-hub",
]

[tool.hatch.version]
source = "vcs"

[tool.hatch.build]
sources = ["src"]

[tool.hatch.build.targets.sdist]
include = ["src/**"]

[tool.hatch.build.targets.wheel]
packages = ["src/maai"]

[tool.uv.workspace]
members = [
    ".",
]

[tool.uv.sources]
maai = { workspace = true, editable = true }

[dependency-groups]
dev = [
    "maai",
]

[project.urls]
Homepage = "https://github.com/MaAI-Kyoto/MaAI"

次に以下を実行して依存ライブラリのインストールします。

uv sync

uv sync により、pyproject.toml に記載された依存関係がすべてインストールされ、uv.lock が生成されます。仮想環境(.venv)も自動で作成されます。

実行

以下のコマンドでサンプルを実行し、動作を確認できます。

uv run python example/vap/vap_2wav.py

またGUI版を起動する場合は以下になります。

uv run python example/output/vap_2wav_GuiPlot.py

実行すると以下のようになります

LLM推論ベースのツリーインデックスRAG「PageIndex」でPDF/Markdownから階層構造を抽出する

初めに

github.com

PageIndexは、ベクトルDBやチャンキングを使わず、LLMの推論によって階層ツリーインデックスを構築するRAGシステムです。PDF/Markdownから目次のようなツリー構造を自動生成し、ツリー検索で関連ページを特定します。

従来のRAGはベクトル類似度検索に依存しますが、「類似度≠関連度」という問題があります。PageIndexはLLMの推論能力を活用し、人間の専門家がドキュメントをナビゲートするように関連箇所を特定します。

PageIndexの処理は大きく2ステップに分かれます。

  1. ツリーインデックス構築 — ドキュメントから階層ツリーを生成する
  2. ツリー検索 — 構築済みツリーをたどって関連ページを特定する

このリポジトリは①のツリーインデックス構築を担当します。本記事ではPDFとMarkdownそれぞれの構築方法を紹介します。

開発環境

環境構築

リポジトリをクローンします。

git clone https://github.com/VectifyAI/PageIndex.git
cd PageIndex

依存関係をインストールします。

uv add -r requirements.txt

.envファイルを作成してOpenAI APIキーを設定します。

CHATGPT_API_KEY=your_openai_key_here

PDFの処理

PDF処理では、LLMを複数段階で活用してツリーを構築します。

  1. PDFからテキストを抽出
  2. LLMで目次(TOC)ページを検出
  3. TOCJSON構造に変換(LLM)
  4. 各セクションにページ番号を割当(LLM)
  5. 割当結果を検証・修正(LLM)
  6. 各ノードのサマリを非同期並列生成(LLM)

各ノードは以下のような構造を持ちます。

{
  "title": "INTRODUCTION",
  "node_id": "0000",
  "start_index": 1,
  "end_index": 2,
  "summary": "This section introduces...",
  "nodes": []
}

start_index/end_indexがページ範囲、nodesが子ノードの配列です。

サンプルPDF(earthmover.pdf)でツリー構造を生成します。

python3 run_pageindex.py --pdf_path tests/pdfs/earthmover.pdf

results/earthmover_structure.jsonが生成されます。可視化スクリプトで確認すると以下のようなツリーが得られます。

[doc] earthmover.pdf  (19 nodes)

[0000] INTRODUCTION (p.1-2) [S]
[0001] PRELIMINARIES (p.2) [S]
├── [0002] Computing the EMD (p.3) [S]
└── [0003] Filter-and-Refinement Framework (p.3-4) [S]
[0004] SCALING UP SSP (p.4-5) [S]
[0005] BOOSTING THE REFINEMENT PHASE (p.5) [S]
├── [0006] Analysis of EMD Calculation (p.5-8) [S]
├── [0007] Progressive Bounding (p.8-6) [S]
├── [0008] Sensitivity to Refinement Order (p.6-9) [S]
├── [0009] Dynamic Refinement Ordering (p.9-7) [S]
└── [0010] Running Upper Bound (p.7-8) [S]
[0011] EXPERIMENTAL EVALUATION (p.8) [S]
├── [0012] Performance Improvement (p.8-10) [S]
├── [0013] Scalability Experiments (p.10-11) [S]
└── [0014] Parameter Tuning in DRO (p.11-12) [S]
[0015] RELATED WORK (p.12) [S]
[0016] CONCLUSION (p.12) [S]
[0017] ACKNOWLEDGMENT (p.12) [S]
[0018] REFERENCES (p.12) [S]

12ページのPDFから19ノードのツリーが構築され、各ノードにページ範囲とサマリ([S])が付与されています。

Markdownの処理

Markdownファイルでもツリー構造を生成できます。PDFとは異なり、ヘッダ階層(#, ##, ###...)から直接ツリーを構築するため、TOC検出やページ割当のステップは不要です。LLMはサマリ生成時のみ使用されます。

python3 run_pageindex.py --md_path tests/md/japanese_sample.md

Markdownの場合はヘッダ階層(#, ##, ###...)からツリーを構築します。

[doc] japanese_sample  (25 nodes)

[0000] Webアプリケーション開発ガイド (L1) [S]
├── [0001] 第1章 フロントエンド開発 (L5) [S]
│   ├── [0002] 1.1 HTML/CSSの基礎 (L9) [S]
│   └── [0003] 1.2 JavaScriptフレームワーク (L22) [S]
│       ├── [0004] 1.2.1 React (L26) [S]
│       ├── [0005] 1.2.2 Vue.js (L38) [S]
│       └── [0006] 1.2.3 パフォーマンス最適化 (L42) [S]
├── [0007] 第2章 バックエンド開発 (L51) [S]
│   ├── [0008] 2.1 API設計 (L55) [S]
│   │   ├── [0009] 2.1.1 RESTful API (L59) [S]
│   │   └── [0010] 2.1.2 GraphQL (L69) [S]
│   ├── [0011] 2.2 データベース設計 (L85) [S]
│   │   ├── [0012] 2.2.1 リレーショナルデータベース (L89) [S]
│   │   └── [0013] 2.2.2 NoSQLデータベース (L93) [S]
│   └── [0014] 2.3 認証・認可 (L97) [S]
├── [0015] 第3章 インフラストラクチャ (L106) [S]
│   ├── [0016] 3.1 コンテナ技術 (L110) [S]
│   │   ├── [0017] 3.1.1 Docker (L114) [S]
│   │   └── [0018] 3.1.2 Kubernetes (L128) [S]
│   ├── [0019] 3.2 CI/CDパイプライン (L132) [S]
│   └── [0020] 3.3 監視とオブザーバビリティ (L151) [S]
└── [0021] 第4章 開発プラクティス (L160) [S]
    ├── [0022] 4.1 テスト戦略 (L164) [S]
    ├── [0023] 4.2 コードレビュー (L174) [S]
    └── [0024] 4.3 アジャイル開発 (L185) [S]

Markdownのヘッダ構造がそのままツリーに反映され、25ノードが生成されています。PDFと異なりページ番号ではなく行番号(L)で位置が示されます。

主なオプション

run_pageindex.pyにはいくつかのオプションがあります。

  • --if-add-node-summary yes/no — 各ノードにLLM生成のサマリを付与(デフォルト: yes)
  • --if-add-doc-description yes/no — ドキュメント全体の説明文を生成(デフォルト: no)
  • --if-thinning yes/noMarkdown用。トークン数の少ないノードを親に統合してツリーを簡略化(デフォルト: no)
  • --model — 使用するOpenAIモデル(デフォルト: gpt-4o-2024-11-20)
  • --max-pages-per-node — PDF用。1ノードあたりの最大ページ数(デフォルト: 10)
  • --max-tokens-per-node — PDF用。1ノードあたりの最大トークン数(デフォルト: 20000)

MegaTTS3の英語推論環境をWindowsで構築してGradioから音声合成をする

初めに

bytedanceから英語・中国語に対応したTTSモデルが公開されたので動かしてみます

github.com

開発環境

重要な制約

  • WeTextProcessingpyniniWindows でのビルドが難しく、uv add だけでは導入できません。
  • そのため、この構成では 英語のみ で推論します。
  • 中国語を入力すると tts/infer_cli.py が例外を出します。

環境構築

uvを使って以下の手順で環境構築を進めていきます

uv init --bare --name megatts3 --python 3.10

Windows 用の依存リスト ( requirements.windows.txt ) を用意します

torch==2.6.0
torchaudio==2.6.0
numpy<2
attrdict==2.0.1
librosa==0.10.2.post1
langdetect==1.0.9
pydub==0.25.1
pyloudnorm==0.1.1
modelscope==1.22.2
transformers>=4.41.2,<=4.49.0,!=4.46.*,!=4.47.*,!=4.48.*;python_version<'3.10'
transformers>=4.41.2,<=4.49.0,!=4.46.*,!=4.47.*,!=4.48.0;python_version>='3.10'
x-transformers==1.44.4
torchdiffeq==0.2.5
openai-whisper==20240930
httpx==0.28.1
gradio==5.23.1

依存のインストールします

uv add -r requirements.windows.txt -p 3.10

この実行で以下が作成/更新されます

  • .venv
  • pyproject.toml
  • uv.lock

実行

CLIで実行する場合は以下で実行します

uv run python tts/infer_cli.py --input_wav "assets/English_prompt.wav" --input_text "Your English text here." --output_dir ./gen

Gradio UIは以下で起動します.

uv run python tts/gradio_api.py

音声とテキストから発話の時間境界付きの Praat TextGrid を生成する「Wav2TextGrid」を英語音声で試してみる

初めに

まだ試験的ですが以下のライブラリが出てきていたので触ってみます

github.com

対応言語は英語のみのため、日本語を使いたい場合は自前で学習する必要があります。

モデル/アーキテクチャ

  • Wav2Vec2 によるフレームレベルの音素予測
  • 強制アラインについては、予測された音素 posterior を、Viterbi decodingで転写音素列に整列して時間境界を得る

開発環境

環境構築

uvが入っている前提で以下を実行します

uv sync

実行

サンプルデータを使って実行してみます

uv run python scripts\\run_inference_workflow.py --examples-dir examples --output-dir outputs

出力結果 (0.wav)は以下のようになります

File type = "ooTextFile"
Object class = "TextGrid"

xmin = 0 
xmax = 2.37 
tiers? <exists> 
size = 2 
item []: 
    item [1]:
        class = "IntervalTier" 
        name = "phones" 
        xmin = 0 
        xmax = 2.37 
        intervals: size = 8 
        intervals [1]:
            xmin = 0 
            xmax = 0.42 
            text = "[SIL]" 
        intervals [2]:
            xmin = 0.42 
            xmax = 0.57 
            text = "B" 
        intervals [3]:
            xmin = 0.57 
            xmax = 1.37 
            text = "ER" 
        intervals [4]:
            xmin = 1.37 
            xmax = 1.43 
            text = "D" 
        intervals [5]:
            xmin = 1.43 
            xmax = 1.51 
            text = "HH" 
        intervals [6]:
            xmin = 1.51 
            xmax = 1.73 
            text = "AW" 
        intervals [7]:
            xmin = 1.73 
            xmax = 2.05 
            text = "S" 
        intervals [8]:
            xmin = 2.05 
            xmax = 2.37 
            text = "[SIL]" 
    item [2]:
        class = "IntervalTier" 
        name = "words" 
        xmin = 0 
        xmax = 2.37 
        intervals: size = 3 
        intervals [1]:
            xmin = 0 
            xmax = 0.42 
            text = "[SIL]" 
        intervals [2]:
            xmin = 0.42 
            xmax = 2.05 
            text = "birdhouse" 
        intervals [3]:
            xmin = 2.05 
            xmax = 2.37 
            text = "[SIL]"