かくすけのいろいろ作るブログ

かくすけの開発者ブログです。開発の他いろいろなモノづくりについて書きます。

【C#】Windows10のOCRを使用する

Visual Studio 2019 C#

こんにちは。かくすけです。
前回、アプリの進捗を報告しました。

kakusuke98.hatenablog.com

画像をキャプチャし、文字認識、翻訳をするところまで作ったわけですが・・・
お金が気になる~~~
なんてったって、今の状態だと
・文字認識API呼び出し用のGateway(Amazon API Gateway)
・文字認識API呼び出し(AWS Lambda)
・文字認識(Google Cloud Vision API)
・翻訳API呼び出し用のGateway(Amazon API Gateway)
・翻訳API呼び出し(AWS Lambda)
・翻訳(Google Cloud Translation API)

という6工程において課金が発生するのです。
うーん。ただでさえ最近収入が減ったのにこれは苦しい。

翻訳は仕方ないとして、せめて文字認識だけでも無料で何とかしたい・・!
それができれば、いずれは「常に文字の変化を監視し、変化したら翻訳をする」といったことも可能になるかもしれません!
私が自動的にではなく手動で翻訳する方向性で考えているのは、少しでも課金を抑えようという考えからですからね。

調べました。
そして知りました。Windows10にはOCRエンジンが標準で搭載されていることを。

codezine.jp

先に調べるべきでした!!!
では、開発中のアプリからWindows10のOCRを使えるようにします。

目次

  • Windows SDKを参照する
  • SoftwareBitmap画像の準備
  • Windows10 の 搭載 OCR を使って画像内文字列を取得する
  • Windows10 の 搭載 OCR 注意点
  • Google Cloud Vision API と Windows10 の 搭載 OCR の比較
    • 価格
    • 精度
    • 速度
  • 使ってみて

Windows SDKを参照する

ここは躓きました。
別記事としてまとめたのでそちらを参考にしてくださいませ!

kakusuke98.hatenablog.com

SoftwareBitmap画像の準備

では、実際に Windows10 のOCRを使用します。
その前に下の記事で書いた画面キャプチャ部分のソースコードも修正しなければなりません。

kakusuke98.hatenablog.com

前の記事ではBitmap形式で画面キャプチャを取得したのですが、Windows10のOCRではSoftwareBitmapという形式で画像を渡さなければいけないのです。
そのくらい簡単にSoftwareBitmap形式でとれるでしょ~♪と軽く見ていましたが全然情報がない・・
仕方なくBitmapで取得したものをSoftwareBitmapに変換する方式に方向転換しました。
しかし変換も簡単にはできないようで・・・

Bitmapとして取得した画像をファイルとして保存し、それをSoftwareBitmap形式で読み込む

という方法をとることにしました。
まわりくどいよ~。ほかに良い方法をご存じの方がいらっしゃったら教えてくださいまし。
画像の保存場所は実行ファイルが配置されている場所。一時的に使うファイルなのですぐに消してます。

        // SoftwareBitmapのスクリーンキャプチャ取得
        public async Task<SoftwareBitmap> GetSoftwareSnapShot()
        {
            // 画面のスクリーンキャプチャをBitmapとして取得(このメソッドは上の記事で作成したもの)
            Bitmap snap = await this.GetSnapShot();

            // 上で取得したキャプチャ画像をファイルとして保存
            var folder = Directory.GetCurrentDirectory();
            var image_name = "ScreenCapture.png";
            StorageFolder appFolder = await StorageFolder.GetFolderFromPathAsync(@folder);
            snap.Save(folder + "\\" + image_name, System.Drawing.Imaging.ImageFormat.Png);
            SoftwareBitmap softwareBitmap;
            var bmpFile = await appFolder.GetFileAsync(image_name);

            // 保存した画像をSoftwareBitmap形式で読み込み
            using (IRandomAccessStream stream = await bmpFile.OpenAsync(FileAccessMode.Read))
            {
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
                softwareBitmap = await decoder.GetSoftwareBitmapAsync();
            }

            // 保存した画像ファイルの削除
            File.Delete(folder + "\\" + image_name);

            // SoftwareBitmap形式の画像を返す
            return softwareBitmap;
        }

参考にさせていただいたページ

作業フォルダのパス取得
www.atmarkit.co.jp

Bitmapからの画像ファイル保存
dobon.net

SoftwareBitmap形式での画像読み込み
docs.microsoft.com

Windows10 の 搭載 OCR を使って画像内文字列を取得する

いよいよOCRを使います! メソッドはこちら

// 取得した画像のテキストを抽出する(Windows10 OCR)
// snap: 文字取得させたいSoftwareBitmap
async Task<OcrResult> RunWin10Ocr(SoftwareBitmap snap)
{
    // OCRの準備。言語設定を英語にする
    Windows.Globalization.Language language = new Windows.Globalization.Language("en");
    OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(language);

    // OCRをはしらせる
    var ocrResult = await ocrEngine.RecognizeAsync(snap);
    return ocrResult;
}

呼び出しは↓みたいな感じで行います。

