【Python & OpenCV】虫の侵入経路を監視するプログラムを作ってみた

OpenCV

虫の侵入経路を監視するプログラムをPython+OpenCV+OpenGLで作ってみました。

大嫌いなクモの侵入経路を特定するための監視プログラムを作るつもりやったけど、我が家ではクモ以外にムカデ、ゲジゲジ、ゴキブリ、ダンゴムシなどを見たことがあるためクモに限定することをやめました。

そそたた
そそたた

1階の角部屋ですぐ横が田んぼやから虫が多いのだと思う。

一時期室内にダンゴムシが大量発生したことがある。

ヒィィィィ∑(゚Д゚; )

監視するプログラムは、前回作成した異物検知プログラムを検知したら録画するように改造しました。

結果

家の中で玄関をしばらく監視していても都合よく虫が侵入してくることはありませんでした。。

侵入経路を監視するという趣旨から外れてしまいますが、あきらめて外から玄関を1時間ほど監視してみたら複数録画されており、再生してみると全てアリでした。

2匹のアリを検出している部分を切り出して再生できるようにしておきました。

異物検知の部分を緑で縁取りしてますが小さくてほぼ見えないですね。

ちなみに、愛用のMacBook Proの内蔵カメラで撮影するのは無理があるのでWebカメラを購入しました。

そそたた
そそたた

Macだと周辺機器がちゃんと動くか心配でしたが我が家のMacBook ProにUSB接続するだけで問題なく使えました。

安いわりに映像も結構きれいやし大満足です。

ソースコード

作成したプログラムのソースコードを載せておきます。

開発環境は、MacBook Pro(2016年モデル)+Python3.5.5+OpenCV3.4.2+OpenGL3.1.1a1です。

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import cv2
import numpy as np
import datetime
import time

baseFrame = None    #比較元のフレーム
curFrame = None     #現在のフレーム
start = False       #比較開始フラグ
writer = None       #VideoWriterオブジェクト
detectStart = 0     #検出開始時間

cap = cv2.VideoCapture(0)

fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fps = int(cap.get(cv2.CAP_PROP_FPS))
writeWidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) / 2)
writeHeight = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) / 2)

def startWriter(frame):
    global writer, writeHeight, writeHeight
    if writer == None:
        now = datetime.datetime.now()
        writeFileName = '/Users/shibata/work/Python/data/' + now.strftime('%Y%m%d_%H%M%S') + '.mp4'
        writer = cv2.VideoWriter(writeFileName, fourcc, fps, (writeWidth, writeHeight))

    writer.write(frame)

def stopWriter():
    global writer
    if writer != None:
        writer.release()
        writer = None

def draw():
    global start, curFrame, baseFrame, writeWidth, writeHeight, detectStart

    ret, curFrame = cap.read()
    drawFrame = curFrame.copy()

    if(start == True):
        bf = cv2.cvtColor(baseFrame,cv2.COLOR_BGR2GRAY)
        cf = cv2.cvtColor(curFrame,cv2.COLOR_BGR2GRAY)

        diff = cv2.absdiff(bf, cf)
        ret, diff = cv2.threshold(diff, 35, 255, cv2.THRESH_BINARY)

        kernel = np.ones((5, 5), np.uint8)
        erosion = cv2.erode(diff, kernel, iterations = 1)

        im2, contours, hierarchy = cv2.findContours(erosion, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        #異物を検出したら左上に赤●を描画して録画する
        detected = False
        for c in contours:
            x, y, w, h = cv2.boundingRect(c)
            if 75 < (x + w) and (x + w) < drawFrame.shape[1] - 75 and 75 < (y + h) and (y + h) < drawFrame.shape[0] - 75:
                detected = True
                break

        if detected == True:
            if detectStart == 0:
                detectStart = time.time()

            if 1 < time.time() - detectStart:
                cv2.circle(drawFrame, (40, 40), 30, (0, 0, 255), -1)
                drawFrame = cv2.drawContours(drawFrame, contours, -1, (0,255,0), 1)
                writeframe = cv2.resize(drawFrame, (writeWidth, writeHeight))
                startWriter(writeframe)
        else:
            detectStart = 0
            stopWriter()

    drawFrame = cv2.cvtColor(drawFrame, cv2.COLOR_BGR2RGB)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawFrame.shape[1], drawFrame.shape[0], 0, GL_RGB, GL_UNSIGNED_BYTE, drawFrame)

    glEnable(GL_TEXTURE_2D)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

    glBegin(GL_QUADS) 
    glTexCoord2d(0.0, 1.0)
    glVertex3d(-1.0, -1.0, 0.0)
    glTexCoord2d(1.0, 1.0)
    glVertex3d(1.0, -1.0, 0.0)
    glTexCoord2d(1.0, 0.0)
    glVertex3d(1.0,  1.0, 0.0)
    glTexCoord2d(0.0, 0.0)
    glVertex3d(-1.0, 1.0, 0.0)
    glEnd()

    glFlush()
    glutSwapBuffers()

