West Gate Laboratory

人生を少しでも,面白く便利にするモノづくり

Python+opencvでウェブカメラの画像からインターホンモニタ部分を抽出・射影変換する

はじめに

この記事はオートロックマンション用不在時荷物受け取りシステムの開発記事です。

westgate-lab.hatenablog.com

前回までに、インターホン音の検知、共同玄関の解錠、インターホンモニタの撮影、スマホへのプッシュ通知、Google Calendarとの連携などができるようになった。これで不在中に荷物が届いても、あらかじめ配達時間帯を指定しておけば自動で共同玄関が開き、家の玄関前まで配達員がたどり着ける。そこまで来たらOKIPPAなどの簡易宅配ボックスに荷物を入れてもらうことができる。その意味で最低限の機能は実装できたが、もう少し工夫していこうと思う。

今回は、ウェブカメラで撮影してスマホにプッシュする画像を改良する。

具体的には、前回はウェブカメラで撮影した画像をそのままプッシュしていたが、今回は加えて実質的に重要なモニタ部分のみをPythonで抜き出し、射影変換して補正した上でプッシュする。

もしカメラの位置が固定されているのなら座標固定で抜き出せばいいのだが、今回の環境ではウェブカメラはきちんと固定されておらず、衝撃などで微妙に位置がずれる恐れがあったので、毎回自動的にモニタを抽出することとした。

なお、RaspberryPi 3B+、Python3.7.3を使っている。

撮影画像からモニタ部分の抽出する

Pythonで画像処理をするのに今回OpenCVを使ったわけだが、やはり似たことをした人はいるもので、今回は以下の記事を参考にしつつ実装した。(というかほとんどそのまま)

qiita.com

備忘録を兼ねてこの記事でも方法を述べていこうと思う。

今回相手にする画像は我が家のインターホンの画像だ。共同玄関で呼び出されるとモニタが表示される。スマホにプッシュするとしたら全体は必要なく、中央のモニタ部分だけユーザに送れば良い

f:id:kaname_m:20200108224150p:plain
我が家のインターホンの画像(呼び出し中)

前回の記事に、ウェブカメラを使って撮影する方法を書いたが、それを使ってframeという変数にBGRの画像データが入っているものとする。

まずはデータを二値化する。

import cv2
import matplotlib.pyplot as plt

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
ret, th = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
plt.imshow(th, cmap="gray")
plt.show()

f:id:kaname_m:20200108224153p:plain
二値化画像

明るい環境ではモニタ以外も二値化で抽出されてしまうが、このあとのfindContourとapproxPolyDPが最強なので気にせず進む。

次に二値化画像から輪郭を抽出する。抽出した輪郭の中から面積でインターホンモニタを選別し、四角形に近似する。計測してみたところ、インターホンモニタは面積が25000前後だったので、それ前後の輪郭があえばそれをモニタと見なした。

#輪郭検出
contours, hierarchy = cv2.findContours(th, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 面積がインターホンモニタに近いもののみ選別
areas = []
for cnt in contours:
    area = cv2.contourArea(cnt)
    if 22500 < area and area < 27500 :
        print(area)
        epsilon = 0.1*cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, epsilon, True)
        areas.append(approx)

img_contour = cv2.drawContours(frame, areas, -1,(255,0,0),3)
plt.imshow(img_contour)
plt.show()

f:id:kaname_m:20200108224156p:plain
インターホンモニタを抽出した

無事にモニタ部分を四角形で抽出することができた。

ここでは何をやっているかというと、findContoursで二値化画像から輪郭を抽出し、approxPolyDPで検出した輪郭をより少ない点数で近似している。epsilonは近似のパラメータである。正直これらの関数が最強なので、周りの照明環境が変動してもほとんど問題ない。

最後に、抽出したモニタ領域を射影変換して補正する。つまり、斜めから撮影した画像をあたかも正面から見たかのように変換する。

import numpy as np

# 射影変換
dst_size = [640,450]   # 射影変換後の画像サイズ
dst = []
pts1 = np.float32(areas[0])   # 抽出したモニタ領域の四隅の座標
pts2 = np.float32([[0,0],[0,dst_size[1]],[dst_size[0],dst_size[1]],[dst_size[0],0]])   # 射影変換後の四隅の座標

# ホモグラフィ行列を求め、射影変換する
M = cv2.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(frame, M, (dst_size[0],dst_size[1]))

plt.imshow(dst)
plt.show()

f:id:kaname_m:20200108224200p:plain
射影変換後のモニタ画像

無事モニタ部分を射影変換することができた。

実際に配達員が来てインターホンを押した時にはこんな感じにスマホに通知される。このときは不在時だったが、無事に荷物を受け取ることができた。やったね。

f:id:kaname_m:20200108230925p:plain
実際の通知画像(スマホキャプチャ)

f:id:kaname_m:20200108231839p:plain
OKIPPAで荷物受け取り