OcrResult ocrResult = await RunWin10Ocr(softwareSnap);  // softwareSnapはSoftwareBitmap形式の画像
string ocrText = ocrResult.Text;   // OCR結果文字列

↓のソースコードを参考にさせていただきました。

Windows 10 組み込みのOCRを利用するサンプル。UWP の Windows.Media.Ocr が OCR に関するクラス。 · GitHub

Windows10 の 搭載 OCR 注意点

今回は英語を指定してOCRを走らせているのですが、Windows Updateの項目から英語の言語パックをインストールする必要がありました
つまり人に使ってもらうためにはその人のWindows10に言語パックをインストールしてもらう必要があるということ。
苦しいな~、、、個人的に何かソフトウェアをインストールするときに別の何かがインストールされるのは非常に嫌なイメージがあります。
その点Google Vision APIの方は利用者側は何も考えなくていいからいいですよね。かわりに私のお財布が犠牲になるんですけどね。。。

Google Cloud Vision API と Windows10 搭載OCR の比較

# Google Cloud Vision API Windows10 搭載OCR
価格 月に1,000回以上使うと課金が発生 無料
精度 高い 低め(文字の色で精度が変わる)
速度 遅い(おそらくネットワークを介するため) 速い
その他 利用者に言語パックをインストールしてもらう必要あり。
Windows10限定

価格

今回Windows10搭載のOCRを使おうと思ったきっかけは価格の問題なのでもちろんそちらの方が安いです。
ただし、Google Cloud Vision API も月に1,000回までは無料で呼び出せるので個人で使う分にはなかなかそれを超えることはないと思います。
今回のアプリはいつかは公開するつもりなのでこの課金が怖いんですよね。

ちなみにこの課金は↓のような価格になってます(2019/10/02 現在)
f:id:kakusuke98:20191002215609p:plain

しばらくは1,000回使われるごとに150円ほどの課金といったところですね。
安く感じますが。。ちりも積もればというやつです。
私のように自作APIからこのGoogle Cloud Vision API を呼ぶとなったらさらにお金はかかっちゃいますしね。

精度

精度は所感ではありますがGoogle Cloud Vision APIの方が高い気がします。
例えば、次のような画像を読ませてみたところ

f:id:kakusuke98:20191002215126p:plain

以下のような結果になりました。

Google Cloud Vision API

private void save As Images Too StripMenultem Cl ick (object sender, EventArgs e)
Ut ility.SaveAsImages (snaps);
/private void saveAsWordTool StripMenultem Click(object sender, Event Args e)
//Ut ility.SaveAs Wo rd (snaps) ;
//

Windows10 搭載OCR

saveAsImagesToolStripMenuItem_Cl ick(obj ect sender, Ut i lit y .SavegsImages(snaps); Eventgrgs e)

元画像がソースコードなのも精度比較用としてはどうかと思いますが(自分でやっておいて)、Windows10 搭載のOCR明度の差が少ない文字をほぼ認識しません。
黒背景に緑や青といった文字の部分は全然認識していません。
Windows10搭載のOCRを使用する場合には事前に画像加工を行った方がよさそうです。
また、変なところで次の行の認識を行ったりしています。1行目最後の "EventArgs e)"がなぜか最後に来てるんですよね。","を区切りとして扱っちゃってたりするんでしょうか。この問題は背景色に関係なく発生しました。

速度

速度は断然Windows10搭載のOCRの方が速いです。びっくりしました。
その比較がこちら。

Google Cloud Vision API
f:id:kakusuke98:20191002221036g:plain

Windows10 搭載OCR
f:id:kakusuke98:20191002221046g:plain

"取得範囲"列の"Name_1"が表示されてから"翻訳前"の列が読み込まれるまでの間が処理時間です。

・・・はやすぎません!?

確かにGoogle Cloud Vision APIの方は私の自作APIを介しているのでそこで時間がかかっているかもしれませんが。。この速度に慣れていたので初めてWindowsの方を使ったときはびっくりしました。
ネットワークを介さないとこんなにもOCRって素早く動くんですね!!!

ということで一長一短という印象です。
利用シーンにあわせて選択するのがいいんだろうなあ。

使ってみて

動くところまで開発したわけですが、やはり一長一短でどちらを使うかは悩むところです。

Windows10は利用者さんに言語パックをインストールしてもらう必要があるのですが、今回私がやったようにWindowsのコントロールパネルを開いて設定してもらわないといけないんであれば厳しいですね。
このアプリからボタンなどでさらっとインストールさせてあげられるのであれば、Windows10に搭載されているOCRを使いたいところです。
精度についてはきっと画像加工処理とかいれたらもっといい感じになるはず。

ひとまず開発した「Windows10 OCR 版」と「Google Cloud Vision API OCR 版」はいつでも切り替えられる状態にして、どうするかは後で考えることにします。
今回使わないことになっても、どちらもきっと別の場面で活用できるでしょう!

【Visual Studio】Windows Runtime の参照が一覧に表示されない問題の対処

