OpenCV ile ArUco işaretçilerinin takibi

Ali Yasin Eser
İyi Programlama
Published in
6 min readJun 14, 2018

Herkese selamlar. Bitirme projemi gerçekleştirirken (Ultrason destekli biyopsi operasyonunda artırılmış gerçeklik rehber yazılımı) kaynak eksikliğini fark ederek bir raporlama yoluna gitmeye karar verdim. Her aşamayı bir parçaya ayırarak size aktarmaya çalışacağım. Bugün size ArUco işaretçilerinin takibinden bahsedeceğim. Matematik kısmını işlemeyeceğim fakat gerekli bilgiyi kaynaklar kısmında bulabilirsiniz.

Öncelikle işaretçilerin ne olduğuna dair biraz bilgi edinelim. İşaretçiler ingilizcede “fiducial marker” olarak geçiyor ve 2B uzaydan 3B uzaya geçiş niteliğinde bilgi verebilecek bir resim olarak ifade ediliyor. ArUco squared binary fiducial marker kullanmakta, bunun manası kare şeklinde ve siyah-beyaz görüntülerden oluşuyor. Kare kodlara çok benziyor ki basitçe söylemek gerekirse aynı şeyi tarif ediyor olabilirim. Bu işaretçiler ArUco sözlüklerinde(dictionary) mevcut ve binary(ikili) olarak saklanıyor. Görüntü üzerinde işaretçi adayları bulunduktan sonra bu sözlüklerden karşılaştırılarak özel işaretçi numarası(marker ID) bulunuyor ve kullanılan fonksiyonlar üzerinden döndürülüyor. Daha fazla ayrıntıya ArUco makalesini inceleyerek erişebilirsiniz.

Örnek bir ArUco işaretçisi.

OpenCV dökümanından edindiğim bilgilere göre kamera görüntüsü üzerinden ArUco işaretçilerinin bulunma aşamaları basit olarak şu şekildedir( görüntüler dökümandan alıntıdır):

  1. İşaretçi adaylarının tespiti: Bu aşamada işaretçinin kare olduğunu bildiğimiz için kare olabilecek adayları bulmamız gerekiyor.
  • Görüntüye öncelikle bir eşikleme(thresholding) uygulamamız gerekiyor. Kullanılan eşiklemenin ismi adaptive thresholding olarak geçiyor. Adaptive thresholding, belli bir büyüklükteki pencere (3x3, 5x5, 11x11, …) ile gri seviyeli görüntüde gezerek her pencere için bir optimum gri seviye değeri bulur. Bu seviyenin altında kalanlar siyah, üstünde kalanlar beyaz olarak belirlenir.
  • Binary görüntüden(siyah beyaz görüntü) kenar tespiti ( contour olarak geçiyor) yapılır. Bulunan kenarlar konveks değilse veya kare şekli oluşturmuyorlarsa ihmal edilir. Ekstra filtreler de mevcuttur (fazla büyük veya küçük kenarlar, kenarların yakınlık değerleri vb. )

2. Adayların değerlendirilmesi: Bu aşamada adaylar ön işlemden geçirilir ve sözlükte aranır.

  • Kare olarak kabul edilen adaylar perspektif dönüşümden (perspective transform) geçer ve kare bir görüntü halini alır. Bu hale kanonik (canonical) form deniyor. Bundan sonra otsu eşiklemesi yapılır. Otsu eşiklemesi, “görüntünün histogramını öyle bir yerden ayırmalıyım ki seçtiğim değer siyah(arka plan) ve beyaz(ön plan) dağılımını minimuma indirmeli” mantığı ile çalışır.
  • Arama için kullanılan sözlük boyutu 5x5 piksel diyelim. Görüntü 5x5 piksel görüntüye indirilecek şekilde bir parçalanma işlemi yapılır(işaretçilerin en dışında genellikle siyah bir sınır(border) bulunur, bu da dahil edilirse 7x7 piksel). Daha sonra siyah 0, beyaz 1 olmak üzere binary bir matris haline getirilerek sözlükte aranır. Bulunan adaylar için hata hesaplaması gerçekleştirilir. Bulunanlar ve reddedilenler fonksiyon tarafından döndürülür.
(Soldan sağa) Orjinal görüntü, bulunan işaretçiler ve numaraları, reddedilen adaylar(kırmızı ile işaretlenmiş).

