コンテンツにスキップ

Whisperローカル実装完全ガイド:CPUオンリーで実現する高精度音声認識

Badge

実現できること

  • 完全プライベート処理

    オープンソースのWhisperをローカル環境で実行、機密情報の漏洩ゼロ

  • CPU専用高速化

    faster-whisperでGPU不要、オリジナルより4倍高速な処理

  • Windows統合

    Win+H音声入力との連携で既存ワークフローに自然統合

  • リアルタイム処理

    2025年最新のlarge-v3-turboで3倍高速、実用レベルの応答速度

📖 Overview

2025年、OpenAI Whisperのローカル実装は革命的な進化を遂げています。従来GPUが必須だった高精度音声認識が、CPUオンリーで実用レベルに到達しました。

特に注目すべきは「faster-whisper」と「large-v3-turbo」モデルの登場です。これらの技術により、プライバシーを完全に保護しながら、商用レベルの音声認識をローカル環境で実現できます。

本記事では、Whisperをローカル環境でCPU処理により実装し、Windows標準の音声入力機能と連携させる完全な手順を解説します。

🔧 Implementation

Step 1: 環境構築

Python環境の準備

# Python仮想環境の作成
python -m venv whisper-local
whisper-local\Scripts\activate.bat  # Windows

# 必要パッケージのインストール
pip install faster-whisper
pip install numpy==1.26.4  # 互換性確保
pip install pyaudio
pip install keyboard  # キーボードフック用
pip install pywin32   # Windows API用

FFmpegのインストール

# Chocolateyを使用(推奨)
choco install ffmpeg

# または手動インストール
# 1. https://github.com/BtbN/FFmpeg-Builds/releases からダウンロード
# 2. C:\ffmpeg に展開
# 3. 環境変数Pathに C:\ffmpeg\bin を追加

Step 2: Whisperローカル実装の基本

基本的な音声認識

# whisper_basic.py
from faster_whisper import WhisperModel
import warnings
warnings.filterwarnings("ignore")

class LocalWhisper:
    def __init__(self, model_size="base", device="cpu", compute_type="int8"):
        """
        Whisperモデルの初期化

        Args:
            model_size: "tiny", "base", "small", "medium", "large-v3", "large-v3-turbo"
            device: "cpu" (GPU不使用)
            compute_type: "int8" (CPU最適化)
        """
        print(f"Whisperモデル読み込み中: {model_size}")
        self.model = WhisperModel(
            model_size, 
            device=device, 
            compute_type=compute_type,
            cpu_threads=4  # CPUコア数に応じて調整
        )
        print("モデル読み込み完了")

    def transcribe_file(self, audio_path, language="ja"):
        """音声ファイルの文字起こし"""
        try:
            segments, info = self.model.transcribe(
                audio_path,
                language=language,
                beam_size=1,  # CPU処理時は1が最適
                temperature=0.0,
                vad_filter=True,  # 無音部分除去
                vad_parameters=dict(
                    min_silence_duration_ms=1000,
                    speech_pad_ms=400
                )
            )

            # 結果をテキストとして結合
            text = ""
            for segment in segments:
                text += segment.text

            return text.strip()

        except Exception as e:
            print(f"エラー: {e}")
            return None

# 使用例
if __name__ == "__main__":
    whisper = LocalWhisper(model_size="base")
    result = whisper.transcribe_file("test_audio.wav")
    print(f"認識結果: {result}")

リアルタイム音声認識

# whisper_realtime.py
import pyaudio
import wave
import threading
import queue
import time
from faster_whisper import WhisperModel