2019/09/25 Visual Studio 2019 にて C#Windows Runtime を使おうとしたところ参照できない問題にぶつかった。
一時的な問題のようで情報も少なく、困っている人もいるかもしれないのでメモ。

Visual Studio の参照ウィンドウでWindows の項目を選択すると・・・
あ、あれ・・・?
f:id:kakusuke98:20190925230804p:plain 一つも表示されていないぞ・・・?
そこには「参照が Windows SDK に見つかりませんでした。」という一文が。
どうやら 2019/09/25 現在 Windows のバージョン1903で問題が発生しているようで、そのままじゃうまくいかないらしい。

参考
developercommunity.visualstudio.com

Visual Studioから
C:\ Windows \ System32 \ WinMetadata
にアクセスできなくなっているのが原因のようです。

C:\ Windows \ System32 \ WinMetadata をまるっと以下のディレクトリにコピーします。
C : \ Windows \ SysWOW64 \ WinMetadata

再度、参照ウィンドウを開くと
ちゃんと読み込めました~!
f:id:kakusuke98:20190925005153p:plain

2019/09/30 追記

上の方法の方がいいんだろうけど、他で循環参照が起こってしまう問題が発生したので別の読み込み方をするようにした。
参照に以下を追加した
C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.16299.0\Windows.winmd

f:id:kakusuke98:20190930123133p:plain

この方法だとWindowsの項目からわざわざひとつづつ選ばなくていいが、たぶんWindowsの機能全部を読み込んでしまう。

【C#】(進捗 2019/09/23) PC画面の一部を切り出し、翻訳するWindowsアプリの開発

こんにちは。かくすけです。
PC画面の一部を切り出し、翻訳するWindowsアプリ、ちょっとずつ開発を進めています。
現時点での動作をGifにしました。

最低限使えるアプリになりました。

前回の記事以降に開発したのは以下の内容
・アプリ全体の大枠レイアウト
・画面からの文字列取得結果表示処理
・翻訳結果表示処理
・ショートカットキーでの翻訳処理呼び出し機能
・ショートカットキー変更機能
・処理非同期化

前回の記事はこちら
kakusuke98.hatenablog.com

細かい内容を全部まとめていては開発が全然進まないので
参考にさせていただいたページをまとめました。

参考

【画面からの文字列取得結果表示、翻訳結果表示】

文字取得、翻訳結果の表示には今回 Windows Form の dataGridView を使用しました。

dataGridViewへの行追加
www.sejuku.net

dataGridView表示範囲内での折り返し改行表示設定
dobon.net

【ショートカットキーでの翻訳処理呼び出し】

作成した Windows Form アプリがアクティブ状態の場合、ショートカットキー入力取得は比較的簡単です。
しかし、今回はゲームアプリがアクティブの状態でもショートカットキー入力を取得する必要があり、苦労しました。
その際に次のページを参考にさせていただきました。

フォーム外ショートカットキー取得
qiita.com

入力されたショートカットキーが対応するものかどうかの判定には次のページを参考にしました。

ショートカットキー判別
kitigai.hatenablog.com

【ショートカットキー変更機能実装】

設定されたショートカットキーの保持には連想配列(Dictionary)を使用しています。

連想配列使用(Dictionary)
www.sejuku.net

【非同期化】

非同期化には苦労しました。
しかし、ゲームプレイを邪魔せず使用してもらうためには必須の機能です。
(これをしないと翻訳するたびにゲームの操作が一時的にできなくなる)

非同期処理実行(async/await)
qiita.com

どこのページをみても"await Task.Delay(1000);"を使われており、実際に重い処理で動かしている例が少ないためピンときませんでした。

await Task.Run(() =>
{
    // 処理内容
 });

とすることで重い処理を非同期化できました。

今回のアプリでは
・画像取得処理
OCR API呼び出し処理
・翻訳API呼び出し処理

を非同期化することで連続して翻訳できるようにしました。

今後の課題

見ていただいてわかると思いますが、最低限動くものができただけで課題はまだまだあります。
今後はまず以下の対応を行う予定です。
・文字列取得と翻訳の精度をなんとか上げられないか頑張る
・フルスクリーンモードのゲームに対応していないため対応する

【Ruby】Google Translation API を使って文字列を翻訳する

開発環境OS CeonOS7 Rubyのバージョン 2.5.0

こんにちは。かくすけです。
今回はGoogleのTranslation APIを使って文字列を翻訳させます。

最終的に完成させたいアプリ

kakusuke98.hatenablog.com

前回の記事

kakusuke98.hatenablog.com

前回はGoogle Cloud Vision APIを使って画像内の文字列を取得するところまで作りました。
今回は、画像内から取得した文字列を翻訳させる部分を作ります!

今回は単純にRubyのコードのみの紹介です。
実際に使うときは前回と同様にLambda関数を作成しWindows Form Appから呼び出すのですが、前回とほぼ一緒になっちゃいますからね。

目次

  • Gemfileの準備
  • 書いたコードの紹介
  • 動作確認

Gemfileの準備

