├── Functions ├── auxiliary_functions.py ├── ocam_functions.py └── opencv_functions.py ├── README.md └── Scripts ├── Data ├── ocam_calibration.txt └── opencv_calibration.dat ├── comparison.py └── opencv_calibration.py /Functions/auxiliary_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Oct 05 11:42:23 2017 4 | 5 | @author: Carlo Sferrazza 6 | PhD Candidate 7 | Institute for Dynamics Systems and Control 8 | ETH Zurich 9 | """ 10 | 11 | import cv2 12 | import sys 13 | 14 | 15 | """ Shows three parallel video streams (original, undistorted through opencv, 16 | undistorted through Scaramuzza's toolbox) """ 17 | def undistortedStream(idx, mtx, dist, mapx_persp_32, mapy_persp_32): 18 | cap = cv2.VideoCapture(idx) 19 | 20 | h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) 21 | w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) 22 | 23 | newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1) 24 | 25 | while(True): 26 | # Capture frame-by-frame 27 | ret, frame = cap.read() 28 | 29 | # Our operations on the frame come here 30 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 31 | 32 | # undistort 33 | dst = cv2.fisheye.undistortImage(gray, mtx, dist, None, newcameramtx) 34 | dst_scaramuzza = cv2.remap(gray, mapx_persp_32, mapy_persp_32, cv2.INTER_LINEAR) 35 | 36 | # Display the resulting frame 37 | cv2.imshow('undistorted opencv',dst) 38 | cv2.imshow('undistorted scaramuzza',dst_scaramuzza) 39 | cv2.imshow('original',gray) 40 | 41 | q = cv2.waitKey(1) 42 | 43 | if q & 0xFF == ord('q'): 44 | break 45 | 46 | # When everything done, release the capture 47 | cap.release() 48 | cv2.destroyAllWindows() 49 | 50 | """ Acquires images and returns them for calibration """ 51 | def acquireImages(idx): 52 | cap = cv2.VideoCapture(idx) 53 | images = [] 54 | count = 0 55 | 56 | print("Press t for acquiring a new image, q to stop acquisition and proceed to calibration. "\ 57 | "Try to collect at least 15 images from different viewpoints, and as close as possible "\ 58 | "to the chessboard") 59 | sys.stdout.flush() 60 | while(True): 61 | # Capture frame-by-frame 62 | ret, frame = cap.read() 63 | 64 | # Our operations on the frame come here 65 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 66 | 67 | # Display the resulting frame 68 | cv2.imshow('frame',gray) 69 | 70 | q = cv2.waitKey(1) 71 | 72 | if q & 0xFF == ord('t'): 73 | images.append(gray) 74 | print("Images acquired: ", count) 75 | sys.stdout.flush() 76 | count+=1 77 | elif q & 0xFF == ord('q'): 78 | break 79 | 80 | # When everything done, release the capture 81 | cap.release() 82 | cv2.destroyAllWindows() 83 | 84 | return images 85 | 86 | -------------------------------------------------------------------------------- /Functions/ocam_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Sep 27 10:54:23 2017 4 | 5 | @author: Carlo Sferrazza 6 | PhD Candidate 7 | Institute for Dynamics Systems and Control 8 | ETH Zurich 9 | 10 | All this functions are a translation of the C++ code provided with the Scaramuzza's OCamCalib Toolbox 11 | and are better described here: https://sites.google.com/site/scarabotix/ocamcalib-toolbox 12 | """ 13 | 14 | import numpy as np 15 | 16 | """ Reads file containing the ocamcalib parameters exported from the Matlab toolbox """ 17 | def get_ocam_model(filename): 18 | o = {} 19 | with open(filename) as f: 20 | lines = [l for l in f] 21 | 22 | l = lines[2] 23 | data = l.split() 24 | o['length_pol'] = int(data[0]) 25 | o['pol'] = [float(d) for d in data[1:]] 26 | 27 | l = lines[6] 28 | data = l.split() 29 | o['length_invpol'] = int(data[0]) 30 | o['invpol'] = [float(d) for d in data[1:]] 31 | 32 | l = lines[10] 33 | data = l.split() 34 | o['xc'] = float(data[0]) 35 | o['yc'] = float(data[1]) 36 | 37 | l = lines[14] 38 | data = l.split() 39 | o['c'] = float(data[0]) 40 | o['d'] = float(data[1]) 41 | o['e'] = float(data[2]) 42 | 43 | l = lines[18] 44 | data = l.split() 45 | o['height'] = int(data[0]) 46 | o['width'] = int(data[1]) 47 | 48 | return o 49 | 50 | def cam2world(point2D, o): 51 | point3D = [] 52 | 53 | invdet = 1.0/(o['c']-o['d']*o['e']) 54 | 55 | xp = invdet*((point2D[0]-o['xc']) - o['d']*(point2D[1]-o['yc'])) 56 | yp = invdet*(-o['e']*(point2D[0]-o['xc']) + o['c']*(point2D[1]-o['yc'])) 57 | 58 | r = np.linalg.norm([xp,yp]) 59 | 60 | zp = o['pol'][0] 61 | r_i = 1.0 62 | 63 | for i in range(1,o['length_pol']): 64 | r_i *= r 65 | zp += r_i*o['pol'][i] 66 | 67 | invnorm = 1.0/np.linalg.norm([xp,yp,zp]) 68 | 69 | point3D.append(invnorm*xp) 70 | point3D.append(invnorm*yp) 71 | point3D.append(invnorm*zp) 72 | 73 | return point3D 74 | 75 | def world2cam(point3D, o): 76 | point2D = [] 77 | 78 | norm = np.linalg.norm(point3D[:2]) 79 | 80 | if norm != 0: 81 | theta = np.arctan(point3D[2]/norm) 82 | invnorm = 1.0/norm 83 | t = theta 84 | rho = o['invpol'][0] 85 | t_i = 1.0 86 | 87 | for i in range(1,o['length_invpol']): 88 | t_i *= t 89 | rho += t_i*o['invpol'][i] 90 | 91 | x = point3D[0]*invnorm*rho 92 | y = point3D[1]*invnorm*rho 93 | 94 | point2D.append(x*o['c']+y*o['d']+o['xc']) 95 | point2D.append(x*o['e']+y+o['yc']) 96 | else: 97 | point2D.append(o['xc']) 98 | point2D.append(o['yc']) 99 | 100 | return point2D 101 | 102 | def create_perspective_undistortion_LUT(o, sf): 103 | 104 | mapx = np.zeros((o['height'],o['width'])) 105 | mapy = np.zeros((o['height'],o['width'])) 106 | 107 | Nxc = o['height']/2.0 108 | Nyc = o['width']/2.0 109 | Nz = -o['width']/sf 110 | 111 | for i in range(o['height']): 112 | for j in range(o['width']): 113 | M = [] 114 | M.append(i - Nxc) 115 | M.append(j - Nyc) 116 | M.append(Nz) 117 | m = world2cam(M, o) 118 | mapx[i,j] = m[1] 119 | mapy[i,j] = m[0] 120 | 121 | return mapx, mapy 122 | 123 | def create_panoramic_undistortion_LUT(Rmin, Rmax, o): 124 | 125 | mapx = np.zeros((o['height'],o['width'])) 126 | mapy = np.zeros((o['height'],o['width'])) 127 | 128 | for i in range(o['height']): 129 | for j in range(o['width']): 130 | theta = -(float(j))/o['width']*2*np.pi 131 | rho = Rmax - float(Rmax-Rmin)/o['height']*i 132 | mapx[i,j] = o['yc'] + rho*np.sin(theta) 133 | mapy[i,j] = o['xc'] + rho*np.cos(theta) 134 | 135 | return mapx, mapy 136 | 137 | 138 | -------------------------------------------------------------------------------- /Functions/opencv_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Oct 05 12:02:33 2017 4 | 5 | @author: Carlo Sferrazza 6 | PhD Candidate 7 | Institute for Dynamics Systems and Control 8 | ETH Zurich 9 | 10 | Calibration routine using the provided fisheye opencv libraries 11 | """ 12 | 13 | import numpy as np 14 | import cv2 15 | 16 | def opencvCalibrate(m,n,criteria,calibration_flags,images): 17 | # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0) 18 | objp = np.zeros((1,m*n,3), np.float32) 19 | objp[0,:,:2] = np.mgrid[0:m,0:n].T.reshape(-1,2) 20 | 21 | # Arrays to store object points and image points from all the images. 22 | objpoints = [] # 3d point in real world space 23 | imgpoints = [] # 2d points in image plane. 24 | 25 | for gray in images: 26 | 27 | # Find the chess board corners 28 | ret, corners = cv2.findChessboardCorners(gray, (m,n),None) 29 | 30 | # If found, add object points, image points (after refining them) 31 | if ret == True: 32 | objpoints.append(objp) 33 | 34 | cv2.cornerSubPix(gray,corners,(3,3),(-1,-1),criteria) 35 | imgpoints.append(corners) 36 | # Draw and display the corners 37 | gray = cv2.drawChessboardCorners(gray, (m,n), corners,ret) 38 | cv2.imshow("img",gray) 39 | cv2.waitKey(0) 40 | 41 | cv2.destroyAllWindows() 42 | 43 | try: 44 | return cv2.fisheye.calibrate(objpoints, imgpoints, gray.shape[::-1],None,None,flags=calibration_flags) 45 | except: 46 | raise ValueError("Calibration failed: try to load new images") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-Calibration 2 | This repository provides a Python translation of the undistortFunctions that are part of the Scaramuzza's OCamCalib for fisheye cameras. It contains sample code for comparing this Toolbox to the builtin OpenCV fisheye library. 3 | This repo's objective is to provide sample code for calibrating a fisheye camera with two different methods. The code regarding the OCamLib is only a translation and adaptation from C++ to Python. 4 | 5 | 6 | Information about the OpenCV calibration procedure for fisheye camera can be found at: http://docs.opencv.org/3.0-beta/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html 7 | 8 | Information about the OCamCalib calibration procedure for fisheye camera can be found at: https://sites.google.com/site/scarabotix/ocamcalib-toolbox 9 | 10 | ---------------------- 11 | -----INSTRUCTIONS----- 12 | ---------------------- 13 | 14 | 1. Run opencv_calibration.py to extract (and save to a .dat file) the camera calibration parameters using opencv, and save the images for doing the same with the OCamLib Matlab Toolbox. 15 | 16 | 2. Use the images saved in the previous step to obtain (and save to a .txt file through the export data option) the camera calibration parameters with the OCamLib Matlab Toolbox. 17 | 18 | 3. Run comparison.py to load the files generated in the previous steps to show a parallel view of the results obtained with the calibration and undistortion methods of OpenCV and OCamLib. 19 | -------------------------------------------------------------------------------- /Scripts/Data/ocam_calibration.txt: -------------------------------------------------------------------------------- 1 | #polynomial coefficients for the DIRECT mapping function (ocam_model.ss in MATLAB). These are used by cam2world 2 | 3 | 5 -2.536908e+02 0.000000e+00 1.539476e-03 -1.807672e-06 8.005641e-09 4 | 5 | #polynomial coefficients for the inverse mapping function (ocam_model.invpol in MATLAB). These are used by world2cam 6 | 7 | 10 362.120104 188.179547 -10.673281 33.957618 16.123820 38.812001 84.970118 68.822686 24.023156 3.064844 8 | 9 | #center: "row" and "column", starting from 0 (C convention) 10 | 11 | 223.182738 326.816586 12 | 13 | #affine parameters "c", "d", "e" 14 | 15 | 1.000940 0.000033 -0.001851 16 | 17 | #image size: "height" and "width" 18 | 19 | 480 640 20 | 21 | -------------------------------------------------------------------------------- /Scripts/Data/opencv_calibration.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosferrazza/Python-Calibration/7a6c4cc53bd432d89569f446ce569aa2894af6fa/Scripts/Data/opencv_calibration.dat -------------------------------------------------------------------------------- /Scripts/comparison.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Oct 05 11:38:41 2017 4 | 5 | @author: Carlo Sferrazza 6 | PhD Candidate 7 | Institute for Dynamics Systems and Control 8 | ETH Zurich 9 | 10 | Compares the calibration and undistortion methods of OpenCV and OCamLib 11 | """ 12 | 13 | import sys 14 | import os 15 | sys.path.append(os.path.abspath('../functions')) 16 | 17 | import pickle 18 | from ocam_functions import get_ocam_model, create_perspective_undistortion_LUT 19 | from auxiliary_functions import undistortedStream 20 | 21 | # Change here the index that corresponds to your camera (on Windows, add 700 to use DSHOW, which is generally faster) 22 | idx = 0 # + 700 23 | 24 | # Change here the path of the calibration file that contains the parameters obtained through opencv_calibration.py 25 | PIK_opencv = "Data/opencv_calibration.dat" 26 | 27 | # Change here the path of the calibration file that contains the parameters obtained through ocam_calibration.py 28 | path_ocam = "Data/ocam_calibration.txt" 29 | 30 | # Change here the number of internal corners that have to be detected on the chessboard in each dimension 31 | m = 9 32 | n = 6 33 | 34 | # Parameter that affect the result of Scaramuzza's undistortion. Try to change it to see how it affects the result 35 | sf = 4.0 36 | 37 | # You don't need to change anything else below 38 | with open(PIK_opencv, "rb") as f: 39 | data = pickle.load(f) 40 | ret, mtx, dist, rvecs, tvecs = data 41 | 42 | o = get_ocam_model(path_ocam) 43 | mapx_persp, mapy_persp = create_perspective_undistortion_LUT(o, sf) 44 | 45 | mapx_persp_32 = mapx_persp.astype('float32') 46 | mapy_persp_32 = mapy_persp.astype('float32') 47 | 48 | undistortedStream(idx, mtx, dist, mapx_persp_32, mapy_persp_32) -------------------------------------------------------------------------------- /Scripts/opencv_calibration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Oct 05 11:51:44 2017 4 | 5 | @author: Carlo Sferrazza 6 | PhD Candidate 7 | Institute for Dynamics Systems and Control 8 | ETH Zurich 9 | 10 | Extracts the camera calibration parameters using opencv, and saves the images for 11 | doing the same with the OCamLib Matlab Toolbox 12 | """ 13 | 14 | import sys 15 | import os 16 | sys.path.append(os.path.abspath('../functions')) 17 | import cv2 18 | from auxiliary_functions import acquireImages 19 | from opencv_functions import opencvCalibrate 20 | import pickle 21 | from datetime import datetime 22 | 23 | # Change here the index that corresponds to your camera 24 | idx = 1 25 | 26 | # Change here the path of the calibration file that you want to generate 27 | PIK = "Data/opencv_calibration.dat" 28 | 29 | # Change here the folder where you want to save the images 30 | path = "Images" 31 | 32 | # Change here the number of internal corners that have to be detected on the chessboard in each dimension 33 | m = 9 34 | n = 6 35 | 36 | # termination criteria and calibration flags (see opencv documentation if you need to adapt them) 37 | criteria = (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) 38 | calibration_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC+cv2.fisheye.CALIB_CHECK_COND+cv2.fisheye.CALIB_FIX_SKEW 39 | 40 | 41 | # You don't need to change anything else below 42 | images = acquireImages(idx) 43 | 44 | now = datetime.now() 45 | path += now.strftime("/%Y%m%d-%H%M%S") 46 | os.mkdir(path) 47 | count = 0 48 | for img in images: 49 | cv2.imwrite(os.path.abspath(path+"/im"+str(count)+".jpg"), img) 50 | count += 1 51 | 52 | ret, mtx, dist, rvecs, tvecs = opencvCalibrate(m,n,criteria,calibration_flags,images) 53 | 54 | with open(PIK, "wb") as f: 55 | data = [ret,mtx,dist,rvecs,tvecs] 56 | pickle.dump(data, f) --------------------------------------------------------------------------------