class RealtimeWhisper:
    def __init__(self, model_size="base"):
        # 音声設定
        self.CHUNK = 1024
        self.FORMAT = pyaudio.paInt16
        self.CHANNELS = 1
        self.RATE = 16000
        self.RECORD_SECONDS = 3  # 3秒チャンクで処理

        # Whisperモデル
        self.model = WhisperModel(model_size, device="cpu", compute_type="int8")

        # PyAudio初期化
        self.audio = pyaudio.PyAudio()

        # キューとフラグ
        self.audio_queue = queue.Queue()
        self.is_recording = False

    def record_audio(self):
        """音声録音スレッド"""
        stream = self.audio.open(
            format=self.FORMAT,
            channels=self.CHANNELS,
            rate=self.RATE,
            input=True,
            frames_per_buffer=self.CHUNK
        )

        print("録音開始... (Ctrl+Cで停止)")

        while self.is_recording:
            frames = []
            for _ in range(int(self.RATE / self.CHUNK * self.RECORD_SECONDS)):
                if not self.is_recording:
                    break
                data = stream.read(self.CHUNK)
                frames.append(data)

            if frames:
                # 音声データをキューに追加
                audio_data = b''.join(frames)
                self.audio_queue.put(audio_data)

        stream.stop_stream()
        stream.close()

    def process_audio(self):
        """音声処理スレッド"""
        while self.is_recording:
            try:
                # キューから音声データ取得(タイムアウト付き)
                audio_data = self.audio_queue.get(timeout=1)

                # 一時ファイルに保存
                temp_file = "temp_audio.wav"
                with wave.open(temp_file, 'wb') as wf:
                    wf.setnchannels(self.CHANNELS)
                    wf.setsampwidth(self.audio.get_sample_size(self.FORMAT))
                    wf.setframerate(self.RATE)
                    wf.writeframes(audio_data)

                # Whisperで認識
                segments, _ = self.model.transcribe(
                    temp_file,
                    language="ja",
                    beam_size=1,
                    vad_filter=True
                )

                # 結果出力
                text = ""
                for segment in segments:
                    text += segment.text

                if text.strip():
                    print(f"認識結果: {text.strip()}")

            except queue.Empty:
                continue
            except Exception as e:
                print(f"処理エラー: {e}")

    def start(self):
        """リアルタイム認識開始"""
        self.is_recording = True

        # 録音と処理を並列実行
        record_thread = threading.Thread(target=self.record_audio)
        process_thread = threading.Thread(target=self.process_audio)

        record_thread.start()
        process_thread.start()

        try:
            record_thread.join()
            process_thread.join()
        except KeyboardInterrupt:
            print("\n停止中...")
            self.is_recording = False

    def __del__(self):
        if hasattr(self, 'audio'):
            self.audio.terminate()

# 使用例
if __name__ == "__main__":
    whisper_rt = RealtimeWhisper(model_size="base")
    whisper_rt.start()

Step 3: Windows音声入力統合

Win+Hフック実装

# windows_voice_integration.py
import keyboard
import time
import subprocess
import win32clipboard
import win32con
from faster_whisper import WhisperModel
import pyaudio
import wave
import threading

