{
    "componentChunkName": "component---src-templates-blog-post-jsx",
    "path": "/post/ocr-web-gl-with-tesseract-js/",
    "result": {"data":{"site":{"siteMetadata":{"title":"WEB EGG","author":"Leko - CTO at Yuimedi"}},"markdownRemark":{"id":"8f785e3a-ed23-561f-8023-e8d552520cf1","excerpt":"この記事は闇の魔術に対する防衛術 Advent Calendar 2019…","html":"<blockquote>\n<p>この記事は<a href=\"https://qiita.com/advent-calendar/2019/yaminomajutu\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">闇の魔術に対する防衛術 Advent Calendar 2019</a>2 日目の記事です。</p>\n</blockquote>\n<p>ある日、会社で<a href=\"http://typingx0.net/sushida/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">寿司打</a>というタイピングの速度や正確さを競うゲームが流行った。<br>\nみんなやってるので私も挑戦してみたところ、結果は惨敗。全エンジニアの中でもっともスコアが低かった。もともとタイピングが早くも正確でもないことを自覚していたつもりだったが、現実を突きつけられ大人しく家に返って枕を濡らそうと思った。が、あまりの悔しさにまみれ「JSer ならタイピング速度ではなく JS で勝負すればいいんだ」とダークサイドに堕ち闇の力に手を染めてしまった。</p>\n<p>本記事はムキになって寿司打（WebGL 版）のスコアを稼ぐ自動化 JavaScript の話。<br>\nなお動作確認には Ubuntu 19.04、Google Chrome 80.0.3955.4 を使用した。</p>\n<h2 id=\"寿司打-webgl-版\" style=\"position:relative;\"><a href=\"#%E5%AF%BF%E5%8F%B8%E6%89%93-webgl-%E7%89%88\" aria-label=\"寿司打 webgl 版 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>寿司打 WebGL 版</h2>\n<p><a href=\"http://typingx0.net/sushida/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http://typingx0.net/sushida/</a></p>\n<p>寿司打はもともと Flash ゲームだったが、いつからか WebGL 版に移植されたらしい。<br>\n難読化されてなかったのでサイト上で動作している JavaScript を軽く読んでみたところ Unity が使用されていることが分かった。“WebGL”版とうたっているだけあり、ゲーム画面は Canvas で描画されている。Canvas を用いているのであれば JavaScript から座標データを読み取って OCR できそうだ。</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 668px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/02d6ea1a9a6018a60b4f0a41fc511615/c67d4/2019-12-01-15-26-15.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 43.712574850299404%; position: relative; bottom: 0; left: 0; background-image: url('data:image/svg+xml,%3csvg%20xmlns=\\'http://www.w3.org/2000/svg\\'%20width=\\'400\\'%20height=\\'176\\'%20viewBox=\\'0%200%20400%20176\\'%20preserveAspectRatio=\\'none\\'%3e%3cpath%20d=\\'M0%2088v88h22v-69l1-70c1-2%202-2%206-2l5-1c0-2-5-5-8-5h-3V12l63-1h64v18H39l-2%203-2%203h172c160%200%20172%200%20173%202l1%2070v69h20V0H0v88m107%2013l2%202c2%200%201%204-1%207-2%202-13%206-20%208h-4l3%202%2011%201c7%200%207%200%207%202l-8%204-7%202%203%202%203%202-5%204-7%204-3%202c-2%202-2%202%201%205l3%202%206-2%205-3c0%204-3%207-12%2011l-14%207-2%203%204%204c11%2010%2019%207%2028-10%202-6%203-6%209-9l10-2%203-1v10c0%2012%200%2013-4%2013-7%200-18-9-14-12%201-2%205%201%205%204-1%203%200%203%205%200%207-4%206-8-2-8-12%200-13%207-3%2017l3%204h19v-30h8c9%200%2011-1%208-5-1-2-16-4-17-3h-4l-5-2c-2%200%200-1%205-3%208-4%209-7%200-7-3%200-4%200-5-3-1-2-2-3-4-3-6%200-1-4%209-7%203-2%202-4-3-6l-4-2c0-2-8-6-10-6l-2%202m161%201l3%204c2%203%202%204%202%209v5l-11%205-10%205c0%203%2011%205%2018%203%204-2%203%2012-1%2015-5%205-9%207-11%207-5-2-6%201-3%2011%202%205%203%204%209-3%207-7%208-7%208%202-1%205-1%205-6%204l-2-1%203%204c2%204%203%204%207%204h5v-14c1-12%201-14%205-21%204-9%204-12%201-7l-3%203v-6c0-4%200-5%203-7%204-3%204-4%200-7-2-1-2-2-1-4%200-4-1-7-5-10-5-3-11-4-11-1m-77%205l-17%202c-6%200-5%201%202%205%208%205%2011%205%2017%201%209-5%2021-7%2023-4%203%202%202%2053%200%2055-2%203-12%202-18%200-6-3-6-2-2%201%2010%208%2011%209%2021%209%2011%200%2010%202%2010-29%200-22%201-24%203-28%203-6%201-8-7-12-7-4-16-4-32%200m129%208l-6%203c-8%202-23%208-23%209l13%202%204-1v20c0%2019%200%2020-2%2021h-15c-4-2-3%200%202%203%205%204%205%204%2015%204h10v-19c-1-17-1-19-3-22-4-6-3-8%205-9l10-2c3-1%204-4%200-7-3-2-10-3-10-2m-128%207c-2%202-18%207-23%207h-4l2%202c1%202%203%203%205%203%202%201%202%201%201%203-2%201-2%202%2012-3l16-4%206-1c2-4-11-10-15-7m-2%2018c-7%201-11%203-15%206-3%202-3%202-5%201-2-3-3-2-3%202s1%207%207%2013c5%205%207%205%207%200%200-3-1-5-4-8l-2-5a595%20595%200%200018-2c0%201-5%207-8%208-5%202%200%206%209%206%208%201%2010-1%207-5l-2-2%205-3c6-4%206-4%200-9-6-4-6-4-14-2\\'%20fill=\\'%23d3d3d3\\'%20fill-rule=\\'evenodd\\'/%3e%3c/svg%3e'); background-size: cover; display: block;\"\n  ></span>\n  <picture>\n          <source\n              srcset=\"/static/02d6ea1a9a6018a60b4f0a41fc511615/5251b/2019-12-01-15-26-15.webp 167w,\n/static/02d6ea1a9a6018a60b4f0a41fc511615/7390e/2019-12-01-15-26-15.webp 334w,\n/static/02d6ea1a9a6018a60b4f0a41fc511615/7c056/2019-12-01-15-26-15.webp 668w,\n/static/02d6ea1a9a6018a60b4f0a41fc511615/6c8c7/2019-12-01-15-26-15.webp 723w\"\n              sizes=\"(max-width: 668px) 100vw, 668px\"\n              type=\"image/webp\"\n            />\n          <source\n            srcset=\"/static/02d6ea1a9a6018a60b4f0a41fc511615/21521/2019-12-01-15-26-15.png 167w,\n/static/02d6ea1a9a6018a60b4f0a41fc511615/86d36/2019-12-01-15-26-15.png 334w,\n/static/02d6ea1a9a6018a60b4f0a41fc511615/74866/2019-12-01-15-26-15.png 668w,\n/static/02d6ea1a9a6018a60b4f0a41fc511615/c67d4/2019-12-01-15-26-15.png 723w\"\n            sizes=\"(max-width: 668px) 100vw, 668px\"\n            type=\"image/png\"\n          />\n          <img\n            class=\"gatsby-resp-image-image\"\n            src=\"/static/02d6ea1a9a6018a60b4f0a41fc511615/74866/2019-12-01-15-26-15.png\"\n            alt=\"2019 12 01 15 26 15\"\n            title=\"2019 12 01 15 26 15\"\n            loading=\"lazy\"\n            decoding=\"async\"\n            style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n          />\n        </picture>\n  </a>\n    </span></p>\n<h2 id=\"tldr-最終的なコード\" style=\"position:relative;\"><a href=\"#tldr-%E6%9C%80%E7%B5%82%E7%9A%84%E3%81%AA%E3%82%B3%E3%83%BC%E3%83%89\" aria-label=\"tldr 最終的なコード permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>TL;DR 最終的なコード</h2>\n<p>こちら。</p>\n<blockquote>\n<p>— <a href=\"https://gist.github.com/Leko/713f278bf2ffd3cc010f21145293492c\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">寿司打(Sushi-da) AI http://typingx0.net/sushida/play.html</a></p>\n</blockquote>\n<p>必要最小限の自動化しかしてないため、以下のような手順で実行する。</p>\n<ol>\n<li><a href=\"http://typingx0.net/sushida/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">寿司打 WebGL 版</a>を開く</li>\n<li>タイトル画面が表示されるまで待つ</li>\n<li>開発者ツールを起動し、以下の JS を実行</li>\n<li>手動でゲームを開始する。最初の寿司が置かれるまで待つ</li>\n<li><strong>タブを切り替えて画面を表示しないようにして待つ</strong></li>\n</ol>\n<p>音を出してプレイすれば分かりやすく、モリモリ寿司を食べてくれる。<br>\n詳しくは後述するが、最後の手順が重要で、なぜかこれをやらないと WebGL のピクセル情報が全て 0 になってしまう。画面が非表示になってるときだけうまく動くという悲しい完成度だけど、そこさえ守れば動作はする。</p>\n<p>Chrome 拡張とかにすれば多少便利になると思うけど、そこまでやるようなことでもないので割愛する。</p>\n<h2 id=\"基本戦略\" style=\"position:relative;\"><a href=\"#%E5%9F%BA%E6%9C%AC%E6%88%A6%E7%95%A5\" aria-label=\"基本戦略 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>基本戦略</h2>\n<blockquote>\n<p>— <a href=\"https://blog.leko.jp/post/automate-cookie-clicker-with-js/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">CookieCliker を js から操作してみる | WEB EGG</a></p>\n</blockquote>\n<p>６年前に CookirClicker を自動化したことがあった。CookieClicker のゲーム画面は HTML だったので JavaScript による操作の自動化や値の読み取りが非常に簡単だった。<br>\nこと WebGL においてはどうだろう。当然ながら JavaScript から目的の要素を取り出しクリックすることも、表示されているテキストを JavaScript の文字列として読み取ることもできない。その制約で真っ先に思いつくであろう戦略は OCR だ。</p>\n<p>ゲーム画面のうち入力すべきローマ字が書かれている近辺の座標に OCR をかけ解析された文字列を JavaScript からキーボード入力を模倣すれば自動化できそうだ。ローマ字部分は背景と文字のコントラスト差が明瞭なので二値化などの前処理も不要だろう。</p>\n<p>この記事を書くにあたって類似記事を調べたところ OCR を用いて寿司だを自動化する先駆者がすでにいた。この記事では Selenium を使用して画面のキャプチャを OCR している。アプローチは同じだが今回は JS でスコアを稼ぐことを目的にしているため全く同じ実装ではない。</p>\n<blockquote>\n<p>— <a href=\"https://qiita.com/tetsuya-ogawa/items/09f9578a30bffc47603b\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">弊社の新卒タイピング課題「寿司打」を自動化?してみた - Qiita</a></p>\n</blockquote>\n<h2 id=\"デバッグ過程\" style=\"position:relative;\"><a href=\"#%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E9%81%8E%E7%A8%8B\" aria-label=\"デバッグ過程 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>デバッグ過程</h2>\n<p>以下の内容は、最終的なコードに至るまでの変遷をつらつらと書いていく。</p>\n<h3 id=\"webgl2renderingcontext-と-canvasrenderingcontext2d\" style=\"position:relative;\"><a href=\"#webgl2renderingcontext-%E3%81%A8-canvasrenderingcontext2d\" aria-label=\"webgl2renderingcontext と canvasrenderingcontext2d permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>WebGL2RenderingContext と CanvasRenderingContext2D</h3>\n<p>クラスタによると思うが「Canvas を操作する」と聞いたとき、以下のようなコードをまずイメージすると思う。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> canvas <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">'canvas'</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">const</span> context <span class=\"token operator\">=</span> canvas<span class=\"token punctuation\">.</span><span class=\"token function\">getContext</span><span class=\"token punctuation\">(</span><span class=\"token string\">'2d'</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>getContext に<code>'2d'</code>を与えると<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">CanvasRenderingContext2D</a>というオブジェクトが返却される。**しかし寿司打の画面で実行してみるとなぜか null が返ってくる。**Canvas 要素は存在しているのに<code>getContext('2d')</code>の戻り値が null。もちろんタイポなどはしていない。<br>\nstackoverflow で調べてみたところ、「すでに他のコンテキストで呼び出されている場合も戻り地が null になるよ」と書いてあったので試したところ、寿司打においては<code>'webgl2'</code>という値ですでにコンテキストが生成されていた。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> context <span class=\"token operator\">=</span> canvas<span class=\"token punctuation\">.</span><span class=\"token function\">getContext</span><span class=\"token punctuation\">(</span><span class=\"token string\">'2d'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// => null</span>\n<span class=\"token keyword\">const</span> context <span class=\"token operator\">=</span> canvas<span class=\"token punctuation\">.</span><span class=\"token function\">getContext</span><span class=\"token punctuation\">(</span><span class=\"token string\">'webgl'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// => null</span>\n<span class=\"token keyword\">const</span> context <span class=\"token operator\">=</span> canvas<span class=\"token punctuation\">.</span><span class=\"token function\">getContext</span><span class=\"token punctuation\">(</span><span class=\"token string\">'welgl2'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// => WebGL2RenderingContext</span></code></pre></div>\n<p>getContext に<code>webgl2</code>を指定した場合の戻り値は<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">WebGL2RenderingContext</a>になる。2d のときとはメソッド体系が全く異なっている。WebGL を扱ったことがないので、そもそもわからないしメソッドの説明を読んでもよくわからない。</p>\n<p>だが OCR において必要なのはあくまで特定座標（吹き出し部分）のピクセル情報を抜き出すことなので、ピクセル情報が読めさえすればいい。ということで、OCR 用の Canvas を生成し、そちらに対して WebGL2 の Canvas の一部分を描画させる作戦を取る</p>\n<h2 id=\"drawimage\" style=\"position:relative;\"><a href=\"#drawimage\" aria-label=\"drawimage permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>drawImage</h2>\n<p>CanvasRenderingContext2D の <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">drawImage</a> は Canvas を受け取りその Canvas を画像として再描画させることができる。これを利用し不慣れな WebGL2 を扱わずに CanvasRenderingContext2D の世界観に変換できる。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token constant\">WIDTH_ROMAJI_AREA</span> <span class=\"token operator\">=</span> <span class=\"token number\">440</span>\n<span class=\"token keyword\">const</span> <span class=\"token constant\">HEIGHT_ROMAJI_AREA</span> <span class=\"token operator\">=</span> <span class=\"token number\">30</span>\n<span class=\"token keyword\">const</span> <span class=\"token constant\">X_ROMAJI_AREA</span> <span class=\"token operator\">=</span> <span class=\"token number\">30</span>\n<span class=\"token keyword\">const</span> <span class=\"token constant\">Y_ROMAJI_AREA</span> <span class=\"token operator\">=</span> <span class=\"token number\">230</span>\n\n<span class=\"token comment\">// 寿司打のゲーム画面のCanvas</span>\n<span class=\"token keyword\">const</span> canvas <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">'canvas'</span><span class=\"token punctuation\">)</span>\n<span class=\"token comment\">// OCR用のCanvasを生成する。DOMにappendしなくても使える</span>\n<span class=\"token keyword\">const</span> bufferCanvas <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">createElement</span><span class=\"token punctuation\">(</span><span class=\"token string\">'canvas'</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">const</span> bufferContext <span class=\"token operator\">=</span> bufferCanvas<span class=\"token punctuation\">.</span><span class=\"token function\">getContext</span><span class=\"token punctuation\">(</span><span class=\"token string\">'2d'</span><span class=\"token punctuation\">)</span>\n\nbufferContext<span class=\"token punctuation\">.</span><span class=\"token function\">drawImage</span><span class=\"token punctuation\">(</span>\n  canvas<span class=\"token punctuation\">,</span> <span class=\"token comment\">// drawImageにはHTMLCanvasElementが渡せる</span>\n  <span class=\"token comment\">// gl.canvasのこの領域を、</span>\n  <span class=\"token constant\">X_ROMAJI_AREA</span><span class=\"token punctuation\">,</span>\n  <span class=\"token constant\">Y_ROMAJI_AREA</span><span class=\"token punctuation\">,</span>\n  <span class=\"token constant\">WIDTH_ROMAJI_AREA</span><span class=\"token punctuation\">,</span>\n  <span class=\"token constant\">HEIGHT_ROMAJI_AREA</span><span class=\"token punctuation\">,</span>\n  <span class=\"token comment\">// コピー用キャンバスのこの領域に描画</span>\n  <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n  <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n  <span class=\"token constant\">WIDTH_ROMAJI_AREA</span><span class=\"token punctuation\">,</span>\n  <span class=\"token constant\">HEIGHT_ROMAJI_AREA</span>\n<span class=\"token punctuation\">)</span></code></pre></div>\n<p>DOM に append しなくても動作するが、デバッグ用に画面に表示してみるとこのように入力すべきローマ字部分だけが描画されている Canvas を手に入れることができた。入力するローマ字の長さによって吹き出しのサイズが変わるため横幅を広めに確保している。余計なノイズは除去したのであとは OCR にかけるだけで入力すべき文字列が手に入るはずだ。</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 668px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/316bcfd3008f7c33d9d13d96ef979eb4/d67ca/2019-12-01-15-21-25.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 11.377245508982037%; position: relative; bottom: 0; left: 0; background-image: url('data:image/svg+xml,%3csvg%20xmlns=\\'http://www.w3.org/2000/svg\\'%20width=\\'400\\'%20height=\\'45\\'%20viewBox=\\'0%200%20400%2045\\'%20preserveAspectRatio=\\'none\\'%3e%3cpath%20d=\\'M0%2023v22h401V0h-18v3l-3%205c-1%202-9%202-180%202H22l-1-2-2-5V0H0v23m46-9H19v21h57V24l-1-10H46m281%200l-1%2011v10h57V14h-16a2142%202142%200%2001-40%200\\'%20fill=\\'%23d3d3d3\\'%20fill-rule=\\'evenodd\\'/%3e%3c/svg%3e'); background-size: cover; display: block;\"\n  ></span>\n  <picture>\n          <source\n              srcset=\"/static/316bcfd3008f7c33d9d13d96ef979eb4/5251b/2019-12-01-15-21-25.webp 167w,\n/static/316bcfd3008f7c33d9d13d96ef979eb4/7390e/2019-12-01-15-21-25.webp 334w,\n/static/316bcfd3008f7c33d9d13d96ef979eb4/7c056/2019-12-01-15-21-25.webp 668w,\n/static/316bcfd3008f7c33d9d13d96ef979eb4/80c40/2019-12-01-15-21-25.webp 714w\"\n              sizes=\"(max-width: 668px) 100vw, 668px\"\n              type=\"image/webp\"\n            />\n          <source\n            srcset=\"/static/316bcfd3008f7c33d9d13d96ef979eb4/21521/2019-12-01-15-21-25.png 167w,\n/static/316bcfd3008f7c33d9d13d96ef979eb4/86d36/2019-12-01-15-21-25.png 334w,\n/static/316bcfd3008f7c33d9d13d96ef979eb4/74866/2019-12-01-15-21-25.png 668w,\n/static/316bcfd3008f7c33d9d13d96ef979eb4/d67ca/2019-12-01-15-21-25.png 714w\"\n            sizes=\"(max-width: 668px) 100vw, 668px\"\n            type=\"image/png\"\n          />\n          <img\n            class=\"gatsby-resp-image-image\"\n            src=\"/static/316bcfd3008f7c33d9d13d96ef979eb4/74866/2019-12-01-15-21-25.png\"\n            alt=\"2019 12 01 15 21 25\"\n            title=\"2019 12 01 15 21 25\"\n            loading=\"lazy\"\n            decoding=\"async\"\n            style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n          />\n        </picture>\n  </a>\n    </span></p>\n<h3 id=\"タブが表示されているとすべての座標が-0-になってしまう\" style=\"position:relative;\"><a href=\"#%E3%82%BF%E3%83%96%E3%81%8C%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%81%A8%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%BA%A7%E6%A8%99%E3%81%8C-0-%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86\" aria-label=\"タブが表示されているとすべての座標が 0 になってしまう permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>タブが表示されているとすべての座標が 0 になってしまう</h3>\n<p>結局解決できなかったのだが、記事の冒頭に書いたとおりタブが表示されているとこの現象が発生する。<br>\ndrawImage だけでなく WebGL2RenderingContext の readPixels メソッドも同様にすべての座標データが 0（つまり黒）で返ってきてしまうため WebGL2 関連の問題だと思うが、調査の勘所がわからず解決を諦めた。\n最前面に表示するタブを切り替えて裏側で処理させれば動作するため、いったんそれで妥協した。もしかしたら Chrome のバージョンや OS によっては動作するのかも知れない。</p>\n<h2 id=\"ocr微調整\" style=\"position:relative;\"><a href=\"#ocr%E5%BE%AE%E8%AA%BF%E6%95%B4\" aria-label=\"ocr微調整 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>OCR+微調整</h2>\n<p>OCR 自体は<a href=\"https://github.com/naptha/tesseract.js\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">tesseract.js</a>というライブラリを使用している。OCR をするなら<a href=\"https://www.chromestatus.com/feature/4757990523535360\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Shape Detection API</a>の<a href=\"https://paul.kinlan.me/detecting-text-in-an-image/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">TextDetector</a>を使えたら面白いと思ったが、Origin-trial の申込みが必要だったので今回は見送った。</p>\n<p>まずは tesseract.js を画面に挿入する。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> script <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">createElement</span><span class=\"token punctuation\">(</span><span class=\"token string\">'script'</span><span class=\"token punctuation\">)</span>\nscript<span class=\"token punctuation\">.</span>src <span class=\"token operator\">=</span>\n  <span class=\"token string\">'https://unpkg.com/tesseract.js@v2.0.0-alpha.16/dist/tesseract.min.js'</span>\nscript<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">onload</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// ここにOCRの処理を書いていく</span>\n<span class=\"token punctuation\">}</span>\ndocument<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span><span class=\"token function\">appendChild</span><span class=\"token punctuation\">(</span>script<span class=\"token punctuation\">)</span></code></pre></div>\n<p>次に OCR をかける。ここは tesseract.js の README の通りなので説明は割愛する。やってることは<code>TesseractWorker</code>のインスタンスを生成して<code>recognize</code>を呼ぶだけ。解析の速度を上げるために解析される可能性の文字列のホワイトリストを指定している。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> worker <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Tesseract<span class=\"token punctuation\">.</span>TesseractWorker</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  tessedit_char_whitelist<span class=\"token operator\">:</span> <span class=\"token string\">'!?,-abcdefghijklmnopqrstuvwxyz01234567890'</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\nworker\n  <span class=\"token punctuation\">.</span><span class=\"token function\">recognize</span><span class=\"token punctuation\">(</span>bufferCanvas<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">r</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    r<span class=\"token punctuation\">.</span>text <span class=\"token comment\">// => 解析された文字列</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">catch</span><span class=\"token punctuation\">(</span>console<span class=\"token punctuation\">.</span>error<span class=\"token punctuation\">)</span></code></pre></div>\n<h2 id=\"キー入力の模倣\" style=\"position:relative;\"><a href=\"#%E3%82%AD%E3%83%BC%E5%85%A5%E5%8A%9B%E3%81%AE%E6%A8%A1%E5%80%A3\" aria-label=\"キー入力の模倣 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>キー入力の模倣</h2>\n<p>OCR で文字列を解析した後は、JavaScript からキーボード入力させるために<code>KeyboardEvent</code>を生成して<code>dispatchEvent</code>するだけだが一点注意があった。入力を同期的に実行すると入力が早すぎて処理が追いつかないという問題が発生した。なので１文字ごとに意図的に数ミリ秒のディレイを入れることで入力漏れが起きないようにしている。</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// キー入力の模倣をする</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">sendKey</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">key</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> event <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">KeyboardEvent</span><span class=\"token punctuation\">(</span><span class=\"token string\">'keypress'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> charCode<span class=\"token operator\">:</span> key<span class=\"token punctuation\">.</span><span class=\"token function\">charCodeAt</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  document<span class=\"token punctuation\">.</span><span class=\"token function\">dispatchEvent</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// キー入力を同期でやると処理が早すぎてうまく行かないので入力を遅延させる</span>\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">sendKeys</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">keys<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> inputDelay <span class=\"token operator\">=</span> <span class=\"token constant\">DELAY_KEYINPUT</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Promise</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">resolve</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    keys<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">''</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">c<span class=\"token punctuation\">,</span> i</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">sendKey</span><span class=\"token punctuation\">(</span>c<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>i <span class=\"token operator\">+</span> <span class=\"token number\">1</span> <span class=\"token operator\">===</span> lastParsed<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token function\">resolve</span><span class=\"token punctuation\">(</span>keys<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> inputDelay <span class=\"token operator\">*</span> i<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span>\n\nworker\n  <span class=\"token punctuation\">.</span><span class=\"token function\">recognize</span><span class=\"token punctuation\">(</span>bufferCanvas<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">r</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">sendKeys</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">.</span>text<span class=\"token punctuation\">)</span> <span class=\"token comment\">// => 解析された文字列を入力</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">catch</span><span class=\"token punctuation\">(</span>console<span class=\"token punctuation\">.</span>error<span class=\"token punctuation\">)</span></code></pre></div>\n<p>これで OCR した結果を画面に入力できるようになった。あとは OCR の精度を向上し、ループさせれば良い。</p>\n<h2 id=\"誤解析誤入力の防止\" style=\"position:relative;\"><a href=\"#%E8%AA%A4%E8%A7%A3%E6%9E%90%E8%AA%A4%E5%85%A5%E5%8A%9B%E3%81%AE%E9%98%B2%E6%AD%A2\" aria-label=\"誤解析誤入力の防止 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>誤解析・誤入力の防止</h2>\n<p>ここまでの処理で寿司打が自動化できるかといえば、NO である。なぜなら解析結果がかなりの確率でそのまま入力可能な形式にはなってないからだ。スコアを上げるために微調整したことは以下の通り</p>\n<ul>\n<li>枠線が<code>I</code>とか<code>|</code>（パイプ）と認識される\n<ul>\n<li>ノイズ除去のため認識結果を正規表現で整形</li>\n</ul>\n</li>\n<li>解析結果が正しくない\n<ul>\n<li>小文字英字＋<code>!</code>+<code>?</code>+<code>-</code>だけが文字列に含まれていれば OK、それ以外は無視</li>\n<li>解析に失敗した場合その寿司を逃すことになるが、ミスのほうが痛いためミスしないように調整した</li>\n</ul>\n</li>\n<li>明らかに正しくない文字列が解析結果に出てくる（<code>jgKx</code>など）\n<ul>\n<li>４文字以下の文字列が解析されたら無視するようにした</li>\n</ul>\n</li>\n<li>キーボード入力中にキーボード入力してしまい入力が阻害されミス判定になる\n<ul>\n<li>キーの入力中が重複しないように排他制御</li>\n</ul>\n</li>\n</ul>\n<p>これらの微調整を終えた上で、出てくる寿司（入力する文字）の組み合わせが良ければミス 0 の解析ができるようになった。</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 668px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/4e3531e2e221701c2e38d665e404a6f2/30d16/2019-12-02-21-24-15.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 83.83233532934132%; position: relative; bottom: 0; left: 0; background-image: url('data:image/svg+xml,%3csvg%20xmlns=\\'http://www.w3.org/2000/svg\\'%20width=\\'400\\'%20height=\\'335\\'%20viewBox=\\'0%200%20400%20335\\'%20preserveAspectRatio=\\'none\\'%3e%3cpath%20d=\\'M0%20168v167h401V0H0v168M31%2029v6h336V22H31v7M7%20171v126l1-126V46h12l12-1H7v126M31%2067v193h323l6-7%207-6V67H31m43%209l-18%205c-19%205-18%203-12%2026a1545%201545%200%20016%2019%20149%20149%200%200038-9c3-2%203-6-2-22-5-19-6-20-12-19m-3%204a1298%201298%200%2001-28%208c-1%201%207%2034%209%2035%201%201%2033-7%2035-9%201%200%200-7-3-18-4-14-6-18-8-18l-5%202m166%203l-2%202c-1%200-2%201-1%202l-1%202-1%201c1%201%200%202-1%202v3h4c2-2%202-2%202%200%201%203%204%203%204-1%200-2%201-3%202-3l1-1-1-1-2-1-1-2-1-2c1-2-2-3-2-1m-119%202c-3%203-1%2011%203%2011%202%200%205-3%205-6%200-6-5-9-8-5m15-1c-2%202-1%208%201%2010%204%204%2010-3%207-8-2-3-6-4-8-2m21%201c-2%204%201%2011%204%2011%204%200%207-8%204-12-2-2-6-1-8%201m22-1v1c1%201%202%202%201%203%200%201-1%201-2-1-2-2-2-2-2%202v6c0%202%202%201%202-1s1-3%202-3c4-1%205-1%205%201l-2%201h-2l2%202c3%203%204%202%204-5%201-4%200-6-1-6h-7m73%200v3l-2%201%201%201v1c-2%201%200%204%202%204l3%201c4%204%205%201%205-7%200-4-1-5-5-5l-4%201M72%2092v4l-1%202-1-2c-1-4-3-3-4%201-1%203-1%204%201%205v2l1%202c1%201%204-2%204-4v-3l1%202c0%204%201%205%203%204%201-2%202-2%203-1l2%201-1-2-2-7v-1l-2-1v-2l-3-1-1%201m-19%206c1%202-1%205-2%204l-1%201%201%205c0%203%200%203%201%202%202-2%208-3%2012-1%202%200%202%200%201-2l-2-2c0%202-2%201-2-1l-2-3-2-1c0-2-2-4-3-4l-1%202m67%2010c-4%201-5%208-2%2010%203%203%206%202%207-1%202-5-1-11-5-9m14%201c-2%201-3%208-1%2010s6%201%207-1c2-2%202-2%204%200%203%203%207%203%208-1%200-2%201-2%203%201%203%203%203%203%205%202%204-3%204-10%200-12-2-1-2-1-5%202-2%203-3%203-3%201-1-4-5-4-8-1-2%202-2%202-3%201-2-3-4-4-7-2m-83%2024c-5%202-6%204-6%2018%200%2013%200%2013%202%2016l3%202h148c157%200%20152%200%20153-5l1-14c0-11%200-12-2-15l-3-2-147-1-149%201m-2%205c-3%204-3%2021%200%2025l2%203h147c142%200%20148%200%20149-2%203-3%203-25%200-27-2-2-13-2-149-2H51l-2%203m45%206c-6%201-9%209-5%2014%204%203%209%202%2010-3%201-3-2-6-6-6-2%201-3%200-1-2h4l3-1c0-1-2-3-5-2m9%201c-3%204-3%2010%201%2013%203%203%204%203%207%200%204-4%203-12-2-14-3-1-3-1-6%201m22-1c-2%200-5%205-5%208s4%208%207%208c2%200%206-5%206-8l-2-7c-3-2-3-2-6-1m12%201c-2%201-2%202-2%204v5c-3%205%205%208%2010%204%202-2%202-2%201-5l-1-4c3-4-4-7-8-4m14%200c-5%205-2%2015%204%2015%202%200%206-5%206-8l-4-8-2-1-4%202m94-1v1c1%201%200%201-1%201h-2l-1%202c0%202%200%202%202%202v1l-2%202-1%201c-2%201-3-1-2-3v-5c2-2%200-3-2%200-2%201-2%202-1%202v4l-1%203c2-1%202%200%202%202l1%203%201-2c0-2%200-2%203-2h5l-2%202v2c2%202%204%201%204-3%200-2%201-3%202-3%201-1%201-1-1-2s-2-1%200-1v-1l-1-3c-1-3-2-5-3-3m-68%201c-1%201-1%201%201%201l2%203c-1%202-3%202-4%200-1-4-2-4-2%204l1%206%201-3c0-3%200-3%203-3s6-2%204-3-2-5-1-5c2%200%203%201%203%207%201%205%201%205-2%205h-2l2%202c4%203%205%201%205-7v-7l-5-1-6%201m-91%2055h-2l-1%201-2%206c-3%205-3%205%201%205l3%201c0%203%204%203%204%200l2-2c2%200%201-2-1-2-1-1-2-1-1-4%200-4-2-7-3-5m-20%201v3l4-1%203-1c2%201%200%204-3%204-4%201-3%203%200%203s4%200%204%202l-4%201c-5%200-5%202-1%203%204%202%2010-2%207-6v-4c3-4-5-7-10-4m43%201c-2%203-2%209%200%2012%202%202%206%202%208%200%204-3%201-10-4-9h-2c0-1%203-3%205-2l2-1c0-3-6-3-9%200m212%200c-6%205-1%2015%205%2013%203%200%205-6%205-9-2-5-7-8-10-4M48%20230v23c1%201%2010%202%2075%202%2085%200%2076%201%2076-13%200-15%2010-13-76-13l-75%201m166%200l-1%2012%201%2011%2066%201c75%200%2067%202%2067-13%200-7%200-9-2-10-2-2-129-3-131-1M31%20272v9l1%206h49l51-1%201-9v-7H32l-1%202m118-1v9l1%207h48l51-1c2-1%202-2%202-9v-7h-51l-51%201m119%200l-1%208v8h49l51-1c1-1%202-3%202-8v-7l-51-1-50%201m-161%201l-2%202-1%202-2%205c-2%204-1%205%202%202l2-2c1%201%200%202-1%203s0%201%204%201%206%200%205-1l-1-1c-1%200-2-1-2-3l1-3%201-2c1-1%200-2-1-2l-2-1h-3m104%201c0%201-1%202-2%201l-1%201%202%202c2%200%203%203%201%203l-1-1-1-1-1%202v2l-1%201c1%201%2013%202%2014%201s1-1-1-1-2%200-2-3l1-4v-2c0-2-3-3-3-1h-5m-58%2025l96%201a5857%205857%200%2000-96-1m-86%205c-3%202-4%205-3%207h1l2-2v4c0%205-1%205-3%204-3%200-2%201%201%205%207%209%2022%204%2022-8%200-8-5-12-12-12l-8%202m60-1c-3%201-5%206-3%207h1l2-1%201%203c-2%207-2%207-4%205-1-1-2-2-1-3l-1-3c-2-1-1%207%201%2011%204%205%2013%206%2019%201%2011-9%200-26-15-20m57%202l-3%204c1%201%202%201%204-1%201-2%204-3%205-3%205-2%2014%204%2014%209%200%204-7%2010-12%2010-4%200-11-6-11-11%200-2%200-2-1%200-3%206%206%2015%2014%2014%209-2%2014-12%209-20-4-6-14-7-19-2m60%200c-3%203-4%204-1%206h1c0-2%2011-2%2013-1h1c3-4%207%202%205%206-1%203-15%204-17%201h-2c0%202%200%202-2%200-3-2-3%200%200%204%207%2011%2024%205%2023-8-1-7-5-11-13-11-4%200-5%200-8%203m61-1l-3%203c-1%202%200%205%201%203h16c3-2%204%200%204%204s-1%206-4%204h-2l-6%201c-4%200-5-1-5-4-1-3-1-3-1-1%200%203-2%205-4%203s-1%200%201%204c4%207%2014%208%2020%201%204-5%204-13-1-17-4-4-12-4-16-1\\'%20fill=\\'%23d3d3d3\\'%20fill-rule=\\'evenodd\\'/%3e%3c/svg%3e'); background-size: cover; display: block;\"\n  ></span>\n  <picture>\n          <source\n              srcset=\"/static/4e3531e2e221701c2e38d665e404a6f2/5251b/2019-12-02-21-24-15.webp 167w,\n/static/4e3531e2e221701c2e38d665e404a6f2/7390e/2019-12-02-21-24-15.webp 334w,\n/static/4e3531e2e221701c2e38d665e404a6f2/7c056/2019-12-02-21-24-15.webp 668w,\n/static/4e3531e2e221701c2e38d665e404a6f2/0fa99/2019-12-02-21-24-15.webp 672w\"\n              sizes=\"(max-width: 668px) 100vw, 668px\"\n              type=\"image/webp\"\n            />\n          <source\n            srcset=\"/static/4e3531e2e221701c2e38d665e404a6f2/21521/2019-12-02-21-24-15.png 167w,\n/static/4e3531e2e221701c2e38d665e404a6f2/86d36/2019-12-02-21-24-15.png 334w,\n/static/4e3531e2e221701c2e38d665e404a6f2/74866/2019-12-02-21-24-15.png 668w,\n/static/4e3531e2e221701c2e38d665e404a6f2/30d16/2019-12-02-21-24-15.png 672w\"\n            sizes=\"(max-width: 668px) 100vw, 668px\"\n            type=\"image/png\"\n          />\n          <img\n            class=\"gatsby-resp-image-image\"\n            src=\"/static/4e3531e2e221701c2e38d665e404a6f2/74866/2019-12-02-21-24-15.png\"\n            alt=\"2019 12 02 21 24 15\"\n            title=\"2019 12 02 21 24 15\"\n            loading=\"lazy\"\n            decoding=\"async\"\n            style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n          />\n        </picture>\n  </a>\n    </span></p>\n<p>ランキングでも無事一位を獲得できた。社内どころか世界最高の寿司職人になれたが、闇の力で手に入れたスコアは実に空虚で、賢者モードになりながらこの記事を書いた。<br>\nOCR の解析精度も処理速度も入力の最適化もまだまだ最適化の余地があるため、大きくスコアが改善できたらその方法をブログ等にしてください。</p>\n<p>※寿司打のランキングは２週間に１回リセットされるそうで、チートでスコアを出してしまっても今後ずっと人力でハイスコアが塗り替えられないことは起こらないようになっている。</p>","timeToRead":11,"frontmatter":{"title":"寿司打の限界を目指して ~WebGLのOCR~","tags":["JavaScript"],"date":"November 27, 2019","featuredImage":{"childImageSharp":{"fluid":{"tracedSVG":"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='400'%20height='336'%20viewBox='0%200%20400%20336'%20preserveAspectRatio='none'%3e%3cpath%20d='M0%20168v168h401V0H0v168M30%2021v7l-1%206h340V20H200L30%2021m126%2023c-2%202-3%207-1%2010%201%203%206%203%208%200%205-7-2-16-7-10m40-1l1%201%202%203c-1%202-3%203-3%201l-1-3c-1-1-2%200-2%205l1%207%201-3c0-4%200-4%203-4%204%200%205-1%204-3v-1l-1-1c-1-2-1-2%201-2s2%201%202%203l1%206c0%201%200%202-3%202l-2%201%202%201c4%203%205%202%205-7l-1-6-5-1-5%201M5%20171v128l1-127V45h12l11-1H5v127M30%2066v195h325l7-7%207-6V66l-170-1-169%201m28%2013c-17%204-20%206-20%209%200%204%209%2036%2011%2037%202%202%204%201%2021-3%2014-4%2017-5%2018-7l2-3-4-18c-5-17-7-20-10-20l-18%205m1%202l-17%206c-1%201%206%2032%208%2035%201%202%2033-6%2036-8%201-2-3-18-7-32-2-6-2-6-20-1m179%201l-2%201c-1%201-2%202-1%203l-1%202-1%201c1%201%200%202-1%202l-1%202h1l1%201v1l3-1%201-2%201%201c-1%201%202%204%203%203l1-3c0-2%201-3%202-3l1-1-2-1-2-1v-3l-1-2h-2m-132%201v11c3%203%2010%200%208-4v-4c0-3-1-4-4-4l-4%201m49%201c-4%203-1%2011%203%2011s6-7%204-11c-1-3-5-3-7%200m21-1l1%201%201%203c-1%201-2%201-3-1l-2-2v5l1%206%201-2c0-2%201-3%202-3%204-1%205-1%205%201l-1%201c-3%200-2%202%200%203%203%202%204%200%204-7l-1-5-4-1-4%201m73%200c-1%200-1%201%201%202v1c-2%200-3%202-1%202v1c-2%201-1%204%201%204l4%201c4%203%205%201%205-6v-5l-4-1-6%201M71%2091v4l-1%202-2-2c0-4-1-4-3%201-1%203-1%204%201%205v2l1%202c2%201%204-2%203-4l1-2c1-1%201%200%201%202%200%204%201%204%203%202h2l2%201%201-1-1-1-1-2-1-3v-3l-1-1-1-2-3-1-1%201m-19%206c1%201%200%203-1%204l-1%206%201%202%202-1c1%201%203%200%204-1h3c2%202%205%201%204-1s-1-2-2-1-1%201-3-4h-1c-1%201-1%201-1-1l-1-2-2-2c-2-1-2-1-2%201m65%2012c-2%202-2%207%201%209%204%205%2010-2%207-8-2-3-6-4-8-1m16%200c-3%204-1%2011%203%2011%202%200%205-3%205-5l2%201c2%205%209%204%209-1l2%201c3%206%209%204%209-2%200-7-5-10-9-4l-2%203-1-3c-1-4-5-4-8%200l-1%202-2-2c-1-4-5-4-7-1m-87%2025c-2%203-3%203-3%2014l1%2015c3%206-9%206%20155%206h149l3-3%203-3v-14c0-12-1-13-3-15s-6-2-152-2H49l-3%202m2%203c-3%204-3%2024%200%2027s299%203%20301%200c3-3%203-25%200-27-2-2-13-2-151-2H49l-1%202m73%2011c-1%205%200%207%203%204%202-1%205-1%205%201%200%203-3%204-5%204-4-1-4%201-1%202%204%202%208-1%209-4%200-4-2-6-5-6-5%200-3-2%201-3%206%200%204-2-2-3h-5v5m15-3c-2%201-2%202-1%203h3c2-2%205-3%205-1l-5%206c-5%206-5%207%203%206%207%200%208-2%202-3h-3l3-4c3-4%204-5%201-7-2-3-5-2-8%200m14%200c-3%204-3%209%201%2013l4%202%202-1%203-3c4-8-4-17-10-11m27%200l1%201%202%203c-1%202-3%202-4%200-1-5-2-3-2%204l1%206%201-3c0-2%200-3%202-3%206%200%206-1%205-5-2-3-1-4%202-3l1%206c0%206%200%206-3%206h-2l2%202c4%202%205%200%205-8v-6l-6-1-5%201m-40%2038l-1%201c0%205%200%205-1%202-2-3-6-4-8-1-3%202-2%204%200%202l2-1-2%202-1%202c1%201%205-3%205-5h1c1%202%201%203-1%204-1%202-1%203%202%201%201-1%202-1%203%201h8c2%201%203%200%201-1v-4l1-1-1-1c-1%201-2%200-2-1l-1-1h-2c-1-1-2%200-3%201m-71%2018c-2%201-2%202-1%203s2%201%203-1c2-1%205-1%205%201l-4%202c-4%202-3%204%201%203%203%200%203%200%203%202%200%201-1%202-4%202-5%200-5%202-1%203%205%201%2011-4%207-7v-3c3-5-3-8-9-5m14%201c-4%204-1%2010%204%209%203-1%203-1%202%201l-4%202-2%201c0%202%206%201%208-1%206-7-1-19-8-12m16-1c-6%205-3%2016%204%2015%207-2%206-11-1-10-2%201-3%200-3-1l4-2%204-1c0-2-6-2-8-1m109%201c-4%203-1%2010%204%208%202%200%202%200%201%201l-3%203-2%201c5%203%2011-4%209-10-1-5-6-7-9-3m117%200c-4%204-1%2014%204%2014%203%200%206-4%206-8s-3-8-6-8l-4%202M47%20231c-2%202-3%2019-1%2022s150%203%20152%200%201-20-1-22c-3-3-148-3-150%200m167%200l-1%2012c0%209%200%2010%202%2011%203%202%20131%201%20132-1%202-1%202-3%202-10%200-15%208-13-68-13l-67%201M31%20272l-1%208v8h50l52-1%201-9v-7H82l-51%201m119%200l-1%208v8h50c58%200%2053%201%2053-10v-6l-51-1-51%201m119%200l-1%208v8h50c58%200%2053%201%2053-10v-6l-51-1-51%201m-163%201c1%201%200%202-1%202-2%201-3%202-2%203l-1%205c-3%203-2%204%201%200l3-3v3l-1%202%204%201%204-1-1-1c-2%200-2-5%200-6%201-1%201-3-2-5h-4m106%202l-1%201h-2l2%202c2%201%203%205%201%205l-1-3-1-1c-1%200-2%201-2%203%200%203%201%203%205%203h9l-1-1-2-1v-5l1-2c-1-4-4-4-4-1l-1%203v-3c0-2-3-3-3%200M6%20300l-1%2013%201%2013v-12l1-12%2026-1%2025-1H6m59%205c-3%203-3%204-2%206%201%201%201%201%201-1%202-3%203%200%202%204-1%205-2%206-3%204-3-2-2%200%200%204%204%206%2016%207%2021%201%202-3%203-10%202-14-4-7-15-10-21-4m62-2c-3%202-6%206-4%208h2l1-2v9c0%202%200%202-2%200l-2-4c0-2%200-2-1-1-2%202%201%209%205%2012%206%204%2016%202%2019-5%205-11-6-22-18-17m59%201c-3%201-5%205-4%206l3-2c4-4%2010-4%2015%200%207%205%204%2013-4%2016-6%202-14-3-15-10-1-2-1-2-1%201%200%2010%2011%2015%2020%2010%2014-9%200-29-14-21m61%200c-3%201-5%205-4%206h17c3-2%206%203%204%207-1%202-2%202-3%202h-11c-3%200-3%200-3-2l2-4c1-1%201-1-1-1l-2%202c0%204-2%206-3%204s-2-1-1%202c2%206%2012%209%2018%206%204-2%205-4%206-9%202-11-9-19-19-13m61-1c-3%201-6%206-5%208h2c0-2%2010-2%2013-1h3c3-3%206%205%202%208-1%202-2%202-3%201h-9l-4-1h-5l3%206c9%208%2024%200%2022-12-2-8-11-12-19-9'%20fill='%23d3d3d3'%20fill-rule='evenodd'/%3e%3c/svg%3e","aspectRatio":1.1928571428571428,"src":"/static/09c4d9f124c02db5eb6c6abac00c3a66/9a696/2019-11-27-12-23-40.png","srcSet":"/static/09c4d9f124c02db5eb6c6abac00c3a66/1ec58/2019-11-27-12-23-40.png 334w,\n/static/09c4d9f124c02db5eb6c6abac00c3a66/9a696/2019-11-27-12-23-40.png 665w","srcWebp":"/static/09c4d9f124c02db5eb6c6abac00c3a66/56aa2/2019-11-27-12-23-40.webp","srcSetWebp":"/static/09c4d9f124c02db5eb6c6abac00c3a66/cd98f/2019-11-27-12-23-40.webp 334w,\n/static/09c4d9f124c02db5eb6c6abac00c3a66/56aa2/2019-11-27-12-23-40.webp 665w","sizes":"(max-width: 665px) 100vw, 665px"}}}}}},"pageContext":{"slug":"/ocr-web-gl-with-tesseract-js/","previous":{"fields":{"slug":"/deno-ja-on-techbookfest-7/"},"frontmatter":{"title":"技術書典に初参加しdenobook 02を執筆・頒布しました＆電子版のご案内","tags":["JavaScript","TypeScript","Rust","Deno"]}},"next":{"fields":{"slug":"/jsconf-jp-2019/"},"frontmatter":{"title":"JSConf JP 2019スタッフ参戦後記","tags":["JavaScript","JSConf","Community"]}}}},
    "staticQueryHashes": ["2585454260","2954598359"]}