Bu kısımdan sonra poz tahmini( pose estimation ) dediğimiz kısma geliyoruz. ArUco fonksiyonları, işaretçileri bulduğu zaman bize işaretçi başına iki vektör döndürüyor. Bunlara rotasyon ve öteleme (rotation and translation) deniyor. Bu vektörler, işaretçi uzayında kameranın pozisyonunu ve rotasyonunu vermekte. Yani aslında kamera uzayında çalışmıyoruz. Kod kısmında buna dair bir ekstra efor görmeyeceksiniz, ta ki birden fazla işaretçi ile çalışmaya başladığınız zaman. Buna sonraki yazılarımda değineceğim. Rotasyon vektörü kamera ile işaretçinin merkezi arasındaki Rodriguez açılarını veriyor. Öteleme vektörü ise aradaki uzaklığı sırasıyla x, y ve z eksenleri cinsinden veriyor.
İki boyutlu görüntüden derinlik bilgisi nasıl elde ediliyor? Burada önemli bir “hiper parametre” ihtiyacı mevcut. İşaretçilerinizi yukarıdaki görüntülerde göreceğiniz gibi kağıda vb. bastırmanız gerekiyor. Çıkardığınız zaman, işaretçilerin bir köşesini ölçüyorsunuz. Kaç santimetre ise bu değeri veriyorsunuz. Bu şekilde ilişkilendirebileceği bir bilgiye de sahip olmuş oluyor. Bu değerin düşük hatada olması çok önemli. Diğer türlü uygulamanızın hata oranını etkileyecektir.

Kod kısmına başlamadan önce kamera kalibrasyonunu yapmış olmanız gerektiğini hatırlatayım. Yapmadıysanız sizi şuraya alalım. Eğer yaptıysanız, fonksiyonumuza başlayabiliriz:

import numpy as np
import cv2
import cv2.aruco as aruco
cap = cv2.VideoCapture(1) # Get the camera sourcedef track(matrix_coefficients, distortion_coefficients):
while True:
ret, frame = cap.read()
# operations on the frame come here
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Change grayscale

VideoCapture kısmında 1 değeri benim ikinci kamerayı kullandığımı belirtmekte. Tek kameranız var ise 0 yazmanız gerekiyor. Fonksiyonumuz kamera matrisini ve gürültü katsayılarını almakta. Daha önce yaptığımız kalibrasyondan gelen matrisler buraya parametre olarak verilecek. Sürekli video akışı aldığımız için sonsuz döngü oluşturuyoruz ve görüntümüzü almaya başlıyoruz. Gri seviyeli görüntüde iş yapmanın kolaylığı ve ArUco tespiti için gri seviyeye geçiyoruz.

aruco_dict = aruco.Dictionary_get(aruco.DICT_5X5_250)  # Use 5x5 dictionary to find markers
parameters = aruco.DetectorParameters_create() # Marker detection parameters
# lists of ids and the corners beloning to each id
corners, ids, rejected_img_points = aruco.detectMarkers(gray, aruco_dict, parameters=parameters, cameraMatrix=matrix_coefficients, distCoeff=distortion_coefficients)

Öncelikle hangi sözlüğü kullanacağımızı belirlememiz gerekiyor. ArUco 4x4 boyutundan 7x7 piksel boyutuna kadar 50, 100, 250 ve 1000 işaretçi sayısına sahip sözlükler barındırıyor. İşiniz ne kadar basitse o kadar küçük sözlüğü seçmekte fayda var. Dictionary_get ile sözlüğümüzü alıyoruz. Ardından bir dedektör parametre serisi oluşturuyoruz. Bunlar işaretçileri bulacak parametrelerdir. detectMarkers fonksiyonu ile işaretçi tespitini gerçekleştiriyoruz.

Fonksiyonda gördüğünüz parametreler şu şekilde:

  • gray: Gri seviyeli görüntü.
  • aruco_dict: Oluşturduğumuz sözlük
  • parameters: Dedektör parametreleri.
  • cameraMatrix: Kalibrasyon ilehesaplanan iç parametreler.
  • distCoeff: Kalibrasyon ile hesaplanan gürültü vektörü.