class WindowsVoiceIntegration:
    def __init__(self, model_size="base"):
        self.whisper = WhisperModel(model_size, device="cpu", compute_type="int8")
        self.is_recording = False
        self.audio_config = {
            'chunk': 1024,
            'format': pyaudio.paInt16,
            'channels': 1,
            'rate': 16000
        }
        self.audio = pyaudio.PyAudio()

        print("Windows音声入力統合システム初期化完了")
        print("使用方法:")
        print("  Ctrl+Shift+V: カスタム音声入力開始")
        print("  Win+H: 標準音声入力(置き換え)")

    def record_and_transcribe(self, duration=5):
        """音声録音と文字起こし"""
        print("録音開始...")

        stream = self.audio.open(
            format=self.audio_config['format'],
            channels=self.audio_config['channels'],
            rate=self.audio_config['rate'],
            input=True,
            frames_per_buffer=self.audio_config['chunk']
        )

        frames = []
        for _ in range(int(self.audio_config['rate'] / self.audio_config['chunk'] * duration)):
            data = stream.read(self.audio_config['chunk'])
            frames.append(data)

        stream.stop_stream()
        stream.close()

        # WAVファイルに保存
        temp_file = "voice_input.wav"
        with wave.open(temp_file, 'wb') as wf:
            wf.setnchannels(self.audio_config['channels'])
            wf.setsampwidth(self.audio.get_sample_size(self.audio_config['format']))
            wf.setframerate(self.audio_config['rate'])
            wf.writeframes(b''.join(frames))

        print("音声認識中...")

        # Whisperで文字起こし
        segments, _ = self.whisper.transcribe(
            temp_file,
            language="ja",
            beam_size=1,
            vad_filter=True,
            temperature=0.0
        )

        text = ""
        for segment in segments:
            text += segment.text

        return text.strip()

    def set_clipboard_text(self, text):
        """クリップボードにテキスト設定"""
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardText(text, win32con.CF_UNICODETEXT)
        win32clipboard.CloseClipboard()

    def simulate_paste(self):
        """Ctrl+Vでペースト実行"""
        time.sleep(0.1)
        keyboard.send('ctrl+v')

    def custom_voice_input(self):
        """カスタム音声入力処理"""
        try:
            text = self.record_and_transcribe(duration=5)
            if text:
                print(f"認識結果: {text}")
                self.set_clipboard_text(text)
                self.simulate_paste()
                print("テキスト入力完了")
            else:
                print("音声を認識できませんでした")
        except Exception as e:
            print(f"エラー: {e}")

    def windows_h_hook(self):
        """Win+H置き換え処理"""
        # 標準のWin+Hを無効化し、カスタム処理を実行
        print("Win+H が押されました - カスタム音声入力を開始")
        self.custom_voice_input()

    def start_monitoring(self):
        """キーボード監視開始"""
        print("キーボード監視開始...")

        # カスタムホットキー: Ctrl+Shift+V
        keyboard.add_hotkey('ctrl+shift+v', self.custom_voice_input)

        # Win+H置き換え(注意: 管理者権限が必要)
        try:
            keyboard.add_hotkey('win+h', self.windows_h_hook)
            print("Win+H フック設定完了")
        except:
            print("Win+H フック設定失敗(管理者権限が必要)")
            print("代替: Ctrl+Shift+V を使用してください")

        print("監視中... (Ctrl+C で終了)")
        keyboard.wait()

# 使用例
if __name__ == "__main__":
    try:
        integration = WindowsVoiceIntegration(model_size="base")
        integration.start_monitoring()
    except KeyboardInterrupt:
        print("\nシステム終了")

Step 4: 最適化とパフォーマンス調整

CPU最適化設定

# performance_optimizer.py
import os
import psutil
from faster_whisper import WhisperModel

class WhisperOptimizer:
    def __init__(self):
        self.cpu_count = psutil.cpu_count()
        self.memory_gb = psutil.virtual_memory().total / (1024**3)

    def get_optimal_settings(self):
        """システムに最適な設定を提案"""

        # CPUコア数に基づくスレッド数
        if self.cpu_count >= 8:
            cpu_threads = 6
            model_size = "medium"
        elif self.cpu_count >= 4:
            cpu_threads = 4
            model_size = "base"
        else:
            cpu_threads = 2
            model_size = "tiny"

        # メモリに基づくモデル選択
        if self.memory_gb < 4:
            model_size = "tiny"
            compute_type = "int8"
        elif self.memory_gb < 8:
            model_size = "base"
            compute_type = "int8"
        else:
            compute_type = "float16"

        return {
            'model_size': model_size,
            'cpu_threads': cpu_threads,
            'compute_type': compute_type,
            'beam_size': 1,  # CPU処理時は1が最適
            'batch_size': 1
        }

    def create_optimized_model(self):
        """最適化されたWhisperモデルを作成"""
        settings = self.get_optimal_settings()

        print(f"システム情報:")
        print(f"  CPU: {self.cpu_count}コア")
        print(f"  メモリ: {self.memory_gb:.1f}GB")
        print(f"推奨設定:")
        print(f"  モデル: {settings['model_size']}")
        print(f"  スレッド: {settings['cpu_threads']}")
        print(f"  計算タイプ: {settings['compute_type']}")

        return WhisperModel(
            settings['model_size'],
            device="cpu",
            compute_type=settings['compute_type'],
            cpu_threads=settings['cpu_threads']
        ), settings

