Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

ARマーカー作成とARでの画像表示

さて、今回はAR技術についてです。
ポケモンGoドラクエウォークなど、ARを使ってキャラクターを現実世界に表示させるアプリがありますが、
ARってどうやって作ればいいんだろう??🤔と思い、調べてみました。

ARマーカー作成と表示にあたりPythonOpenCVを用いたいと思います。
Pythonのバージョンは"3.7.4"を用います。

独学プログラマー Python言語の基本から仕事のやり方まで

新品価格
¥2,178から
(2020/10/10 12:51時点)



■環境構築

OpenCVにてARマーカーの作成や読み込みを行うにあたり、
"aruco"ライブラリが用意されているとのこと!!
早速、

import cv2
aruco = cv2.aruco
~~~

を実行してみたところ、

AttributeError: module 'cv2.cv2' has no attribute 'aruco'

といったエラーが発生!!

どうやら、opencv-pythonと"opencv-contrib-python"が必要なようです!!
opencv-contrib-python入れてないや😅

ということで、

pip install opencv-contrib-python

を実行!!
インストール成功!!👍

こちらを実現したところ問題なく通った!!

import cv2
aruco = cv2.aruco
~~~

■ARマーカー作成

マーカー作成のソースコードはこちら。
ちょっと欲張って10枚のマーカー作成させ、
それらを5×5に配置して1枚の画像として出力させてみました。
※もちろんそれぞれ単体のマーカー生成も行っております。

import cv2.aruco
import numpy as np

aruco = cv2.aruco
dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
SIZE = 150
ImgList1 = []
ImgList2 = []
WiteList = []
def arGenerator():
    img_white = np.ones((SIZE,SIZE, 3),np.uint8)*255
    for i in range(1,6):
        fileName = "ar_" + str(i) + ".png"
        generator = aruco.drawMarker(dictionary, i, SIZE)
        cv2.imwrite(fileName, generator)
        ImgList1.append(cv2.imread(fileName))
        ImgList1.append(img_white)
        WiteList.append(img_white)
        WiteList.append(img_white)
        convImg1 = cv2.hconcat(ImgList1)
        convWhite = cv2.hconcat(WiteList)
    for i in range(6,11):
        fileName = "ar_" + str(i) + ".png"
        generator = aruco.drawMarker(dictionary, i, SIZE)
        cv2.imwrite(fileName, generator)
        ImgList2.append(cv2.imread(fileName))
        ImgList2.append(img_white)
        convImg2 = cv2.hconcat(ImgList2)
    TestList = [convImg1, convWhite, convImg2]
    convImg3 = cv2.vconcat(TestList)
    cv2.imshow('ArMarker1',convImg3)
    cv2.imwrite("Result.jpg", convImg3)
    cv2.waitKey(0)

arGenerator()

こちらでARマーカーの形状や形式??を定義。

dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)

そして、実際にARマーカーを生成している箇所はこちら。

generator = aruco.drawMarker(dictionary, i, SIZE)

後はimwriteでpng画像にした後にそれらの情報をリスト化して5×5に置き換えているのみなので割愛しますが、

cv2.hconcat()

が渡されたリスト情報を元に1枚ずつ横に画像を並べ、

cv2.vconcat()

が縦に画像を並べます。

生成されるARマーカーはこんな感じになります。
f:id:Elsammit:20201010121255j:plain

■ARマーカー読み取り

ARマーカーを読み取り、ARマーカー上に画像を表示するコードはこちら。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cv2

aruco = cv2.aruco #arucoライブラリ
dictionary = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)

img1 = cv2.imread("makewani.png")
img2 = cv2.imread("hamstar.png")
img3 = cv2.imread("mogura.png")

def ConvImg(corners, i, img, convimg):
    x=int(corners[i][0][0][0])
    y=int(corners[i][0][0][1])
    w=int(corners[i][0][2][0]) - int(corners[i][0][0][0])
    h=int(corners[i][0][2][1]) - int(corners[i][0][0][1])
    if w > 0 and h > 0:
        convimg = cv2.resize(convimg,(w,h))
        img[y:y+h,x:x+w] = convimg
    return img

