C#で英語ゲームのスクリーンショットからOCRと翻訳を行う

# はじめに
この記事はゲーム The Elder Scrolls シリーズの1作目である Arena を何とか日本語で遊べるようにしようとする試みの中で、主だった技術部分のコードを備忘録として残すためのものです。
いきさつなどの詳細ははてなブログの記事にて記載しております。
[The Elder Scrolls: Arena の日本語化 - 1 - プログラミング・動画編集 備忘録](https://rrryutaro.hatenablog.com/entry/2020/07/16/204416)

なお、環境や作成するツールの形態は次の通りです。

```
Windows 7
Microsoft Visual Studio Community 2019
Version 16.6.3

Windows フォーム アプリケーション（.NET Framework）
.NET Framework 4.7.2
```

また、現時点での翻訳精度などは同じくはてなブログの次の記事に記載しております。
[The Elder Scrolls: Arena の日本語化 - 2 - プログラミング・動画編集 備忘録](https://rrryutaro.hatenablog.com/entry/2020/08/14/235214)

# 主な使用技術
## OCR処理
ゲームのスクリーンショットからOCRを行う部分は Tesseract を使用します。
理由は以前調査した事があるため。
[【随時更新】Python・OpenCV・Tesseractで参考にした記事などの一覧 - Qiita](https://qiita.com/R_TES_/items/933ec2ed754a9f3a2d3e)

## 翻訳処理
OCRで認識した文字列を翻訳するのは Selenium を使用して、Google翻訳にかける方法を使用します。
なお、以前同じような事を実施しており、その際は標準のWebBrowserコントロールを使用しましたが、スクリプトエラー等が発生しうまくいかないため、もっと行動な操作の行えるSeleniumを使用することにしました。

また、Google翻訳を使うだけであれば、GASを使って自分でAPI化するすばらしい方法が次の記事などでありました。
[Google翻訳APIを無料で作る方法 - Qiita](https://qiita.com/satto_sann/items/be4177360a0bc3691fdf)

ただ、今回は一応使えるものが出来たらツールは公開するつもりなので、自前のAPIを使う方法は避けるためブラウザを操作してGoogle翻訳を実行させるという方式をとります。

## 画像補正など
OCRの認識精度を高めるため、画像補正を行うのに OpenCvSharp を使用します。
OpenCvも使い方をほとんど忘れていますが、画像操作するならOpenCvだろうということで選択しています。

# Tesseract
NuGet パッケージ マネージャーから `Tesseract` で検索。
`Tesseract` とあるのでインストール。バージョンは `3.3.0`。
サンプルコード内では、[jTessBoxEditor](http://vietocr.sourceforge.net/training.html)にて作成したトレーニングデータを使用しているので、データが無い場合は以下の場所なりからデータを取得して適当な場所に配置してパスを指定する。
[GitHub - tesseract-ocr/tessdata: Trained models with support for legacy and LSTM OCR engine](https://github.com/tesseract-ocr/tessdata)

## OCR
必要な部分のみ抜粋。

```C#
using Tesseract;

var tesseract = new TesseractEngine(@"C:\tesseract\training\tessdata", "eng01");
var image = new Bitmap(@"C:\hoge.png");
var page = tesseract.Process(image);
var text = page.GetText();
page.Dispose();
```


# Selenium
NuGet パッケージ マネージャーから `Selenium` で検索。
`Selenium.WebDriver` をインストールする。バージョンは `3.141.0` 。
さらに、個別のブラウザのドライバーが必要なので、ここではChromeを使用する事にする。
`Selenium.Chrome.WebDriver` をインストール。バージョンは `83.0.0`。

## ChromeにてGoole翻訳させる
SeleniumのChromeのドライバーを使って、文字列を渡して翻訳結果を返すクラスを次のように作成した。
Google翻訳部分の操作は、とりあえずこれで動いたというレベルのもの。もっといい書き方は今後模索する。
注意点としては、`Quit`を呼ばないと非表示にしているブラウザやコンソールが残ったままとなるので、特にデバッグ起動で強制終了などをすると裏で残されてしまう。

```C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System;

namespace RTranslation
{
    public class RSeleniumChrome
    {
        IWebDriver driver;
        WebDriverWait wait;
        public string TranslationString;

        public RSeleniumChrome()
        {
            var driverService = ChromeDriverService.CreateDefaultService();
            // コマンドプロンプト非表示
            driverService.HideCommandPromptWindow = true;

            // ヘッドレス（ブラウザを表示しない）
            var options = new ChromeOptions();
            options.AddArgument("--headless");

            driver = new ChromeDriver(driverService, options);
            wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

            driver.Navigate().GoToUrl($"https://translate.google.com/?sl=en&tl=ja");
        }

        public void Close()
        {
            driver.Quit();
        }

        public string Translation(string src)
        {
            try
            {
                driver.FindElement(By.ClassName("clear")).Click();
                wait.Until(condition => condition.FindElement(By.Id("source")).Text == "");
                driver.FindElement(By.Id("source")).SendKeys(src);
                if (wait.Until(condition =>
                {
                    try
                    {
                        TranslationString = condition.FindElement(By.ClassName("tlid-translation")).Text;
                        return true;
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.Print(ex.Message);
                    }
                    return false;
                }))
                {
                    return TranslationString;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
            }
            return String.Empty;
        }

    }
}
```


# OpenCvSharp
こちらは正直どれを選ぶのが良いのかよくわかっていないが次のようにした。
NuGet パッケージ マネージャーから `OpenCvSharp` で検索。
`OpenCvSharp4.Windows` をインストール。バージョンは `4.4.0.20200725`。
プロジェクトURLは次の通り。
https://github.com/shimat/opencvsharp

## 2値化
```C#
private Bitmap Threshold(string filename)
{
    var src = Cv2.ImRead(filename, ImreadModes.Grayscale);
    var dst = new Mat();
    Cv2.Threshold(src, dst, 100, 255, ThresholdTypes.Binary);
    return dst.ToBitmap();
}
```

# クリップボード操作
実際にゲームをプレイしている際は、[Alt]+[PrintScreen]にてゲーム画面のスクリーンショットを取得する。
このため、クリップボードを監視して画像を取得できるようにする。

## クリップボードを監視して画像を取得する
```C#
public partial class Form1 : Form
{
    [DllImport("user32.dll", SetLastError = true)]
    private extern static void AddClipboardFormatListener(IntPtr hwnd);

    [DllImport("user32.dll", SetLastError = true)]
    private extern static void RemoveClipboardFormatListener(IntPtr hwnd);

    private void Form1_Load(object sender, EventArgs e)
    {
        AddClipboardFormatListener(this.Handle);
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        RemoveClipboardFormatListener(this.Handle);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x31D)
        {
            GetImage();
        }
        else
        {
            base.WndProc(ref m);
        }
    }

    private void GetImage()
    {
        if (!Clipboard.ContainsImage())
            return;

        var img = Clipboard.GetImage() as Bitmap;
    }
}
```





# 参考にした記事など
- [Selenium+ChromeDriverで表示されてしまうコマンドプロンプトの画面を非表示で実行させたい - Google グループ](https://groups.google.com/forum/#!topic/seleniumjp/PXFJTcdnx_0)
- [webdriver - Selenium C# 4.0 release says you can hide the command prompt - how do you actually do it? - Software Quality Assurance & Testing Stack Exchange](https://sqa.stackexchange.com/questions/7898/selenium-c-4-0-release-says-you-can-hide-the-command-prompt-how-do-you-actual)
</div></details>
- [C#でClipboard監視(Windows API) - Qiita](https://qiita.com/kob58im/items/2697ea4c12c72ecd86c8)