「背景差分で物体検出をしてみた」の記事が面白くて、「複数の物体」でも
検出できるのか検証してみました。
ディープラーニングを使わずに、ラズパイで複数の「物体検出」を
— shinmura0 (@shinmura0) 2019年5月24日
実装しました。OpenCVを使っております。 pic.twitter.com/5DQjPO1sfH
#はじめに
きっかけは、ラズパイで2つのディープラーニングモデルを動かしていたときのこと。
予想通り、速度は激遅で使いものになりませんでした。
そのため、OpenCVで物体検出できないか?と考え実装してみました。
本稿では、ディープラーニングを使わないOpenCVによる複数の物体検出を行ってみます。
#OpenCVによる物体検出
まずは、背景写真を用意します。
そして、フィルターによる前処理を行います。
import cv2
img1 = cv2.imread("background.jpg")
img1 = cv2.GaussianBlur(img1, (5, 5), 0)
同じく前処理をしておきます。
img1 = cv2.imread("test.jpg")
img2 = cv2.GaussianBlur(img2, (5, 5), 0)
そして、OpenCVの機能で物体検出をさせます。
fgbg = cv2.bgsegm.createBackgroundSubtractorMOG()
fgmask = fgbg.apply(np.uint8(img1))
fgmask = fgbg.apply(np.uint8(img2))
cv2.imshow(fgmask)
恐ろしく簡単に検出することができました。
しかも、爆速です。
#k-means法によるクラスタリング
次に得られたモノクロマップで物体を分割します。
今回は、k-means法によるクラスタリングを行います。
本当は混合ガウス分布の方が良いのですが、処理を少しでも軽くするために
k-means法(正確にはk-means++)を使います。
得られたマップに対し、k-means法を適用します。
from sklearn.cluster import KMeans
Y, X = np.where(fgmask > 200)
y = KMeans(n_clusters=2, random_state=0).fit_predict(np.array([X,Y]).T)
これも爆速で実行することができます。
一応可視化します。
import matplotlib.pyplot as plt
plt.scatter(X, -Y+288, c=y)
plt.xlim(0,288)
plt.ylim(0,288)
plt.show()
あとは、クラスタに合わせてバウンディングボックスを作ります。
def get_x_y_limit(Y, X, result, cluster):
NO = np.where(result==cluster)
x_max = np.max(X[NO])
x_min = np.min(X[NO])
y_max = np.max(Y[NO])
y_min = np.min(Y[NO])
x_max = int(x_max)
x_min = int(x_min)
y_max = int(y_max)
y_min = int(y_min)
return x_min, y_min, x_max, y_max
def bounding_box(img, x_min, y_min, x_max, y_max):
img = cv2.rectangle(img, (x_min, y_min), (x_max, y_max), (0, 255, 0), 5)
return img
x_min, y_min, x_max, y_max = get_x_y_limit(Y, X, y, 0)
img2 = bounding_box(img2, x_min, y_min, x_max, y_max)
x_min, y_min, x_max, y_max = get_x_y_limit(Y, X, y, 1)
img2 = bounding_box(img2, x_min, y_min, x_max, y_max)
cv2_imshow(img2)
注目すべきは「矢印」の部分。
影が映り込んでいるのにも関わらず、検出されない点が素晴らしいです。
単に背景写真と差分をとると、どうしても影が検出されてしまうのですが、
ここは天下のOpenCV、さすがです!
#制約
ただし、本手法は制約があります。
それは、「物体が一つでも必ず複数に分割されてしまう」ということです。
具体例を挙げます。
今回は手を二つ認識させたいため、クラスタリングの数を「2」に設定します。
手が二つある写真は、先ほど見ていただいたとおり、手が別々に検出されます。
ところが、手が一つの写真を入れても二つの物体が検出されてしまいます。
ここはk-means法を使っている以上、しょうがないところです。
また、k-means法は特性上、距離が近いものを同じ物体と認識します。
従って、よく言われる半月が重なったような形状ではうまく検出することが
できません。
「物体数が決まっていない」、もしくは「複雑な形状を検出したい」場合は、
DBSCANを使うと良いかもしれません。
#ラズパイで実行
ラズパイでリアルタイムに動かすコードはこちらに置きました。
main_opencv.pyを実行し、「p」キーを押せば、以下のようにリアルタイムで
検出することができます。
ディープラーニングを使わずに、ラズパイで複数の「物体検出」を
— shinmura0 (@shinmura0) 2019年5月24日
実装しました。OpenCVを使っております。 pic.twitter.com/5DQjPO1sfH
本気を出せば、13FPSくらいになります。
終了したい場合は、「q」キーを押してください。
ただし、単にopencvをインストールしただけだと動かず、以下のようにラズパイで
contribをインストールする必要がありました。
sudo -H pip3 install opencv-contrib-python==3.4.3.18
バージョンはお好みで指定してください。
#まとめ
- ラズパイでも、OpenCVを使えば爆速で複数の物体検出ができる。
- ただし、固定カメラ限定で、かつ事前に検出したい物体数を決めておく必要がある。
- 本技術を応用すれば、物体検出で必要なアノテーションデータも手軽に作成することができる。