rubyつかいにはおなじみGemfileを準備します。
まず適当に専用のディレクトリを作って

$ mkdir google-translate

そのディレクトリ内に次のようなGemfileを作成します。

source 'https://rubygems.org'
gem 'rest-client'

シンプル!API呼び出しにはいつも通り、私の大好きなRestClientを使います。

github.com

bundle install でGemをインストール

$ bundle install --path vendor/bundle

つらつらと色々表示されたあと、確認するといろいろとファイルとディレクトリが追加されています。

[f:id:kakusuke98:20190826205313p:plain]

書いたコードの紹介

同じ場所に以下の内容のrubyファイルを作成します。
私は"google_translate.rb"ファイル名にしました。

require 'json'
require 'rest-client'

#--------------------------------------------
# メインメソッド
# 英語を日本語に翻訳する
# Google Translation API 使用
#--------------------------------------------
url = "https://translation.googleapis.com/language/translate/v2?key={API Key}"
payload = {
  q: "{翻訳したい英文}",
  target: 'ja',
  source: 'en'
}
header = {:content_type=>"text/html; charset=utf-8"}
RestClient.post(url, payload.to_json, header) { |response, request, result|
  case response.code
  when 200
    # 成功時の処理
    parse_response =  JSON.parse(response)
    lambda_response_body = parse_response['data']['translations'][0]['translatedText']
    p lambda_response_body  # Google Translation APIからのレスポンスを出力
  else 
    # 失敗時の処理
    parse_response =  JSON.parse(response)
    p parse_response
  end
}

↓の API Key はGoogleのコンソールで生成します。
f:id:kakusuke98:20190826210051p:plain

生成の仕方は前回の記事の項目「Google Cloudのプロジェクト設定」を見たらわかりますよ!
前回の記事で使用したのは Google Cloud Vision API ですがここの手順は同じです。
すべて「Cloud Vision API」を「Translation API」に読みかえてくださいね
kakusuke98.hatenablog.com

こちらは Translation API に投げるデータの設定をしている部分。

f:id:kakusuke98:20190826210559p:plain

q翻訳したい文字列
target翻訳結果として受け取りたい言語
source翻訳前の文字列の言語

今回は「英文→日本語文」の翻訳をしたいのでtargetを'ja'、sourceを'en'としています。

こちらは実際にAPI呼び出し処理をする部分。
f:id:kakusuke98:20190826211251p:plain Translation API から受け取ったjsonデータ内の"parse_response['data']['translations'][0]['translatedText']"内に翻訳結果が入っているので、その部分を出力します。

動作確認

ではでは実際に翻訳します!
前回に引き続きゲーム「The Wolf's Bite」の画面に含まれるこちらの画像から取得した英語を翻訳します。
f:id:kakusuke98:20190822230724p:plain

rubyコードを実行!

$ ruby google_translate.rb

そして返ってきた翻訳結果は...
f:id:kakusuke98:20190826211958p:plain

おお...可愛い見た目に反したカタイ文章!!
ゲーム的にあえて固い英文を使って表現されているのか、Translate API がカタめに翻訳するクセがあるのかはわかりませんが、言いたい意味はわかりますね。
「3匹のこぶた」のお話にでてきた狼がレンガ造りの家を壊せなかったことが理由でレストラン事業に方向転換したと。

内容的にあえてカタく表現しているような気がしてきました。

さて、今回は前回とほぼ同じ内容だったのもあって簡単に動かすことができました。
はやく実際にゲームをしながら使いたいな~~

これでとりあえず作りたいアプリの大枠機能をすべて作り終えました!!
しばらくはちまちまとした作業になると思うのでブログの更新は間が空くかもしれません。
でもなんとか作ってみせるぞ!

今回も最後までありがとうございました!

【Amazon API Gateway】【AWS Lambda】【Ruby】【C#】Google Cloud Vision API を使って画像内の文字を取得する

こんにちは。かくすけです。
今回はGoogleのCloud Vision APIを使って画像内の文字を取得(OCR)させます。

最終的に完成させたいアプリ

kakusuke98.hatenablog.com

前回の記事

kakusuke98.hatenablog.com

前回はWindowsの画面の範囲キャプチャを取得するところまで作りました。
今回はその画像からGoogle Cloud Vision APIを使って文字取得します。

目次

  • 今回の記事で開発する内容
  • Google Cloudのプロジェクト設定
    • 認証情報の準備
  • Google Cloud Vision API を使った文字取得方法の確認
  • AWS Lambda でAPIを呼び出す
  • Amazon API Gateway の準備
  • C#からのAPI呼び出し
  • 文字認識結果

今回の記事で開発する内容

今回はいろいろなサービスを使用します。
全体像はこんな感じ

f:id:kakusuke98:20190822221049p:plain

Windows Form App】
前回作った「Windowsの画面の一部を画像として取得する」アプリです。
このアプリで取得した画像を Amazon API Gateway に送信します。

Amazon API Gateway
今回 Windows Form App とAWS Lambda の橋渡しをする役割を持ちます。
Lambda関数を別のアプリから呼び出すためにはこの Amazon API Gateway を使う必要があります。

