アイディア概要
冷蔵庫の中にある食べ物の賞味期限や、消費期限、覚えてますか?
私はしょっちゅう忘れて、ちょっとおいしく無くなってしまったり、ひどいときはダメになって食べれなくなってしまったりする時があります。
それを解決するべく、賞味期限や消費期限を私の代わりに覚えてくれる、「旨い期限オボエテール」を作ってみました。
使い方は簡単。まずカメラで「名称」が記載された部分を撮影します。
撮影ができたら「ピっ」と音が鳴るので、次は「期限」が記載されている部分を撮影します。撮影が完了したらまた音が鳴ります。そしたら全部完了です。
あとは、LINEの「旨い期限オボエテール」で「おしえて」とつぶやけば、記録した内容を教えてくれます!
実装
全体概要
必要な部品
- ボタンスイッチ
- スピーカー
- 高画質カメラ(今回はiPhoneのカメラを使用しています)
- Obniz
- その他、ジャンパワイヤ、ブレッドボードなど細々した部品
「名称」と「期限」について
この記事の中でいう「名称」とは、食品をいれるパッケージにある食品表示の名称項目のことで、期限は、賞味期限や消費期限のことを指します。
Obniz側の実装
Obniz側でやっていることは、撮影、OCR、文字解析の三つになります。
基本は公式サンプルの 文字認識をして動かそうを参考にしました。
まず、起動してボタンを押すとカメラが起動して、「名称」を撮影の状態になります。
もう一度押すと、待機状態になり、さらにもう一度押すと、撮影が「名称」の撮影から再スタートするように実装しました。
実際のカメラの起動方法は、公式サンプルをそのまま利用しています。
var shootingStatus = 0
// 0: 撮影停止中
// 1: 「名称」撮影中
// 2: 「期限」撮影中
// 3: 撮影待機中
button.onchange = function (pressed) {
if (pressed) {
if (shootingStatus == 0) {
prepare();
} else if (shootingStatus == 3) {
onVideoRestart();
} else {
onVideoStopped();
}
}
};
function prepare() {
const medias = {
audio: false,
video: {
facingMode: { exact: "environment" },
},
};
mediaDevices.getUserMedia(medias).then(successCallback).catch(errorCallback);
}
function successCallback(stream) {
showObnizMessage("「名称」を撮影してください");
shootingStatus = 1;
video.srcObject = stream
streaming = true;
start();
}
start()
が呼ばれると、撮影画像のキャプチャとOCR処理が開始されます。
async function start() {
const FPS = 100;
async function processVideo() {
try {
if (!streaming) {
return;
}
light.on();
let begin = Date.now();
let base64String = capture();
if (base64String != null) {
postToGoogle(base64String);
}
setTimeout(processVideo, 1000);
} catch (err) {
console.error(err);
}
}
// schedule the first one.
setTimeout(processVideo, 1000);
}
ここで注意するべき二点があります。それは、カメラの画質とOCR処理の頻度です。
カメラの画質について
今回OCRを使用するため、カメラの画質は非常に重要になってきます。
私はまず、JpegSerialCamを試してみましたが、このカメラの最高画質は "640x480" で、流石に文字は全部潰れていました。且つ、起動が遅くてちょっと今回の要件にはマッチしませんでした。それからArduCAMMiniも試したのですが、こちらに関してはうまくカメラが起動せず、時間の都合上、一番確実なiPhoneのカメラを使用することにしました。iPhoneを使ってしまうと、アプリ作った方が早いんじゃない?となってしまうので、その辺はちょっと残念でしたが、もうちょっとカメラ周り勉強してから再チャレンジかなと言う感じです。
OCRについて
今回OCRはGoogleが提供しているVision APIを使用しています。こちら、公式サンプルの 文字認識をして動かそうをそのまま使ってしまうと、結構な量のリクエストがとんでしまい、費用がかなりかかってきます。私は予算の都合上、1秒に1回という超節約な設定にしています。OCRのリクエスト頻度については、コストがかかってくるので、実際に検証して費用と利便性を両立できる閾値を決める必要があると思います。
文字解析
文字解析といっても、現在はそんなに難しいことはしていなくて、最低限の実装になっています。
名称の解析はこんな感じです。
if (result && result.textAnnotations) {
result.textAnnotations.some((anotation, index) => {
if (index == 0) {
return;
}
if (findKey) {
// 「名称」が見つかったので、その対象を取得する
// 「原材料名」が「名称」の次の項目とみなし、「原材料名」までを対象の文字列とする
if (isKeywordIncluded(anotation.description, "原材料名") || isKeywordIncluded(anotation.description, "原材料") || isKeywordIncluded(anotation.description, "材料")) {
return true;
}
var reg = new RegExp(/[!"#$%&'()\*\+\-\.,\/:;<=>?@\[\\\]^_`{|}~]/g);
if (reg.test(anotation.description)) {
// 記号は取得しない
return;
}
resultWord = resultWord + anotation.description;
return;
}
// 「名称」を検索
if (isKeywordIncluded(anotation.description, "名称")) {
findKey = true;
return;
}
})
}
}
身近にある食品表示をいくつか見ると、「名称」と「原材料名」は必ず順番に記載されているようだったので、「名称」と「原材料名」の間の文字列を撮影している対象の名前として扱うようにしています。現在は手元にあるものを頼りにやっているのですが、真面目にやるならば食品表示法とか確認する必要がありあそうです。
期限についてはyy/mm/dd
yyyy.mm.dd
等々日付の形式の文字列を、正規表現で判定するようにしています。正直、名称よりも期限の判定は難しいと思いました。まず、日付の記載方法が多種多様で、今実装している正規表現では我が家の冷蔵庫にある食品の期限の表記を網羅できていません。また、OCRの結果も空白等があると数字が別のanotationとして入ってきたり、食品表示の中に類似の数値列があったりなど、課題があります。これは情報の蓄積がもっと必要かなと思います。
GAS側の実装
撮影したものの名前と、賞味期限の情報が取得できたら、その文字列をGASに記録します。GASに記録方法は こちらの実装を参考にさせていただきました。多分今回の実装で一番すんなりいった箇所だと思います。思っていたよりも手軽にできてよかったです。
OCR結果の保存について、参考の通り、Postデータを受け取ったら、シートの最後の行にOCR結果を追加しているだけです。
LINE Bot
今回、撮影したデータを閲覧するのにLINE BOTを使用しています。実装は こちら を参考にしています。
「おしえて」とつぶやくと、GASに保存している賞味期限の情報を返信しています。今回保存用のアプリと、LINE BOT用のアプリ、二つデプロイしているので、返信する情報については、LINE BOT用のウェブアプリで以下のようにして保存しているスプレッドシートから取得するようにしています。
// ユーザーのメッセージを取得
var userMessage = JSON.parse(e.postData.contents).events[0].message.text;
if (userMessage != "おしえて") {
return;
}
// 応答メッセージ用のAPI URL
var url = 'https://api.line.me/v2/bot/message/reply';
var spreadsheet = SpreadsheetApp.openById('スプレッドシートのID');
var sheet = spreadsheet.getActiveSheet();
var values = sheet.getRange('欲しいセル').getValues();
var message = ""
values.forEach(function(key){
if (key[0] != "") {
message = message + key[0]
}
});
全部だすと期限切れのものまででてしまうので、ここで一週間以内のものなどフィルターをかけるともっと良い感じにりそうなきがしています。
おわりに
今回、カメラに大苦戦して、ハード(組み込み)は奥が深いなぁと改めて思いました。それから、OCRの頻度や、文字列の解析はもうすこし時間をかけてやれば精度が高まると思うので、今後ブラッシュアップできたら良いなと思います。
その他、ObnizとGAS、LINEなど、他のソフトウェアと割と簡単に連携ができることを知れたのはよかったです。また何か作るときに役に立ちそうです。
電子工作はまだまだ初心者ですが、前よりも作りたいものを形にできたので、よかったなと思いました。今度は何をつくろうかなー
終わり
-
kie
さんが
2021/05/16
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する