OpenCV ile kamera kalibrasyonu

Ali Yasin Eser
İyi Programlama
Published in
5 min readJun 11, 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 kamera kalibrasyonunun ne olduğundan sözlü/yazılı olarak bahsedeceğim. Matematik kısmını işlemeyeceğim fakat gerekli bilgiyi kaynaklar kısmında bulabilirsiniz.

Günümüz kameraları üretim masrafları bakımından inanılmaz derecede ucuzladı. Fakat bu ucuzluğun en önemli yan etkisi kalibre olmaması, yani inanılmaz bir gürültü derecesine sahipler. Kameranın iç parametreleri( yere düştüğünde bile kamera kalibrasyonu iç merceğin hareketi sebebiyle kaymakta olduğundan ve üretim sorunlarından ) ve dış parametreleri hesaplandığı takdirde bu problem rahatlıkla çözülebilir. Kalibre edilmemiş kamerada iki türlü gürültü mevcuttur. Birincisi fıçı gürültüsü(barrel distortion) adını alır ve yanlardan esnek bir görüntüye sebebiyet verir. İkincisi de iğne yastığı gürültüsü(pincushion distortion) adını alır ve yanlardan basıktır. Şu şekilde özetlenebilir:

Standart bir görüntü ve gürültülerin ilişkileri.
(Soldan sağa)Gürültüye sahip bir görüntü ve görüntünün gürültüsüz hali.

Gürültü engelleme amacıyla bilinmesi gereken matrise kamera matrisi diyoruz. İşin arkasındaki matematiği merak edenler OpenCV kaynağına göz gezdirebilirler(kaynak kısmından erişebilirsiniz). OpenCV çeşitli metotlar ile kalibrasyon desteği vermekte. Bunlardan en bilineni satranç tahtasıdır. Yapılacaklar şu şekilde:

  1. Satranç tahtası görüntüsünü indirip çıktı alıyoruz. Çıktı aldıktan sonra bir karenin köşe uzunluğunu kaydetmeniz gerekecek. Satranç tahtası:
Kalibrasyon için gerekli görüntü. A4'e çıkarın ve boyutunu değiştirmeyin(%100).

2. Satranç çıktısını düz duracak şekilde bir yere yapıştırıyoruz, sekreter altlığı olabilir. Bu şekilde en az 20–30 görüntü alıyoruz ve her görüntüde farklı bir açıda ve uzaklıkta olmaya dikkat ediyoruz. Kameranın sınır noktalarını unutmuyoruz. Işığın yüksek veya alçak olması ve bu şekilde örnek almak da kalibrasyon başarısı dediğimiz başarıyı artıracaktır elbette. Örnekler:

Örnek olarak hazırladığım kalibrasyon görüntülerim.

3. Bu görüntüleri bir klasör altında toplayarak python dilinde yazacağımız koda geçiyoruz. Tabii bundan önce python opencv kütüphanesi kurmak gerekiyor. Ben python 3.6.4 kullanıyorum, opencv kurmak için ise şu komutu kullanabilirsiniz:

Burada opencv-python, opencv kütüphanesidir. opencv-contrib-python ise bitirme projemin anlatacağım sonraki aşamasında kullanılacak kısımlarını içeren pakettir. numpy ise bilimsel hesaplama paketidir.

Kalibrasyon fonksiyonumuza başlayabiliriz.

import numpy as np
import cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
def calibrate(dirpath, prefix, image_format, square_size, width=9, height=6):
""" Apply camera calibration operation for images in the given directory path. """
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(8,6,0)
objp = np.zeros((height*width, 3), np.float32)
objp[:, :2] = np.mgrid[0:width, 0:height].T.reshape(-1, 2)

Parametreler:

  • dirpath: Görüntülerin bulunduğu dizin.
  • prefix: Görüntülerin ortak ismi (resim1.jpg, resim2.jpg … gibi ilerlerse “resim” yazacaksınız. Kod genelleştirilmiş olduğu için bu şekildedir.)
  • image_format: “jpg” veya “png” gibi uzantılar. Uzantıların openCV tarafından desteklenmesi önemlidir.
  • square_size: Satranç tahtasındaki bir karenin uzunluğu. Bunu istediğiniz cinsten yazabilirsiniz, doğru değeri girmeniz bundan sonraki aşamalarda önemli.
  • width: Satranç tahtasının uzun kısmının içinde(dikkat!) kaç nokta varsa onu yazacaksınız. Yukarıdaki görüntüyü kullanacağınızı varsayarak 9 olarak ayarladım.
  • height: Satranç tahtasının geniş kısmının içinde(dikkat!) kaç nokta varsa onu yazacaksınız. Yukarıdaki görüntüyü kullanacağınızı varsayarak 6 olarak ayarladım.

objp olarak oluşturduğumuz dizi satranç tahtasının koordinatlarıdır. (0,0,0) (1,0,0) gibi koordinatlara satranç düzlemi oturtulacaktır. Poz tahmini gibi işlemler yapacağımızı öngörerek satranç tahtamızın düzlemine göre ayarlamamız daha iyi olur. Bunun için square_size parametremizi aldık. Tüm elemanları hesapladığımız ve yazdığımız birim ile çarpalım:

objp = objp * square_size # square_size değerimiz 1.5 santimetre diyelim. Ben olsam buraya 0.015 yazardım. Çünkü, metre cinsinin daha genel olduğunu düşünüyorum ve her işi sabit bir birimde yapmak benim işime gelir. İsteyen inç ile yapsın, kendi tercihi tabii ki.

