初めに
家の電気料金が高いので1日の電気料金を可視化できたらいいなぁということで始めました。
このプロジェクトを開始する前に事前に電気メーターがどういったものかを確認しましょう。最近はスマートメーターを導入しているところが多いです。スマートメーターの場合はネットから30分ごとの電気使用量を確認できるなどのサービスがあります。比較的新しい家の方は確認してみましょう。私の場合はオール電化住宅でデジタル数字が電気メーターに表示されていたのでスマートメーターかと期待しましたがネットから確認はできませんでした。(´;ω;`)
既存のサービスが使える方はそれを使って幸せになってください。
プロジェクトの主な構成
1,node.jsでサーバーを立てておく。
2,esp32camで写真を撮りnode.sサーバーに送る。
3,node.jsでcloudVisionに画像を投げて画像の中から文字列を読み取ってもらう。
4,googleSheetsなどに結果を保存してうまいことやる!
使うものとしては
esp32-cam
適当なPC(サーバーのような24時間放置しているものがあればベスト)
私の場合は古いノートPCをファイルサーバーにしていたのでこいつを使います。
coludVision(個人で使う分には無料の範囲を超えることはないかと思います(多分))
Esp32Camのコード
基本的には以下のサイトを参考にしました。英語だけど翻訳すれば余裕です!
コードを全部乗せるのは大変なのでgitにあげときました。gitの使い方わかりませんがとりあえず見れればokです。
基本的にesp32camのwebCameraSeverのプロジェクトをもとに作っています。
コメントアウトでコードの解説をしていますが私のわかる範囲ですのであしからず。
詰まったところと補足
*画質が下がるようなことやうまく撮れないなどのエラーがあれば基本的に再起動させるようにしましょう。
*カメラの部分は回せるようになっているのでペンチなどで回してフォーカスを合わしてあげましょう。(これを知らなくて結構苦労しました)
*esp32camにもともとあるflashはデフォルトでは明るすぎるのでpwmで制御しています。その際chを設定しますがカメラで使うchと被っているのでch15を使いましょう。
*Preferencesで設定を外部から変更できるようにしたかったのですがなぜか値が保存できませんでした。(カメラでメモリを使っているから?)潔くあきらめましょう。
*個人的にEsp32でwifiを使うときのメタなのですがloop文でwifiと正常につながっているか確認してあげましょう。必要に応じて再起動したり再接続してあげましょう。これはほかのesp32系にも当てはまることです。
*今回のプロジェクトではnode.jsから写真リクエストがあった場合直接responseで写真を返すのではなく新しいrequestで画像を返しています。responseで返す方法もありそうなのですが...(知ってる方教えてくださいm(__)m)
外見
タッパーに入れてメーターの前にスマホを挟むやつでうまいことしました。電源はコンセントが近くにあったので延長して使っています。
電源部分ではこの商品を使っています。
タッパーはできるだけ密閉するためにコードなどの部分もビニールテープで囲うとよいです。
node.jsの設定
cloudVisionの部分とnode.jsのinstallなどは割愛します。
コードについてですが必要最低限の部分だけ書きました。lineに通知する処理やSheetsに値を保存する処理は書いていません。あまりこのコードはあてにせず自分で調べて書きましょう。
jsは初めて書いたのでところどころvarを使ったり変なことをしていますがお許しください。
"use strict";
const fs = require("fs");
const http = require("http");
const server = http.createServer();
const sharp = require("sharp");
const line = require("@line/bot-sdk");
const sleep = require('./sleep');
require("date-utils");
//コマンドたたき
const { exec, execSync } = require('child_process')
server.on("request", (request, response) => {
if (request.method == "POST" && request.url === "/imageUpdate") {
console.log("画像を処理するよ。");
const date = new Date();
const currentTime = date.toFormat("YYYYlMMlDDlHH24lMIlSS");
const filePath = "./resources/" + currentTime + ".jpg";
fs.writeFileSync(filePath, "");
var ImageFile = fs.createWriteStream(filePath, { encoding: "utf8" });
request.on("data", function (data) {
ImageFile.write(data);
});
request.on("end", async function () {
ImageFile.end();
await sharp(filePath)
.extract({left: 0, width: 1597, height: 658, top: 0})
.normalise()
//.sharpen(13)
//.median(5)
.jpeg({ quality: 100 })
.toFile("./resources/" + currentTime + "meter.jpg");
}
response.writeHead(200, { "Content-Type": "text/plain" });
response.end();
});
}else {
response.writeHead(405, { "Content-Type": "text/plain" });
response.end();
}
});
async function labelAPI(filePath) {
var o = [];
const vision = require("@google-cloud/vision");
const client = new vision.ImageAnnotatorClient();
const [result] = await client.textDetection(filePath);
return result.textAnnotations;
}
const port = 8888;
server.listen(port);
console.log("listening at ${port}");
const sleep = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, time)
})
}
module.exports = sleep
画像の加工について
今回のコードで一番困ったのは画像の切り抜き方でした。メータの画像はそのままだとこんな感じです。
はじめは数字の部分だけ切り取ってあげたほうがいいと思っていました。しかし数字だけだといまいち精度が安定しません。なのでなにも加工せずに渡すか少し大きめにして渡してあげるとうまくいくと思います。
私の場合はこんな感じ
この画像ではsharpのsharpen(13)やmedian(5)などをしていますが、結局今はnormalise()をかけるだけのほうが精度が安定すると感じています。そのほかの方法としてはvanceAiなどで画像を鮮明化するなどの方法があります。(お金はかかってしまいますが...)
数字の判定
文字列から使いたい部分だけを抜き出すのには正規表現とcloudVisionの精度を見ながら行いましょう。
正規表現とか使ったことなかったのでほとんど調べて書きました...
私は以下のようにしました。
function foundLabel(labels) {
let returnNum = 0;
if(0<labels.length){
let arrayLabel=labels[0].description.split('\n');
for(let value of arrayLabel){
console.log(value);
const regexp = /([\u{3005}\u{3007}\u{303b}\u{3400}-\u{9FFF}\u{F900}-\u{FAFF}\u{20000}-\u{2FFFF}][\u{E0100}-\u{E01EF}\u{FE00}-\u{FE02}]?)/mug;
const regexp1=/[a-zA-Zぁ-んー]/g;
let data = value.replace(/[^0-9]/g, "");
const includeAlfabets=(value.match(regexp1)||[]);
const includeKanjis=(value.match(regexp)||[]);
if(data.length == 7){
data=data.slice(1);
}
if(includeKanjis.length<=2
&&data.length == 6
&&includeAlfabets.length<=2){
returnNum = parseInt(data);
break;
}
}
}
console.log(returnNum);
return returnNum * 0.1;
}
そのほかの処理について
Sheetsに書き込むなどの処理はjsで書きたくなかったのでpythonで書きました。jsからpythonを実行するにはpyshellなどを使うとよさげですが私はうまくいかなかったのでchild_processで直接pythonを実行して処理を書きました。普通にpythonを実行するときと同じですが一応書いておきます。pythonのインストールなどは各自でお願いしますね。
const { exec, execSync } = require('child_process')
let stdout=execSync('python C:/Users/nisini/Desktop/python/main.py');
感想
急ぎ足で書いたのでおかしい部分などありますがご了承ください。
また思い出したらコメントで加筆します。