# ベンチマークテスト
def benchmark_models():
    """各モデルの性能テスト"""
    import time

    models = ["tiny", "base", "small"]
    test_file = "test_audio.wav"  # テスト用音声ファイル

    for model_name in models:
        print(f"\n{model_name}モデルテスト中...")

        start_time = time.time()
        model = WhisperModel(model_name, device="cpu", compute_type="int8")
        load_time = time.time() - start_time

        start_time = time.time()
        segments, _ = model.transcribe(test_file, beam_size=1)
        transcribe_time = time.time() - start_time

        print(f"  読み込み時間: {load_time:.2f}秒")
        print(f"  認識時間: {transcribe_time:.2f}秒")

if __name__ == "__main__":
    optimizer = WhisperOptimizer()
    model, settings = optimizer.create_optimized_model()
    print("\n最適化モデル作成完了")

💡 Best Practices

1. モデル選択の指針

tiny (39MB): - 用途: リアルタイム処理、低スペックPC - 精度: 基本レベル(会話の大まかな内容把握) - 速度: 最高速(実時間の0.1倍)

base (74MB): - 用途: 一般的な音声認識、バランス重視 - 精度: 実用レベル(日常会話、会議録音) - 速度: 高速(実時間の0.16倍)

large-v3-turbo (809MB): - 用途: 高精度が必要な文字起こし - 精度: 最高レベル(94%の日本語認識精度) - 速度: medium相当の速度でlarge相当の精度

2. CPU最適化テクニック

# CPU最適化の実装例
import os

