【Python】OpenCVでコーナーの検出【Harris/Shi-Tomasi】

【Python】OpenCVでコーナーの検出【Harris/Shi-Tomasi】

この記事ではPythonでOpenCVを使って、コーナーの検出する方法を学んでいきます。コーナー検出の中でも、Harrisコーナー検出とShi-Tomasiコーナー検出を取り上げます。

コーナーというと曲がり角やカーブというイメージがありますが、ここでいうコーナーとは、2つの線の交点だったり四角形の頂点のようなエッジだったりを指します。

コンピューターに画像や映像を理解させる研究分野であるコンピュータビジョンでは、コーナー検出によってさまざまなことが行われます。画像のマッチング、画像の追跡、モザイク処理、パノラマ生成、3Dモデリング、物体認識などです。【Python】VidStabで手ぶれ補正【動画編集への道#2】の手ぶれ補正ライブラリVidStabでも、コーナ検出によって前後の動画フレームを比較し、移動平均で手ぶれ補正をしていました。

はじめに

ここではPython3.xでOpenCVを扱います。あらかじめOpenCVとnumpyをpipでインストールしておいてください。

$ pip install opencv-python
$ pip install opencv-contrib-python
$ pip install numpy

OpenCVを触るのがはじめての方であれば、先に下の記事で基礎的な画像処理方法を学ばれることをオススメします。

記事の最後では、こちらのツイートのようにメダカのみのコーナー検出する方法もご紹介しますのでご期待ください。

Harrisコーナー検出(cornerHarris)

Harrisコーナー検出は、ある画像の領域をあらゆる方向へ移動させて、その特徴をとらえてコーナーを検出する方法です。

下記ページは英語ですが、画像が多いので眺めているだけでも何となくやっていることが理解できるかと思います。

Lecture 06: Harris Corner Detector

Harrisコーナー検出器の公式です:

\begin{align} R = Det(H)-k(Trace(H))^2 \end{align}

数式の意味は有識者の方々の説明に任せるとして、数式が理解できなくてもPythonとOpenCVを使って恐れずコーナー検出をやってみましょう。

OpenCVの cv2.cornerHarris() 関数で、Harrisコーナー検出がカンタンにできます。

cv2.cornerHarris(src, blockSize, ksize, k, dts, borderType)

引数 意味
src float32型の画像データ
blockSize コーナー検出時に考慮する隣接領域サイズ
ksize Sobelの勾配オペレータのカーネルサイズ
k 式中のフリーパラメータ
dts Harris検出器の応答が格納される画像
borderType ピクセル外挿手法

画像のHarrisコーナー検出

以下はHarrisコーナ検出の例です:

import cv2
import numpy as np


filename = '../assets/go.jpg' # 600 x 400
img = cv2.imread(filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

gray = np.float32(gray)
img_dst = np.copy(gray)

dst = cv2.cornerHarris(gray, 2, 3, 0.05, img_dst)
dst = cv2.dilate(dst,None,iterations = 3) # ドットを膨張(Dilation)させて見やすくする処理

img[dst>0.05*dst.max()]=[0,0,255] # 0.05はドット表示の閾値


cv2.imwrite('../corner_harris.jpg', img)
cv2.imwrite('../corner_harris_dst.jpg', img_dst)

cv2.imshow('img', img)

if cv2.waitKey(0) & 0xff == 27:
    cv2.destroyAllWindows()

結果画像が以下になります。

Harrisコーナー検出
Harrisコーナー検出

画像はもとより、画像サイズやblockSizekパラメータによって結果がだいぶ違ってきます。

実際にHarrisコーナー検出で捉えられたコーナーの映像は次の通りです:

Harrisコーナー検出で捉えられたコーナー
Harrisコーナー検出で捉えられたコーナー

動画のHarrisコーナー検出

以下は動画でのHarrisコーナ検出の例です:

import cv2
import numpy as np


filename = '../assets/medaka2021.mp4' # 1280 x 720
outpath = '../build/medaka2021_corner_harris.mp4'


vidcap = cv2.VideoCapture(filename)


w = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = 30 #vidcap.get(cv2.CAP_PROP_FPS)

fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
writer = cv2.VideoWriter(outpath, fmt, fps, (w, h)) # ライター作成


while True:
    grabbed_frame, frame = vidcap.read()
    if frame is None:
        break
    

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray = np.float32(gray)


    dst = cv2.cornerHarris(gray, 2, 3, 0.05)    
    #cv2.imshow('dst', dst)
    dst = cv2.dilate(dst,None,iterations = 2)

#    frame[dst>0.0001*dst.max()]=[0,255,255]
    frame[dst>0.01*dst.max()]=[0,0,255]

    frame = cv2.resize(frame,(w, h)) # リサイズしないと動画が正しく書き出せない    
    writer.write(frame)


    cv2.imshow('Frame', frame)

    key = cv2.waitKey(5)
    if key == 27: # Esc
        break

vidcap.release()
writer.release()

結果動画が以下になります。

Harrisコーナー検出
Harrisコーナー検出

Shi-Tomasiコーナー検出(goodFeaturesToTrack)

Shi-Tomasiコーナー検出とは、Jianbo Shi と Carlo Tomasi らが1993年に書いた論文 Good Features to Track のコーナー検出法です。Shi-Tomasiコーナー検出では、Harrisコーナー検出の結果よりも良い結果が得られるようです。

OpenCVの cv2.goodFeaturesToTrack() 関数で、Shi-Tomasiコーナー検出が扱えます。

cv2.goodFeaturesToTrack()関数に与えるパラメータは次のとおりです:

cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance, corners, mask)

