自分用に画像翻訳ツール作った

1、2年程前に作って割と重宝したので今更ながら記事にしました。

## つくったもの

翻訳したい範囲を指定して、画像認識→Google翻訳するWindowsFormsアプリ。

![gif1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1873792/2f68cba5-9710-5bc2-c1d1-36bc4e2cd702.gif)

<br>
MS言語パックを読み込めば中国語も翻訳できる。

※非人哉1話より(最近日本でもアニメが放送された)

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1873792/bd6d0a29-1d57-e339-7b31-ac4d2bc5a4cb.gif)


## つくった目的

画像翻訳ツールは調べれば色々でてきますが
キャプチャした画面を赤の他人に送信するのが嫌だったので
少なくとも、その送り先を明確にするために開発しました。


## 構成

構成は以下の通り。

![kousei.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1873792/84951ad2-c6cd-0ea7-68b3-bb96567e384a.png)


①WindowsFormsアプリが画面をキャプチャ

②キャプチャした画像をMicrosoftのOCR機能で文字を読み取る

③読み取った文字をGASへ送信し、GAS上でGoole翻訳をリクエスト

④翻訳結果を画面へ表示

## 実装
実装方法を簡単に紹介。(細かく憶えていないのでソースを見ながらざっくりと書いた)

## 画面キャプチャ


開始ボタン押下時、半透明のキャンバス画面をモニターいっぱいに表示する。

```c#:ScreenCapture.cs
public Boolean SetCanvas()
{
    Boolean result = false;//キャプチャー範囲取得成功
    using (Canvas canvas = new Canvas())
    {
        if (canvas.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {//Canvasクラスからキャプチャー範囲(矩形)を取得
            _CanvasBounds = canvas.GetRectangle();

            if (_CanvasBounds.Width > 0 && _CanvasBounds.Height > 0)
            {//矩形が縦横0以上のサイズがあれば成功
                result = true;
            }
        }
    }
    return result;
}
```

```C#:Canvas.cs
        public Canvas()
        {
            InitializeComponent();

            SetDpiAwareness();//DPIを調整する　※調整しないとモニターによってキャプチャ範囲がずれる

            // モニタ情報の取得
            Screen[] screens = Screen.AllScreens;
            int left = 0;
            int top = 0;
            int right = 0;
            int bottom = 0;
            int i = 0;
            foreach (Screen screen in screens)
            {
                i++;
                if (left > screen.Bounds.Left)
                {
                    left = screen.Bounds.Left;
                }
                if (top > screen.Bounds.Top)
                {
                    top = screen.Bounds.Top;
                }
                if (right < screen.Bounds.Right)
                {
                    right = screen.Bounds.Right;
                }
                if (bottom < screen.Bounds.Bottom)
                {
                    bottom = screen.Bounds.Bottom;
                }
            }

            CanvasStartPos = new Point(left, top);
            this.Location = new Point(left - 1, top - 1);// フォームの開始地点をモニタの左上に設定(モニターサイズピッタリだと別アプリにフォーカス取られる)
            this.Size = new System.Drawing.Size(Math.Abs(left - right) + 1000, Math.Abs(top - bottom) + 1000);    // LeftとRightの距離, TopとBottomの距離
            //※マルチモニターだと解像度の関係か何かで大きさ足りなくなったので固定で大きくした
            this.StartPosition = FormStartPosition.Manual;

            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.BackColor = Color.White;
            this.Opacity = 0.75;
            this.Cursor = Cursors.Cross;
            this.DoubleBuffered = true;
        }
```

<br>
<br>
マウスクリック時に赤の矩形がCanvas画面に描画され

クリックを離したらキャンバス画面を閉じて、矩形の座標を取得する。

```C#:Canvas.cs
/// <summary>
/// 画面描画時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Canvas_Paint(object sender, PaintEventArgs e)
{
    if (_Drawing)
    {
        //マウス選択範囲を赤枠で囲う
        e.Graphics.DrawRectangle(Pens.Red, DrawRectangle());
    }
}

/// <summary>
/// マウスアップ時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Canvas_MouseUp(object sender, MouseEventArgs e)
{
    //画面を閉じる
    this.DialogResult = System.Windows.Forms.DialogResult.OK;
    this.Close();
}
```



<br>
<br>
正しくキャプチャー範囲を取得出来たら

取得した矩形の座標の範囲をキャプチャーし、Bitmap形式の画像にする。

```C#:MainFrm.cs
ScreenCapture screen = new ScreenCapture();
    if (screen.SetCanvas())
    {
     //キャプチャー範囲が設定されたらスクリーンから範囲をキャプチャーする
     Bitmap cap = screen.GetCapture();
    }
```

```C#:ScreenCapture.cs
/// <summary>
/// キャプチャーを取得
/// </summary>
/// <returns>キャプチャー画像</returns>
public Bitmap GetCapture()
{
    int left = SystemInformation.VirtualScreen.Left;
    int top = SystemInformation.VirtualScreen.Top;
    int width = SystemInformation.VirtualScreen.Width;
    int height = SystemInformation.VirtualScreen.Height;

    //枠線(Rectangle)は全モニターの左上から右下までのサイズを指定
    Rectangle rect = new Rectangle(left, top, width, height);
    Bitmap bmp = new Bitmap(_CanvasBounds.Width, _CanvasBounds.Height);
    //キャンパス(Graphics)に描いた絵を保存する器(Bitmap)のサイズはキャンパスと同じ
    //あまり部分が出れば描画されず黒くなり、逆に余りがないとキャンパスがはみ出て器に収まりきらなくなる。
    using (var g = Graphics.FromImage(bmp))
    {
        //描画元のX/Y座標はモニターからコピーしてくる枠組みの左上開始地点
        //描画先のX/Y座標はキャンパス左上を0,0として、描画開始地点を設定する。
        g.CopyFromScreen(_CanvasBounds.X, _CanvasBounds.Y, 0, 0, _CanvasBounds.Size, CopyPixelOperation.SourceCopy);        
    }
    return bmp;
}
```