AWS Lambda】
サーバーのことを気にせず、呼び出したいときにだけ使用できる関数を作成できます。
今回は Amazon API Gateway から受け取った画像データを Google Cloud Vision API に送り 、画像内の文字列を受け取ります。
受け取った文字列を Amazon API Gateway に受け渡します。

Google Cloud Vision API
このサービスに画像を渡すと、画像内に含まれている文字列を取得してくれます。

なぜ Windows Form App から直接 Google Cloud Vision API に画像を送らなかったかというと、将来的にサーバー側でログをとったり不正な呼び出しを制限するためです。

GoogleCloudプロジェクト設定

公式の次ページの項目「プロジェクトを設定する」を参考にGoogle Cloud Vision APIのプロジェクトを準備します。
今回はCloud Storage を使わない方式で使用するので、「Cloud Storage バケットを作成する」以降は行いません。

クイックスタート  |  Cloud Vision API ドキュメント  |  Google Cloud

プロジェクト作成→課金許可設定→Cloud Vision API の有効化
ページの通りにやればできるはず!

※この機能は課金が発生しますので使いすぎには気を付けましょう

認証情報の準備

APIでアクセスするための認証情報を準備します。
Google Cloud Platform のコンソール画面から認証情報を作成します。

Google Cloud Platform

f:id:kakusuke98:20190816000802p:plainf:id:kakusuke98:20190816001005p:plain

使用する認証方法を聞かれます。
今回はGoogle関連のサービスのユーザーデータにアクセスする必要はないので「APIキー」を使用します。
f:id:kakusuke98:20190816001449p:plain

APIキーが生成されました。このキーは絶対に外部の人に見られないように気を付けましょう!
f:id:kakusuke98:20190816001652p:plain

これでGoogleCloudプロジェクトの設定はおしまいです。

ちなみに以下の記事ではYoutubeの自分の動画一覧を取得する必要があったのでOathを使用しました。

kakusuke98.hatenablog.com

Google Cloud Vision API を使った文字取得方法の確認

実際に画像から文字取得させる方法を確認しました。

OCR機能の使用方法の説明ページ(公式)
cloud.google.com

RESTで処理を呼び出します。
今回はOAuthではなくAPI Keyでの認証なので、ヘッダーではなくURLパラメータに認証情報(key)を持たせることで認証します。上の公式ページの例はOAuth式なのでページの通りヘッダーにKayを持たせようとしてちょっとつまりました。
上のページには画像URLを渡す方法が例になっているのですが、私は画面キャプチャしたローカル画像データから文字取得させたいので↓の画像赤枠部分の「base64 エンコード文字列」で渡す方法を確認します。

f:id:kakusuke98:20190816002650p:plain

赤枠部分のリンク先がこちら
Make a Vision API request  |  Cloud Vision API Documentation  |  Google Cloud

↓のような形式でデータを送ればいいみたいですね。
f:id:kakusuke98:20190816003020p:plain

AWS Lambda でAPIを呼び出す

Lambda関数の作り方は前に書いた記事↓とほぼ同じなので、そちらを参考にされてください。

kakusuke98.hatenablog.com

今回は上で書いた通り、 Amazon API Gateway から受け取った画像データを Google Cloud Vision API に送る関数を準備しました。
今回作成した関数の内容(ruby)

require 'json'
require 'rest-client'

#--------------------------------------------
# メインメソッド
# Amazon API Gatewayから受け取った画像をGoogle Cloud Vision APIに送る
#--------------------------------------------
def google_ocr_handler(event:, context:)
  url = "https://vision.googleapis.com/v1/images:annotate?key=#{ENV['API_KEY']}"
  payload = {
    requests: [{
      image: {
        content: event['body']
      },
      features: [
        {
          type: 'TEXT_DETECTION',
          maxResults: 1
        }
      ],
      imageContext: {
        languageHints: 'en'
      }
    }]
  }
  header = {:content_type=>"text/html; charset=utf-8"}
  RestClient.post(url, payload.to_json, header) { |response, request, result|
    case response.code
    when 200
      parse_response =  JSON.parse(response)
      lambda_response_body = parse_response['responses'][0]['textAnnotations'][0]['description']
      return { statusCode: 200, body: lambda_response_body }
    else
      return { statusCode: 500, body: 'error' }
    end
  }
end

項目「認証情報の準備」で作成したAPI KeyをLambdaの環境変数(API_KEY)として登録しています。
f:id:kakusuke98:20190822223229p:plain

これが Google Cloud Vision API に送る内容です。
「content: event['body']」ここの"event['body']"は Amazon API Gateway から受け取った内容、つまり文字取得させたい画像の base64 エンコード文字列 です。
また、「languageHints: 'en'」は必須ではないのですが、画像に含まれている言語をここで指定することで認識の精度が上がるそうです。今回は英語が含まれている画像を送るので'en'と指定しています。
f:id:kakusuke98:20190822223406p:plain

