1. それぞれの画像から対応する四点を取得する
2. 対応する四点から変換行列を作成する
3. 元の画像から選手の座標を取得する
4. 変換行列を使い選手の座標を変換する
5. 変換した座標の地点に円を描画する
※ Pythonで座標を扱う際は、x軸もy軸も左上から数えることに注意してください!
※ また、ここどうなってんの?となった時はprint()などで出力して確認してみてください。
# 必要なものをインポート
import cv2
import numpy as np
import yolov5
import copy
#GPUを使用する場合
import os
# GPUを指定
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
# 使う変数をいろいろ定義
# 実際の画面の画像
img = cv2.resize(cv2.imread(r'practice_exercise_data\img\opencv_img\demoimg_0000.jpg'), (1920, 1080))
# 宛先となるfieldの画像
field_img = cv2.resize(cv2.imread(r'practice_exercise_data\img\field_img\line_soccer_field.jpg'), (1920, 1080))
setMouseCallback()を使用することで、マウスでクリックした場所の座標を取得することができます。
引数: ウィンドウ名, コールバック関数
コールバック関数として onMouse()を用意しています。左クリックに反応して座標を返します。
# 画像を表示する際の倍率です。お好みの大きさに変更してください
img_size = 0.8
window_name = 'img'
def onMouse(event, x, y, flags, params):
if event == cv2.EVENT_LBUTTONDOWN:
cv2.destroyAllWindows()
return print("クリックした座標 x: ", int(x / img_size), ", y: ", int(y / img_size))
# setMouseCallbackは、指定したウィンドウ名の画像がクリックされたときのみに反応します。
# そのためimshowの第一引数でウィンドウ名を設定する際は同じ名前を設定する必要があります。
cv2.imshow(window_name, cv2.resize(img, (int(1920 * img_size), int(1080 * img_size))))
cv2.setMouseCallback(window_name, onMouse)
cv2.waitKey(0)
cv2.destroyAllWindows()
クリックした座標 x: 950 , y: 232
サンプルコードはsample_code.ipynbにあります
取得出来たらそれぞれの座標を下の [corners] 配列に代入して実行してください。(左上 → 右上 → 右下 → 左下 の座標の順で格納します)
下のコードでは、赤線を引いて、取得した座標が正しいかどうか確認しています。
copy_img = copy.copy(img)
# 例) corners = [[2, 101],[1646, 105],[1918, 1027],[2, 1030]]
corners = [[2, 101],[1646, 105],[1918, 1027],[2, 1030]] # ここに代入
# 確認用に赤線を描画しています。マッピングと直接関係はないので気にしなくて大丈夫です。
cv2.line(copy_img, corners[0], corners[1], (0, 0, 255), thickness=5)
cv2.line(copy_img, corners[1], corners[2], (0, 0, 255), thickness=5)
cv2.line(copy_img, corners[2], corners[3], (0, 0, 255), thickness=5)
cv2.line(copy_img, corners[3], corners[0], (0, 0, 255), thickness=5)
cv2.imshow(window_name, cv2.resize(copy_img, (int(1920 * img_size), int(1080 * img_size))))
cv2.waitKey(0)
cv2.destroyAllWindows()
[corners] 配列に四点の座標を設定することができました。
同じようにしてフィールドの画像でも四点の座標を取得しますが、今回はあらかじめ用意していた座標を使用します。
after_corner = [[1185, 28], [1860, 28], [1788, 1050], [1365, 1050]]
これをすることで、beforeの四角形をafterの四角形に変換する際の変換行列が作成されます。
# getPerspectiveTransformで扱えるデータの型に変更
before = np.array(corners, dtype=np.float32) # 変換前の四角形の座標
after = np.array([[1185, 28], [1860, 28], [1788, 1050], [1365, 1050]], dtype=np.float32) # 変換後の四角形の座標
mat = cv2.getPerspectiveTransform(before, after)
print(mat) # 行列 = matrix
[[ 0.44028 1.6112 1143.9] [ -0.0057018 2.2888 -200.26] [-7.2404e-06 0.0010238 1]]
practice_exercise_yolov5で .xyxy[0]を使用して選手を囲ったバウンティボックスの座標を取得することができました。
今回はバウンティボックスの下辺の中央(選手の足元)をその選手の座標として扱います。
yolov5.loadで作成したモデルの.classesに値を指定することで、検知するクラスを絞り込むことができます。
以下のクラスが用意されています。
names: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
'hair drier', 'toothbrush'] # class names
personのみを検知してほしい場合
model.classes = 0
# yolov5モデルをロード
model = yolov5.load('yolov5s.pt', device='cuda')
# 人間のみ検知してもらう
model.classes = 0
# 検知
result = model(img)
result_xyxy = result.xyxy[0]
for xyxy in result_xyxy:
xmin = int(xyxy[0].item())
ymin = int(xyxy[1].item())
xmax = int(xyxy[2].item())
ymax = int(xyxy[3].item())
# 選手の座標を取得
position = (xmin + (xmax - xmin) / 2, ymax)
break
print(position)
(508.5, 576)
選手一人の座標が取得できたので、その地点に円を描画して座標がどこなのか確認します。
円の描画はcircleメソッドを使用します。
OpenCVの描画系メソッドをまとめたサイトがあったのでここに貼っておきます
cv2.circle(img, list(map(int, position)), 5, (0, 0, 255), thickness=-1)
cv2.imshow("", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
前回使用したwarpPerspectiveは変換行列に応じて画像を変形させることができました。
しかし今回は選手の座標のみを変換するため、違うメソッドを使用します。
perspectiveTransform() を使用することで、変換前の座標を変換行列をもとに変換することができます。 引数: 変換するもの, 変換行列
# perspectiveTransform()の引数作成。おまじない。
pts = np.float32(position).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts,mat)
print("渡した座標: ", position)
print("帰って来た座標: ", dst)
print("きれいにすると→ ", list(map(int, dst[0][0])))
渡した座標: (508.5, 576) 帰って来た座標: [[[ 1447.6 703.14]]] きれいにすると→ [1447, 703]
cv2.circle(img, list(map(int, dst[0][0])), 5, (0, 0, 255), thickness=-1)
cv2.imshow("", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
今ままでの技術を応用すると、以下のようなものを作ることができます。
2Dマッピングの資料は以上となります。
僕らが実際に書いたコードをsample_code.ipynbに配置しておくので、気になった方は見てみてください。
お疲れさまでした!