ワンクリックでインテリジェントなビデオ音声をテキストに変換 - ビデオ音声を簡単に抽出し、PaddlePaddle 音声認識と Python に基づいてコピーを生成します。

序文

セルフメディア業界に参入する人が増えている昨今、短い動画が徐々に主流になってきていますが、会議の動画を録画した後に議事録を作成するなど、動画内の音声をテキストに変換したい場合も多いでしょう。 ; たとえば、インターネット上でチュートリアル ビデオについてメモを取りたい; たとえば、ビデオからコピーを抽出して使用する必要がある; たとえば、ビデオに字幕を追加する必要がある; 現時点では、動画をテキストに変換する必要があります。
動画編集のプロではない人にとっては扱いがかなり面倒ですが、ネット上にはガジェットがたくさんあり、その多くは独自の技術やモデルを誇っていますが、どれもオンラインモデルだったり、一定期間が経過すると再利用できなくなったりします。実際に使用すると、これらのツールは一部の大企業が提供するインターフェイスから派生した AI ツールであり、結果は良好です。ただし、処理中に、処理されたファイルを大企業のサーバーにアップロードして処理する必要があるため、データのセキュリティ上の問題が発生する可能性があります。このデータの大部分には、データ漏洩やセキュリティの問題が含まれる可能性があります。
このプロジェクトのコア アルゴリズムは、PaddlePaddle の音声認識と Python 実装に基づいています。使用されるモデルは、独自のトレーニングを行うことができ、ローカル デプロイメントをサポートし、GPU と CPU の両方の推論をサポートし、短い音声認識、長い音声認識を処理し、入力を実現できます。スピーチ、識別。

1. ビデオ音声抽出

ビデオ内の音声を認識したい場合は、まずビデオ内の音声を抽出する必要があります。ビデオ内の音声を抽出するには、さまざまな方法があります。ビデオ編集ソフトウェア (Adobe Premiere Pro、Final Cut Pro など) を使用できます。をクリックしてオーディオ トラックを抽出し、オーディオ ファイルとしてエクスポートします。FFmpeg や moviepy などのツールを使用して、コマンド ラインを通じてビデオから音声を抽出することもできます。
ここでは、ビデオ内の音声を抽出するために Moviepy が使用されています。MoviePy は、ビデオ編集用に設計された機能豊富な Python モジュールです。MoviePy を使用すると、ビデオ編集、ビデオの結合、タイトルの挿入など、さまざまな基本的なビデオ操作を簡単に実行できます。さらに、ビデオ合成と高度なビデオ処理をサポートしており、カスタムの高度な特殊効果を追加することもできます。このモジュールは、GIF を含むほとんどの一般的なビデオ形式を読み書きできます。MoviePy は、Mac、Windows、Linux システムのいずれを使用しているかに関係なくシームレスに動作し、さまざまなプラットフォームで使用できます。
MoviePy および FFmpeg 環境のインストール:

pip インストール moviepy
pip インストール ffmpeg

moviepy を使用してビデオから抽出されたオーディオ トラックのビット レートは 16000 ではないため、音声認識モデルに直接入力できません。ここでは、オーディオ トラックを抽出するために FFmpeg を使用してオーディオ サンプリング レートを 16000 に変換する必要もあります
ここに画像の説明を挿入します

def video_to_audio(video_path,audio_path):
    video = VideoFileClip(video_path)
    audio = video.audio
    audio_temp = "temp.wav"

    if os.path.exists(audio_path):
        os.remove(audio_temp)

    audio.write_audiofile(audio_temp)
    audio.close()

    if os.path.exists(audio_path):
        os.remove(audio_path)
    cmd = "ffmpeg -i " + audio_temp + " -ac 1 -ar 16000 " + audio_path
    subprocess.run(cmd,shell=True)

ここに画像の説明を挿入します

2. 音声認識

1.PaddleSpeech音声認識

PaddleSpeech は、Feipian によって開発されたオールインワンの音声アルゴリズム ツールボックスで、さまざまな主要な国際的な音声アルゴリズムと事前トレーニングされたモデルが含まれています。ユーザーが選択できるさまざまな音声処理ツールと事前トレーニング済みモデルを提供し、音声認識、音声合成、音声分類、声紋認識、句読点回復、音声翻訳などの機能をサポートします。PaddleSpeech に基づいた質の高いプロジェクトとトレーニング チュートリアルは、こちらで見つけることができます: https://aistudio.baidu.com/projectdetail/4692119?contributionType=1

