最初に、MeanShiftの理解の為に写真を撮ります。
その後で、タチコマを追いかけます。
MeanShiftの概要
ミーンシフト法は(mean shift)法は、画像領域の分割や対象画像の追跡に用いられる手法である。
ディジタル画像処理 p.205
近傍の点群の平均(mean)位置に移動(shift)を繰り返し、極大点を求める方法からミーンシフト法と呼ばれる。
ディジタル画像処理 p.206
MeanShiftのサンプルプログラムの説明
OpenCV 3.2のMeanShiftのサンプルプログラムを少し変更したソースコードを掲載します。
コメントを付けて、写真を保存するように変更しています。
open_cv_meanshift.py
# -*- coding: utf-8 -*-
# [USAGE]
# python3 open_cv_meanshift.py
# ------------------------------------------------------------------------------
import os
import time
## library for array
import numpy as np
## OpenCV 3.2
import cv2 as open_cv
import matplotlib.pyplot as plt
# ------------------------------------------------------------------------------
SLEEP_TIME = 0.0
MEANSHIFT_RECOVERY_TIME = 0.5
WIDTH = 640
HEIGHT = 480
IMAGE_CENTER_X = WIDTH/2
IMAGE_CENTER_Y = HEIGHT/2
ROI_WIDTH = 240
ROI_HEIGHT = 240
ROI_X_START = int((WIDTH - ROI_WIDTH) / 2)
ROI_Y_START = int((HEIGHT - ROI_HEIGHT) / 2)
# ------------------------------------------------------------------------------
## create instance
video_capture = open_cv.VideoCapture(0)
if video_capture.isOpened():
print('video capture: 0')
else:
video_capture = open_cv.VideoCapture(1)
if video_capture.isOpened():
print('video capture: 1')
else:
print('can not get video capture')
quit()
# ------------------------------------------------------------------------------
## video -> photo
## get a frame
while True:
ret, frame = video_capture.read()
if ret:
print("GET VIDEO CAPTURE")
break
else:
print("NOT GET VIDEO CAPTURE")
open_cv.imwrite("001_frame.png", frame)
# ------------------------------------------------------------------------------
## Width
video_capture.set(open_cv.CAP_PROP_FRAME_WIDTH, WIDTH)
# ------------------------------------------------------------------------------
## Height
video_capture.set(open_cv.CAP_PROP_FRAME_HEIGHT, HEIGHT)
# ------------------------------------------------------------------------------
## setup ROI for tracking
## setup initial location of window
## C525 (depend on specification) -> 1280 x 720
## OpenCV (depend on setting) -> 640 x 480
## ROI (depend on setting) -> 240 x 240
# 640
# <----------------------->
# (0,0) x = 200
# -------------------------- ^
# | | |
# | w = 240 | |
# y = 120 | ------- | |
# | | | h = 240 | | 480
# | ------- | |
# | | |
# | | |
# -------------------------- v
x, y, w, h = ROI_X_START, ROI_Y_START, ROI_WIDTH, ROI_HEIGHT
track_window = (x, y, w, h)
print('x, y, w, h: ', x, y, w, h)
## Region of Interest / Region of Image
## from y to y+h
## from x to x+w
roi = frame[y:y+h, x:x+w]
open_cv.imwrite("002_roi.png", roi)
# ------------------------------------------------------------------------------
## ignore dark pixel
## BGR (Blue Green Red) -> HSV (Hue Saturation Value)
## Value == Brightness
hsv_roi = open_cv.cvtColor(roi, open_cv.COLOR_BGR2HSV)
## open_cv.inRange( hsv_roi, np.array( Hue Min, Saturation Min, Value Min ), np.array( Hue Max, Saturation Max, Value Max ) )
img_mask = open_cv.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
open_cv.imwrite("003_img_mask.png", img_mask)
# ------------------------------------------------------------------------------
## histgram
roi_hist = open_cv.calcHist([hsv_roi], [0], img_mask, [180], [0, 180])
print('roi_hist')
print(roi_hist)
plt.plot(roi_hist)
plt.savefig("004_roi_hist.png")
open_cv.normalize(roi_hist, roi_hist, 0, 255, open_cv.NORM_MINMAX)
print('normalized_roi_hist')
print(roi_hist)
plt.plot(roi_hist)
plt.savefig("005_normalized_roi_hist.png")
# ------------------------------------------------------------------------------
## make criteria for mean shift
## type : open_cv.TERM_CRITERIA_EPS or open_cv.TERM_CRITERIA_COUNT
## max_iter : 10
## epsilon / accuracy : 1.0
termination_criteria = (open_cv.TERM_CRITERIA_EPS | open_cv.TERM_CRITERIA_COUNT, 10, 1)
# ------------------------------------------------------------------------------
while True:
## input q -> quit
k = open_cv.waitKey(1)
if k == ord('q'):
S.write('q'.encode('utf-8'))
time.sleep(2)
break
## get a frame
ret, frame = video_capture.read()
if ret:
## BGR (Blue Green Red) -> HSV (Hue Saturation Value) ## Value == Brightness
hsv = open_cv.cvtColor(frame, open_cv.COLOR_BGR2HSV)
## open_cv.calcBackProject( images, channels, mask, hist_size, ranges )
dst = open_cv.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
open_cv.imwrite("006_back_project.png", dst)
## get the position
ret, track_window = open_cv.meanShift(dst, track_window, termination_criteria)
x, y, w, h = track_window
print('x, y, w, h: ', x, y, w, h)
if ret:
print('meanshift success')
else:
print('meanshift fail')
## write rectangle
## color : Scalar(255,255,255) == white
## thickness : thickness of lines : 3
img_dst = open_cv.rectangle(frame, (x, y), (x+w, y+h), (255, 255, 255), 3)
## get center of meanshift
roi_center_x = int(x + (ROI_WIDTH/2))
roi_center_y = int(y + (ROI_HEIGHT/2))
print('roi_center_x, roi_center_y: ', roi_center_x, roi_center_y)
print('IMAGE_CENTER_X, IMAGE_CENTER_Y: ', IMAGE_CENTER_X, IMAGE_CENTER_Y)
## write small circle to roi center
## image
## center
## radius : diameter / 2
## color
## thickness : negative value -> filled
open_cv.circle(img_dst, (roi_center_x, roi_center_y), 10, (0, 180, 0), -1)
open_cv.imshow('SHOW MEANSHIFT IMAGE', img_dst)
time.sleep(SLEEP_TIME)
quit()
# ------------------------------------------------------------------------------
else:
break
video_capture.release()
open_cv.destroyAllWindows()
それぞれについて説明します。(重要なところだけ)
video_capture = open_cv.VideoCapture(0)
if video_capture.isOpened():
print('video capture: 0')
else:
video_capture = open_cv.VideoCapture(1)
if video_capture.isOpened():
print('video capture: 1')
else:
print('can not get video capture')
quit()
WebカメラをOpenCVから使えるようにします。大体の場合に0か1の値で良いのですが、失敗するときがあります。pythonスクリプトの実行を繰り返すと成功します。
while True:
ret, frame = video_capture.read()
if ret:
print("GET VIDEO CAPTURE")
break
else:
print("NOT GET VIDEO CAPTURE")
open_cv.imwrite("001_frame.png", frame)
Webカメラから画像を1枚取得します。retは、return valueの略で画像の取得に成功するとtrueとなり、画像の取得に失敗するとfalseになります。画像の取得に成功しているときframeには画像データが入ります。
roi = frame[y:y+h, x:x+w]
open_cv.imwrite("002_roi.png", roi)
左上を(0, 0)として(120, 200)から、高さ240、幅240の領域をROI(Region of Interest / Region of Image)として使います。
hsv_roi = open_cv.cvtColor(roi, open_cv.COLOR_BGR2HSV)
## open_cv.inRange( hsv_roi, np.array( Hue Min, Saturation Min, Value Min ), np.array( Hue Max, Saturation Max, Value Max ) )
img_mask = open_cv.inRange(hsv_roi, np.array((0., 60., 32.)), np.array((180., 255., 255.)))
open_cv.imwrite("003_img_mask.png", img_mask)
ROIの部分だけをcvtColor関数を利用してBGRをHSVに変換します。inRange関数を利用して暗すぎる部分(彩度59以下、明度31以下)を無視するためのフィルターを作成します。
HSVについて
BGRは、Blue、Green、Redを表します。OpenCVではRGBでなくBGRが使われています。
HSVは、Hue(色相)、Saturation(彩度)、Value(明度)を表します。
色相は、赤を0度、緑を120度、青を240度として、360度で色を表します。OpenCVでは1/2の180度で同じことを表しています。ですので、緑は60度、青は120度の値になります。
彩度は、0のときは黒や白になり、値が大きいほど白や黒ではなく赤、青、黄、緑、紫だけの色になります。OpenCVでは上限は255です。
明度は、OpenCVでは0が黒、255が白になり、1から254は値が大きいほど白に近いグレーになります。
OpenCV以外では、考え方は同じですが、値の範囲などが異なる場合があります。
BGRやRGBより、HSVの方が適した処理(全体的に明るくしたい、など)があります。BGRやRGBから簡単な計算でHSVを得ることができます。
roi_hist = open_cv.calcHist([hsv_roi], [0], img_mask, [180], [0, 180])
print('roi_hist')
print(roi_hist)
plt.plot(roi_hist)
plt.savefig("004_roi_hist.png")
calcHist関数を利用して、ヒストグラムを作成します。
calcHist関数の引数について説明します。
[hsv_roi] | 入力画像。 |
[0] | 入力画像がHSVなので、[0]はHue。[1]ならSaturation。[2]ならValue。 |
img_mask | inRange関数で作成したフィルター。 |
[180] | Hueの全ての値についてヒストグラムを作るため180を設定。入れ物の数を設定する。 |
[0,180] | Hueの全ての値についてヒストグラムを作るため0-180を設定。範囲を設定する。 |
色相だけに基づいたヒストグラムを作成しています。色相だけのヒストグラムを使ったMeanShiftになります。
ROIの部分(=2枚目の小さな写真)について色相だけを考える場合、大部分は濃い青なので、120°付近だけが飛び出たグラフになっています。横軸は色相(範囲は0-180)で、縦軸はpixel数です。
open_cv.normalize(roi_hist, roi_hist, 0, 255, open_cv.NORM_MINMAX)
print('normalized_roi_hist')
print(roi_hist)
plt.plot(roi_hist)
plt.savefig("005_normalized_roi_hist.png")
calcBackProject関数の入力とするために0-255の範囲に正規化します。見えにくいですが、緑色の線が正規化したヒストグラムです。
ret, frame = video_capture.read()
if ret:
## BGR (Blue Green Red) -> HSV (Hue Saturation Value) ## Value == Brightness
hsv = open_cv.cvtColor(frame, open_cv.COLOR_BGR2HSV)
## open_cv.calcBackProject( images, channels, mask, hist_size, ranges )
dst = open_cv.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
open_cv.imwrite("006_back_project.png", dst)
## get the position
ret, track_window = open_cv.meanShift(dst, track_window, termination_criteria)
もう一度、USBカメラから画像を取得して、HSVに変換します。この画像と、正規化したROIの色相のヒストグラムをcalcBackProject関数に渡します。
ROIの色相のヒストグラムに基づいたグレースケール画像を取得します。
roi_histは更新されずに、最初の計算のまま使われ続けます。
色がある部分については、濃い青の部分のみ白くなっていて、赤、緑、黄、薄い青の部分は黒くなっています。
白い部分は白く、黒い部分は黒く、そのままです。
ROIの色相のヒストグラムに基づいて、正確に処理している様子がわかります。
この関数の仕様を調べると、single-channel arrayを出力するとなっているので、これはグレースケール画像の出力とわかります。
これから先、ここで得たROIの中の画素の分布密度に基づいてtrack_windowを移動させることを繰り返して、タチコマを追いかけます。
MeanShiftでタチコマを追いかける
四角の中の中心の点は不要だったかもしれません。
他の処理と連携させると色々とできそうです。