7
10

More than 3 years have passed since last update.

OpenCVでの色検出方法の比較 inRange , numpy , cupy

Posted at

はじめに

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通りつけています。

ソース画像

color.jpg

inRangeでの問題点

通常inRangeを用いて色検出を行う場合、色相ベースで色検出がしたいため、まずRGB色空間からHSV色空間へ変換を行うかと思います。
この場合

  • inRangeは、基本的に、色空間上で各軸に平行な定数での閾値しか取れず自由度が低い
  • inRangeをHSV色空間で使用する場合、赤周辺の色がH=0とH=180の境界を跨ぐため、inRange一発でフィルタできない
  • 白と黒の周辺では照明の条件によりHが変動してしまう。、
  • inRangeではCUDAのアクセラレーションを効かせられない

結果として、inRangeを用いた場合
hsv_h.jpg
のように、画像下部の黒いケーブルや、画像上部の白い壁部分を緑のボールと区別することに非常に苦労することになります。

Numpyでの実装

NumpyでinRangeと同様のフィルタは、

numpy1.py
        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

などと色空間の軸に平行な定数としての閾値だけでなく、要素毎の比率などを元に閾値が設定可能で、線形もしくはより複雑な閾値も設定が可能です。上記の例では、検出後の画像は以下のようになり、より期待した結果を容易に得られるかと思います。
上記の例では、緑の要素が赤、青の倍明るい場合に検出され、以下のように黒、白部分を容易に除外できます。
rgb_g.jpg

速度の比較

一方で、速度については、
1. inRange
2. numpy( inRange同様のフィルタ条件 )
3. numpy( R/B > 2 & R/G>2 )
4. 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で緑のボールを検出

inrange.py
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値化する方法

numpy1.py
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だとできない条件で検出する方法

numpy2.py
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で高速化した場合

cupy.py
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()

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10