音声認識 (自動音声認識、ASR) は、音声から言語とテキストのコンテンツを抽出するタスクです。
ここに画像の説明を挿入します
現在、音声認識分野では Transformer と Conformer が主流となっており、この分野のチュートリアルについては、PaddleSpeech の公式コース「PaddleSpeech音声技術コース」をご覧ください。

2. 環境依存インストール

現在の環境は win10、GPU は N カード 3060、cuda 11.8、cudnn 8.5 を使用しています。後でパッケージ化を容易にするために、conda を使用して環境をインストールします。GPU がない場合は、CPU バージョンをインストールすることもできます。

conda create -n video_to_txt python=3.8
python -m pip install paddlepaddle-gpu==2.5.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

3. モデルのダウンロード

公式 git から適切なモデルをダウンロードできます: https://github.com/PaddlePaddle/PaddleSpeech/blob/develop/README_cn.md
モデルを変換します:

import argparse
import functools

from ppasr.trainer import PPASRTrainer
from ppasr.utils.utils import add_arguments, print_arguments

parser = argparse.ArgumentParser(description=__doc__)
add_arg = functools.partial(add_arguments, argparser=parser)
add_arg('configs',          str,   'models/csfw/configs/conformer.yml',    '配置文件')
add_arg("use_gpu",          bool,  True,                       '是否使用GPU评估模型')
add_arg("save_quant",       bool,  False,                      '是否保存量化模型')
add_arg('save_model',       str,   'models',                  '模型保存的路径')
add_arg('resume_model',     str,   'models/csfw/models', '准备导出的模型路径')
args = parser.parse_args()
print_arguments(args=args)


# 获取训练器
trainer = PPASRTrainer(configs=args.configs, use_gpu=args.use_gpu)

# 导出预测模型
trainer.export(save_model_path=args.save_model,
               resume_model=args.resume_model,
               save_quant=args.save_quant)

4. 音声認識

短い音声認識にモデルを使用します。

 def predict(self,
                audio_data,
                use_pun=False,
                is_itn=False,
                sample_rate=16000):
        # 加载音频文件,并进行预处理
        audio_segment = self._load_audio(audio_data=audio_data, sample_rate=sample_rate)
        audio_feature = self._audio_featurizer.featurize(audio_segment)
        input_data = np.array(audio_feature).astype(np.float32)[np.newaxis, :]
        audio_len = np.array([input_data.shape[1]]).astype(np.int64)

        # 运行predictor
        output_data = self.predictor.predict(input_data, audio_len)[0]

        # 解码
        score, text = self.decode(output_data=output_data, use_pun=use_pun, is_itn=is_itn)
        result = {
    
    'text': text, 'score': score}
        return result

認識結果を見てください。短い文や句読点がなく、すべてが 1 つの部分に統合されています。
ここに画像の説明を挿入します

5. 文の区切りと句読点

Feipiao のERNIEに基づいて句読点行番号モデルをトレーニングできます。
ここに画像の説明を挿入します
句読点コードを追加します。

import json
import os
import re

import numpy as np
import paddle.inference as paddle_infer
from paddlenlp.transformers import ErnieTokenizer
from ppasr.utils.logger import setup_logger

logger = setup_logger(__name__)

__all__ = ['PunctuationPredictor']


