スマホにカメラついてるんだからOCRできるでしょという気持ち

どうも、 株式会社Progate で SoftwareEngineer チームのマネージャーをしています @satetsu888 です。本記事は Progate AdventCalendar 2020 10日目です。

普段仕事ではエンジニア組織のことやプロダクトの技術戦略的なことを考えたり、ミーティングしたり採用活動したりタスクをお願いして回ったりなどを担当していますが、今日はそういうのとはなんの関係もないただの日常の話を書こうと思います。

ことの始まり

我が家では子どもの朝ごはんとして週に2,3回くらいの頻度でポケモンパンを買っています。 先日(2020/09/18 ~ 11/24) ポケモンパンについてるポイントを5点集めるとポケモンシールホルダーの抽選に1回応募できるキャンペーンがありました。(キャンペーン自体はすでに終了しています)

いつも通りのペースでパンを買ってると何回か挑戦できるくらいポイントがたまったのでまとめて抽選に応募しようとしました。応募用のシールとフォームは下記のような感じで14桁の数字を入力する形でした。

f:id:satetsu888:20201130232508p:plain
応募用シール

f:id:satetsu888:20201130231907p:plain
応募用フォーム

これを見て僕は

「あーQRコードを読むだけじゃないのか... 間違えないように入力するのめんどくさいな... 20枚くらいあるんだけどこれ全部手入力するくらいならキャンペーンの応募やめようかな... というかこの程度なら手で打たなくてもスマホにカメラついてるんだからOCRできるでしょ...」

という気持ちになったので、スマホのカメラで数字を読み取ってフォームに入力するやつを勝手に作ることにしました。

設計

OCR部分は適当なライブラリを探すとして、よそのサイトの入力フォームに読み取ったデータを渡してやるところには少し工夫が必要そうです。

ブラウザ拡張などを使うことを考えましたがスマホブラウザで拡張を動かすのは現状なかなか難しそうです、スマホにカメラがついてるからOCRできるはずでしょというのがスタートなのでスマホで読み取ってからPCのブラウザで入力という仕組みだとイマイチな感じがします。

そこで、操作はスマホで完結できる設計としてPCでプロキシしてやってキャンペーンサイトに自分で書いたjavascriptのコードをくっつけたものをスマホブラウザに渡してやるMITM方式でいくことにしました。

f:id:satetsu888:20201209163146p:plain
全体構成イメージ図

OCRといえば最初に思いつくのは Cloud Vision API なんですが、今回の対象は比較的読み取りが簡単そうなので多少性能は落ちてもローカルで動くものがいいかなと思って、ブラウザで簡単に動きそうな Tesseract.js を使うことにしました。tessedit_char_whitelist で読み取る文字列をホワイトリストで指定できるので、今回のように目的の文字列が数字のみで構成されていることがわかっている場合十分な読み取り精度が期待できそうです。 tesseract.projectnaptha.com

PCで通信をプロキシしてスクリプトを埋め込むために mitmproxy を使います。 mitmproxy.org

これでパーツは出揃ったので組み立てていきます。

実装

OCR を呼び出して結果をフォームに入力するだけだし一瞬で完成しちゃうかなと思ったんですが、実際にコードを書いて動かしてみると細かい仕様を検討しないといけないところがたくさん出てきてそれなりに大変でした。

当初はボタンを押したらカメラ起動、再度ボタンを押したら撮影して読み取りという操作で問題ないと思っていたんですが、実際に動かしてみるとなんだか違和感がありました。カメラを向けた時点で自動的に読み取りを開始して、ある程度正しそうなデータが読み取れた時に撮影を終了する挙動の方がだいぶ便利なことにしばらく触って気づきました。

あと、カメラ画像そのままをOCRの読み取り対象にした実装をしてしまうと読み取りたい部分だけでカメラ画像全体をカバーするように撮影するのが大変だったので、カメラ画像を表示しているcanvas内に読み取り枠をつけて、該当箇所だけを切り取ってから読み取りを行う必要がありました。

すごく単純なプログラムだと思っていましたが、実際に作ってみないとちゃんとわかっていないことは多いですね。

他にも

  • OCRのworkerの初期化タイミング
  • カメラへのアクセス許可をとるタイミング
  • 1つ目のコードを読み取ってる途中に2つめの入力フォームのカメラボタンを押した時の挙動

などの細かい問題が出てきましたが、キャンペーンに応募するためだけに使う書き捨てのスクリプトだしいいでしょと唱えながら乱雑に乗り切っていきました。

書いた乱雑なコードはここに置いておきます。

ポケモンシールホルダーキャンペーン用の工作 · GitHub

完成

欲しいものがなんとか完成しました

  • mitmproxyにより応募フォームのページにスクリプトを混ぜ込んだページを用意します
  • 埋め込んだスクリプトでOCRのworkerを動かしたりボタンを表示したりします
  • OCRで読み取った数字7桁 x 2行を応募フォームに入力します

これでポケモンパンをたくさん買うとキャンペーンの応募がめんどくさい問題を回避できるようになりました。

f:id:satetsu888:20201128234800g:plain
動作の様子

おわりに

今回は日常で見つけためんどくさいことをプログラミングで解決する例としてちょっとした工作をしてみました。

ほんの些細な例をあげた後に急に大きい話をしますが、僕はプログラミングというものが単に仕事で何かシステムを作るだけのものではなくて、コンピュータの力を借りて目の前の問題をねじ伏せられるパワーであって欲しいなと思っています。

スマホにカメラついてるんだからOCRできるでしょなんて勝手なことを言いだすのにも前提として多少のパワーが必要です。 もっとたくさんの人にプログラミングを学んでもらって、日常のいろんなところにコンピュータの使い道を見いだすパワーを手に入れてもらって、 もっともっと大きな様々な問題に対してコンピュータの力をぶつけられる人が増えて、世の中が良くなっていってくれるといいなみたいなことを日々考えながらProgateで仕事をしています。

それではいつもの締めです!Progate ではプログラミングのパワーを世界中にばらまきたい人を募集しています! もし興味がわいたら採用情報などなどぜひ確認してみてください!! prog-8.com

明日は chandy が CSS 周りのことを書いてくれるようです、お楽しみに!