Satranç tahtası görüntüsü aslında [9][6] boyutunda bir matris olduğundan parametreleri width=9 ve height=6 olarak kabul etmiştik. Bu sayılar siyah ve beyaz karelerin buluştuğu noktaları temsil etmektedir. Satranç görüntünüz farklı sayıda noktaya eşitse değiştirebilirsiniz, satranç tahtanızın en ve boyundaki nokta sayısı aynı olmadığı sürece sorun yok. Criteria diye bahsettiğimiz “kriter”, ileride kullanacağımız fonksiyonun iterasyon şartlarıdır. Tam hakimiyetim olmadığı için genellikle bunun kullanıldığını ve isteyenlerin araştırabileceğini belirteyim.

# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
# Bazı arkadaşlar komut satırında son kısma dizin olduğunu belirtirken "/" işareti koyuyorlar. Ben images tanımlamasında onu ayrı olarak koyduğum için tekrar etmesini istemiyorum. Bu sebeple bunu ekleme gereği duydum:
if dirpath[-1:] == '/':
dirpath = dirpath[:-1]

images = glob.glob(dirpath+'/' + prefix + '*.' + image_format) #

objpoints değişkeni 3 boyutta öngördüğümüz nokta karşılıklarıdır. imgpoints ise kameranın aldığı görüntüde satranç tahtasının siyah/beyaz buluşma noktalarının hangi piksel koordinatlarında olduğunu tutacaktır. Belirtilen dizindeki dosyaları alıyor ve bunları gezilebilir (iterable) bir şekilde tutuyoruz.

for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (width, height), None)

# If found, add object points, image points (after refining them)
if ret:
objpoints.append(objp)

corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners2)

# Draw and display the corners
img = cv2.drawChessboardCorners(img, (width, height), corners2, ret)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

return [ret, mtx, dist, rvecs, tvecs]

Görüntüler üzerinde bir döngü ile işleme başlıyoruz. Görüntüyü imread fonksiyonu ile okuyor ve cvtColor fonksiyonu ile gri seviyeli görüntüye çeviriyoruz. OpenCV’nin sağladığı findChessboardCorners fonksiyonu bizim girdiğimiz boyutlarda bir satranç tahtası arayarak bize köşeleri döndürüyor. ret değeri satranç tahtasını bulup bulmadığını belirtmekte.

Eğer bulmuş ise obje noktalarımızı ekliyoruz ve ardından bir interpolasyon uygulamasına geçiyoruz. Bu işlemin amacı, satranç tahtasının köşelerini olabildiğince az hata ile belirlemek. Ardından drawChessboardCorners ile satranç tahtamızı kullanıcıya gösteriyoruz. Bunu yapmak şart değil fakat kişi doğrulamasının da önemli olabileceğini vurgulamakta yarar var.

Son aşamamız, görüntülerin tamamında bulunan obje noktaları ve iki boyutlu görüntüdeki piksel karşılıklarını calibrateCamera fonksiyonuna vererek dönen parametreleri saklamak. Bu fonksiyonda döndürmeyi tercih ettim, bu şekilde bu haliyle kullanılabilir. Kamera matrisini kaydetmek, yüklemek ve kalibrasyonu yapmak amacıyla tüm fonksiyonları şu şekilde yazabiliriz( kopyala yapıştır sevenlere gelsin.):

Örnek kullanım:

argparse kütüphanesi zorunlu olmamakla beraber işimizi kolaylaştırması açısından kullanıyorum. Argümanların tamamı fonksiyona gönderilenler ile aynı, sadece “save_file” argümanı bunun dışında. Bu argüman da kamera matrisini kaydedeceğimiz dosya ismini istiyor bizden. Örnek: “camera.yml” olabilir.

Olabildiğince halk diline yakın(?) anlatmaya çalıştım. Umuyorum ki faydalı olur. ArUco işaretçilerinden ve takibinin nasıl yapıldığı hakkındaki yazıma şuradan ulaşabilirsiniz. Sevgiler.

Kaynaklarım:

  1. Görüntüler:
    https://www.google.com.tr/search?q=camera+distortion+example&client=chrome-omni&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjv9sDFoMrbAhWKhqYKHZsHDk8Q_AUICigB&biw=1920&bih=929#imgrc=BbnVAnjEndc0qM:
    https://www.google.com.tr/search?q=barrel+distortion&source=lnms&tbm=isch&sa=X&ved=0ahUKEwj54qXSn8rbAhXBlCwKHTraA_QQ_AUICigB&biw=1920&bih=929#imgrc=FD8BNL4aL3iFaM:
    https://www.google.com.tr/search?q=opencv+chessboard&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjPnt3TocrbAhXH2SwKHaM1DscQ_AUICigB&biw=1920&bih=929#imgrc=3Y_uhSD2kFeCqM:
  2. OpenCV ingilizce kaynak. Matematiği ile anlatmışlar. Halk dilini sevmeyenlere:
    https://docs.opencv.org/2.4/doc/tutorials/calib3d/camera_calibration/camera_calibration.html
    https://docs.opencv.org/3.1.0/dc/dbb/tutorial_py_calibration.html
  3. Kodun iskeleti, biraz kirlisi aruco github reposundan alınmaktadır. Codebase:
    https://github.com/njanirudh/Aruco_Tracker

--

--

Ali Yasin Eser
İyi Programlama

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