« ^ »

今日やった事 - 20240829

所要時間: 約 3分
  • レビュー

PDFからテキストに変換する

PDFからテキストを抽出する方法はいくつかある。ここでは、popplerのpdftotextを使う例を紹介する。

popplerをインストールする。

brew install poppler

pdftotextを使ってテキストを抽出する。

pdftotext example.pdf

i18n用POTファイルを作成する

pip install polib
import polib

po = polib.POFile()
entry = polib.POEntry(
    msgid="Hello world",
    msgstr="",
)
po.append(entry)
po.save("output.pot")

本を読む

pdftotext -f 35 -l 35 foo.pdf - | python pdfreader.py

本を読む続き

pdftotext -f 35 -l 35 foo.pdf - | python pdfreader.py

sounddeviceからデータを取得しwavファイルとして出力する

import time
import wave

import numpy as np
import sounddevice

SAMPLE_RATE = 16000
CHANNELS = 1
WINDOW_SIZE_SAMPLES = 512

wav_file = wave.open("output.wav", 'wb')
wav_file.setnchannels(CHANNELS)  # チャネル数 (モノラル)
wav_file.setsampwidth(2)         # サンプル幅 (2バイト)
wav_file.setframerate(SAMPLE_RATE)

def callback_sound(indata: np.ndarray[np.ndarray[np.int16]],
                   outdata, frames, time, status):
    if status:
        print(status)
    # インプットデータを書き込み
    wav_file.writeframes(indata.copy())

try:
    with sounddevice.Stream(samplerate=SAMPLE_RATE, dtype="int16",
                            channels=CHANNELS,
                            blocksize=WINDOW_SIZE_SAMPLES,
                            callback=callback_sound):
        while True:
            time.sleep(0.01)
except KeyboardInterrupt:
    print("録音終了")
finally:
    wav_file.close()

ffmpegで音声のノイズを除去する

ffmpegにノイズを除去するフィルターがある事を知った1。そこで、これを使ってみる事にした。

ffmpeg -i INPUT_FILE.wav -af 'afftdn=nr=10' OUTPUT_FILE.wav -y

afftdn2は、STFT3を使用し周波数特性の差によりノイズを除去する。いくつかのパラメーターを渡す事で挙動を調整できる。

param意味説明
n16ノイズプロファイル特定のノイズプロファイルを指定する。
o0.75オーバーラップ高くすることで、スムーズなノイズ除去となる。
w1024ウィンドウサイズSTFTのウィンドウサイズを指定する。通常、指定する必要はない。
sn1間引きノイズプロファイルのスケール。0: 間引きしない、1: 間引きする

自前で音声ノイズ除去を実装してみる

未完成。ノイズ除去どころか、ノイズだらけになった。なんでだ…

import time
import wave

import librosa
import numpy as np
import sounddevice as sd

SAMPLE_RATE = 16000
CHANNELS = 1
NOISE_PROFILE_DURATION = SAMPLE_RATE  # 最初の1秒間をノイズプロファイルに使用
WINDOW_SIZE_SAMPLES = 512

print("ノイズ除去のため環境音を取得します。声を出さないでください。")
noise_audio = sd.rec(NOISE_PROFILE_DURATION,
                     samplerate=SAMPLE_RATE,
                     channels=CHANNELS, dtype='float32')
sd.wait()
print("ノイズ除去のため環境音を取得しました。")