ここが実際に画像を送り、文字列を取得する部分です。
ここで注意。return で返している内容がポイントです。Amazon API Gateway を使ってLambda関数を呼び出す場合には必ず「{statusCode: 三桁数字, body '文字列'}」という形でなければエラーが返ってしまいます。
今回は返したい内容が文字列だったので良かったのですが、もっと複雑な内容を返したい場合には困りそうですね。無理矢理文字列にして送って、受け取った側で分解しないといけないのかな?
f:id:kakusuke98:20190822223918p:plain

Amazon API Gateway の準備

前項目で作成したLambda関数を呼び出すための Amazon API Gateway を準備します。
ここはすみません。メモも取らずにサカサカやっちゃったのであまりまとめられません。

こちらのページを参考にさせていただいて、特に問題なく進めることができました。

qiita.com

C#からのAPI呼び出し

いよいよ最終段階!
Windows Form App から Amazon API Gateway を呼び出すメソッドです。

C#からのAPI呼び出しについてはこちらのページを参考にさせていただきました
www.ipentec.com

        // 取得した画像のテキストを抽出する(Google Vision API)
        // snap: 文字取得させたいBitmap
        private void RunOcr(Bitmap snap)
        {
            string url = string.Format("{Amazon API Gateway 呼び出し用のURL}");
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Headers.Set("x-api-key", "{Amazon API Gateway 用のAPI Key}");
            request.Method = "POST";
            request.ContentType = "text/plain";

            // Bitmap(snap)をbyteに変換
            System.IO.MemoryStream memory_stream = new System.IO.MemoryStream();
            snap.Save(memory_stream, System.Drawing.Imaging.ImageFormat.Bmp);
            byte[] imageBytes = memory_stream.ToArray();
            string base64String = Convert.ToBase64String(imageBytes);
            request.SendChunked = true; 

            try
            {
                // API Gateway を呼び出す
                using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
                {
                    writer.WriteLine(base64String);
                }
                var response = (HttpWebResponse)request.GetResponse();
                var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
                System.Diagnostics.Trace.WriteLine(responseString);  // 返ってきた文字列を Visual Studio のコンソールに出力する
            }
            catch (WebException e)
            {
                System.Diagnostics.Trace.WriteLine(e);
            }
        }

このメソッドを呼び出すことで画像内に含まれる文字列を取得できます。
引数として文字取得したいBitmap画像要素を渡します。

ここでBitmapをBase64に変換しています。
f:id:kakusuke98:20190822225809p:plain

ここが実際の通信部分。
うまくいってもいかなくてもとりあえず返ってきた内容をVisual Studioのコンソールに出力します。
f:id:kakusuke98:20190822225900p:plain

文字認識結果

今回開発した機能を実際に使ってみました。
このアプリはゲーム画面翻訳するために作っているのでゲーム画面を使わせていただきました。気になっていたけど日本語対応していないので今までプレイできていなかったゲーム「The Wolf's Bite」の画面に含まれる英語を取得します。
作成したWindows Form Appで以下ゲーム画面の赤枠部分を画像として取得し、APIに送ってみました。
f:id:kakusuke98:20190822230724p:plain

そして結果は・・・
f:id:kakusuke98:20190822231213p:plain

完璧!!!!

このゲームはテキスト表示がはっきりしているものうまくいった理由だと思いますが、ここまで完璧だと嬉しくなります!
いろいろなゲーム画面で試さないとですね。

次回は今回取得した英文を Google Translate API で日本語に翻訳します。
長文になりましたがお読みくださりありがとうございました!

【C#】WindowsFormアプリでPC画面の一部を画像として切り出す

■開発環境

 ※C#初心者です。

こんにちは。かくすけです。

前回の記事の続きです

kakusuke98.hatenablog.com

前回アプリを作り始めたところですね。
今回はやることの第一歩である「PC画面の一部を画像として取得する」部分を開発します!

目次

  • 参考ページ
  • いただいたソースの埋め込み
  • エラー対処
  • 動かしてみる!
  • カスタマイズ

参考ページ

ありがたーい参考ページ(英語)

https://www.codeproject.com/Tips/988951/Custom-Snipping-Tool-using-Csharp-WinForms

英語のページですがまさに私のやりたいことに近いです。
しかもソースまで配布されていて…本当にありがたい!
Thank you so much!!

いただいたソースの埋め込み

MySnippingTool_Source\MySnippingTool\MySnippingToolの中身を
作ったばかりのプロジェクトにいただいたファイルを追加します。

f:id:kakusuke98:20190725233138p:plain

とりあえず動きを見てみよう!
と、開始ボタンを押したのですが、ビルド失敗。。。

f:id:kakusuke98:20190725233644p:plain

エラー文のメモを忘れました。開発者としてまだまだですね。
大体の内容としては

  • MainWindow.resxは信用ならんから読み込めんよ
  • 参照が足りんよ

という内容
順番に対処しましょう

エラー対処

■MainWindow.resxは信用ならんから読み込めんよ 問題

なぜ信用ならないかというとWebページから持ってきたファイルだから。
Windowsさんが安全のためにブロックしてくれています。
対処法は2つ
(1)そのファイルの使用許可設定をする
(2)そのファイルを使わない

