jonajiro が 2021年05月09日15時45分58秒 に編集
見出し追加
タイトルの変更
家の鍵開閉状態を外出先から確認するシステムの構築
【画像処理】家の鍵開閉状態を外出先から確認するシステムの構築【Obniz】
タグの変更
画像処理
本文の変更
- 作成意図
# 作成意図
外出先で家の鍵閉めたっけ?と不安になることはないでしょうか? この不安を解消するため、出先から鍵の様子を確認できるシステムを作りました。
- 主要部品
# 主要部品
| 品名 | 用途 | 価格 | |:---:|:---:|:---:| | [obniz Board 1Y](https://obniz.io/ja/products) | メインコンピュータ | 6,930円(税込) | | [Arducam Mini 2MP Plus](https://www.arducam.com/product/arducam-2mp-spi-camera-b0067-arduino/) | カメラ | 25.99$ | | [リレーモジュール](https://www.amazon.co.jp/gp/product/B08CKGNHP9/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1) | 照明スイッチング用 | 1000円くらい | | 照明 | 撮影時の光源 | 家に転がってたので値段不明 | | [人体赤外線感応モジュール](https://www.amazon.co.jp/gp/product/B081GYJ9CR/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&psc=1) | 人検知用 | 499円くらい |
- システム構成
# システム構成
下記にシステム構成を示す。 ざっくり説明すると人感検知をトリガーにカメラ撮影してSlackに画像と鍵開閉判断結果を送信している。 ![システム構成](https://camo.elchika.com/e5983ef313954de2cf638d2834249c2a77161716/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f33616330656236332d336466362d346232392d613061322d313566313236343230353261/) 外観を下記に示す。 ![キャプションを入力できます](https://camo.elchika.com/ceda6b6bd55127bb24504b4fc485d08e92449afa/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f32643562663866302d306634652d343561352d393034632d313938343137313261313637/)
- ソフトウェア
# ソフトウェア
以下に論理アーキテクチャを示す。※Pythonで開発 ![論理アーキテクチャ](https://camo.elchika.com/7c134bc62ffdb367ba6cb82937d5d5b6ee421f29/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f61306266363161622d636332652d343162332d613662362d373138633162313464653235/) 1. Slack送信 Slack公式ページに画像のアップロードの手法が詳細に記載してある。 [本URL](https://api.slack.com/methods/files.upload/code)参照のこと。 1. 鍵開閉判断 OpenCVとTensorFlowを使用し実現。 2.1. Step1 二値化 画像をいっぱい集めて二値化する。いきなり二値化すると具合が悪いのでグレースケールしてから二値化するといい感じになる。 ![元画像](https://camo.elchika.com/26a12e6c94a3a41195983ac25967dc037f18573a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f36646339373239632d383230302d343932362d383961332d653632386361613734643939/) ![グレースケール](https://camo.elchika.com/e7f73722b872494762b7f3aace8aedc6fa0bf637/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f31663930373738362d623431362d343865322d613338342d656465656431613861663034/) ![二値化](https://camo.elchika.com/541d176b74141286aa623f57b4918ed4e14a61cf/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f66663462393832392d303231332d343437312d393266362d303165303433323135373461/) 2.2. Step2 テンプレートマッチング テンプレートマッチングで画像からドアの鍵部分のみをトリミングする。 ![キャプションを入力できます](https://camo.elchika.com/35cc41d7d2e29c12e1c7e453152a05d9373c2bce/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f36363030643461642d396230642d346661312d613465642d306261346335656138623032/) ![キャプションを入力できます](https://camo.elchika.com/cd4441e0ac1a0110ad2919bfa4a7499d42d895e8/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f38616264653464352d353932392d346336382d613435372d623261633062313739633766/) 2.3. Step3 機械学習 ドアの鍵部分の二値化後画像をいっぱい集めてラベルを付ける。目視で開いてるか閉じてるか確認してラベルつけるからクッソめんどくさい。ラベルつけた後、分類器を学習させる。 ![キャプションを入力できます](https://camo.elchika.com/ddae2763a4e33cc3ce5e47474842cd9eebc8a686/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f65376564323730312d333438332d346134642d396334372d383332303266363730393733/) 2.4. Step4 分類器 学習させた分類機に直近撮影した画像をぶち込むと、鍵が開いてるか閉じてるかを分類してくれる。 ![キャプションを入力できます](https://camo.elchika.com/41dc88fc6f95edff0de5dfbd9ee8284479fe130a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63313736656430382d643162352d343939342d616263302d3934323065333537313538652f66626336623439372d626438352d343233632d393361322d306264333239613066303933/) 1. カメラ撮影制御 obniz-python-sdkを使用。 [本URL](https://github.com/obniz/obniz-python-sdk/blob/master/obniz/parts/Camera/ArduCAMMini/README-ja.md)参照のこと。 1. 照明制御 カメラ撮影前にオンにして撮影終了後にオフするだけ。 以下にソースコードを示す。 ```python:家の鍵開閉状態を外出先から確認するシステム import asyncio from obniz import Obniz import datetime import base64 import io import time import threading import codecs from slack import WebClient import aiohttp import json import requests import sys import os import tensorflow as tf from tensorflow import keras import numpy as np import glob import re import cv2 import shutil import matplotlib.pyplot as plt #dooropen TOKEN = "xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" CHANNEL = "XXXXXXXXXXXXX" file_url = "" save_path = 'download.jpeg' TARGETPATH = "media/TARGET/" TEMPPATH = "media/TEMP/" OPENPATH = "open.jpeg" CLOSEPATH = "close.jpeg" TARGEFILETPATH = "*.jpeg" last_vol = 0 last_time = 0 f_detect = 0 f_getpic = 0 f_delaypic = 0 obniz = Obniz('XXXX-XXXX') async def onconnect(obniz): global f_detect global f_getpic cam = obniz.wired("ArduCAMMini", { "cs": 7, "mosi": 6, "miso": 5, "sclk": 4, "gnd": 3, "vcc": 2, "sda": 1, "scl": 0 ,"spi_frequency":100000,"module_version":1 }) obniz.io9.pull("5v") while True: vol = await obniz.ad8.get_wait() hdetect(vol) if f_getpic == 1: obniz.io9.output(True) await cam.startup_wait() # data = await cam.take_wait('640x480') data = await cam.take_wait('800x600') head = 255 f_cnt = 0 cnt1 = 0 f = open("list.jpeg", 'bw') f.write(head.to_bytes(1,'big')) for x in data: f.write(x.to_bytes(1,'big')) if f_cnt == 1: if data[cnt1] == 217: if data[cnt1-1] == 255: break f_cnt = 1 cnt1 = cnt1 + 1 f.close() print("done") worker() obniz.io9.output(False) obniz.onconnect = onconnect def hdetect(vol): global last_vol global last_time global f_detect global f_getpic global f_delaypic if vol - last_vol > 3.0: print("detect!!!") last_time = time.time() f_detect = 1 if time.time() - last_time < 60.0 and f_detect == 1: f_getpic = 1 f_delaypic = 1 f_detect = 0 if time.time() - last_time > 60.0 and f_delaypic == 1: f_getpic = 1 f_delaypic = 0 last_vol = vol def get_grayscale(image_path): img = cv2.imread(image_path) gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) return gray def do_grayscale(image_path): img = cv2.imread(image_path) gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) save_image(image_path,"src", "gray", gray) def do_binarization(image_path): img = cv2.imread(image_path,0) img_thresh = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,41,2) # ret, img_thresh = cv2.threshold(img, 110, 255, cv2.THRESH_BINARY) save_image(image_path, "gray", "binary", img_thresh) def do_template_matching(target_path,tmp_path): template_img = cv2.imread(tmp_path + "binary/" + OPENPATH) img_list = glob.glob(target_path + "binary/" + TARGEFILETPATH) for path in img_list: img = cv2.imread(path) result = cv2.matchTemplate(img, template_img, cv2.TM_CCOEFF) minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result) ya, yb, xa, xb = maxLoc[0] + 0, maxLoc[0] + 45, maxLoc[1] + 0, maxLoc[1] + 45 lx, ly, rx, ry = maxLoc[0] + 45, maxLoc[1] + 45, maxLoc[0] + 0, maxLoc[1] + 0 source = cv2.imread(path.replace('binary', 'src')) img_clip = source[xa : xb, ya : yb] tmp_path = path.replace('.jpeg', '_') tmp_path2 = tmp_path.replace('binary', 'result') cv2.imwrite(tmp_path2 + "clip.jpeg" ,img_clip) def save_image(img_path,bese_dir,dir, img): dir_name = img_path.replace(bese_dir,dir) cv2.imwrite(dir_name, img) def worker(): global file_url global f_getpic file_url = "list.jpeg" files = glob.glob('*.jpeg') cnt = 0 for f in files: shutil.copyfile(f,TARGETPATH + "src/" +str(cnt)+'_0.jpeg' ) cnt = cnt + 1 do_grayscale(TEMPPATH + "src/" + OPENPATH) do_grayscale(TEMPPATH + "src/" + CLOSEPATH) for path in glob.glob(TARGETPATH + "src/" + TARGEFILETPATH): do_grayscale(path) do_binarization(TEMPPATH + "gray/" + OPENPATH) do_binarization(TEMPPATH + "gray/" + CLOSEPATH) for path in glob.glob(TARGETPATH + "gray/" + TARGEFILETPATH): do_binarization(path) do_template_matching(TARGETPATH,TEMPPATH) test_images = np.array(get_grayscale("media/TARGET/result/0_0_clip.jpeg")) class_names = ['Close', 'Open', 'Cannot Detect'] model = tf.keras.models.load_model('saved_model/my_model') model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) test_images = (np.expand_dims(test_images,0)) predictions = model.predict(test_images) index_name = np.argmax(predictions) if index_name > 2: index_name = 2 if index_name < 0: index_name = 2 print(class_names[index_name]) param = { "token": TOKEN, "channels": CHANNEL, # "filename":"filename", "initial_comment": class_names[index_name] # "title": "title" } files = {'file': open(file_url, 'rb')} url = "https://slack.com/api/files.upload" requests.post(url, data=param, files=files) f_getpic = 0 if __name__=="__main__": print("start") asyncio.get_event_loop().run_forever() ```
- 動作確認
# 動作確認
@[twitter](https://twitter.com/j03964608/status/1389080544412016643) @[twitter](https://twitter.com/j03964608/status/1389080976681095171) - まとめ 作った後に分類器なんか使わんでも画像送信だけでいいのでは?と考えたが、読み上げ機能と組み合わせることで目が不自由な人や画像を確認できない状況にある人に対して効果を発揮できると考えられる。 この出先から鍵の様子を確認できるシステムにより、外出先で家の鍵閉めたっけ?と不安になることはないでしょう。やったね!!!