# OpenMPスレッド数を制限(CPUコア数の75%)
os.environ["OMP_NUM_THREADS"] = str(max(1, psutil.cpu_count() * 3 // 4))

# NumPyのスレッド数制限
os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"

# faster-whisperの最適設定
model = WhisperModel(
    "base",
    device="cpu",
    compute_type="int8",  # メモリ使用量削減
    cpu_threads=4,       # CPUコア数に応じて調整
    num_workers=1        # 並列処理数
)

3. メモリ使用量削減

# メモリ効率化の設定
def create_memory_efficient_model():
    return WhisperModel(
        "base",
        device="cpu",
        compute_type="int8",      # FP16→INT8で大幅削減
        download_root="./models", # モデル保存場所指定
        local_files_only=False
    )

# ガベージコレクション明示的実行
import gc
gc.collect()

🚀 Advanced Usage

プログラマブル音声コマンド

# voice_commands.py
import re
from faster_whisper import WhisperModel

class VoiceCommandProcessor:
    def __init__(self):
        self.model = WhisperModel("base", device="cpu", compute_type="int8")
        self.commands = {
            r"ファイルを開く": self.open_file,
            r"新しいファイル": self.new_file,
            r"保存": self.save_file,
            r"コピー": self.copy_text,
            r"ペースト": self.paste_text,
            r"検索.*": self.search_text
        }

    def process_command(self, audio_file):
        """音声コマンドの処理"""
        # 音声認識
        segments, _ = self.model.transcribe(audio_file, language="ja")
        text = "".join([segment.text for segment in segments]).strip()

        print(f"認識されたコマンド: {text}")

        # コマンドマッチング
        for pattern, action in self.commands.items():
            if re.match(pattern, text):
                action(text)
                return True

        # コマンドが見つからない場合はテキスト入力
        self.input_text(text)
        return False

    def open_file(self, text):
        print("ファイルを開きます")
        keyboard.send('ctrl+o')

    def new_file(self, text):
        print("新しいファイルを作成します")
        keyboard.send('ctrl+n')

    def save_file(self, text):
        print("ファイルを保存します")
        keyboard.send('ctrl+s')

    def copy_text(self, text):
        print("テキストをコピーします")
        keyboard.send('ctrl+c')

    def paste_text(self, text):
        print("テキストをペーストします")
        keyboard.send('ctrl+v')

    def search_text(self, text):
        # "検索 キーワード" からキーワードを抽出
        keyword = text.replace("検索", "").strip()
        print(f"'{keyword}'を検索します")
        keyboard.send('ctrl+f')
        time.sleep(0.1)
        keyboard.write(keyword)

    def input_text(self, text):
        """通常のテキスト入力"""
        print(f"テキスト入力: {text}")
        keyboard.write(text)

バッチ処理と自動化

# batch_processor.py
import os
import glob
from concurrent.futures import ThreadPoolExecutor
from faster_whisper import WhisperModel

class BatchWhisperProcessor:
    def __init__(self, model_size="base", max_workers=2):
        self.model = WhisperModel(model_size, device="cpu", compute_type="int8")
        self.max_workers = max_workers

    def process_file(self, audio_file):
        """単一ファイルの処理"""
        try:
            print(f"処理中: {audio_file}")
            segments, info = self.model.transcribe(
                audio_file,
                language="ja",
                beam_size=1,
                vad_filter=True
            )

            # 結果をテキストファイルに保存
            text = "".join([segment.text for segment in segments])
            output_file = os.path.splitext(audio_file)[0] + "_transcript.txt"

            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(text)

            print(f"完了: {output_file}")
            return output_file

        except Exception as e:
            print(f"エラー ({audio_file}): {e}")
            return None

    def process_directory(self, directory, pattern="*.wav"):
        """ディレクトリ内の音声ファイルを一括処理"""
        audio_files = glob.glob(os.path.join(directory, pattern))
        print(f"処理対象: {len(audio_files)}ファイル")

        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            results = list(executor.map(self.process_file, audio_files))

        successful = [r for r in results if r is not None]
        print(f"処理完了: {len(successful)}/{len(audio_files)}ファイル")

        return successful

# 使用例
if __name__ == "__main__":
    processor = BatchWhisperProcessor(model_size="base", max_workers=2)
    processor.process_directory("./audio_files", "*.wav")

⚠️ Troubleshooting

よくある問題と解決策

問題1: NumPy互換性エラー

# 解決策
pip uninstall numpy
pip install numpy==1.26.4

問題2: FFmpeg not found

# Windows環境での解決
# 1. FFmpegバイナリをダウンロード
# 2. 環境変数Pathに追加
# 3. コマンドプロンプトで確認
ffmpeg -version

問題3: メモリ不足エラー

# 軽量モデルに変更
model = WhisperModel("tiny", device="cpu", compute_type="int8")

# または処理チャンクサイズを調整
segments, _ = model.transcribe(
    audio_file,
    beam_size=1,  # ビームサイズを1に
    best_of=1     # ベスト候補数を1に
)

問題4: 認識精度の低下

# VADフィルターの調整
segments, _ = model.transcribe(
    audio_file,
    vad_filter=True,
    vad_parameters=dict(
        min_silence_duration_ms=500,  # 無音判定を短く
        speech_pad_ms=200            # 音声パディングを調整
    )
)

パフォーマンス改善

# performance_tips.py

# 1. プロセス優先度の設定
import psutil
import os

process = psutil.Process(os.getpid())
process.nice(psutil.HIGH_PRIORITY_CLASS)  # Windows

# 2. CPUアフィニティの設定
cpu_count = psutil.cpu_count()
if cpu_count > 4:
    # 物理コアのみ使用(ハイパースレッドを避ける)
    process.cpu_affinity(list(range(0, cpu_count//2)))

# 3. メモリ使用量監視
def monitor_memory_usage():
    memory = psutil.virtual_memory()
    print(f"メモリ使用率: {memory.percent}%")
    if memory.percent > 85:
        print("警告: メモリ使用量が高すぎます")