(1)そのファイルの使用許可設定をする
私は信用する!という方は
WindowsGUI操作で MainWindow.resx の許可設定をします。

f:id:kakusuke98:20190725235600p:plain

f:id:kakusuke98:20190725235920p:plain

この状態でまたプロジェクトに入れてあげればOK!

(2)そのファイルを使わない
やっぱりちょっと怖い...という方は
いっそのこと MainWindow.resx を使わずに進めます。
このファイル、使うアイコンの設定をしているだけなんで読み込まなくても何とかなるみたいです。

そのアイコンの読み込み部分である「MainWindow.Designer.cs」の137行目をコメントアウトします。

f:id:kakusuke98:20190726000451p:plain

これでOK!

■参照が足りんよ 問題

ソースをいただいたアプリではどうも Microsoft Word のファイルに出力する機能があるらしく
この機能を使うために特殊な参照を設定してるみたいです。

こちらも対処法は2つ
(1)参照を追加する
(2)その機能を使わない

(1)参照を追加する
Word出力機能を使いたい方はこちらの方法で対処しましょう。
[プロジェクト]→[参照の追加]で参照マネージャーウィンドウを開き
[COM]内の以下二つにチェックを付けてOKボタン

f:id:kakusuke98:20190726001421p:plain f:id:kakusuke98:20190726001707p:plain

これでWord出力もつかえるはず!(動作未確認)

(2)その機能を使わない
Word機能を使わなくていいなら、無駄な参照は省いた方が良いはず。
その呼び出しをしている部分をごっそりコメント化します!
その場所は「Utility.cs」の26~55行目です。

f:id:kakusuke98:20190726001928p:plain

メソッドそのまま消すと他に影響でそうで消せないチキンです。
もうちょっと内容を理解してから取捨選択します。

動かしてみる!

気を取り直して、再度開始ボタンをポチッ
(私は2つの問題両方(2)の方法で回避しています。)

f:id:kakusuke98:20190725233644p:plain

すると、画面全体がうっすらと白掛かりました。

f:id:kakusuke98:20190728235112p:plain

この状態で画面のどこかを左上から右下にドラッグ&ドロップすると

f:id:kakusuke98:20190728235142p:plain

シンプルなウィンドウが表示されました。
この状態で[Ctrl]+[Shift]+[Z]すると

f:id:kakusuke98:20190728235319p:plain

ドラッグ&ドロップした範囲の加画像が表示されます。
ここの表示ではちょっとつぶれた状態ですが、画像として保存したところ元の比率で保存されました。
画像保存はメニューの[File]→[Save as Images]からできます。

こんな簡単に画面キャプチャの仕組みが作れるなんて本当にありがたいです!
以降は自分用メモ

カスタマイズ

デフォルトの動作がちゃんと動いたので、自分の作りたいアプリにあわせたカスタマイズを行います。
主に以下のカスタマイズを行います。

  • 不要な機能の削除
  • デュアルモニタ対応
  • 見た目の調整

デュアルモニタ対応だけ説明を簡単に残しておきます。(説明漏れあるかも)
重要なのはCanvas.csの編集です。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MySnippingTool
{
    class Canvas : Form
    {
        Point startPos;      // mouse-down position
        Point currentPos;    // current mouse position
        Point canvasStartPos;
        bool drawing;

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

            canvasStartPos = new Point(left, top);
            this.Location = canvasStartPos;  // フォームの開始地点をモニタの左上に設定
            this.Size = new System.Drawing.Size( Math.Abs(left - right), Math.Abs(top - bottom));    // 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.MouseDown += Canvas_MouseDown;
            this.MouseMove += Canvas_MouseMove;
            this.MouseUp += Canvas_MouseUp;
            this.Paint += Canvas_Paint;
            this.KeyDown += Canvas_KeyDown;
            this.DoubleBuffered = true;
        }

        private void Canvas_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
                this.Close();
            }
        }

        // 指定した範囲を返す
        public Rectangle GetRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X, currentPos.X),
                Math.Min(startPos.Y, currentPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        // 指定中の範囲を返す
        public Rectangle DrawRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X - canvasStartPos.X, currentPos.X - canvasStartPos.X),
                Math.Min(startPos.Y - canvasStartPos.Y, currentPos.Y - canvasStartPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        // 範囲指定開始地点の保持
        private void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            currentPos = startPos = e.Location + new Size(canvasStartPos.X, canvasStartPos.Y);
            drawing = true;
        }

        // 範囲指定マウスドラッグ中の処理
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            currentPos = e.Location + new Size(canvasStartPos.X, canvasStartPos.Y);
            if (drawing) this.Invalidate();
        }

        // 範囲指定終了処理
        // 範囲終了位置はCanvas_MouseMoveで取得している
        private void Canvas_MouseUp(object sender, MouseEventArgs e)
        {
            this.DialogResult = System.Windows.Forms.DialogResult.OK;
            this.Close();
        }

        // 選択中範囲の描画
        private void Canvas_Paint(object sender, PaintEventArgs e)
        {
            if (drawing) e.Graphics.DrawRectangle(Pens.Red, DrawRectangle());
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Canvas
            // 
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "Canvas";
            this.ShowInTaskbar = false;
            this.ResumeLayout(false);

        }
    }
}

