Posted at

OpenCVを使ってマウスイベント(手動)でテニスコート領域を選択できるようにする

More than 1 year has passed since last update.


やりたいこと

↓の画像のようにテニスコート領域をマウスイベントで選択できるようにします。

具体的には、コート隅の4点をクリックすることで、テニスコート領域を作成します。

テニスボールの着弾点などを記録できるようにしようとしてますが、テニスコート内のどの位置に着弾したかを確認するためには、コートの座標情報が必要となります。

ちなみに、自分の過去記事ですが、OpenCV(Python版)でテニスのボール軌道を検出するなんてこともやってます。

image.png


参考にしたサイト

その辺うろうろ27km / Python + OpenCV でマウスイベントを取得

こちらのサイトは、コードのかなりの部分を流用させていただきました。

PythonとOpenCVで画像処理④【マウスイベント】

onMouse関数についてわかりやすく説明されています。


mouse_event関数の中にマウスイベントで処理する内容を記述

1つ目は、マウスが移動したときにx方向とy方向に通しの青線を表示します。

クロスした箇所がマウスの位置となり、マウスの位置がみやすくなります。

if event == cv2.EVENT_MOUSEMOVE:  # マウスが移動したときにx線とy線を更新する

img2 = np.copy(img)
h, w = img2.shape[0], img2.shape[1]
cv2.line(img2, (x, 0), (x, h - 1), (255, 0, 0))
cv2.line(img2, (0, y), (w - 1, y), (255, 0, 0))
cv2.imshow(wname, img2)

image.png

2つ目は、マウスをクリックしたときの処理を記述します。

クリックした箇所を赤丸で囲います。

また、4点クリックしたら、4点を線でつなぎ、コート領域を線で囲うような形となります。

if event == cv2.EVENT_LBUTTONDOWN:  # レフトボタンをクリックしたとき、ptlist配列にx,y座標を格納する

if ptlist.add(x, y):
print('[%d] ( %d, %d )' % (ptlist.pos - 1, x, y))
cv2.circle(img, (x, y), 3, (0, 0, 255), 3)
cv2.imshow(wname, img)
else:
print('All points have selected. Press ESC-key.')
if(ptlist.pos == ptlist.npoints):
print(ptlist.ptlist)
cv2.line(img, (ptlist.ptlist[0][0], ptlist.ptlist[0][1]),
(ptlist.ptlist[1][0], ptlist.ptlist[1][1]), (0, 255, 0), 3)
cv2.line(img, (ptlist.ptlist[1][0], ptlist.ptlist[1][1]),
(ptlist.ptlist[2][0], ptlist.ptlist[2][1]), (0, 255, 0), 3)
cv2.line(img, (ptlist.ptlist[2][0], ptlist.ptlist[2][1]),
(ptlist.ptlist[3][0], ptlist.ptlist[3][1]), (0, 255, 0), 3)
cv2.line(img, (ptlist.ptlist[3][0], ptlist.ptlist[3][1]),
(ptlist.ptlist[0][0], ptlist.ptlist[0][1]), (0, 255, 0), 3)

image.png


コード

import numpy as np

import cv2

class PointList():
def __init__(self, npoints):
self.npoints = npoints
self.ptlist = np.empty((npoints, 2), dtype=int)
self.pos = 0

def add(self, x, y):
if self.pos < self.npoints:
self.ptlist[self.pos, :] = [x, y]
self.pos += 1
return True
return False

def onMouse(event, x, y, flag, params):
wname, img, ptlist = params
if event == cv2.EVENT_MOUSEMOVE: # マウスが移動したときにx線とy線を更新する
img2 = np.copy(img)
h, w = img2.shape[0], img2.shape[1]
cv2.line(img2, (x, 0), (x, h - 1), (255, 0, 0))
cv2.line(img2, (0, y), (w - 1, y), (255, 0, 0))
cv2.imshow(wname, img2)

if event == cv2.EVENT_LBUTTONDOWN: # レフトボタンをクリックしたとき、ptlist配列にx,y座標を格納する
if ptlist.add(x, y):
print('[%d] ( %d, %d )' % (ptlist.pos - 1, x, y))
cv2.circle(img, (x, y), 3, (0, 0, 255), 3)
cv2.imshow(wname, img)
else:
print('All points have selected. Press ESC-key.')
if(ptlist.pos == ptlist.npoints):
print(ptlist.ptlist)
cv2.line(img, (ptlist.ptlist[0][0], ptlist.ptlist[0][1]),
(ptlist.ptlist[1][0], ptlist.ptlist[1][1]), (0, 255, 0), 3)
cv2.line(img, (ptlist.ptlist[1][0], ptlist.ptlist[1][1]),
(ptlist.ptlist[2][0], ptlist.ptlist[2][1]), (0, 255, 0), 3)
cv2.line(img, (ptlist.ptlist[2][0], ptlist.ptlist[2][1]),
(ptlist.ptlist[3][0], ptlist.ptlist[3][1]), (0, 255, 0), 3)
cv2.line(img, (ptlist.ptlist[3][0], ptlist.ptlist[3][1]),
(ptlist.ptlist[0][0], ptlist.ptlist[0][1]), (0, 255, 0), 3)

if __name__ == '__main__':
img = cv2.imread("tennis.jpg")
wname = "MouseEvent"
cv2.namedWindow(wname)
npoints = 4
ptlist = PointList(npoints)
cv2.setMouseCallback(wname, onMouse, [wname, img, ptlist])
cv2.imshow(wname, img)
cv2.waitKey()
cv2.destroyAllWindows()