2023/03/06 :スプレッドシートへの書き込み処理のソースコードを変更しました。(みさきゅうりさんコメントありがとうございました!)
先日、節約系YouTuberの方が
お金を貯めるなら、まずは収支を可視化することが大事です!
と言っていて、本当そうだよなぁと思いました。
・・・思ったんですけど、実際家計簿つけるのって面倒ですよね。私は今まで紙の家計簿(挫折)→家計簿アプリ(挫折)→スプレッドシート(イマココ)で今半年くらい続けています。正直、スプレッドシートでも入力を忘れたりすることもあります。
そこで、完全無料・自作OCRで、LINEからレシート画像を読み取ってスプレッドシートへ書き込むまでを検証してみました。
本記事では読み込んだテキストの成形までは行なっていませんが、LINEとGASの連携からOCR処理までを実装していますので、宜しければ参考にしてみてください。コピペ用のソースコードも掲載しています。
OCRってなに?
OCRとは、印刷された文字や手書きの文章、画像に含まれる文字などを読み取り、テキストデータに変換する技術です。
身近なところでいうと、郵便物の自動読み取り技術が挙げられます。これは1967(昭和42年)に世界ではじめて導入され、枠の中に書かれた郵便番号を機械が読み取ることで集配区分を自動で振り分けることを実現しました。私も小学校の社会科見学で郵便局に行った時、画面に宛先が映り、ものすごいスピードで郵便が振り分けられる光景に感動した記憶があります。
最近ではレシートの読み取りや、うつした画像を瞬時に翻訳してくれるサービスなど、帳票の記入ミスチェックなど、様々な分野で利用されている技術です。
ちなみに私は基本情報の勉強をしているときにはじめて「OCR」という言葉を知りました。
オクラでスキャン、オクラでスキャン・・・
という訳のわからない覚え方をしたせいで、今でもOCRを「オーシーアール」ではなく「オクラ」と読みそうになってしまいます。
OCRを実現するための手段は何がある?
OCRはどんな手段を使えばできるのかを紹介します。なお、OCRアプリやツールなど製品については除外して、あくまで「自作」するための手段について記載します。
PAD (Power Automate Desktop) | GAS (Google Apps Script) | python | |
---|---|---|---|
難易度 | ★☆☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
OCRエンジン | Windows OCR Tesseractエンジン | Google Vision API | PyOCR EasyOCR Tesseractエンジン 等 |
値段 | 無料 | 1000回/月まで無料 | 無料 |
他にもOCR実現のための手段はありますが、大きく「ノーコード開発(RPA)」「ローコード開発」「ローコード+α」と3種類に分類して比較しました。読み取り精度は読み取り対象や使うOCRエンジンによって変わってくるとは思いますが、どれも基本的には無料で使用できるようです。
家計簿管理のために最適なOCRは?
OCRを利用して家計簿でどのようなことを実現したいかを考えてみると、
「レシートを読み取ってスプレッドシートに書き込みたい」
「できればLINEで完結させたい」
「家族と家計簿を共有したい」
私の場合は上記が実現できたらいいなと思いました。となると、PADは選択肢から外れてGASかPython、もしくはその他のプログラミング言語を利用することになります。
ただ、家計簿自体を家族で共有することを考えると、スプレッドシート×GASが良さそうかなと思いました。
「Excelで管理したい」、「Googleフォームの画像添付でレシート投稿したい」など実現したい内容はそれぞれかと思います。なので、もしかしたら以降の記事内容は参考にならないという方もいらっしゃるかもしれませんが、もし一から家計簿を作りたいという方は以降の手順に沿って試してみていただけたらと思います!
GASでOCRを実現
Google Apps Script(GAS)を使ってOCR機能を作成していきます。GASというのはスプレッドシートやGmailなどGoogleのサービスとの連携をしたり、スクリプトを実行したりするのに使えるツールです。
先ほども触れましたが、GASは「ローコード開発」と言って、全くプログラムを書かないというわけではありませんが、限りなく少ないプログラムで動くので初心者向きと言えます。
事前準備その1:LINEとGASの連携設定
連携方法については以下の記事で解説しています。手順通りに設定をすれば問題なく動くかと思います。もしうまく設定ができないという場合には気軽にご質問ください♪
事前準備その2:Google Driveの設定
レシート画像を一時的にGoogle Driveに保存するため、保存用のフォルダを作成しておきます。
Google Driveは、画像やドキュメントなどを保存しておける無料のオンラインストレージです。Googleアカウントをお持ちの方はGoogle Driveにアクセスすればマイページに遷移するかと思います。
マイページの「新規」ボタンから「新しいフォルダ」を選んで任意の名前をつけます。
作成したフォルダのIDをコピーしておいてください。こちらを以降で使用します。
GASで画像をOCRするコード
続いてGASのコードを記載します。gasの操作や、コードをどこに書いたら良いのか分からない場合はこちらの記事をご参照ください。
Drive API サービスの設定
Google Driveの操作を行うには、gas上でサービスを有効化する必要があります。
左側にあるメニューの「サービス」のプラスボタンを押下します。
「Drive API」を選択して追加を押します。
LINEで送った画像を取得する
// LINEから送信されたレシート画像を取得
var img_url = LINE_DATA_ENDPOINT + json.events[0].message.id + "/content";
var img_options = { "headers" : { 'Authorization': 'Bearer ' + ACCESS_TOKEN } };
const blob = UrlFetchApp.fetch(img_url, img_options).getBlob(); // blob型で画像を取得
LINEから投稿した画像は、Webhookというhttpでサービス間の連携をする仕組みで送られます。送られてきたコンテンツ(=画像や音声データ)は、メッセージIDを使って取り出すことができます。(LINE developers参考)
次に、HTTPリクエストヘッダにアクセストークンを指定し、トークン認証なのでAuthorizationはBearerを設定しています。最後の行で「UrlFetchApp.fetch(画像のURL, オプション)」によりHTTPリクエストを実行して画像を取得します。(Apps Scriptリファレンス > Class UrlFetchApp参考)
取得した画像をGoogle Driveにアップロード+OCRの実行
// Google Driveに取得した画像をアップロード、OCRを同時実行してGoogleドキュメント形式で保存
var driveOptions = {
"title": "test.jpg",
"parents": [{id: FOLDER_ID}]
};
const image = Drive.Files.insert(driveOptions, blob, { "ocr": true, "ocrLanguage": "ja" });
var ocrText = DocumentApp.openById(image.id).getBody().getText(); // ドキュメント内のテキストを取得
先ほど取得した画像をいったんGoogle Driveにアップロードします。
driveOptions内でアップロード時のオプションを指定します。titleはファイル名、parentsは保存先のフォルダIDです。ファイル名はAPIから実行するときは拡張子まで指定する必要があるようです。また、parentsはなくても実行できますが、指定がないとマイドライブ直下にアップロードされます。
(参考:Google Drive Reference Files:insert )
Drive.Files.insertでGoogle Driveへの画像追加をしています。このとき、ocrをtrueにするとアップロードと同時にOCRが実行されます。
※Drive APIを有効にしてから使えるようになるまで時間がかかる場合があり、有効化されないうちに実行するとDrive.Files.insertが成功しないことがあります。
OCR実行した画像はGoogleドキュメントとして保存される
OCR実行した画像はGoogle ドキュメントとして保存されます。GoogleドキュメントはMicrosoftのWordのような、文章を作成するためのソフトです。
DocumentApp.openById(image.id).getBody().getText();
でドキュメント内にあるテキストを取得してocrTextに入れています。
OCR変換後のテキストをスプレッドシートに転記
▼2023/03/06 スプレッドシートへの書き込み処理のソースコードを変更しました。
// 書き込み対象のスプレッドシートを取得
var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName("test");
// 値が入っている最終行を取得
var lastRow = sheet.getLastRow();
// スプレッドシートにOCRで読み取った内容を書き込み
SpreadsheetApp.openById(SHEET_ID).getSheetByName("test").getRange(lastRow + 1, 1).setValue(ocrText);
上記コードでOCR変換後のテキストをスプレッドシートに転記します。
getSheetByNameには書き込み対象のシート名を、getRangeにはセルの座標を設定します。
スプレッドシートのIDは、スプレッドシートのURL「https://docs.google.com/spreadsheets/d/XXXXXXX/edit#gid=123456」のXXXXXXX部分になります。gid以降ではないので注意してください。
読み取り後はアップロードした画像を削除する
Google Driveにいったんアップロードした画像はOCR処理が終わったら削除します。
// 読み取り後は指定したフォルダ内のファイルすべて削除
var folder = DriveApp.getFolderById(FOLDER_ID);
var files = folder.getFiles();
while(files.hasNext()){
var file = files.next();
file.setTrashed(true);
}
setTrashedにtrueを渡すことで指定したフォルダ内のファイルを削除します。削除といってもゴミ箱には残ります。
コピペ用ソースコードはこちら
以下がGASのスクリプトに設定した内容です。gasのプロジェクトを新規作成したときに「コード.gs」の中にmyFunction()が書かれていますが、そちらを全部削除して以下を貼り付ければOKです。
変更が必要な部分を後述します。
const ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; // LINEアクセストークン
const LINE_ENDPOINT = "https://api.line.me/v2/bot/message/reply";
const LINE_DATA_ENDPOINT = "https://api-data.line.me/v2/bot/message/";
const SHEET_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // スプレッドシートのID
const FOLDER_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // Google DriveのフォルダID
function doPost(e) {
// LINEからPOSTされるJSON形式のデータをGASで扱える形式(JSオブジェクト)に変換
var json = JSON.parse(e.postData.contents);
// LINEから送信されたレシート画像を取得
var img_url = LINE_DATA_ENDPOINT + json.events[0].message.id + "/content";
var img_options = { "headers" : { 'Authorization': 'Bearer ' + ACCESS_TOKEN } };
const blob = UrlFetchApp.fetch(img_url, img_options).getBlob();
// Google Driveに取得した画像をアップロード、OCRを同時実行してGoogleドキュメント形式で保存
var driveOptions = {
"title": "test.jpg",
"parents": [{id: FOLDER_ID}]
};
const image = Drive.Files.insert(driveOptions, blob, { "ocr": true, "ocrLanguage": "ja" });
var ocrText = DocumentApp.openById(image.id).getBody().getText(); // ドキュメント内のテキストを取得
// 書き込み対象のスプレッドシートを取得
var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName("test");
// 値が入っている最終行を取得
var lastRow = sheet.getLastRow();
// スプレッドシートにOCRで読み取った内容を書き込み
SpreadsheetApp.openById(SHEET_ID).getSheetByName("test").getRange(lastRow + 1, 1).setValue(ocrText);
// 読み取り後は指定したフォルダ内のファイルすべて削除
var folder = DriveApp.getFolderById(FOLDER_ID);
var files = folder.getFiles();
while(files.hasNext()){
var file = files.next();
file.setTrashed(true);
}
// 応答用のメッセージを作成
var message = {
"replyToken" : json.events[0].replyToken,
"messages" : [{"type": "text",
"text" : "書き込み完了"}] // 応答メッセージの内容
};
// LINE側へデータを返す際に必要となる情報
var options = {
"method" : "post",
"headers" : {
"Content-Type" : "application/json; charset=UTF-8", // JSON形式を指定、LINEの文字コードはUTF-8
"Authorization" : "Bearer " + ACCESS_TOKEN
},
"payload" : JSON.stringify(message) // 応答文のメッセージをJSON形式に変換する
};
// LINEへ応答メッセージを返す
UrlFetchApp.fetch(LINE_ENDPOINT, options);
}
上記コードのうち、以下の変数の値をご自身の環境のものに変更してください。
変数名 | 設定する値 |
---|---|
ACCESS_TOKEN | LINE Developers>Messaging API設定の 「チャネルアクセストークン(長期) 」 |
SHEET_ID | GoogleスプレッドシートのID |
FOLDER_ID | Google Drive画像の保存先フォルダのID |
まとめ
LINEで気軽にレシート投稿できるのはかなり便利ですよね。今回は、GASとLINEの連携からOCR処理までを検証しましたが、より実用的なものになるよう今度は取得したテキストを変換する処理を実装したいと思います。
過去記事にてGAS×LINEで家計簿の自動投稿の実装もしているのでよろしければご参照ください。
所感:ブログに書きたいネタは沢山あるけど、やっぱり節約やら家計簿やら主婦目線で欲しいものに目が向きがち・・・!よりエンジニア向けの記事は主人が書いているので、そちらもご活用ください!>prtn執筆記事
コメント
こんにちは。
有用な記事ありがとうございます。
このスクリプトは、いまも動作していますか?
記事の通りTOKENやIDを記述して、Googleのスクリプトを承認させて、
LINEのアカウントbotにレシートの画像を投げると、
「メッセージありがとうございます!
申し訳ありませんが、このアカウントでは
個別のお問い合わせを受け付けておりません。
次の配信までお待ち下さい」
が返ってきて、OCRスキャンが行われませんでした。
こんにちは、コメントありがとうございます。
こちらのスクリプトは現在も動きます。
トークン、IDはご自身の環境のものに変更されているということですよね?
GASのスクリプトをデプロイしたあと、LINE Developersに発行されたURLの反映も済んでいますでしょうか?
(参考:https://prtn-life.com/blog/gas-linebot#toc8)
記載いただいた、自動応答が出てしまう件については
応答が返ってこない件とは直接関係はありませんが、オフにするには
LINE Developersの対象のチャネルの、「チャネル基本設定」タブ内にある
「チャネルアイコンとチャネル名は、LINE Official Account Managerで変更できます。」のリンク先で
「応答設定」の「応答メッセージ」をオフにすることで自動応答されなくなります。
もし何か不明点等ありましたら、お気軽にコメントいただければと思います!
よろしくお願い致します。
返信、ありがとうございます。
デバッグしたところ、原因がわかりました。
スプレッドシートのシート名を「test」にしていないせいでした。
新規作成のままスクリプトを動かしてたのでデフォルトの「シート1」でした。
お騒がせしました!
ご連絡ありがとうございます!
シート名が違っていたんですね、
原因が分かって良かったです。
わざわざコメントくださりありがとうございました!
こんにちは。
GAS初心者です。すごくわかりやすい記事ありがとうございます。
教えていただけたらありがたいのですが、このOCRのコードと、LINEから受信したテキストデータのコードを同じシート内で書いて動かすことはできますか?
別々じゃないとできないのでしょうか?
こんばんは。コメントいただきましてありがとうございます!
「LINEから受信したテキストデータのコード」というのはどれを指していますでしょうか?
本記事のコピペ用ソースコードの通り、LINEから受信した内容をOCR処理するということで
ひとつのシート内で動かすことができる認識です。
的外れな回答でしたらすみません。
返信いただきありがとうございます。説明不足ですみません。
2023.02.06の記事の、「LINEで家計簿をつける」のコードと、こちらのOCRの記事のコードを同シートで記述して動かすことができますでしょうか?
OCR処理も行いたいし、LINEからのメッセージもスプレッドシートに書き込みたいのですが、可能でしょうか?
大変参考になりました!ありがとうございます!
2024年2月現在、Google Drive APIはデフォルトでv3がデプロイされるみたいです。
こちらのコードはv2のメソッドを利用しているのでその旨を記載してあげるといいかもしれません。
うぇ様
コメントありがとうございます!
そうなんですね、その旨追記させていただきたいと思います。
この度はどうもありがとうございました!