LoginSignup
4
1

More than 1 year has passed since last update.

SPAJAM 2022 決勝戦で `Unity (C#)` で `Speech-to-Text API` と `DeepL API` を使ったので紹介するよ

Posted at

この記事は フリュー Advent Calendar 2022 の3日目の記事となります。

紹介の前に

2022/11/19 〜 20 に、SPAJAM 2022 の本選が開催されました。
その本選にて、めでたく 優秀賞 を受賞しました!!🎉

残念ながら最優秀賞は逃しましたが、初めての本選参加はとても楽しめました♨️

今回は、その開発の中で使った技術周りの紹介をしたいと思います。
(Unity 初心者なので、専門用語とか間違ってても許してください)

2022年 本選結果

アプリの紹介

  • アプリ名: PiCCOM (読み方: ピッコム)
  • 使用技術:
    • Unity (C#): 2021.3.2f1 (SILICON)
    • Google Cloud Speech-to-Text API
    • Speech Recognition using Google Cloud Pro
    • DeepL API

Google Cloud Speech-to-Text API

概要

Google が提供するAPIで、「音声データをテキストデータに変換する」ことができます。

準備

Google Cloud Console で、 Speech-to-Text API の利用できるように、サービスの有効化を行います。

次に、APIを利用するための認証情報を登録します。

(誤字ってますね、今更なので気にしない)

使い方

今回は、ハッカソンという時間が限られた状況、且つまだまだ実装しなければならいものがあったので、時間をお金で買う決断をしました。
というのも、本来であれば、諸々を時前実装すべきなのですが、ライブラリ周りを探していたところ Speech Recognition using Google Cloud Pro を見つけました。

なので、今回は実装方法については Speech Recognition using Google Cloud Pro を使う方法での説明を次項でやりたいと思います。
とはいえ、今回のアプリを使う中で、調べた情報は追記しておこうと思います。

APIを駆使(?)する方法

多言語化対応

多言語化対応というと少し大袈裟ですが「テキストへ変換したい音声データの言語の候補(代替言語)を複数指定する」ことができます。
ただし 最大3つまで という制限はありますが「本来の変換対象の言語」の指定と合わせれば 最大4つ までの指定が可能にはなりそうです。

指定する方法は RecognitionConfig を使い、必要な項目を設定します。

Field 説明
languageCode String (required) 変換対象とする音声データの言語コード。 (例: "en-US")
alternativeLanguageCodes String の配列 (optional) 変換対象とする音声データの代替言語コード。この項目が設定されている場合は、認識結果には languageCode を含め、検出された最も可能性が高い言語での認識が含まれ、認識結果には検出された言語タグが含まれます。

言語コード は、共に「BCP-47 に対応した言語タグである」必要があります。
Speech-to-Text API としての サポート状況 は確認して利用してください。

Speech Recognition using Google Cloud Pro

概要

Google Cloud サービスで提供される Speech-to-Text API を利用して、音声をテキストに変換する機能を提供する。

準備

Speech Recognition using Google Cloud Pro を上記、Unity Asset Store から購入し、該当プロジェクトにインポートします。
インポートしたあとは Speech-to-Text API を利用するために取得した、認証コードを Api Key に設定します。

この時の注意点として「GC Recognitionnition を使いたいスクリプトなどに Component として追加」すると、1回目の実行はうまくいうものの、2度目以降の実行がうまくいかなくなります。

アプリ起動かビルドタイミングでエラーが発生します。
発生する内容は、メモってません!!
エラーの内容的には「GCSpeechRecognition の初期化に失敗しました」のようなエラーが出ます。

回避策としては GCSpeechRecognition の Prefab を Scene に直接配置して、共通参照できる状態にしてあげることです。

使い方

とりあえず、下のコードを参考にしてもらえばよいかなと思います。
C# 特有?の改行はしていません!!!)

音声データの録音から、 Speech-to-Text API へのデータ送信など全てをやってくれるので、かなり実装が楽にできるようになります。

using FrostweepGames.Plugins.GoogleCloud.SpeechRecognition;


public class SpeechToTextText: MonoBehaviour {

	[SerializeField]
	private Button recordButton = null;
    
    // MEMO: `GCSpeechRecognition` を `Scene` に追加するときは、Prefabを配置すること
    private GCSpeechRecognition _speechRecognition;

    private void Awake() {
        _speechRecognition = GCSpeechRecognition.Instance;

        _speechRecognition.FinishedRecordEvent += OnFinishedRecordEvent;
        _speechRecognition.RecognizeSuccessEvent += OnRecognizeSuccessEvent;

        if (_speechRecognition.HasConnectedMicrophoneDevices()) {
            _speechRecognition.SetMicrophoneDevice(_speechRecognition.GetMicrophoneDevices()[0]);
        }

        if (recordButton != null) {
            // ボタンを押している最中は録音する
            recordButton.OnPointerDownAsObservable()
                .Subscribe(_ => StartRecord())
                .AddTo(this);

            // ボタンを離した段階で録音を終了する
            recordButton.OnPointerUpAsObservable()
                .Subscribe(_ => StopRecord())
                .AddTo(this);
        }
    }

    private void OnDestroy() {
        _speechRecognition.FinishedRecordEvent -= OnFinishedRecordEvent;
        _speechRecognition.RecognizeSuccessEvent -= OnRecognizeSuccessEvent;
    }

    private void StartRecord() {
        _speechRecognition.StartRecord(false);
    }

    private void StopRecord() {
        _speechRecognition.StopRecord();
    }

    private void OnFinishedRecordEvent(AudioClip clip, float[] raw) {
        if (clip == null) return;

        RecognitionConfig config = RecognitionConfig.GetDefault();
        // メインの言語コードを設定
        config.languageCode = Enumerators.LanguageCode.en_US.Parse();
        // メインの言語コード以外の、代替言語コードを設定
        List<string> autoLangCodes = new List<string>() {
             Enumerators.LanguageCode.ja_JP.Parse(),
             Enumerators.LanguageCode.en_GB.Parse(),
             "zh",  // `Enumerators.LanguageCode` に定義が無いのでも、 `Speech-to-Text API` 側がサポートしていれば指定は可能
         };
        config.alternativeLanguageCodes = autoLangCodes.ToArray();

        config.audioChannelCount = clip.channels;

        GeneralRecognitionRequest recognitionRequest = new GeneralRecognitionRequest() {
            audio = new RecognitionAudioContent() {
                content = raw.ToBase64()
            },
            config = config
        };

        _speechRecognition.Recognize(recognitionRequest);
    }

    private async void OnRecognizeSuccessEvent(RecognitionResponse recognitionResponse) {
        string transcript = "";

        foreach (var result in recognitionResponse.results) {
            foreach (var alternative in result.alternatives) {
                if (recognitionResponse.results[0].alternatives[0] != alternative) {
                    transcript = alternative.transcript;
                }
            }
        }
    }
}

こんな感じです。

実装などについては、以下の記事を参考にさせていただきました。

DeepL API

概要

DeepL SE が提供するAPIで、「テキストデータの翻訳する」ことができます。

ちなみに、読み方は「ディープエル」です。
(実は、ディープルって呼んでたんですよねー・・・)

準備

DeepL API を使うにはアカウント登録が必要です。
現時点ではプラン(開発者向け)は現時点では、2つあります。

  • DEEPL API FREE
  • DEEPL API Pro

フリープランでも「1か月に500,000文字まで翻訳 (2022/12/03 時点)」可能なので、お試しするなら、フリープランで十分です。

登録が完了したら、マイページを確認します。
マイページの「アカウント」タブを開き、下の方にスクロールすると DeepL APIで使用する認証キー があるので、それをコピーしてください。

注意点

DeepL のアカウントでログインしていると、Web版の DeepL 翻訳 は使えません。
ログアウトしてから使うようにしましょう!!

使い方

こちらも、 下のコードを参考にしてもらえばよいかなと思います。
C# 特有?の改行はしていません!!!)

public class DeepL {
    static string APIKey = "{DeepL APIで使用する認証キー}";
    static string BaseURL = "https://api-free.deepl.com";
    static string TranslateAPI = BaseURL + "/v2/translate";

    public async UniTask<TranslateModel> TranslateAPIRequest(string text, string targetLang) {
        try {
            HttpClient client = new HttpClient();

            // リクエストヘッダーに認証キーを設定
            client.DefaultRequestHeaders.Add("Authorization", $"DeepL-Auth-Key {APIKey}");

            // DeepL API を利用するテキストデータは `UTF-8` にエンコードする必要がある
            byte[] bytesData = System.Text.Encoding.UTF8.GetBytes(text);
            Encoding utf8Enc = Encoding.GetEncoding("UTF-8");
            string utf8EncodedText = utf8Enc.GetString(bytesData);

            // `source_lang` を指定しなければ、自動認識による翻訳をしてくれる
            Dictionary<string, string> requestParameters = new Dictionary<string, string>() {
                    { "text", utf8EncodedText },
                    { "target_lang", targetLang }
            };

            HttpResponseMessage response;

            using (FormUrlEncodedContent content = new FormUrlEncodedContent(requestParameters)) {
                content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
                response = await client.PostAsync(TranslateAPI, content);
            }

            string contentString = await response.Content.ReadAsStringAsync();
            TranslateResult result = JsonConvert.DeserializeObject<TranslateResult>(contentString);

            var dic = JsonConvert.DeserializeObject<Dictionary<string, List<Dictionary<string, string>>>>(contentString);
            List<Dictionary<string, string>> translations = dic["translations"];

            TranslateModel model = result.translations[0];
            Debug.Log($"ほんやくこんにゃく({targetLang}): {model.language}, {model.text}");
            return model;

        } catch (Exception e) {
            Debug.Log("Error: \n" + e.Message);

            return null;
        }
    }
}

public class TranslateResult {
    [JsonProperty("translations")]
    public List<TranslateModel> translations { get; set; }
}

public class TranslateModel {
    /// 変換元のテキストデータの言語コード
    [JsonProperty("detected_source_language")]
    public string language { get; set; }
    /// 変換後のテキストデータ
    [JsonProperty("text")]
    public string text { get; set; }
}

あとは、呼び出すだけですね。
細かいところは省いています。

private DeepL deepL = new DeepL();

// テキストデータを、指定した言語コードに翻訳する
// 言語コードは `DeepL API` 側で対応されているもに限る
// See: https://www.deepl.com/ja/docs-api/translate-text/translate-text/
TranslateModel translated = await deepL.TranslateAPIRequest(transcript, "JA");

translated.language
translated.text

DeepL API のレスポンスには「翻訳前の言語コード」が含まれているのがとても助かります。

アプリ利用開始タイミングでテキスト入力をしてもらい、その結果をもとに、自動でロケールの変更やUIの変更などにもつかえるかもですねー・・・

さいごに

  • Speech-to-Text API を使ってみた感想としては、日本語メインで使いましたが「音として」は認識していますが「意図する単語にならない」ことは多々ありましたが、意味は通じそうだなという感じでした
    • ただし、それを「翻訳」する場合は意味が変わってくるので使い方次第かもしれないです
  • 端末にはよりますが、なかなか認識してくれない時もありました(滑舌の問題かもしれませんけど)
    • ゆっくりはっきり発音すれば、拾ってくれたので、やっぱり滑舌のせいですね・・・
  • DeepL API をあえて使ってみましたが、翻訳精度は高かったですね
    • ちなみに、同じテキストをAPI版とWeb版で試しましたが、同じ結果返ってきました!!(当たり前ですけど、一回は検証しますよね、エンジニアなら)
  • 今回のアプリとしては本来は翻訳後のテキストデータを Google Cloud Natural Language API を通して、テキスト分析も行いたかったんですが、これについてはまたいつか試したいと思います
  • あと Unity(2021.3.2f1) と古いバージョンで、しかも SILICON 版を使ったのですが、Unity Hub を通して使う裏技的なものも見つけた(?)ので、別の記事で書こうと思います
    • Unity 初心者ですので、今回はUI部分はノータッチだったので、勉強していってもよさそうだなとは思いました

参考文献まとめ

Google Cloud Speech-to-Text API

Speech Recognition using Google Cloud [VR\AR\Mobile\Desktop] Pro

【Unity(C#)】Google Cloud Speech-to-TextをUnityで使用できるGoogle Cloud Speech Recognitionの使い方

DeepL API

Google Cloud Natural Language API

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1