#はじめに
OpenCVで遊ぶときに、色をベースに物体検出がしたくなるときがあります。
多くの場合、OpenCVで色検出をするときに
- cv2.cvtColorをつかって、RGB色空間からHSV色空間へ変換
- cv2.inRangeでHSV色空間の範囲を指定して2値化
- 出てきた画像からfindContoursして形でフィルタリング
という方法が紹介されています。
一方で同様に、Numpyを使って画素毎の条件で2値化する方法が考えられます。
ここでは、この2つの方法のメリット、デメリットとcupyでの実装も含め速度をXavierNX上で比較しました。
#やったこと
HSVに変換したあとinRangeで2値化する方法を含め以下の4条件を比較しました
1、inRangeを使って緑のボールを検出する方法
2、numpyで条件を指定して2値化する方法
3、numpyでinRangeだとできない条件で検出する方法
4、3をCUPYで高速化した場合
1と23で結果の画像を 1234で速度をそれぞれ比較してみました。
python3で試したソースは後半に4通りつけています。
#inRangeでの問題点
通常inRangeを用いて色検出を行う場合、色相ベースで色検出がしたいため、まずRGB色空間からHSV色空間へ変換を行うかと思います。
この場合
- inRangeは、基本的に、色空間上で各軸に平行な定数での閾値しか取れず自由度が低い
- inRangeをHSV色空間で使用する場合、赤周辺の色がH=0とH=180の境界を跨ぐため、inRange一発でフィルタできない
- 白と黒の周辺では照明の条件によりHが変動してしまう。、
- inRangeではCUDAのアクセラレーションを効かせられない
結果として、inRangeを用いた場合
のように、画像下部の黒いケーブルや、画像上部の白い壁部分を緑のボールと区別することに非常に苦労することになります。
#Numpyでの実装
NumpyでinRangeと同様のフィルタは、
hsv = cv2.cvtColor( frame , cv2.COLOR_BGR2HSV )
h = frame[:,:,0]
s = frame[:,:,1]
v = frame[:,:,2]
mask_g = np.zeros(h.shape, dtype=np.uint8)
mask_g[ (h>20) & (h <100) & (s>200) & (s < 255) & (v>50) & (v<150 ) ] = 255
として実装が可能かと思います。
この場合
mask_g[ (h>20) & (h <100) & (s>200) & (s < 255) & (v>50) & (v<150 ) ] = 255
の部分は
mask_g[ ( g/r> 2.0) & (g/b>2.0) ] = 255
などと色空間の軸に平行な定数としての閾値だけでなく、要素毎の比率などを元に閾値が設定可能で、線形もしくはより複雑な閾値も設定が可能です。上記の例では、検出後の画像は以下のようになり、より期待した結果を容易に得られるかと思います。
上記の例では、緑の要素が赤、青の倍明るい場合に検出され、以下のように黒、白部分を容易に除外できます。
速度の比較
一方で、速度については、
- inRange
- numpy( inRange同様のフィルタ条件 )
- numpy( R/B > 2 & R/G>2 )
- cupy( R/B >2 & R/G > 2 )
の4条件で以下のようになりました。
1.inRange | 2.numpy(inRange同様) | 3.numpy(RGB相関) | 4.CUPY |
---|---|---|---|
0.009[s] | 0.047[s] | 0.055[s] | 0.021[s] |
inRangeが一番はやいとはおもわなかった。。。。 |
#試したソースコード
##1, inRangeで緑のボールを検出
import cv2
import numpy as np
import time
src = 'v4l2src device= /dev/video0 ! image/jpeg,width=1920,height=1080 !jpegdec !videoconvert ! appsink'
cap=cv2.VideoCapture(src)
W = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
H = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = cap.get(cv2.CAP_PROP_FPS)
while(cap.isOpened()):
sum=0
for i in range( 0,100 ):
ret, frame = cap.read()
start=time.time()
r = frame[:,:,0]
g = frame[:,:,1]
b = frame[:,:,2]
mask_g = np.zeros(r.shape, dtype=np.uint8)
mask_g[ ( g/r> 2.0) & (g/b>2.0) ] = 255
end = time.time()
sum+= (end- start)
cv2.imshow('ReadM',mask_g )
cv2.waitKey(1)
if i % 10 == 0 :
print( i )
print( sum/100)
cap.release()
##2、numpyで条件を指定して2値化する方法
import cv2
import numpy as np
import time
src = 'v4l2src device= /dev/video0 ! image/jpeg,width=1920,height=1080 !jpegdec !videoconvert ! appsink'
cap=cv2.VideoCapture(src)
W = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
H = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = cap.get(cv2.CAP_PROP_FPS)
while(cap.isOpened()):
sum=0
for i in range( 0,100 ):
ret, frame = cap.read()
start=time.time()
hsv = cv2.cvtColor( frame , cv2.COLOR_BGR2HSV )
h = frame[:,:,0]
s = frame[:,:,1]
v = frame[:,:,2]
mask_g = np.zeros(h.shape, dtype=np.uint8)
mask_g[ (h>20) & (h <100) & (s>200) & (s < 255) & (v>50) & (v<150 ) ] = 255
end = time.time()
sum+= (end- start)
cv2.imshow('ReadM',mask_g )
cv2.waitKey(1)
if i % 10 == 0 :
print( i )
print( sum/100)
cap.release()
##3、numpyでinRangeだとできない条件で検出する方法
import cv2
import numpy as np
import time
src = 'v4l2src device= /dev/video0 ! image/jpeg,width=1920,height=1080 !jpegdec !videoconvert ! appsink'
cap=cv2.VideoCapture(src)
W = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
H = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = cap.get(cv2.CAP_PROP_FPS)
while(cap.isOpened()):
sum=0
for i in range( 0,100 ):
ret, frame = cap.read()
start=time.time()
r = frame[:,:,0]
g = frame[:,:,1]
b = frame[:,:,2]
mask_g = np.zeros(r.shape, dtype=np.uint8)
mask_g[ ( g/r> 2.0) & (g/b>2.0) ] = 255
end = time.time()
sum+= (end- start)
cv2.imshow('ReadM',mask_g )
cv2.waitKey(1)
if i % 10 == 0 :
print( i )
print( sum/100)
cap.release()
##4、3をCUPYで高速化した場合
import cv2
import cupy as cp
import time
src = 'v4l2src device= /dev/video0 ! image/jpeg,width=1920,height=1080 !jpegdec !videoconvert ! appsink'
cap=cv2.VideoCapture(src)
W = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
H = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = cap.get(cv2.CAP_PROP_FPS)
while(cap.isOpened()):
sum=0
for i in range( 0,100 ):
ret, frame = cap.read()
start=time.time()
frame_cupy = cp.asarray( frame )
r = frame_cupy[:,:,0]
g = frame_cupy[:,:,1]
b = frame_cupy[:,:,2]
mask_g = cp.zeros(r.shape, dtype=cp.uint8)
mask_g[ ( g/r> 2.0) & (g/b>2.0) ] = 255
mask_gn = cp.asnumpy( mask_g )
end = time.time()
sum+= (end- start)
cv2.imshow('ReadM',mask_gn )
cv2.waitKey(1)
if i % 10 == 0 :
print( i )
print( sum/100)
cap.release()