【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()
結果画像が以下になります。
画像はもとより、画像サイズやblockSize、kパラメータによって結果がだいぶ違ってきます。
実際に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()
結果動画が以下になります。
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の結果画像が以下になります。
パラメーターを変えてもうすこし出力してみます。
maxCorners=10, qualityLevel=0.01, minDistance=200の結果画像が以下になります。
maxCorners=100, qualityLevel=0.1, minDistance=10の結果画像が以下になります。
動画の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()
結果動画が以下になります。
比較的簡単にHarrisコーナー検出とShi-Tomasiコーナ検出ができることが分かりました。正直、ふたつの検出器同士の精度の違いは実感できませんが、とりあえずは良しとします。 さて、ビオトープ動画のような複雑な映像ですと、ターゲットを絞れずにあらゆるコーナーの特徴を検出してしまいます。そこで、たとえばメダカのようなターゲットを絞ってコーナー検出する方法を次で解説します。
ターゲットオブジェクトのみをコーナー検出してみよう!
ここではターゲットオブジェクト(メダカ)のみを、コーナー検出する方法をご紹介します。
先ほどの動画でメダカのみをコーナ検出する場合は、次の手順で行います。
- BGRからHSV色空間への変換
- メダカの色のみを抽出
- グレースケール化
- コーナー検出に不必要な画面領域をマスク
- 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)
結果動画が以下になります。
この映像の続きは下記YouTubeで閲覧できます。
【Python】Corner detection with OpenCV【Shi-Tomasi corner detector】 - YouTube