# ノイズプロファイルのSTFTを計算
noise_profile_data = noise_audio[:, 0]
noise_stft = librosa.stft(noise_profile_data, n_fft=WINDOW_SIZE_SAMPLES, hop_length=WINDOW_SIZE_SAMPLES//2)
magnitude_noise, _ = librosa.magphase(noise_stft)

wav_file = wave.open("output.wav", 'wb')
wav_file.setnchannels(CHANNELS)  # チャネル数(モノラル)
wav_file.setsampwidth(2)         # サンプル幅(2バイト)
wav_file.setframerate(SAMPLE_RATE)

def callback_sound(indata: np.ndarray, outdata, frames, time, status):
    global magnitude_noise
    if status:
        print(status)

    # ノイズリダクションを適用
    y = indata[:, 0].astype('float32')
    y_stft = librosa.stft(y, n_fft=WINDOW_SIZE_SAMPLES, hop_length=WINDOW_SIZE_SAMPLES//2)
    magnitude_y, phase_y = librosa.magphase(y_stft)
    
    # 長さを調整
    if magnitude_y.shape[1] > magnitude_noise.shape[1]:
        magnitude_noise = np.pad(magnitude_noise, ((0, 0), (0, magnitude_y.shape[1] - magnitude_noise.shape[1])), 'constant')
    elif magnitude_noise.shape[1] > magnitude_y.shape[1]:
        magnitude_noise = magnitude_noise[:, :magnitude_y.shape[1]]

    magnitude_reduced = np.maximum(magnitude_y - magnitude_noise, 0.0)
    reduced_noise_stft = magnitude_reduced * phase_y
    reduced_noise_data = librosa.istft(reduced_noise_stft, hop_length=WINDOW_SIZE_SAMPLES//2)
    data = (reduced_noise_data * 32767).astype(np.int16).tobytes()
    wav_file.writeframes(data)


try:
    with sd.Stream(samplerate=SAMPLE_RATE, dtype="int16",
                   channels=CHANNELS, blocksize=WINDOW_SIZE_SAMPLES,
                   callback=callback_sound):
        print("録音を開始します。 Ctrl+C で停止できます。")
        while True:
            time.sleep(0.01)
except KeyboardInterrupt:
    print("終了")
finally:
    # WAVファイルを閉じる
    wav_file.close()

文字起こし

スピーチ to スピーチの作業の続き。途中までの処理は基本的に文字起こしと同じになる。

import time
import sys
import sounddevice
import numpy as np
import torch
from reazonspeech.nemo.asr.audio import audio_from_tensor
from nemo.collections.asr.models import EncDecRNNTBPEModel
from reazonspeech.nemo.asr.transcribe import transcribe


torch.set_num_threads(1)

THREASHOLD = 0.5  # 発話検出の閾値 (ex: 0.5)
SAMPLE_RATE = 16000
SAMPLING_RATE = 16000
WINDOW_SIZE_SAMPLES = 512   # 512 if SAMPLING_RATE == 16000 else 256

model, utils = torch.hub.load("snakers4/silero-vad", "silero_vad")
rez_model = EncDecRNNTBPEModel.from_pretrained(
    'reazon-research/reazonspeech-nemo-v2',
    map_location="cpu")

(get_speech_timestamps,
 save_audio,
 read_audio,
 VADIterator,
 collect_chunks) = utils

model.reset_states()

speech_intervals = []
current_start = None
speech_data = None

speech = None
chunk = None

def callback_sound(indata: np.ndarray[np.ndarray[np.int16]],
                   outdata, frames, time, status):
    global speech
    audio_chunk = indata

    audio_int16 = np.frombuffer(audio_chunk, dtype=np.int16)
    abs_max = np.abs(audio_int16).max()
    sound = audio_int16.astype('float32')
    if abs_max > 0:
        sound *= 1/32768
    audio_float32 = sound.squeeze()

    x = chunk = torch.Tensor(audio_float32)
    # chunk: torch.Tensor = torch.from_numpy(  # torch.Tensor torch.float32
    #     indata.astype(np.float32))
    window_size_samples = len(x[0]) if x.dim() == 2 else len(x)

    speech_prob = model(chunk, SAMPLING_RATE).item()
    # print(speech_prob)
    if speech_prob > THREASHOLD:  # 発話検出
        if speech is None:
            speech = chunk
        else:
            speech = torch.cat((speech, chunk), dim=0)
        return
    elif speech is not None:
        print(speech)
        if speech.shape[0] >= WINDOW_SIZE_SAMPLES :
            audio = audio_from_tensor(speech, SAMPLING_RATE)
            ret = transcribe(rez_model, audio)
            for text in ret.segments:
                print(text, end="")
            print("\n")
            speech = None

with sounddevice.Stream(samplerate=SAMPLE_RATE, dtype="int16",
                        channels=1, blocksize=WINDOW_SIZE_SAMPLES,
                        callback=callback_sound):
    while True:
        time.sleep(0.01)

フーリエ解析

フーリエ解析とは、現象を三角関数に分解して考える。


2

Adaptive Frequency Domain Noise Reduction

3

短時間フーリエ変換