Fonksiyonun değerleri:

  • corners: Tespit edilen her işaretçi için, görüntüdeki köşe koordinatlarıdır. N tane işaretçi için [N][4] boyutunda matris verir.
  • ids: Tespit edilen işaretçilerin numaralarını verir. corners parametresi ile ilişkilidir. Yani ids[4] işaretçisinin köşe noktaları corners[4] dizisinde yer alır.
  • rejected_img_points: Tespit edilen fakat işaretçi olmayan şekillerin görüntüdeki köşe koordinatlarıdır.
if np.all(ids is not None):  # If there are markers found by detector
for i in range(0, len(ids)): # Iterate in markers
# Estimate pose of each marker and return the values rvec and tvec---different from camera coefficients
rvec, tvec, markerPoints = aruco.estimatePoseSingleMarkers(corners[i], 0.02, matrix_coefficients, distortion_coefficients)
(rvec - tvec).any() # get rid of that nasty numpy value array error
aruco.drawDetectedMarkers(frame, corners) # Draw A square around the markers
aruco.drawAxis(frame, matrix_coefficients, distortion_coefficients, rvec, tvec, 0.01) # Draw Axis

Eğer ids dizisi boş değilse, görüntüde işaretçi bulunmuş demektir. if şartına girebiliriz manasına gelir. Dizi üstünde dolaşarak corners dizisindeki ilgili koordinatları estimatePoseSingleMarkers fonksiyonuna veririz. Parametreler neredeyse aynı, 0.02 değeri hariç. Bu değer metre cinsinden 2 santimetreyi ifade ediyor. Benim kağıda bastırdığım işaretçilerin bir kenarının boyutu 2 santimetre olduğu için bu şekilde yazdım. Kalibrasyon sırasında verdiğiniz metrikte (kalibrasyon yaparken satranç tahtasındaki bir karenin köşe uzunluğu, kalibrasyon sırasında veriliyor) ne kullandıysanız burada onu kullanmalısınız. Bu aşamada bize rvec ve tvec denilen rotasyon ve öteleme vektörü veriliyor. Bunları drawAxis fonksiyonu ile çizdirebilir ve sonucun doğruluğundan emin olabiliriz. Burada frame diyerek ilk aldığım görüntüyü verdiğime dikkat edin. Parametrelere artık aşinasınız, 0.01 değerinin de tvec ile aynı cinsten çizilen uzunluk değeri olduğunu belirteyim. İşaretçinin merkezi orta noktası olduğundan değerin yarısını verdim. Bu değer üzerinde dilediğiniz değişikliği yapabilirsiniz, merkez sabit kalacaktır.

ArUco ile takip bu şekilde yapılabilir. Fonksiyonun tamamı aşağıdaki gibidir:

Burada dikkat edilmesi gereken şey, hata paylarının çeşitli olduğunu unutmamaktır. Kamera kalibrasyonunuzun başarısı, ışık ortamı vb. durumlar başarıyı etkiler. Hata payı ihmal edildiğinde, kamera ile alınan ArUco işaretçilerinin görüntülerinden poz tahmini şu şekildedir:

İşaretçi takibi. Düz bir zeminde yapmanız tavsiye olunur.

ArUco işaretçilerini, nasıl bulunduğunu ve bu işlemin nasıl yapıldığını olabildiğince anlatmaya çalıştım. Umuyorum faydası dokunmuştur. ArUco işaretçilerinin birbirlerine göre olan pozisyonlarını almak isterseniz sizi şuraya alabiliriz. Herkese güzel projeler ve güzel günler dilerim.

Kaynaklar:

  1. https://docs.opencv.org/3.1.0/d5/dae/tutorial_aruco_detection.html
  2. https://medium.com/@aliyasineser/opencv-ile-kamera-kalibrasyonu-6b91079c0b80
  3. https://github.com/njanirudh/Aruco_Tracker/blob/master/aruco_tracker.py
  4. https://longervision.github.io/2017/03/10/opencv-external-posture-estimation-ArUco-single-marker/
  5. http://answers.opencv.org/question/20281/is-rotation-vector-from-solvepnp-already-roll-pitch-and-yaw/
  6. https://docs.opencv.org/3.2.0/d9/d6a/group__aruco.html#ga16fda651a4e6a8f5747a85cbb6b400a2
  7. http://www.uco.es/investiga/grupos/ava/sites/default/files/GarridoJurado2014.pdf

--

--

Ali Yasin Eser
İyi Programlama

iOS Developer with Computer Vision and Embedded Systems background. Solo musician with 3 albums.