OpenCV

【python】OpenCVのarucoでマーカー作成、検出、座標抽出する。

OpenCVには、簡単にARマーカーを扱うことのできる "aruco" というモジュールが用意されています

本記事では、arucoの基本的な使い方をpythonのサンプルコードを用いながら紹介します

本記事では、下記の内容を紹介しています。

この記事で分かること

  • arucoを使う準備
  • arucoを用いてマーカーを生成する方法
  • arucoを用いてマーカーを検出する方法
  • arucoを用いてマーカーの座標を抽出する方法
  • arucoを用いてマーカーIDと動画を重ねて表示する方法

スポンサーリンク

arucoを使う準備:OpenCV-contribのインストール

arucoモジュールは、OpenCVの拡張モジュールであるOpenCV-contribをインストールすることで、使用可能になります。

pipの場合は、下記のコマンドでインストールできます。

$ pip install opencv-contrib-python

なお、OpenCVとバージョンを合わせる必要があるので、

OpenCVのバージョンを確認し、同じバージョンを指定してインストールします。

以下は、 3.4.5.20 のバージョンを指定した例です。

$ pip install opencv-contrib-python==3.4.5.20

ラズパイへOpenCV-contribをインストールする場合は、下記の記事を参考にしてみてください。

ラズパイ(Raspberry Pi)へのOpenCVのインストール方法・手順

arucoを用いてマーカーを生成

はじめに、マーカーの生成方法について紹介します。

arucoモジュールを用いてマーカー画像を生成し、画像として保存するコードを紹介します。

### arucoマーカーを生成して、画像として保存する
import cv2
from cv2 import aruco
import os

### --- parameter --- ###

# マーカーの保存先
dir_mark = r'C:\test'

# 生成するマーカー用のパラメータ
num_mark = 20 #個数
size_mark = 500 #マーカーのサイズ

### --- マーカーを生成して保存する --- ###
# マーカー種類を呼び出し
dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)

for count in range(num_mark) :

    id_mark = count #countをidとして流用
    img_mark = aruco.drawMarker(dict_aruco, id_mark, size_mark)

    if count < 10 :
        img_name_mark = 'mark_id_0' + str(count) + '.jpg'
    else :
        img_name_mark = 'mark_id_' + str(count) + '.jpg'
    path_mark = os.path.join(dir_mark, img_name_mark)

    cv2.imwrite(path_mark, img_mark)

出力例

前述のサンプルコードで、マーカーを生成した結果がこちらです。

20個のマーカーの画像が、testフォルダに保存されました。

arucoで生成したマーク画像
arucoで生成したマーカー画像

注意事項

画像の保存先は、 dir_mark = r'C:\test' としています。

OpecCVは、画像の読み込み先や保存先に日本語が含まれているとエラーとなる場合があるので注意してください。

スポンサーリンク

arucoを用いてマーカーを検出する方法

次は、マーカーの検出方法について紹介します。

arucoモジュールを用いて、検出したマーカーのIDをリストで返すコードを紹介します。

### 検出したマーカーのIDをリストで返す
import cv2
from cv2 import aruco
import numpy as np
import time

class MarkSearch :

    ### --- aruco設定 --- ###
    dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
    parameters = aruco.DetectorParameters_create()

    def __init__(self, cameraID):
        self.cap = cv2.VideoCapture(cameraID)

    def get_markID(self):
        """
        静止画を取得し、arucoマークのidリストを取得する
        """
        ret, frame = self.cap.read()
        gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

        corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)

        list_ids = np.ravel(ids)

        return list_ids

 
if __name__ == "__main__" :
    import cv2
    from cv2 import aruco
    import numpy as np
    import time

    ### --- aruco設定 --- ###
    dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
    parameters = aruco.DetectorParameters_create()

    ### --- parameter --- ###
    cameraID = 0
    cam0_mark_search = MarkSearch(cameraID)

    try:
        while True:
            print(' ----- get_markID ----- ')
            print(cam0_mark_search.get_markID())
            time.sleep(0.5)
    except KeyboardInterrupt:
        cam0_mark_search.cap.release()

出力例

カメラ内に映り込んだ、マーカーのIDをリストで返します。

例えば、ID1~5のマーカーを5つ検出した場合は、下記のようになります。

[5 1 2 3 4]

解説

gray画像に変換されたカメラキャプチャ画像から、IDを抽出しているのが、下記の部分になります。

corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)

上記の返り値のうち、マーカーのIDは、 "ids" に格納されています。

idsは、下記のように、そのままでは使いづらい形となってます。

[[1]
[3]
[4]
[5]
[2]]

そのため、list_ids = np.ravel(ids) の部分で、

1次元のリストに変換しています。

キーボードからの中断処理をキャッチできるように、
whileループはtry文の中でまわしています。
try文を用いた例外処理については、下記の記事で紹介しています。

例外処理の基本【try文】

スポンサーリンク

arucoを用いてマーカーの座標を抽出する方法

次に、マーカーの座標を抽出する方法を紹介します。

