Camera calibration and use using opencv-python [chang method]

Overview

When I was researching camera calibration, there were quite a few sites with source code, but there were many messy codes and it was difficult to understand what works and how it works, so I summarized it.

I'm not an image processor, so I can't say much, but I'd like to summarize it for those who want to calibrate anyway.

I will mainly describe the following two.

--Camera calibration --Use of camera parameters and distortion parameters

Folder structure

\root
│  Calib.py //Executable file
│
└─Image //Folder for storing chessboard image files
    │  image1.jpg //Image file
    │  ...
    └─dist //Corner detection status confirmation folder

Camera calibration

The following source code is shown. If you store the image file in the Image folder and execute it, the calibration result (mtx.csv and dist.csv) will be output in the same folder as Calib.py. The evaluation value is displayed on the way (the place called total error), but if the value is 0.05 or less, it is good for the time being.

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 "] #Image format
        self.image = [] #Image buffer
        self.imageSize = None #Image size
        self.imagePath = Path(imagePath)#Image file path
        self.setConfig(cols, rows, squareSize)#Column x row
        
        self.log.debug("initial Calb..")
        
    def setConfig(self, cols, rows, squareSize):
        self.log.debug("setConfig")
        self.patternSize = (cols, rows)#Image size
        
        self.patternPoints = np.zeros( (np.prod(self.patternSize), 3), np.float32 ) #Chess board (X,Y,Z) Coordinate specification(Z=0)
        self.patternPoints[:,:2] = np.indices(self.patternSize).T.reshape(-1, 2)
        self.patternPoints *= squareSize #The size of one side of the square[cm]The set
        
        self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 50, 0.0001)#Threshold for high accuracy. Set the target accuracy, how many times to do when 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)) #Image loading
                    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) #Corner position accuracy correction.(11,11)Where is the size of the search window for higher accuracy?
                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") #Camera matrix
            np.savetxt("dist.csv", dist, delimiter=",", fmt="%0.14f") #Distortion parameters
            #Display calculation result
            print("RMS:", ret)
            print("mtx:", mtx)
            print("dist:", dist)
            
            #Evaluation by reprojection error
            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)) #The closer it is to 0, the better
            
            return True
        else:
            self.log.debug("all image don't exist corner")
            return False

if __name__ ==  "__main__":
    path = "Image" #Folder containing chessboard image files
  #Chessboard intersection
    rows = 10
    cols = 7
  
    size = 2.4 #Chessboard side length
    calb = Calbration(path, cols=cols, rows=rows, squareSize=size)
    calb.calbration()

Utilization of camera parameters and distortion parameters

The following is an example of using the output parameters. You can modify the above program to output the numpy array, but this time I made it read the output csv file.

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("Image file name to enter"))
    cv2.imwrite( "Output file name", img)

trouble shooting

roi becomes (0,0,0,0)

There aren't many Japanese materials for this. If you understand the reasoning, that may be true, but I will explain the reasoning to the minimum.

In overseas Q & A, I say something like "take a better picture of chess board". To put it simply, I interpret it as "** Take a chess board closer, bigger, with a non-overlapping pattern **". The fact that ** does not overlap ** is also very important, and even if it overlaps, (0,0,0,0) may appear.

Recommended Posts

Camera calibration and use using opencv-python [chang method]
Use camera calibration file with OpenCvSharp4
Camera calibration
A supplement to "Camera Calibration" in OpenCV-Python Tutorials
Create a color sensor using a Raspberry Pi and a camera