290.ラズパイ無双[24 ARマーカー(aruco)]
初回:2022/12/14
Raspberry Pi (ラズベリーパイ、通称"ラズパイ")で何か作ってみようという新シリーズです。前回、前々回と『QRコード』関連のプログラムをご紹介しましたが、今回は『ARマーカー』のプログラムをご紹介したいと思います。
P子「AR(拡張現実)で、何かするの?」※1
カメラでとらえた画像の上に、デジタルコンテンツを重ねるためのマーカーを、ARマーカーと呼んでいるので、実際は何でも構いません。QRコードを読み取って、その製品情報を表示してもいいし、車や商品の形状認識から、アニメを表示しても構いません。ただ、読取が不正確だったり、画像の判別が難しく、コンテンツの表示がちらついたり途切れるとストレスがたまります。
そこで、OpenCVのcontrib に含まれているArUcoというマーカーを使えば、検出精度が高く、扱いも簡単なので、色々な使い方ができそうです。
【目次】
1.ArUcoマーカー作成
≪参考1≫
https://python-academia.com/opencv-aruco/
【python】OpenCVのarucoでマーカー作成、検出、座標抽出する。
ArUcoマーカーを使うには、OpenCVのcontribの導入が必要です。すでに、OpenCVはインストール済みだと思いますので、先に確認してみましょう。簡単な方法は、python を起動して、from cv2 import aruco で、エラーが出なければ、OKです。
ダメな場合は、contrib をインストールします。
$ pip install opencv-contrib-python
import cv2 from cv2 import aruco import os dir = '/tmp/aruco' # マーカーの保存先 os.makedirs(dir,exist_ok=True) # 保存先ディレクトリ作成 num = 50 # 生成するマーカーの個数 size = 76 # マーカーのサイズ 約2cm角 dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50) # 50種類のマーカーが生成可能 for id in range(num) : img = aruco.drawMarker(dict_aruco, id, size) # グレイ画像 name = f'id_{id:02}.png' # 先頭ゼロ埋め、2桁固定 path = os.path.join(dir, name) # 画像ファイル名作成 cv2.imwrite(path, img) # 保存
マーカー画像の出力先フォルダやファイル名作成などは枝葉なので、ここで重要なのは、2行だけです。
dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50)
aruco.DICT_4X4_50 のパラメータは、マーカーサイズが4x4 で、マーカーの数は50 です。図形は 4ピクセルで構成され、周りに1ピクセル分の黒枠が生成されます。aruco で検索して出てくるサンプルは、ほとんどがこの(DICT_4X4_50)パラメータですが、他にも色々とありそうです。
>>> dir(aruco)
で、内部属性が見られます。他のパラメータも試してみるとよいでしょう。
P子「自分では試さないのね」
邪魔くさいので...
img = aruco.drawMarker(dict_aruco, id, size)
で作成されるのは、グレイ画像です。id は、マーカーに付ける番号で、DICT_4X4_50 の場合は、0 から 49 までの数字です。size は、画像の大きさで、先のサンプルでは、size = 76 にしています。
本当なら、図形4ピクセルで枠1ピクセルの6の倍数が良いのかもしれませんが、ここでは、96dpi ⇒ 2.54cm から逆算して、2cm ⇒ 75.6px ≒ 76ピクセルとしています。
ちなみに、50 以上の認識をさせる場合、別のパラメータを使うか、複数組み合わせることになりますが、それぞれの画像には、さらに余白の3ピクセル分必要になるそうです。
2.ArUcoマーカー読取
読取は、カメラ画像から行います。
import cv2 from cv2 import aruco import numpy as np dict_aruco = aruco.Dictionary_get(aruco.DICT_4X4_50) parameters = aruco.DetectorParameters_create() cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # 検出位置, ID , 未検出位置 corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters) # マーカーの四辺に線が、左上の頂点に小さい四角が、中央にidが描かれる。 img = aruco.drawDetectedMarkers(frame.copy(), corners, ids) list_ids = np.ravel(ids) # 検出された ID を1次元のリストに変換 print( list_ids ) # 検出 ID cv2.imshow('aruco', img) k = cv2.waitKey(10) # ミリ秒単位で表されるキーボード入力待ち時間 if k == ord('q') or k == 27: # q または、ESC で終了 break cap.release() cv2.destroyAllWindows() # すべてのウィンドウを閉じる
検出処理までは、定型文という事で割愛します。
P子「要するに調査するのが邪魔くさかったのね」
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters)
corners は、検出位置のarray です。
print(corners) (array([[[1068., 883.], 左上 [1098., 887.], 右上 [1096., 915.], 右下 [1067., 910.]]], 左下 dtype=float32), array([[[1026., 877.],
ids は、検出された ID のリストで、np.ravel(ids) で1次元のリストに変換しています。
rejectedImgPoints は、未検出位置 の array で、何となく四角形を見つけたがデコードできなかった図形のアドレスになります。
# マーカーの四辺に線が、左上の頂点に小さい四角が、中央にidが描かれる。
img = aruco.drawDetectedMarkers(frame.copy(), corners, ids)
は、元のカラー画像に対して、corners(検出位置) と ids(検出ID番号) を画像に追記します。
マーカーの四辺に線を引いて、左上の頂点に小さい四角を、中央にidが描かれます。
3.ArUcoマーカー加工
先の aruco.drawDetectedMarkers メソッドで作成されるマーカーやID表示を自分でカスタマイズしたいこともあるでしょう。その部分だけ、解説します。
LIME = ( 0, 255, 0) # 明るい緑 RED = ( 0, 0, 255) # 赤 THICK = 2 # 線の太さ ・・・・ corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, dict_aruco, parameters=parameters) list_ids = np.ravel(ids) # 検出された ID を1次元のリストに変換 # この時、未発見でも 1件のデータが作成されてしまう。 pts = np.array(corners, np.int32) # 検出されたマーカーのコーナーの位置 img = frame.copy() for idx in range(len(pts)) : # len(list_ids) を使うと、未発見時にエラーになる。 id = list_ids[idx] pt = pts[idx] # 左上,右上,右下,左下 の配列 img = cv2.polylines(img,pt,True,LIME,THICK) # Trueは、閉包図形 s = f'{id}' p1 = pt[0][0] # 左上 cv2.putText(img,s,p1,cv2.FONT_HERSHEY_PLAIN,2,RED,2,cv2.LINE_AA)
注意点としては、list_ids = np.ravel(ids) で検出された ID を1次元のリストに変換している個所で、1件も発見できていない場合でも、len(list_ids) は、1 を返します。 なので、 for で使用している range(len(pts)) を使用しています。
pt = pts[idx] で、左上,右上,右下,左下 の配列が取れるので、これを、cv2.polylines でマーカーを閉包します。これで、色や線の太さを指定できます。
そして、p1 = pt[0][0] で、左上 を取得して、cv2.putText で、ID を書き込みます。
もし、検出されなかったマーカーのコーナーの位置を赤色で囲いたい場合は、下記のようにします。
nopts = np.array(rejectedImgPoints, np.int32) for pt in nopts : img = cv2.polylines(img,pt,True,RED,THICK) # Trueは、閉包図形
4.まとめ
ARマーカーの作成も読取も、非常に簡単にできました。
このマーカーを利用して、ドローンの位置認識や工場内でのカーゴや台車の動線把握、また、対象物の4隅(または対角線上の2隅)に配置して、対象物の射影変換を行ったりできます。
マーカーを配置できるような環境なら、難しい画像認識処理を行うよりもマーカーを利用したシステムを考える方が良いと思います。
ほな、さいなら
======= <<注釈>>=======
※1 P子「AR(拡張現実)で、何かするの?」
P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。