CyberAgent Developers Advent Calendar 2017の14日目の記事です。
本日はOpenCVを利用して動画(カメラ)から動体検知をする方法について紹介したいと思います。

 

みなさんこんにちは。全社システム本部のエンジニア李俊浩と申します。

全社システム本部はサイバーエージェント本体や、グループ会社の各種事業に対する技術支援など幅広い領域に手掛けることができる部署です。

今回の記事は全社システム本部で色々積んできた業務経験の内、一つの業務経験を元にして記事にしました。みなさんの役に立ったらいいですね。

 

はじめに

今回のプロジェクトでは、カメラから画像情報を取り出し加工して空き状況を分析するというお仕事にアサインされたので、OpenCVを使いながら色々学んだ情報を共有したいと思います。

本記事はOpenCVについての概要と開発環境の説明そして大まかな流れ説明しています。本記事を読む前にこちらのチュートリアルサイトの一読をおすすめします

 

OpenCVとは?

OpenCV(Open Source Computer Vision Library)とはインテルが開発・公開したオープンソースのコンピュータビジョンライブラリーです。

画像や動画を処理するのに必要な様々な機能が備わっています。いわゆる画像編集ツール機能が低水準で操作することが出来る素晴らしいライブラリーです。

言語はJava、Python、C++などマルチプラットフォームなのでUnix系OS、Linux、Windows、Android、iOS等幅広くサポートしています。

非公式ではC#, Go, Processing, Lua, Ruby, PHP, Haskellなどあります。

 

OpenCVで出来ることは?

物体認識、輪郭抽出、ノイズ除去、ぼかし、切り出し、フィルター処理、領域分割、特徴点抽出、回転など画像処理に関して盛りだくさんの機能が備わっています。

最近は機械学習などでも利用されてコンピュータビジョンライブラリとして高い知名度を誇ります。
OpenCVで出来ること

環境情報

  • macOS Sierra
  • python 3.6.3
  • opencv 3.3.1

Macの場合はbrewで手軽に環境構築が出来るのでおすすめします。

※AVI動画をOpenCVで扱うにはcontribのffmpegを入れることをおすすめします。Python2やOpenCV2は大きく構造が違うのでご注意ください。

 

動画(カメラ)から動体検知する流れ

元画像(加工前)

元画像(加工前)
この画像はこちらの動画からお借りして生成した画像です。

この写真を中心に色々加工していきます。

 

画像をグレースケールする

グレースケール化


gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

画像をグレースケールにする一番大きい理由は彩度0にすることで不要な作業を減らすことができます。そして、OpenCVの2値化関数は入力にグレースケール画像を受け付けるため、カラー画像を2値化するなら、最初にグレースケール化を行う必要があります。

 

フレームを比較するためにの先に1フレームを取っておく


if avg is None:
    avg = gray.copy().astype("float")
    continue

avg = Noneで宣言して1フレームを取って変数に保存します。

常に現在のフレームと以前フレームと比較するため取っておく必要があります。現在のフレームと以前フレームは両方グレースケール画像です。

 

現在のフレームと移動平均との間の差を計算する


cv2.accumulateWeighted(gray, avg, 0.5)
frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))

現在のフレームと以前のフレームとの間の加重平均を累積して計算します。accumulateWeightedを毎フレーム実行することで精度を上げることができます。

累算器画像と現在のフレームを比較します。

 

デルタ画像を閾値処理しする

閾値処理


thresh = cv2.threshold(frameDelta, 3, 255, cv2.THRESH_BINARY)[1]

閾値を設定し、フレームを2値化します。cv2.thresholdこの処理によって画像が黒と白に分かれます。下の画像を参考にしてください。

閾値処理について

 

閾値処理された画像上の輪郭を見つける

輪郭抽出


cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

画像の輪郭抽出を行う場合には、cv2.findContoursという関数を使います。
現在のフレームと以前フレームと比較するので、早歩きする場合は上記のように残像(移動量)が残ります。

この現象はcv2.accumulateWeightedのalphaパラメータを調整することで微調整が可能せです。

 

感想

ちょっと昔、ARが一時的に流行った時に遊んでみた経験だけでこの空き状況を把握するプロジェクトにアサインされましたが、実は私も初心者です。その頃の記憶を辿りながらググりながらなんとなくプロジュエクトを進めています。

改めて色々触りながら気づきましたが、OpenCVのごく一部機能でさらに数行だけで動体検知が出来るのはすごい。びっくりしました。

本記事はシンプルに動体検知を行い、重要なソースコードだけ記載しましたが、最適化でGaussianBlurを利用して平滑化をしてノイズ除去したり、dilateを関数を使って膨張を行い輪郭を表示するときに計算が簡単になったりします。後ほどまた機会があれば続きでそこらへんもやってみたいと思います。

李俊浩
CyberAgentのエンジニア。CyberAgentの社内で困っていることや改善すべきことをシステム化して解決してます。ReactNative, Unityなどハイブリッド開発が好き。最近はMachine Learning勉強に夢中