class PunctuationPredictor:
    def __init__(self, model_dir, use_gpu=True, gpu_mem=500, num_threads=4):
        # 创建 config
        model_path = os.path.join(model_dir, 'model.pdmodel')
        params_path = os.path.join(model_dir, 'model.pdiparams')
        if not os.path.exists(model_path) or not os.path.exists(params_path):
            raise Exception("标点符号模型文件不存在,请检查{}和{}是否存在!".format(model_path, params_path))
        self.config = paddle_infer.Config(model_path, params_path)
        # 获取预训练模型类型
        pretrained_token = 'ernie-1.0'
        if os.path.exists(os.path.join(model_dir, 'info.json')):
            with open(os.path.join(model_dir, 'info.json'), 'r', encoding='utf-8') as f:
                data = json.load(f)
                pretrained_token = data['pretrained_token']

        if use_gpu:
            self.config.enable_use_gpu(gpu_mem, 0)
        else:
            self.config.disable_gpu()
            self.config.set_cpu_math_library_num_threads(num_threads)
        # enable memory optim
        self.config.enable_memory_optim()
        self.config.disable_glog_info()

        # 根据 config 创建 predictor
        self.predictor = paddle_infer.create_predictor(self.config)

        # 获取输入层
        self.input_ids_handle = self.predictor.get_input_handle('input_ids')
        self.token_type_ids_handle = self.predictor.get_input_handle('token_type_ids')

        # 获取输出的名称
        self.output_names = self.predictor.get_output_names()

        self._punc_list = []
        if not os.path.join(model_dir, 'vocab.txt'):
            raise Exception("字典文件不存在,请检查{}是否存在!".format(os.path.join(model_dir, 'vocab.txt')))
        with open(os.path.join(model_dir, 'vocab.txt'), 'r', encoding='utf-8') as f:
            for line in f:
                self._punc_list.append(line.strip())

        self.tokenizer = ErnieTokenizer.from_pretrained(pretrained_token)

        # 预热
        self('近几年不但我用书给女儿儿压岁也劝说亲朋不要给女儿压岁钱而改送压岁书')
        logger.info('标点符号模型加载成功。')

    def _clean_text(self, text):
        text = text.lower()
        text = re.sub('[^A-Za-z0-9\u4e00-\u9fa5]', '', text)
        text = re.sub(f'[{
      
      "".join([p for p in self._punc_list][1:])}]', '', text)
        return text

    # 预处理文本
    def preprocess(self, text: str):
        clean_text = self._clean_text(text)
        if len(clean_text) == 0: return None
        tokenized_input = self.tokenizer(list(clean_text), return_length=True, is_split_into_words=True)
        input_ids = tokenized_input['input_ids']
        seg_ids = tokenized_input['token_type_ids']
        seq_len = tokenized_input['seq_len']
        return input_ids, seg_ids, seq_len

    def infer(self, input_ids: list, seg_ids: list):
        # 设置输入
        self.input_ids_handle.reshape([1, len(input_ids)])
        self.token_type_ids_handle.reshape([1, len(seg_ids)])
        self.input_ids_handle.copy_from_cpu(np.array([input_ids]).astype('int64'))
        self.token_type_ids_handle.copy_from_cpu(np.array([seg_ids]).astype('int64'))

        # 运行predictor
        self.predictor.run()

        # 获取输出
        output_handle = self.predictor.get_output_handle(self.output_names[0])
        output_data = output_handle.copy_to_cpu()
        return output_data

    # 后处理识别结果
    def postprocess(self, input_ids, seq_len, preds):
        tokens = self.tokenizer.convert_ids_to_tokens(input_ids[1:seq_len - 1])
        labels = preds[1:seq_len - 1].tolist()
        assert len(tokens) == len(labels)

        text = ''
        for t, l in zip(tokens, labels):
            text += t
            if l != 0:
                text += self._punc_list[l]
        return text

    def __call__(self, text: str) -> str:
        # 数据batch处理
        try:
            input_ids, seg_ids, seq_len = self.preprocess(text)
            preds = self.infer(input_ids=input_ids, seg_ids=seg_ids)
            if len(preds.shape) == 2:
                preds = preds[0]
            text = self.postprocess(input_ids, seq_len, preds)
        except Exception as e:
            logger.error(e)
        return text

推論結果:
ここに画像の説明を挿入します

6. 長い音声認識

