komeの備忘録

東大院卒外資ITエンジニアの技術ブログ

OpenCVで背景差分法を使って固定カメラ映像から動いている物体の連続合成写真を生成してみた

2016年にCVPRで発表されたLSBP手法をGoogle Summer of Code(GSoC)で改良した背景差分の手法を用いる。

概要

背景差分法の説明

GSoCで開発された背景差分法がなんだかいいという噂を聞いた。別に従来の背景差分法でもなんら問題ない現象もあるが、高精度で動体を判別してくれるらしい。
他の手法との比較は、以下のサイトに詳しく書いてあったので、参考にするといい。

whoopsidaisies.hatenablog.com

動体の検出と重ね合わせ

背景差分を用いることで、動体と静止背景を分離できる。これを用いて、もとの背景に動体を重ね合わせていくことを考える。
いわゆる陸上選手の走りの軌跡や、野球のボールの投球軌跡などを一枚の合成写真で見やすくした画像を生成する。
以下のリンク先にある、体操選手の試技の軌跡のようなものを最終的に作成する。

mainichi.jp

実装

それでは実装してみた。

#!/usr/bin/env python3
#! -*- coding: utf-8

import cv2
import numpy as np
import sys
from PIL import Image
from argparse import ArgumentParser

def get_option():
    argparser = ArgumentParser()
    argparser.add_argument('input', type=str, help='Absolute/relative path to input file')
    argparser.add_argument('-f', '--frame', type=int, default=3, help='Number of interval frame')
    argparser.add_argument('-s', '--start', type=int, default=0, help='Time of union start')
    args = argparser.parse_args()
    return args

def unimove(inputFile, frameNum, startTime):
    cap = cv2.VideoCapture(inputFile)
    bgs_gsoc = cv2.bgsegm.createBackgroundSubtractorGSOC()
    
    cap.set(0, startTime * 1000)
    i = 0
    while(cap.isOpened()):
        ret, frame = cap.read()
        maskIm = bgs_gsoc.apply(frame)
        bg = bgs_gsoc.getBackgroundImage()
        fg = cv2.bitwise_and(frame,frame, mask=maskIm)
        
        cv2.imshow('fg', fg)
        cv2.imshow('bg', bg)
        
        if i == 0:
            union = frame
        elif i % frameNum == 0:
            union = unionFgBg(maskIm, fg, union)
            cv2.imshow('union', union)

        k = cv2.waitKey(1) & 0xFF
        if k == ord('q'):
            cv2.destroyAllWindows()
            exit()
        elif k == ord('w'):
            fileName = '../' + str(i) + '.jpg'
            cv2.imwrite(fileName, union)
        i += 1

def unionFgBg(mask, fg, union):
    notMask = cv2.bitwise_not(mask)
    maskedBg = cv2.bitwise_and(union, union, mask=notMask)
    res = fg + maskedBg
    return res

if __name__ == '__main__':
    args = get_option()
    
    inputFile = args.input 
    frame = args.frame
    startTime = args.start
    unimove(inputFile, frame, startTime)

セットアップと使い方

セットアップ

想定する使用環境は下記のとおりである。

OS: Ubuntu18.04
Python: 3.6, 3.7
OpenCV:3.4.3

pythonをインストールしていなかったら、下記の記事を参考にインストールしよう。
www.komee.org

そしてOpenCVをセットアップする必要がある。これは以前書いた記事を参考にインストールしよう。
www.komee.org

もしもう既にOpenCVはセットアップ済みで必要ないよ!という人も、opencv_contribという追加パッケージをインストールしてもらう必要がある。
次のコマンドでインストール。(以前書いた記事ではついでにインストールしてある)

$ pip3 install opencv-contrib-python

また、実行するに当たって必要なpythonパッケージもインストールする。

$ pip3 install Image

これで使用環境は整った。

使い方

$ python unimove.py [動画のパス] [ -f 合成するフレーム間隔のフレーム枚数] [ -s スタート時刻]

動画のパスは必須項目である。
フレーム枚数、スタート時刻に関してはそれぞれ-f-sで指定して変更できるようにしてあるが、オプション引数である。何も指定しなかった場合、それぞれ3と0がセットされるようになっている。

動かしてみた

次の動画を素材にして、移動軌跡を合成してみた。

  • 元画像
    f:id:komee:20181026165307g:plain

  • 合成画像
    f:id:komee:20181026165322j:plain

精度はカメラの性能に応じて甘くなっているが、カメラの設定を変えたり、色しきい値でもう少しブラッシュアップすれば、よりよい移動軌跡を合成可能である。

まとめ

今回実装したソースコードは以下のgithubに公開している。
github.com

ソースコードを読むとわかるが、背景と全景の重ね合わせはあまり上手な方法では記述していない。というのも、やっつけで作ってしまったため、いい方法が思い浮かばず、気合で合成してしまったからである。
改良、改善はフォークして自由にやってほしいし、こうした方がいいんじゃない?というプルリクも大いにお待ちしております。

参考にしたサイト

OpenCV 3.4.1で背景差分 - whoopsidaisies's diary

opencv_contrib/bgfg_gsoc.cpp at 6520dbaa224a661ca8105b1ab0b71451fd715f4c · opencv/opencv_contrib · GitHub

http://www.cv-foundation.org/openaccess/content_cvpr_2016_workshops/w24/papers/Guo_Background_Subtraction_Using_CVPR_2016_paper.pdf

GitHub - kome2/unimove

(C) komee.org