def arReader():
    cap = cv2.VideoCapture(0) #ビデオキャプチャの開始
    while True:
        ret, frame = cap.read() #ビデオキャプチャから画像を取得

        Height, Width = frame.shape[:2] #sizeを取得
        img = cv2.resize(frame,(int(Width),int(Height)))

        corners, ids, rejectedImgPoints = aruco.detectMarkers(img, dictionary) #マーカを検出
        aruco.drawDetectedMarkers(img, corners, ids, (0,255,0)) #検出したマーカに描画する

        try:
            if corners != []:
                for i in range(len(ids)):
                    if ids[i] == 10:
                        img = ConvImg(corners, i, img, img1)

                    if ids[i] == 5:
                        img = ConvImg(corners, i, img, img2)
                    if ids[i] == 3:
                        img = ConvImg(corners, i, img, img3)

            cv2.imshow('drawDetectedMarkers', img) #マーカが描画された画像を表示

            cv2.waitKey(1) #キーボード入力の受付
        except:
            print("error")

    cap.release() #ビデオキャプチャのメモリ解放
    cv2.destroyAllWindows() #すべてのウィンドウを閉じる


arReader()

処理としては大きく分けて、
 ・ARマーカー読み取り
 ・読み取ったARマーカー上に画像を表示
の2つです。

まず、ARマーカー読み取り
まずはいつもの。

ret, frame = cap.read() #ビデオキャプチャから画像を取得

Height, Width = frame.shape[:2] #sizeを取得
img = cv2.resize(frame,(int(Width),int(Height)))

カメラからキャプチャ画像を取得。

そして取得したキャプチャ画像に対して、

corners, ids, rejectedImgPoints = aruco.detectMarkers(img, dictionary) #マーカを検出

によりARマーカを読み取り。
返り値ですが、
・corners:マーカーの4角の座標情報
・ids:登録されているid値
となります。
デバッグも兼ねて、

aruco.drawDetectedMarkers(img, corners, ids, (0,255,0)) #検出したマーカに描画する

を入れております。
こちらは、読み取ったARマーカーの領域やid値をキャプチャ画像に重ね合わせる処理を行っております。

次にARマーカー上に画像重ね合わせです。

def ConvImg(corners, i, img, convimg):
    x=int(corners[i][0][0][0])
    y=int(corners[i][0][0][1])
    w=int(corners[i][0][2][0]) - int(corners[i][0][0][0])
    h=int(corners[i][0][2][1]) - int(corners[i][0][0][1])
    if w > 0 and h > 0:
        convimg = cv2.resize(convimg,(w,h))
        img[y:y+h,x:x+w] = convimg
    return img

にて重ね合わせ処理を実行しております。

cornersですが、4次元配列で情報が保存されております。
1次元目にARマーカーの各角x座標、y座標が格納。
2次元目にARマーカーの各角番号が格納されています。
角番号の割り振りですが、時計回りに0~3で格納されております。
そして、4次元目に読み取ったマーカー番号が格納されています。


と言っても文字だけだとわかりにくいので図で表すとこんな感じです。
f:id:Elsammit:20201010123602p:plain

この図を例に1番目のマーカーの左上のx座標を取得したい場合には、

X = corners[1][0][0][0]

となります。
右下のy座標の場合には、

Y = corners[1][0][2][1]

ですね。

実際に動かしてみるとこんな感じになりました。
f:id:Elsammit:20201010124213g:plain

なかなかですね😅

■最後に

ARでの画像表示するだけなら結構簡単に行えるんだな!!と思いました。

だけど、、、
最近のARで表示される画像って3次元なんですね。。。
ARマーカーを読み取った際の向きや角度も検出できるようなので、それを用いて切り替えるのかな?🤔
https://sgrsn1711.hatenablog.com/entry/2018/02/15/224615


3次元画像をどうやって表示させるか?はまだ調べなければ、、

う~~ん。奥が深いです。