長い音声認識では、 VADを介して音声を分割し、次に短い音声を識別し、結果を結合して最終的に長い音声認識結果を取得する必要があります。VAD は音声エンドポイント検出技術であり、Voice Activity Detection の略です。その主なタスクは、ノイズのある音声から音声の開始点と終了点を正確に特定することです。

    def get_speech_timestamps(self, audio, sampling_rate):
        self.reset_states()
        min_speech_samples = sampling_rate * self.min_speech_duration_ms / 1000
        min_silence_samples = sampling_rate * self.min_silence_duration_ms / 1000
        speech_pad_samples = sampling_rate * self.speech_pad_ms / 1000

        audio_length_samples = len(audio)

        speech_probs = []
        for current_start_sample in range(0, audio_length_samples, self.window_size_samples):
            chunk = audio[current_start_sample: current_start_sample + self.window_size_samples]
            if len(chunk) < self.window_size_samples:
                chunk = np.pad(chunk, (0, int(self.window_size_samples - len(chunk))))
            speech_prob = self(chunk, sampling_rate).item()
            speech_probs.append(speech_prob)

        triggered = False
        speeches: List[dict] = []
        current_speech = {
    
    }
        neg_threshold = self.threshold - 0.15
        temp_end = 0

        for i, speech_prob in enumerate(speech_probs):
            if (speech_prob >= self.threshold) and temp_end:
                temp_end = 0

            if (speech_prob >= self.threshold) and not triggered:
                triggered = True
                current_speech['start'] = self.window_size_samples * i
                continue

            if (speech_prob < neg_threshold) and triggered:
                if not temp_end:
                    temp_end = self.window_size_samples * i
                if (self.window_size_samples * i) - temp_end < min_silence_samples:
                    continue
                else:
                    current_speech['end'] = temp_end
                    if (current_speech['end'] - current_speech['start']) > min_speech_samples:
                        speeches.append(current_speech)
                    temp_end = 0
                    current_speech = {
    
    }
                    triggered = False
                    continue

        if current_speech and (audio_length_samples - current_speech['start']) > min_speech_samples:
            current_speech['end'] = audio_length_samples
            speeches.append(current_speech)

        for i, speech in enumerate(speeches):
            if i == 0:
                speech['start'] = int(max(0, speech['start'] - speech_pad_samples))
            if i != len(speeches) - 1:
                silence_duration = speeches[i + 1]['start'] - speech['end']
                if silence_duration < 2 * speech_pad_samples:
                    speech['end'] += int(silence_duration // 2)
                    speeches[i + 1]['start'] = int(max(0, speeches[i + 1]['start'] - silence_duration // 2))
                else:
                    speech['end'] = int(min(audio_length_samples, speech['end'] + speech_pad_samples))
                    speeches[i + 1]['start'] = int(max(0, speeches[i + 1]['start'] - speech_pad_samples))
            else:
                speech['end'] = int(min(audio_length_samples, speech['end'] + speech_pad_samples))

        return speeches

長い音声認識の場合:

    def predict_long(self,
                     audio_data,
                     use_pun=False,
                     is_itn=False,
                     sample_rate=16000):
        self.init_vad()
        # 加载音频文件,并进行预处理
        audio_segment = self._load_audio(audio_data=audio_data, sample_rate=sample_rate)
        # 重采样,方便进行语音活动检测
        if audio_segment.sample_rate != self.configs.preprocess_conf.sample_rate:
            audio_segment.resample(self.configs.preprocess_conf.sample_rate)
        # 获取语音活动区域
        speech_timestamps = self.vad_predictor.get_speech_timestamps(audio_segment.samples, audio_segment.sample_rate)
        texts, scores = '', []
        for t in speech_timestamps:
            audio_ndarray = audio_segment.samples[t['start']: t['end']]
            # 执行识别
            result = self.predict(audio_data=audio_ndarray, use_pun=False, is_itn=is_itn)
            score, text = result['score'], result['text']
            if text != '':
                texts = texts + text if use_pun else texts + ',' + text
            scores.append(score)
            logger.info(f'长语音识别片段结果:{
      
      text}')
        if texts[0] == ',': texts = texts[1:]
        # 加标点符号
        if use_pun and len(texts) > 0:
            if self.pun_predictor is not None:
                texts = self.pun_predictor(texts)
            else:
                logger.warning('标点符号模型没有初始化!')
        result = {
    
    'text': texts, 'score': round(sum(scores) / len(scores), 2)}
        return result

推論結果:
ここに画像の説明を挿入します
文分割結果:

大きな赤ちゃんもいるよ、本当にどう思っているのかわからないよ?「私は独身の男女に電話をかけました。二人とも同い年です。28歳の女性は幼稚園の先生で、南部出身の男性はエンジニアです。先月初めに初めて会いました。」お互いに好印象だったので、また会う約束をしました。3、4回会って、断続的に知り合ってから1ヶ月以上経ちます。昨夜、夕食後にまた会いました。

3. UIと保存

1. UIインターフェース

アプリケーションの便宜のために、ここではライブラリ Gradio を使用します。Gradio は、機械学習およびデータ サイエンスのデモンストレーション用のアプリケーションを迅速に構築するために使用されるオープン ソースの Python ライブラリです。シンプルで美しいユーザー インターフェイスをすばやく作成し、機械学習モデルをクライアント、共同作業者、ユーザー、または学生に提示するのに役立ちます。さらに、自動共有リンクを通じてモデルを迅速にデプロイし、モデルのパフォーマンスに関するフィードバックを得ることができます。開発中に、組み込みの操作および解釈ツールを使用して、モデルを対話的にデバッグできます。Gradio は、クライアント/共同作業者/ユーザー/学生に対する機械学習モデルのデモンストレーション、モデルの迅速なデプロイとパフォーマンスのフィードバックの取得、組み込みの操作および解釈ツールを使用した開発中のモデルのインタラクティブなデバッグなど、さまざまな状況に適しています。

pip install gradio
#清華ミラーソースを使用すると、より高速にインストールできます
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gradio

import os
from moviepy.editor import *
import subprocess
import gradio as gr
from ppasr.predict import PPASRPredictor
from ppasr.utils.utils import add_arguments, print_arguments

configs = "models/csfw/configs/conformer.yml"
pun_model_dir = "models/pun_models/"
model_path = "models/csfw/models"

predictor = PPASRPredictor(configs=configs,
                           model_path=model_path,
                           use_gpu=True,
                           use_pun=True,
                           pun_model_dir=pun_model_dir)

def video_to_audio(video_path):
    file_name, ext = os.path.splitext(os.path.basename(video_path))
    video = VideoFileClip(video_path)
    audio = video.audio
    audio_temp = "temp.wav"

    audio_name = file_name + ".wav"
    if os.path.exists(audio_temp):
        os.remove(audio_temp)

    audio.write_audiofile(audio_temp)
    audio.close()

    if os.path.exists(audio_name):
        os.remove(audio_name)

    cmd = "ffmpeg -i " + audio_temp + " -ac 1 -ar 16000 " + audio_name
    subprocess.run(cmd,shell=True)

    return audio_name

def predict_long_audio(wav_path):
    result = predictor.predict_long(wav_path, True, False)
    score, text = result['score'], result['text']
    return text


# 短语音识别
def predict_audio(wav_path):
    result = predictor.predict(wav_path, True, False)
    score, text = result['score'], result['text']
    return text

def video_to_text(video,operation):
    audio_name = video_to_audio(video)
    if operation == "短音频":
        text = predict_audio(audio_name)
    elif operation == "长音频":
        text = predict_long_audio(audio_name)
    else:
        text = ""

    print("视频语音提取识别完成!")
    return text

ch = gr.Radio(["短音频","长音频"],label="选择识别音频方式:")

demo = gr.Interface(fn=video_to_text,inputs=[gr.Video(), ch],outputs="text")

demo.launch()

結果:

ビデオ音声抽出とテキスト変換

2. 認識結果を保存する

4. 最適化とアップグレード

1. 最適化

このプロジェクトが現在認識できる音声の単語誤り率は 0.083327 です。この文のように、似た音を持つ一部の単語はコンテキストに合わせて変更できません。

「この南はエンジニアだ」

ここでの文脈上の関連付けにより、正しいものは次のようになります。

「この人はエンジニアです」

このような認識エラーはそれほど多くはありませんが、文セグメントがうまく分割されていない部分がまだあります。最適化したい場合は、画面エラーに LLM (大規模言語モデル) を追加できます。LLM に接続されたこのコードはトレーニング中です。テスト段階。

2.アップグレード

プロジェクトはアップグレードできます。

  1. 現在のプロジェクトは中国語音声のみを対象としており、多言語サポートは後で追加される予定です。
  2. ビデオに字幕がない場合は、ビデオに字幕生成モジュールを追加できます。
  3. 動画には字幕が付いており、動画画面の字幕を読み取り、OCR認識と音声認識で相互認証します。
  4. Web版のサポートを追加しました。
  5. オプションのセグメントはビデオ音声を抽出して認識します。
  6. 複数人の会話シーンのビデオの場合、声紋認識を追加し、認識用にフォーマットすることができます。
  7. 生成されたテキストを Word にエクスポートして入力します。

3. プロジェクトのソースコード

ソースコード: https://download.csdn.net/download/matt45m/88386353
モデル:

ソースコード構成:

conda create -n video_to_txt python=3.8
python -m pip install paddlepaddle-gpu==2.5.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
cd VideoToTxt
pip install -r要件.txt
python video_txt。パイ

次に、ブラウザhttp://127.0.0.1:7860/で開くと、使用できるようになります。

4.備考

このプロジェクトに興味がある場合、またはインストール プロセス中にエラーが発生した場合は、私のペンギン グループ (487350510) に参加して、一緒に話し合うことができます。

おすすめ

転載: blog.csdn.net/matt45m/article/details/133429008