arucoモジュールを用いて、指定したIDのマーカーが検出された場合に、座標をリストで返すコードを紹介します。

### マーカーの座標を抽出する
import cv2
from cv2 import aruco
import numpy as np
import time

class MarkSearch :

    ### --- aruco設定 --- ###
    dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
    parameters = aruco.DetectorParameters_create()

    def __init__(self, cameraID):
        self.cap = cv2.VideoCapture(cameraID)

    def get_mark_coordinate(self, num_id):
        """
        静止画を取得し、所望のマークの座標を取得する
        """
        ret, frame = self.cap.read()
        gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

        corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)

        ### num_id のマーカーが検出された場合 ###
        if num_id in np.ravel(ids) :
            index = np.where(ids == num_id)[0][0] #num_id が格納されているindexを抽出
            cornerUL = corners[index][0][0]
            cornerUR = corners[index][0][1]
            cornerBR = corners[index][0][2]
            cornerBL = corners[index][0][3]

            center = [ (cornerUL[0]+cornerBR[0])/2 , (cornerUL[1]+cornerBR[1])/2 ]

            print('左上 : {}'.format(cornerUL))
            print('右上 : {}'.format(cornerUR))
            print('右下 : {}'.format(cornerBR))
            print('左下 : {}'.format(cornerBL))
            print('中心 : {}'.format(center))

            print(corners[index])

            return center

        return None


if __name__ == "__main__" :

    import cv2
    from cv2 import aruco
    import numpy as np
    import time

    ### --- aruco設定 --- ###
    dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
    parameters = aruco.DetectorParameters_create()

    ### --- parameter --- ###
    cameraID = 0
    cam0_mark_search = MarkSearch(cameraID)

    markID = 1

    try:
        while True:
            print(' ----- get_mark_coordinate ----- ')
            print(cam0_mark_search.get_mark_coordinate(markID))
            time.sleep(0.5)
    except KeyboardInterrupt:
        cam0_mark_search.cap.release()

出力例

指定したIDのマーカーが検出された場合に、座標をリストで返します。

例えば、ID=1のマークを指定し、マークが検出された場合、返り値は下記のようになります。

[216.5, 448.0]

前述のサンプルコードでは、マークの中心座標を返しています。

解説

gray画像に変換されたカメラキャプチャ画像から、マーカーの座標とIDを抽出しているのが、下記の部分になります。

corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)

下記のコマンドで、マーカーのリストの中に、指定したIDのマーカーが含まれるかを探します。

if num_id in np.ravel(ids) :

np.whereで、マークリストidsのどのindexに、指定したマークが割り当てられているかを探します。

index = np.where(ids == num_id)[0][0]

indexを探すのは、マークリストidsと、4隅の座標リストcornersで、同じマーカーのindexは一致しているので、

これを利用して欲しいマークの座標を取得するためです。

マークの左上の座標は、下記のコマンドで抽出しています。

cornerUL = corners[index][0][0]

これは、下記の図を見ると理解できると思います。

arucoのマーカー座標の確認方法
arucoのマーカー座標の確認方法

マーカーの座標 "corners" の1つ目の引数で、indexを指定します。

ここで、indexであり、マークIDではないことに注意して下さい

マーカーの座標 "corners" の3つ目の引数で、4隅の位置指定します。

左上を0として、時計回りで数字が大きくなります。

indexを引数として渡した、 "corners" をprintで確認すると理解しやすいかと思います。

print(corners[index])

[[[164. 405.]
[208. 402.]
[206. 449.]
[163. 451.]]]

左上、右上、右下、左下の順番で座標が格納されています。

なお、今回の例では、get_mark_coordinate関数の戻り値として、

計算によって算出したマーカーの中心座標を渡しています。

スポンサーリンク

arucoを用いてマーカーIDと動画を重ねて表示する方法

次に、マーカーのIDと領域を動画に重ねて表示する方法を紹介します。

arucoモジュールを用いて、検出したIDと領域を重ねて動画として表示するコードを紹介します。

### マーカーIDと、動画を重ねて表示する
import cv2
from cv2 import aruco
import time

### --- aruco設定 --- ###
dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
parameters = aruco.DetectorParameters_create()

cap = cv2.VideoCapture(0)

try:
    while True:
        ret, frame = cap.read()
        gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)

        corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)

        frame_markers = aruco.drawDetectedMarkers(frame.copy(), corners, ids)
        cv2.imshow('frame', frame_markers)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyWindow('frame')
    cap.release()
except KeyboardInterrupt:
    cv2.destroyWindow('frame')
    cap.release()

出力例

マーカーIDと領域が、表示されています。

スポンサーリンク

まとめ

OpenCVのarucoモジュールの基本的な使い方をpythonのサンプルコードを用いながら紹介しました。

arucoモジュールを使うと、マーカー検出を様々なアプリに応用することができます。

使いこなせると面白いモジュールですので、是非遊びながら使い方を覚えてみてください。

スポンサーリンク

-OpenCV
-