goodFeaturesToTrack

引数 意味
image float32型の画像データ
maxCorners 出力されるコーナーの最大数
qualityLevel 許容される画像コーナーの最低品質
minDistance コーナー間の最小ユークリッド距離
corners 検出されたコーナーが出力されるベクトル
mask コーナーの検出対象となる領域マスク

検出したいコーナーの最大数を指定します。最低品質で指定した以上のコーナーの中から、品質の高いコーナーが抽出されます。またHarris同様、出力される2つのコーナー間の最小ユークリッド距離を指定できます。

画像のShi-Tomasiコーナー検出

以下はShi-Tomasiコーナ検出の例です:

import cv2
import numpy as np


filename = '../assets/go.jpg' # 600 x 400
img = cv2.imread(filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

gray = np.float32(gray)

corners = cv2.goodFeaturesToTrack(gray, maxCorners=4, qualityLevel=0.01, minDistance=10)
# corners = cv2.goodFeaturesToTrack(gray, maxCorners=10, qualityLevel=0.01, minDistance=200)
# corners = cv2.goodFeaturesToTrack(gray, maxCorners=100, qualityLevel=0.1, minDistance=10)

corners = np.int0(corners)

for i in corners:
    x,y = i.ravel()
    cv2.circle(img,(x,y),5,[0, 0, 255],-1)



cv2.imwrite('../corner_shi_tomasi.jpg', img)


cv2.imshow('img', img)

if cv2.waitKey(0) & 0xff == 27:
    cv2.destroyAllWindows()

maxCorners=4, qualityLevel=0.01, minDistance=10の結果画像が以下になります。

Harrisコーナー検出
Harrisコーナー検出

パラメーターを変えてもうすこし出力してみます。

maxCorners=10, qualityLevel=0.01, minDistance=200の結果画像が以下になります。

Harrisコーナー検出
Harrisコーナー検出

maxCorners=100, qualityLevel=0.1, minDistance=10の結果画像が以下になります。

Harrisコーナー検出
Harrisコーナー検出

動画のShi-Tomasiコーナー検出

以下は動画でのShi-Tomasiコーナー検出の例です:

import cv2
import numpy as np


filename = '../assets/medaka2021.mp4' # 1280 x 720
outpath = '../build/medaka2021_corner_shi_tomasi_vid.mp4'


vidcap = cv2.VideoCapture(filename)


w = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = 30 #vidcap.get(cv2.CAP_PROP_FPS)

fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
writer = cv2.VideoWriter(outpath, fmt, fps, (w, h)) # ライター作成


while True:
    grabbed_frame, frame = vidcap.read()
    if frame is None:
        break
    

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray = np.float32(gray)

    # マスク作成
    # mask = np.zeros(gray.shape, dtype=np.uint8)
    # cv2.rectangle(mask, (350, 200), (1180, 700), (255), -1)
    # cv2.imshow('mask', mask)


    corners = cv2.goodFeaturesToTrack(gray, maxCorners=1000, qualityLevel=0.01, minDistance=5) #, mask=mask)

    corners = np.int0(corners)

    for i in corners:
        x,y = i.ravel()
        cv2.circle(frame,(x,y),5,[0, 0, 255],-1)


    frame = cv2.resize(frame,(w, h)) # リサイズしないと動画が正しく書き出せない    
    writer.write(frame)

    cv2.imshow('Frame', frame)

    key = cv2.waitKey(5)
    if key == 27: # Esc
        break

vidcap.release()
writer.release()

結果動画が以下になります。

Shi-Tomasiコーナー検出
Shi-Tomasiコーナー検出

比較的カンタンにHarrisコーナー検出とShi-Tomasiコーナ検出ができることが分かったかと思います。正直、ふたつの検出器同士の精度の違いは実感できませんが、とりあえずは良しとしましょう。

さて、ビオトープ動画のような複雑な映像ですと、ターゲットを絞れずにあらゆるコーナーの特徴を検出してしまいます。そこで、たとえばメダカのようなターゲットを絞ってコーナー検出する方法を次で解説したいと思います。

ターゲットオブジェクトのみをコーナー検出してみよう!

ここではターゲットオブジェクト(メダカ)のみを、コーナー検出する方法をご紹介します。

先ほどの動画でメダカのみをコーナ検出する場合は、次の手順で行います。

  1. BGRからHSV色空間への変換
  2. メダカの色のみを抽出
  3. グレースケール化
  4. コーナー検出に不必要な画面領域をマスク
  5. Shi-Tomasiでコーナー抽出

このアイデアは、以前に画像処理で行った背景から人物のみを切り抜く方法とまったく同じものです。

以下がメダカのみをコーナー検出するプログラム例です:

import cv2
import numpy as np
from pyrsistent import v
from time import sleep

def hsv2scale(hsv):
    h, s, v = hsv
    h = h / 360
    s = s / 100
    v = v / 100
    return int(h*180), int(s*255), int(v*255)


def main(mode):
#    filename = '../assets/medaka2021.mp4' # 1280 x 720
    filename = '../assets/biotope.mov' # 1280 x 720
    outpath = '../build/out_biotope_{}.mp4'.format(mode)


    vidcap = cv2.VideoCapture(filename)


    width = int(vidcap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(vidcap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = 30 #vidcap.get(cv2.CAP_PROP_FPS)

    fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    writer = cv2.VideoWriter(outpath, fmt, fps, (width, height)) # ライター作成

    while True:
        grabbed_frame, frame = vidcap.read()
        if frame is None:
            break
        
        # HSV色空間で処理
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        # 金色メダカ: HSV(40 43 92)
        h,s,v = hsv2scale([40, 43, 92])
        lower =  np.array([h-10, s-15, v-15])
        upper =  np.array([h+10, s+15, v+15]) 
        medaka_gold_mesu = cv2.inRange(hsv,lower,upper)

        # 金色メダカ: HSV(37 37 85)
        h,s,v = hsv2scale([37, 37, 85])
        lower =  np.array([h-10, s-15, v-15])
        upper =  np.array([h+10, s+15, v+15]) 
        medaka_gold_osu = cv2.inRange(hsv,lower,upper)

        # 黒メダカ: HSV(37 47 35)
        h,s,v = hsv2scale([37, 47, 35])
        lower =  np.array([h-8, s-10, v-10])
        upper =  np.array([h+8, s+10, v+10]) 
        # print(lower)
        # print(upper)
        medaka_black = cv2.inRange(hsv,lower,upper)
        
        mask_hsv = cv2.addWeighted(src1=medaka_gold_mesu,alpha=1,src2=medaka_gold_osu,beta=1,gamma=0)
        mask_hsv = cv2.addWeighted(src1=mask_hsv,alpha=1,src2=medaka_black,beta=1,gamma=0)

        bgr = cv2.bitwise_and(frame, frame, mask=mask_hsv)
        #print(counter)

        # グレースケール変換
        gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
        gray = cv2.dilate(gray,None,iterations = 3)


        # コーナ検出する対象とするマスクの作成
        mask = np.zeros(gray.shape, dtype=np.uint8)        
        cv2.rectangle(mask, (250, 100), (1280, 720), (255), -1)


        # Shi-Tomasiコーナー検出
        corners = cv2.goodFeaturesToTrack(gray, maxCorners=100, qualityLevel=0.001, minDistance=10, mask=mask)
        corners = np.int0(corners)

        final = np.copy(frame)
        for i in corners:
            x,y = i.ravel()
            cv2.circle(final,(x,y),4,[0, 0, 255],-1)

        # print(gray_scale)
        # print(final)

        # 書き出し
        if mode == 'gray':
            cv2.imshow(mode, gray)
            gray = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
            writer.write(gray)
        elif mode == 'hsv':
            cv2.imshow(mode, hsv)
            writer.write(hsv)
        elif mode == 'bgr':
            cv2.imshow(mode, bgr)
            writer.write(bgr)
        else:
            cv2.imshow(mode, final)
            final = cv2.resize(final,(width, height))
            writer.write(final)

        key = cv2.waitKey(5)
        if key == 27: # Esc
            break

    vidcap.release()
    writer.release()

if __name__ == '__main__':

    # for mode in ['gray']:
    for mode in ['final', 'bgr','hsv', 'gray']:
        main(mode)
#        sleep(2)

結果動画が以下になります。

OpenCVでターゲットオブジェクトのみのコーナー検出
OpenCVでターゲットオブジェクトのみのコーナー検出

この映像の続きは下記YouTubeで閲覧できます。

記事に関するご質問などがあれば、
Twitter または お問い合わせ までご連絡ください。
Python学習にオススメの本をご紹介!
Pandasでデータサイエンスはじめよう!
関連記事