def idle():
    glutPostRedisplay()

def keyboard(key, x, y):
    global start, curFrame, baseFrame
    key = key.decode('utf-8')
    if key == 'q':
        exit()
    elif key == 's':
        baseFrame = curFrame.copy()
        start = True
    elif key == 'e':
        start = False
        stopWriter()

if __name__ == "__main__":
    glutInitWindowPosition(0, 0)
    glutInitWindowSize(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE)
    glutCreateWindow("Camera")
    glutDisplayFunc(draw)
    glutKeyboardFunc(keyboard)
    glutIdleFunc(idle)
    glutMainLoop()

    cap.release()
    stopWriter()

解説

録画と誤検知を抑える工夫の部分を解説します。

カメラ映像のフレーム描画に関しては、異物検知プログラムで説明していますのでそちらを参照してください。

録画

OpenCVのVideoWriterを使えばめちゃめちゃ簡単に録画できました。

カメラのキャプチャサイズがフルハイ(1920 x 1080)と大きいので、録画容量節約のためキャプチャサイズの半分で録画するようにしています。

startWriter関数でVideoWriterオブジェクト生成+フレーム書込み、stopWriter関数でVideoWriterオブジェクト破棄を実施しています。

fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
fps = int(cap.get(cv2.CAP_PROP_FPS))
writeWidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) / 2)
writeHeight = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) / 2)

def startWriter(frame):
    global writer, writeHeight, writeHeight
    if writer == None:
        now = datetime.datetime.now()
        writeFileName = '/Users/shibata/work/Python/data/' + now.strftime('%Y%m%d_%H%M%S') + '.mp4'
        writer = cv2.VideoWriter(writeFileName, fourcc, fps, (writeWidth, writeHeight))

    writer.write(frame)

def stopWriter():
    global writer
    if writer != None:
        writer.release()
        writer = None

誤検知を抑える工夫

Webカメラのオートゲインや自動ホワイトバランスなどの影響かと思っておりますが、映像の端を誤検出しがちなので映像領域の端から75ピクセルに関しては差分を無視するようにしています。

        detected = False
        for c in contours:
            x, y, w, h = cv2.boundingRect(c)
            if 75 < (x + w) and (x + w) < drawFrame.shape[1] - 75 and 75 < (y + h) and (y + h) < drawFrame.shape[0] - 75:
                detected = True
                break

また、屋外の撮影だと光の加減で細かい誤検出が結構ありました。

1秒以上連続で異物検知した場合に録画することでかなり改善できました。

        if detected == True:
            if detectStart == 0:
                detectStart = time.time()

            if 1 < time.time() - detectStart:
                cv2.circle(drawFrame, (40, 40), 30, (0, 0, 255), -1)
                drawFrame = cv2.drawContours(drawFrame, contours, -1, (0,255,0), 1)
                writeframe = cv2.resize(drawFrame, (writeWidth, writeHeight))
                startWriter(writeframe)
        else:
            detectStart = 0
            stopWriter()

考察

本気で虫の侵入経路を監視するには、下記のような課題があることが分かった。

  • 複数のカメラで長時間の監視が必要
  • 対象が小さいので画角を狭く拡大できるカメラが必要
  • 映ってはいけないもの(幽霊)が映ってしまった時の対処方法(笑)
  • カメラ+パソコンを設置すると思ったより邪魔で家族から苦情が来る
  • 録画のデータ容量が大きいためHDD空き容量の確保
そそたた
そそたた

家族に虫侵入経路監視プログラムってどう?って聞いたらフマキラーのHP見た方よくね?と言われてしまった。

꒪ཀ꒪)グハァ

コメント

タイトルとURLをコピーしました