## 画像認識(OCR)

MicrosoftのOCR機能(OCREngine)に対し、
キャプチャー画像(SoftWareBitmap型)とターゲットの言語を渡して画像認識してもらう。

```C#:MainFrm.cs

/// <summary>
/// OCR、SoftWareBitmapから文字認識
/// </summary>
/// <param name="snap">キャプチャー画像</param>
/// <returns></returns>
private async Task<OcrResult> RecognizeText(SoftwareBitmap snap)
{
    OcrEngine ocrEngine;
    Windows.Globalization.Language language = null;
    switch (this.SourceLangCmb.SelectedIndex)
    {
        case 0:
            language = new Windows.Globalization.Language("ja");                    
            break;
        case 1:
            language = new Windows.Globalization.Language("en-US");
            break;
        case 2:
            language = new Windows.Globalization.Language("zh-CN");
            break;
    }           
    
    ocrEngine = OcrEngine.TryCreateFromLanguage(language);
    if (ocrEngine == null)
    {
        ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
    }

    // OCR実行
    var ocrResult = await ocrEngine.RecognizeAsync(snap);
    return ocrResult;
}

```
<br>
<br>
MS言語パックはWindowsの[設定]-[時刻と言語]-[言語]から

各国の言語がダウンロードできるので、事前にインストールしておく。
![gengo.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1873792/6f18cb2e-3266-2cc3-fd98-e2f4981ac118.png)



## 翻訳

GAS(google api script)を経由してGoogle翻訳APIに翻訳のリクエストを投げる。

GAS側のコードは下記リンクを参考した。
[3 分で作る無料の翻訳 API with Google Apps Script](https://qiita.com/tanabee/items/c79c5c28ba0537112922)


```C#:TransManage.cs

/// <summary>
/// 翻訳処理
/// </summary>
/// <param name="srcTxt">翻訳元</param>
/// <param name="target">翻訳先の言語、0:日本語、1:英語、2:中国語</param>
/// <returns></returns>
public static TransAPIResult Translation(string srcTxt,int target)
{
    TransAPIResult result = new TransAPIResult();

    //サニタイズ
    srcTxt = srcTxt.Replace("#", "＃").Replace("&", "＆").Replace("?", "？");
    srcTxt = SecurityElement.Escape(srcTxt);

    try
    {
        //匿名含む全員
        string url = "「GASのURL」";

        System.Net.WebClient wc = new System.Net.WebClient();
        //NameValueCollectionの作成
        System.Collections.Specialized.NameValueCollection ps =
            new System.Collections.Specialized.NameValueCollection();

        //POSTパラメータの設定
        ps.Add("ID", 「ID」);
        ps.Add("PW", 「パスワード」);
        ps.Add("text", srcTxt);
        ps.Add("source", "ja");
        switch (target)
        {
            case 0://日本語
                ps.Add("target", "ja");
                break;
            case 1://英語
                ps.Add("target", "en");
                break;
            case 2://中国語
                ps.Add("target", "zh");
                break;
        }

        //POSTを送信&受信する
        byte[] resData = wc.UploadValues(url, ps);
        wc.Dispose();

        //翻訳結果エンコード
        string resText = System.Text.Encoding.UTF8.GetString(resData);

        //翻訳結果をJSON形式に変換
        result = JsonConvert.DeserializeObject<TransAPIResult>(resText);
        if (result.code != 1) { throw new Exception(); }
    }
    catch (Exception ex)
    {//翻訳失敗
        Console.WriteLine(ex.ToString());
        throw ex;
    }

    return result;
}
```

## 所感
前述のように開発したのがずいぶん前の為あまり憶えていないのですが、

- 翻訳/OCRの技術選定
- モニターの解像度の違いによるキャプチャ範囲のずれ
- 文字入力ごとに翻訳リクエストを投げると処理を重くなるため、入力待ちの実装(非同期化)

などに苦労した記憶があります。

個人的な使用においては十分使い勝手がよく、開発して良かったと思っています。

ただ、海外の漫画などを翻訳する際にセリフごとにキャプチャする手間がかかる点など、改善の余地があると感じています。(そのうちWindows標準機能に搭載すると思いますが・・・)


## 環境
 - OS：Windows 10 Pro
 - 開発環境：Microsoft Visual Studio 2022
 - 言語：C# (.NET Framework 4.6.2)
 - 翻訳：GAS＋Google翻訳API
 - 画像認識：Microsoft OcrEngine

## 参考サイト様

[【C#】WindowsFormアプリでPC画面の一部を画像として切り出す](https://kakusuke98.hatenablog.com/entry/2019/08/11/231556)

[Custom Snipping Tool using C# WinForms](https://www.codeproject.com/Tips/988951/Custom-Snipping-Tool-using-Csharp-WinForms)

[3 分で作る無料の翻訳 API with Google Apps Script](https://qiita.com/tanabee/items/c79c5c28ba0537112922)