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
\root
│ Calib.py //Executable file
│
└─Image //Folder for storing chessboard image files
│ image1.jpg //Image file
│ ...
└─dist //Corner detection status confirmation folder
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()
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)
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.