"// モニタ情報の取得"というコメントの下につらつらと追加してます。
このifの嵐を何とかしてくれ!と思われそうで怖いですw
考え方は簡単

f:id:kakusuke98:20190811230534p:plain

何画面あろうと全画面の四隅値をとってあたかも1つの画面のように扱っています。

GetRectangle()は画像の取得に使用する座標を返し、
DrawRectangle()は範囲指定のドラッグ中にマウスの動きに合わせて矩形を表示するために使っています。

        // 指定した範囲を返す
        public Rectangle GetRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X, currentPos.X),
                Math.Min(startPos.Y, currentPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        // 指定中の範囲を返す
        public Rectangle DrawRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X - canvasStartPos.X, currentPos.X - canvasStartPos.X),
                Math.Min(startPos.Y - canvasStartPos.Y, currentPos.Y - canvasStartPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

これでデュアルモニタに対応できます!

【C#】PC画面の一部を切り出し、翻訳するWindowsアプリの開発1

こんにちは。かくすけです。
今回からは個人的に欲しいと思っている
「PC画面の一部を翻訳してくれる」アプリケーションを開発していきます!

かくすけWebページも作りたくはありますが、9月以降にいただけるお仕事がある程度決まったので
しばらくは営業ツールは必要ないのかな・・と
いま作りたいものを優先することにしました

2020/06/24 このツールを「CapTra」という名称でベータ版を公開しました!!
↓の記事に詳細とダウンロード方法を載せています。

kakusuke98.hatenablog.com

ほしい理由はずばり日本語化されていない海外のゲームをもっと楽しみたいから!!
英語は全く読めないわけではないのですが、ストーリー重視のゲームとかだとやっぱり理解できずに悔しい思いをするんですよね・・・
きっと同じ気持ちの人は多いはず!(特に私の周りには)ということでこのアプリ開発を開始しました!

目次

  • 完成イメージ
  • 処理の流れ
  • 使用サービスなど
  • 新しいプロジェクトの作成

完成イメージ

イメージは↓のような感じ

f:id:kakusuke98:20190711173207p:plain

英語と日本語を例に出してますが、他の言語も対応したいと思ってます。
先に翻訳する画面範囲を指定しておいて、指定したキーを押すことで翻訳してくれるイメージです。
範囲指定は何か所かできるようにしたいけど、画面のあちこちに文字が出るタイプのゲームには対応できない想定です。
リアルタイム取得ではなくキーを押したタイミングで翻訳するのはお金の問題。
使おうと思っているGoogleの翻訳APIは使った分だけ課金されるのでそこを抑えるためです。

画面をドラッグして翻訳するソフトはすでにあるみたいなのですが、ゲームによっては文字送りが早くて追いつかなかったりするのでこのような形式で作るつもりです!

処理の流れ

翻訳処理の流れは次のような感じ

  1. ショートカットキーの入力を取得
  2. 指定範囲の画像を取得
  3. 画像の文字を認識
  4. 認識した文字を翻訳
  5. 翻訳結果の表示

使用サービスなど

このアプリを開発するために、次のようなソフトを使用する予定です!

C#は過去に次のようなアプリを作ったことがあります(クオリティはおいといて)

  • 仮想ペットとおしゃべりできるアプリ
  • 指定範囲の画面キャプチャをWebページ(自作)に送信するアプリ
  • 指定したWebページの指定した場所の文字列を取得するアプリ

「指定範囲の画面キャプチャをWebページ(自作)に送信するアプリ」を5年ほど前に作ったので
きっと今回の指定範囲の画像取得もできるでしょう。

新しいプロジェクトの作成

では内容にはいります。
今回はVisual Studio 2019を使って開発を行います。

Visual Studio のインストール方法の説明は割愛させていただきます。
次のページからインストールしてくださいね!

docs.microsoft.com

では、アプリケーションを作るための新規プロジェクトを作成しましょう。
人によって画面は違うかもしれませんが、何となくで進めてくださいw
Visual Studio 2019を開き「新しいプロジェクトの作成」を選択します。

f:id:kakusuke98:20190711181733p:plain

次に使用するプロジェクトテンプレートを選択します。
今回はWindowsフォームアプリケーションを選択し「次へ」ボタンをクリックします。

f:id:kakusuke98:20190711182254p:plain

プロジェクト名などを付けられる画面が表示されます。
お好きな名前、場所を選択して作成しましょう。(作成時にちょっと時間がかかります)

f:id:kakusuke98:20190711182954p:plain

作成が完了すると、空の状態の開発画面が表示されます。

f:id:kakusuke98:20190711183156p:plain

さあ、楽しい開発の時間です!!
といいたいところですが、今回はここまで。
次回は画面の基礎を作っていきます。