「社内で自分の席にいる人が何人か把握できるようにしたいんだけど、定点Webカメラ使って何とかできない?」
何だか面白そう!先日新しくこんな仕事をいただいた、新米エンジニア小林です。
パッと思いついたのは
- 一定時間ごとにWebカメラで写真を撮影する
- 画像認識を使って、写真に写った、対象となっている人が自分の席にいるか判定する
という単純な手法。しかし、
画像認識ってどうやってやるの?……うーん、とりあえずやってみるか!
というわけで、「とりあえず画像認識してみたい」という人に向けて、簡単な画像認識の方法を、実際に今回の仕事で行った順序に沿って紹介します。
目的を明確化する
何を、どのように画像認識するのか?
実際に画像認識を始める前に、改めて手短に目的を明確化しておきます。
まずは、「何を」。
定点Webカメラからの撮影範囲内に自分の席を持つ3人を対象に、自分の席にいる人が何人か判定を行います。

次に、「どのように」。
自分の席についている人が何人かという判定条件については、簡素化のため以下のような前提とします。
- 写真内に対象となっている3人以外は現れない
- 必ず自分以外の席には座らない
この前提の元、人の上半身を検出できれば自分の席にいると判定するようにします。この条件にヒットする人が何人いるか数えることで本題を満たします。
また、Pythonというプログラム言語とOpenCVという画像認識ライブラリを用います。今回は以下のような環境を構築しました。
- Mac mini (Late 2012)
- OS X El Capitan ver10.11.5
- Python3.4.4
- OpenCV3.1.0
環境構築の方法についてはこちらを参考に。
画像認識を行う
どうすれば人の上半身を検出できるようになるか?
今回は、
- プログラムに「『上半身』とはこういうものだ」と学習させる
- 実際に、判定させたい画像をプログラムに投げて判定させる
という順序で進めていきます。
始めに、プログラムに「『上半身』とはこういうものだ」と学習させます。具体的には、「この写真は『上半身』の写真だ」「これは『上半身』の写真ではない」ということをプログラムに学習させることによって、「上半身」の写真の特徴量を抽出します。抽出した特徴量をまとめたデータをカスケード分類器といいます。
最後に、判定させたい画像を、カスケード分類器のデータを読み込ませたプログラムに対して投げることで、プログラムが写真内に写っている上半身を検出します。
カスケード分類器を作成する
どのようにカスケード分類器を作成するのか?
以下に手順を示します。
- 「上半身」の画像を集める
- 「上半身」の画像のリストを作成する
- vecファイルをつくる
- 「上半身」でない画像を集める
- 「上半身」でない画像のリストを作成する
- カスケード分類器を作成する
「上半身」の画像を集める
まず、プログラムに「『上半身』とはこういうものだ」と教えるために必要となる「上半身」の画像を集めます。
ここで注意事項があります。それは
画面いっぱいに人の上半身が一つ写っている画像を探すこと
です。こうすることで、「上半身」の画像のリストを作成するの段階で大きく手間を省くことができます。
また、プログラムに学習させるにあたって、「上半身」の画像はできる限り多めに用意しましょう。
こちらによると7000枚ぐらいあると良いです。
しかし、「そんなに大量の画像を集められない!」というそこのあなた。
そんな場合は以下の方法で枚数を増やすことができます。
- データセットを配布しているサイトを探す
- 持っている画像を反転、回転した画像を新たに生成する
「上半身」でない画像を集める
次にプログラムに「これは『上半身』の写真ではない」と教えるために必要となる「上半身」でない画像を集めます。この写真は「上半身」が写っていない画像であれば何でもOKです。「上半身」でない画像もできる限り多めに用意しましょう。
こちらによると3000枚ぐらいあると良いです。
「上半身」の画像のリストを作成する
画像収集が終了したら、次は集めた画像の一覧を作成します。リストのフォーマットは以下のようになります。
${画像ファイルへのパス} ${一枚の画像中に含まれている「上半身」の個数} (${「上半身」を囲む矩形の左上X座標} ${「上半身」を囲む矩形の左上Y座標} ${「上半身」を囲む矩形の横幅} ${「上半身」を囲む矩形の縦幅})を${画像中の物体の個数}個数分

つまり、画面いっぱいに人の上半身が一つ写っている画像を用意した場合には
${画像ファイルへのパス} 1 0 0 ${画像ファイルの横幅} {画像ファイルの縦幅}
--- snip ---
${画像ファイルへのパス} 1 0 0 ${画像ファイルの横幅} {画像ファイルの縦幅}
${画像ファイルへのパス} 1 0 0 ${画像ファイルの横幅} {画像ファイルの縦幅}
と単純化できます。
「上半身」でない画像のリストを作成する
先ほどと同様に「上半身」でない画像の一覧を作成します。リストのフォーマットは以下のようになります。
${画像ファイルへのパス}
--- snip ---
${画像ファイルへのパス}
${画像ファイルへのパス}
vecファイルを作成する
「上半身」の画像及び画像リスト、「上半身」でない画像及び画像リストが作成できたら、これらを用いてvecファイルというカスケード分類器を作成するために必要となるファイルを作成します。
こちらを参考に
user$ opencv_createsamples -info ${「上半身」の画像の一覧ファイル} -vec ${新規に作成するvecファイル} -num ${「上半身」の画像のファイル数}
とコマンドを実行することで、vecファイルを作成できます。
カスケード分類器を作成する
ここまででカスケード分類器を作成する準備が整いました。
こちらを参考に
user$ opencv_traincascade -vec ${「先ほど作成したvecファイル} -data ${カスケード分類器を保存する場所} -bg ${「上半身」でない画像の一覧ファイル} -numPos ${「上半身」の画像のファイル数} -numNeg ${「上半身」でない画像のファイル数}
とコマンドを実行することで、カスケード分類器(xmlファイル)の作成が開始されます。
これでしばらくするとカスケード分類器が完成します!
実験:自分の席にいる人が何人か数える
判定させたい画像を、カスケード分類器のデータを読み込ませたプログラムに対して投げてみます。
具体的には、まず下記のソースコードを.py形式で保存します。
# -*- coding:utf-8 -*-
""" You can detect the targets and add rectangles around the target. """
__author__ = "a.kobayashi"
__version__ = "0.0.1"
__date__ = "September 2016"
import sys import numpy
import cv2
names = sys.argv
length = len(names)
cascade = cv2.CascadeClassifier(names[1])
for identifier in range(2, length):
img = cv2.imread(names[identifier])
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
result = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
if len(result):
for rect in result:
cv2.rectangle(img, tuple(rect[0:2]), tuple(rect[0:2] + rect[2:4]), (0, 0, 255), thickness=2)
cv2.imwrite(names[identifier], img)
そのあと、
user$ python ${上記の保存したファイル} ${作成したカスケード分類器} ${人の上半身を検出したい画像ファイル}
とコマンドを実行することで、人の上半身が検出された際にその部分を赤い四角で囲ってくれます。
まとめ
「とりあえず画像認識に触れてみる」ことに焦点を当てて紹介しました。いかがだったでしょうか?興味を持たれた方は、画像認識の理論的な部分に触れてみるとさらに理解が深まると思います。
また、実際に、自分の席にいる人が何人いるのか確かめてみました。こうなりました。

検出はできているようです。ただ、余計な部分まで検出しています……改善しなきゃ!

















