4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

opencv-pythonを用いてのカメラキャリブレーションおよび利用【changの手法】

Last updated at Posted at 2020-02-03

概要

カメラキャリブレーションについて調べていると、ソースコード有りのサイトは結構あるのですが、とっ散らかったコードが多く、何がどう効くかわかりづらかったので、まとめました。

私は元々画像処理系ではないので大きなことは言えませんが、とにかくキャリブレーションしたい人向けにまとめたいと思います。

主に以下の二つについて述べます。

  • カメラキャリブレーション
  • カメラパラメータおよび歪みパラメータの利用

フォルダ構成

\root
│  Calib.py // 実行ファイル
│
└─Image //チェスボード画像ファイルを格納するフォルダ
    │  image1.jpg //画像ファイル
    │  ...
    └─dist // コーナー検出状態確認用フォルダ

カメラキャリブレーション

以下のソースコードを示します。Imageフォルダに画像ファイルを格納し、実行すると、Calib.pyと同一フォルダにキャリブレーション結果(mtx.csvおよびdist.csv)が出力されます。途中、評価値が表示されます(total errorというところ)が、0.05以下の値であればひとまず良いです。

Calib.py
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import os
from pathlib import Path
import logging

logging.basicConfig(level=logging.DEBUG)

class Calbration():
    def __init__(self, imagePath="", cols=2, rows=2, squareSize=1.0):
        # log
        self.log = logging.getLogger("Calbration")
#        self.log.addHandler(logging.StreamHandler())
#        self.log.setLevel(logging.DEBUG)
        
        self.formats = ["*.jpg", "*.png"] #画像フォーマット
        self.image = [] # 画像バッファ
        self.imageSize = None # 画像サイズ
        self.imagePath = Path(imagePath)# 画像ファイルパス
        self.setConfig(cols, rows, squareSize)# 列×行
        
        self.log.debug("initial Calb..")
        
    def setConfig(self, cols, rows, squareSize):
        self.log.debug("setConfig")
        self.patternSize = (cols, rows)# 画像サイズ
        
        self.patternPoints = np.zeros( (np.prod(self.patternSize), 3), np.float32 ) #チェスボード(X,Y,Z)座標の指定 (Z=0)
        self.patternPoints[:,:2] = np.indices(self.patternSize).T.reshape(-1, 2)
        self.patternPoints *= squareSize # 正方形の1辺のサイズ[cm]を設定
        
        self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 50, 0.0001)# 高精度化する際の閾値。cornerSubPixする際に何回やるか、目標精度を設定
        
    # image read from iamge folder
    def read(self):
        if( os.path.exists(self.imagePath) ):
            for fmt in self.formats:
                for path in self.imagePath.glob(fmt):
                    img = cv2.imread(str(path)) # 画像読込み
                    self.image.append([ path, img])
            
            if( len(self.image) > 0 ):
                self.log.debug("find image..." + str(len(self.image)))
                return True
            else:
                # error
                self.log.debug("Don't exist image file.")
                return False
        else:
            # error
            self.log.debug("Don't exist folder.")
            return False
        
    def calbration(self):
        
        if( not self.read()):# read image
            # error
            return False
        
        self.log.debug("corner finding start")
        imgPoints = []# corner buff
        objPoints = []# obj buff
        count = 0
        for img in self.image: 
            self.log.debug(str(img[0]) + " find...")
            
            gray = cv2.cvtColor(img[1], cv2.COLOR_BGR2GRAY)
            if(self.imageSize is None):
                self.imageSize = gray.shape[::-1]
            ret, corners = cv2.findChessboardCorners(gray, self.patternSize, None)
            if(ret):
                self.log.debug("detected corner")
                
                corners = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1), self.criteria) #コーナー位置精度補正。(11,11)のところは高精度化する際の探索窓の大きさ?
                imgPoints.append(corners)
                objPoints.append(self.patternPoints)

                # debug draw
                distImg = cv2.drawChessboardCorners(img[1], self.patternSize, corners, ret)
                cv2.imwrite( str(self.imagePath) + "/dist/dist_" + str(img[0]).replace( str(self.imagePath) + "\\", ""), distImg)
                count += 1
            else:
                os.remove(str(img[0]))
                self.log.debug("don't detected corner")
            
        self.log.debug("detected image len is..." + str(count))
        
        if(len(imgPoints) > 0):
            ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objPoints, imgPoints, self.imageSize, None, None)
            np.savetxt("mtx.csv", mtx, delimiter=",", fmt="%0.14f") # カメラ行列
            np.savetxt("dist.csv", dist, delimiter=",", fmt="%0.14f") # 歪みパラメータ
            # 計算結果を表示
            print("RMS:", ret)
            print("mtx:", mtx)
            print("dist:", dist)
            
            # 再投影誤差による評価
            mean_error = 0
            for i in range(len(objPoints)):
                image_points2, _ = cv2.projectPoints(objPoints[i], rvecs[i], tvecs[i], mtx, dist)
                error = cv2.norm(imgPoints[i], image_points2, cv2.NORM_L2) / len(image_points2)
                mean_error += error
            print ("total error: ", mean_error/len(objPoints)) # 0に近いほど良い            
            
            return True
        else:
            self.log.debug("all image don't exist corner")
            return False

if __name__ ==  "__main__":
    path = "Image" # チェスボード画像ファイルが格納されているフォルダ
  # チェスボードの交点
    rows = 10
    cols = 7
  
    size = 2.4 # チェスボードの辺の長さ
    calb = Calbration(path, cols=cols, rows=rows, squareSize=size)
    calb.calbration()

カメラパラメータおよび歪みパラメータの利用

以下が出力されたパラメータの利用例です。上のプログラムを改造してnumpy配列を出力してもいいのですが、今回は出力したcsvファイルを読み込む形にしました。

Correction.py
import cv2
import numpy as np

class Correction():
    def __init__(self, mtx, dist, formatType=None):
        
        if ( formatType == "csv" ):
            self.mtx = np.loadtxt(mtx, delimiter=",")
            self.dist= np.loadtxt(dist, delimiter=",")
        else:
            self.mtx = mtx
            self.dist= dist
    
    def __call__(self, image):
        cv2.CALIB_FIX_PRINCIPAL_POINT
#        print(image.shape)
        h, w = image.shape[:2]
        newcameramtx, roi=cv2.getOptimalNewCameraMatrix(self.mtx, self.dist, (w, h), 1, (w, h))
#        print(roi)
        
        # undistort
        dst = cv2.undistort(image, self.mtx, self.dist, None, newcameramtx)

        
        # crop the image
        x,y,w,h = roi
        dst = dst[y:y+h, x:x+w]
        
        return dst

if __name__ ==  "__main__":
    corr = Correction("mtx.csv", "dist.csv", formatType="csv")
    img = corr(cv2.imread("入力する画像ファイル名"))
    cv2.imwrite( "出力ファイル名", img)

トラブルシューティング

roiが(0,0,0,0)となる

これの日本語の資料が少ないんですよね。理屈が分かればその通りなんかもしれませんが、あくまで理屈を最小限に説明します。

海外のQ&Aでは、「チェスボードをもっと上手く撮れ」的なことを言っています。簡単に言うと「チェスボードをもっと近くで大きく、重複しないパターンで撮れ」ということだと私は解釈しています。重複しないというところもとても重要で、重複しても(0,0,0,0)が出ることがあります。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?