├── .idea
└── vcs.xml
├── LICENSE
├── README.md
├── UndistortFishEye
├── .idea
│ ├── UndistortFishEye.iml
│ ├── encodings.xml
│ ├── misc.xml
│ ├── modules.xml
│ └── workspace.xml
├── UndistortFishEye.py
├── UndistortFishEye_process.py
├── UndistortFishEye_widget.py
├── __init__.py
└── fisheye_module.py
├── calibrate.py
├── evaluate.py
└── undistort.py
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Ikomia-dev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FishEyeModel
2 | Python project based on OpenCV module to model FishEye camera and undistort acquired images
3 |
4 | ## Dependencies
5 | Python 3.7 or higher
6 | OpenCV 4.0 or higher
7 |
8 | ## Calibration
9 | OpenCV calibration algorithm for FishEye camera enables to model intrinsic and/or extrinsic parameters from a set of images acquired by the camera.
10 | To use the function, you have to acquire images of known pattern from which it is possible to extract distortions (chessboard for example).
11 | A chessboard pattern can be downloaded following this link:
12 | [Chessboard pattern](https://drive.google.com/file/d/0B3EXsqKhWPfdVk9NRmIzNE1RQms/view?usp=sharing).
13 |
14 | Then you have to acquire several images at different positions to integrate as much distorsion information as possible into the model.
15 | In our tests, a set of 25-30 images can be sufficient to have good undistort results.
16 |
17 | We give a Python script to launch the calibration
18 |
19 | ```console
20 | python calibrate.py "ImageFolder" [-o "OutputFile"] [-s "True/False"]
21 | ```
22 |
23 | **ImageFolder (mandatory)**: folder where acquired images for calibration are stored.
24 | **OutputFile (optional)**: path of the saved model file. By default, file *calibration.txt* is saved into the current folder.
25 | **True/False (optional)**: boolean value to indicate if chessboard detection images have to be saved.
26 |
27 | The main steps of the algorithm are:
28 | 1. Detection of all inner corners of the chessboard for each image.
29 | 2. Improvement of corner positions thanks to super-resolution algorithm.
30 | 3. Valid detections storage.
31 | 4. FishEye modelization on valid detections.
32 | 5. Save calibration parameters to file (extrinsic or intrinsic).
33 |
34 | **Note:** calibration can failed even if chessboard detection is valid.
35 | Error can occured during the model computation (because of *cv2.fisheye.CALIB_CHECK_COND* flag).
36 | You have to remove the corresponding chessboard image to ensure good calibration.
37 |
38 | ## Undistort
39 | We give a Python script to launch the correction:
40 |
41 | ```console
42 | python undistort.py "SourceImage" [-c "CalibrationFile"] [-o "OutputImage"]
43 | ```
44 |
45 | **SourceImage (mandatory)**: path of the image to undistort.
46 | **CalibrationFile (optional)**: path of the model file generated by the calibration.
47 | If not set, the script will search the default file *calibratio.txt" into the current folder and fail if not found.
48 | **OutputImage (optional)**: path of the saved undistorted image.
49 | By default, undistorted image is saved in the file *undistort.png* into the current folder.
50 |
--------------------------------------------------------------------------------
/UndistortFishEye/.idea/UndistortFishEye.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/UndistortFishEye/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/UndistortFishEye/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/UndistortFishEye/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/UndistortFishEye/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/UndistortFishEye/UndistortFishEye.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import numpy
3 | import PyCore
4 | import PyDataProcess
5 | import UndistortFishEye_process as processMod
6 | import UndistortFishEye_widget as widgetMod
7 |
8 |
9 | # --------------------
10 | # - Interface class to integrate the process with Ikomia application
11 | # - Inherits PyDataProcess.CPluginProcessInterface from Ikomia API
12 | # --------------------
13 | class UndistortFishEye(PyDataProcess.CPluginProcessInterface):
14 |
15 | def __init__(self):
16 | PyDataProcess.CPluginProcessInterface.__init__(self)
17 |
18 | def getProcessFactory(self):
19 | # Instantiate process object
20 | return processMod.UndistortFishEyeProcessFactory()
21 |
22 | def getWidgetFactory(self):
23 | # Instantiate associated widget object
24 | return widgetMod.UndistortFishEyeWidgetFactory()
25 |
--------------------------------------------------------------------------------
/UndistortFishEye/UndistortFishEye_process.py:
--------------------------------------------------------------------------------
1 | import PyCore
2 | import PyDataProcess
3 | import copy
4 | import fisheye_module
5 |
6 |
7 | # --------------------
8 | # - Class to handle the process parameters
9 | # - Inherits PyCore.CProtocolTaskParam from Ikomia API
10 | # --------------------
11 | class UndistortFishEyeProcessParam(PyCore.CProtocolTaskParam):
12 |
13 | def __init__(self):
14 | PyCore.CProtocolTaskParam.__init__(self)
15 | # Place default value initialization here
16 | self.calib_file_path = str()
17 |
18 | def setParamMap(self, paramMap):
19 | # Set parameters values from Ikomia application
20 | # Parameters values are stored as string and accessible like a python dict
21 | self.calib_file_path = paramMap['calibrationFile']
22 |
23 | def getParamMap(self):
24 | # Send parameters values to Ikomia application
25 | # Create the specific dict structure (string container)
26 | paramMap = PyCore.ParamMap()
27 | paramMap['calibrationFile'] = self.calib_file_path
28 | return paramMap
29 |
30 |
31 | # --------------------
32 | # - Class which implements the process
33 | # - Inherits PyCore.CProtocolTask or derived from Ikomia API
34 | # --------------------
35 | class UndistortFishEyeProcess(PyDataProcess.CImageProcess2d):
36 |
37 | def __init__(self, name, param):
38 | PyDataProcess.CImageProcess2d.__init__(self, name)
39 |
40 | #Create parameters class
41 | if param is None:
42 | self.setParam(UndistortFishEyeProcessParam())
43 | else:
44 | self.setParam(copy.deepcopy(param))
45 |
46 | def getProgressSteps(self, eltCount=1):
47 | # Function returning the number of progress steps for this process
48 | # This is handled by the main progress bar of Ikomia application
49 | return 3
50 |
51 | def run(self):
52 | # Core function of your process
53 | # Call beginTaskRun for initialization
54 | self.beginTaskRun()
55 |
56 | param = self.getParam()
57 | k, d, dims = fisheye_module.load_calibration(param.calib_file_path)
58 |
59 | # Step progress bar:
60 | self.emitStepProgress()
61 |
62 | img_input = self.getInput(0)
63 | src_img = img_input.getImage()
64 | dst_img = fisheye_module.undistort(src_img, k, d, dims)
65 |
66 | # Step progress bar:
67 | self.emitStepProgress()
68 |
69 | output = self.getOutput(0)
70 | output.setImage(dst_img)
71 |
72 | # Step progress bar:
73 | self.emitStepProgress()
74 |
75 | # Call endTaskRun to finalize process
76 | self.endTaskRun()
77 |
78 |
79 | # --------------------
80 | # - Factory class to build process object
81 | # - Inherits PyDataProcess.CProcessFactory from Ikomia API
82 | # --------------------
83 | class UndistortFishEyeProcessFactory(PyDataProcess.CProcessFactory):
84 |
85 | def __init__(self):
86 | PyDataProcess.CProcessFactory.__init__(self)
87 | # Set process information as string here
88 | self.info.name = "UndistortFishEye"
89 | self.info.description = "This process undistorts images acquired by FishEye camera. " \
90 | "It requires a calibration file that models extrinsic parameters of the camera. " \
91 | "This file can be generated with the given Python script calibrate.py"
92 | self.info.authors = "Ikomia"
93 | # relative path -> as displayed in Ikomia application process tree
94 | self.info.path = "Plugins/Python/FishEye"
95 | # self.info.iconPath = "your path to a specific icon"
96 | self.info.keywords = "distortion,fisheye,camera,calibration"
97 |
98 | def create(self, param=None):
99 | # Create process object
100 | return UndistortFishEyeProcess(self.info.name, param)
101 |
--------------------------------------------------------------------------------
/UndistortFishEye/UndistortFishEye_widget.py:
--------------------------------------------------------------------------------
1 | import PyCore
2 | import PyDataProcess
3 | import QtConversion
4 | import UndistortFishEye_process as processMod
5 |
6 | #PyQt GUI framework
7 | from PyQt5.QtWidgets import *
8 |
9 |
10 | # --------------------
11 | # - Class which implements widget associated with the process
12 | # - Inherits PyCore.CProtocolTaskWidget from Ikomia API
13 | # --------------------
14 | class UndistortFishEyeWidget(PyCore.CProtocolTaskWidget):
15 |
16 | def __init__(self, param, parent):
17 | PyCore.CProtocolTaskWidget.__init__(self, parent)
18 |
19 | if param is None:
20 | self.parameters = processMod.UndistortFishEyeProcessParam()
21 | else:
22 | self.parameters = param
23 |
24 | label_calib_file = QLabel("Calibration file")
25 | self.edit_calib_file = QLineEdit(self.parameters.calib_file_path)
26 | browse_btn = QPushButton("...")
27 | browse_btn.setToolTip("Select calibration file")
28 |
29 | # Mandatory : apply button to launch the process
30 | applyButton = QPushButton("Apply")
31 |
32 | # Create layout : QGridLayout by default
33 | self.gridLayout = QGridLayout()
34 | self.gridLayout.addWidget(label_calib_file, 0, 0)
35 | self.gridLayout.addWidget(self.edit_calib_file, 0, 1)
36 | self.gridLayout.addWidget(browse_btn, 0, 2)
37 | self.gridLayout.addWidget(applyButton, 1, 0, 1, 3)
38 |
39 | # Set clicked signal connection
40 | browse_btn.clicked.connect(self.on_browse)
41 | applyButton.clicked.connect(self.on_apply)
42 |
43 | # PyQt -> Qt wrapping
44 | layoutPtr = QtConversion.PyQtToQt(self.gridLayout)
45 |
46 | # Set widget layout
47 | self.setLayout(layoutPtr)
48 |
49 | def on_browse(self):
50 | filter = "Calibration file(*.txt)"
51 | calibration_file = self.get_selected_file(filter)
52 | self.edit_calib_file.setText(calibration_file);
53 |
54 | def on_apply(self):
55 | # Apply button clicked slot
56 |
57 | # Get parameters from widget
58 | self.parameters.calib_file_path = self.edit_calib_file.text()
59 |
60 | # Send signal to launch the process
61 | self.emitApply(self.parameters)
62 |
63 | def get_selected_file(self, filter):
64 | file_path = str()
65 | file_dlg = QFileDialog()
66 | file_dlg.setFileMode(QFileDialog.ExistingFile)
67 | file_dlg.setViewMode(QFileDialog.Detail)
68 | file_dlg.setNameFilter(filter)
69 |
70 | if file_dlg.exec():
71 | path_list = file_dlg.selectedFiles()
72 | file_path = path_list[0]
73 |
74 | return file_path
75 |
76 |
77 | #--------------------
78 | #- Factory class to build process widget object
79 | #- Inherits PyDataProcess.CWidgetFactory from Ikomia API
80 | #--------------------
81 | class UndistortFishEyeWidgetFactory(PyDataProcess.CWidgetFactory):
82 |
83 | def __init__(self):
84 | PyDataProcess.CWidgetFactory.__init__(self)
85 | # Set the name of the process -> it must be the same as the one declared in the process factory class
86 | self.name = "UndistortFishEye"
87 |
88 | def create(self, param):
89 | # Create widget object
90 | return UndistortFishEyeWidget(param, None)
91 |
--------------------------------------------------------------------------------
/UndistortFishEye/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ikomia-dev/FishEyeModel/eabf544163841c9162afc10b9e5fc7a0e7304d80/UndistortFishEye/__init__.py
--------------------------------------------------------------------------------
/UndistortFishEye/fisheye_module.py:
--------------------------------------------------------------------------------
1 | import os
2 | import cv2
3 | import glob
4 | import pickle
5 | import numpy as np
6 |
7 | # Size of the chessboard used for calibration
8 | CHESSBOARD_SIZE = (6, 9)
9 |
10 | # Calculate curvature for each horizontal and vertical curves from the chessboard
11 | # Return: curvature integral
12 | def chessboard_measure(corners):
13 | cornersMat = np.reshape(corners, (-1, CHESSBOARD_SIZE[0], 2))
14 | curvature_integral = 0
15 | # Vertical curves
16 | for k in range(CHESSBOARD_SIZE[1]):
17 | dx_dt = np.gradient(cornersMat[k, :, 0])
18 | dy_dt = np.gradient(cornersMat[k, :, 1])
19 | d2x_dt2 = np.gradient(dx_dt)
20 | d2y_dt2 = np.gradient(dy_dt)
21 | curvature = np.abs(d2x_dt2 * dy_dt - dx_dt * d2y_dt2) / (dx_dt * dx_dt + dy_dt * dy_dt) ** 1.5
22 | curvature_integral += np.sum(curvature)
23 | # Horizontal curves
24 | for k in range(CHESSBOARD_SIZE[0]):
25 | dx_dt = np.gradient(cornersMat[:, k, 0])
26 | dy_dt = np.gradient(cornersMat[:, k, 1])
27 | d2x_dt2 = np.gradient(dx_dt)
28 | d2y_dt2 = np.gradient(dy_dt)
29 | curvature = np.abs(d2x_dt2 * dy_dt - dx_dt * d2y_dt2) / (dx_dt * dx_dt + dy_dt * dy_dt) ** 1.5
30 | curvature_integral += np.sum(curvature)
31 | return curvature_integral
32 |
33 | # Chessboard detection on the image loaded from the given file
34 | # If saveDetection==True an image with the result of the detection is saved to the disk
35 | # Return: success status (True or False), corners detected in image coordinates, size of the image
36 | def detect_chessboard(img_path, save_detection):
37 | img = cv2.imread(img_path)
38 | ret, corners, img_shape = detect_chessboard_img(img, save_detection)
39 | if ret == True:
40 | if save_detection:
41 | chess_path = img_path.replace('.png', '_chess.jpg')
42 | cv2.imwrite(chess_path, img)
43 |
44 | return ret, corners, img_shape
45 |
46 | # Chessboard detection on input opencv image
47 | # If showChessboard==True(default) we draw corners and lines on input image
48 | # Return: success status (True or False), corners detected in image coordinates, size of the image
49 | def detect_chessboard_img(img, showChessboard=True):
50 | chessboard_flags = cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE
51 | subpix_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.01)
52 | img_shape = img.shape[:2]
53 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
54 | ret, corners = cv2.findChessboardCorners(gray, CHESSBOARD_SIZE, chessboard_flags)
55 | if ret == True:
56 | # Refining corners position with sub-pixels based algorithm
57 | cv2.cornerSubPix(gray, corners, (3, 3), (-1, -1), subpix_criteria)
58 | if showChessboard == True:
59 | cv2.drawChessboardCorners(img, CHESSBOARD_SIZE, corners, ret)
60 | else:
61 | print('Chessboard not detected in image ')
62 |
63 | return ret, corners, img_shape
64 |
65 | # Show deformation measure on source image and undistorted image
66 | # The less the better (no deformation means perfect straight lines which means 0 curvature)
67 | def deformation_measure(src_image, undistorted_img):
68 | ret, corners1, _ = detect_chessboard_img(src_image)
69 | if ret == False:
70 | return
71 | ret, corners2, _ = detect_chessboard_img(undistorted_img)
72 | if ret == False:
73 | return
74 | m1 = chessboard_measure(corners1)
75 | m2 = chessboard_measure(corners2)
76 | r = (1-m2/m1)*100
77 | print('Deformation measure on source image: ' + str(m1))
78 | print('Deformation measure on undistorted image: ' + str(m2))
79 | print('Correction rate in percent: ' + str(r))
80 |
81 | def evaluate(img_path, calibration_path):
82 | src_image = cv2.imread(img_path)
83 | k, d, dims = load_calibration(calibration_path)
84 | undistorted_img = undistort(src_image, k, d, dims)
85 | deformation_measure(src_image, undistorted_img)
86 |
87 | # Launch calibration process from all png images in the given folder
88 | # Return: calibration parameters K, D and image dimensions
89 | def calibrate(img_folder, save_detection=False):
90 | # Calibration paramenters
91 | # NB: when CALIB_CHECK_COND is set, the algorithm checks if the detected corners of each images are valid.
92 | # If not, an exception is thrown which indicates the zero-based index of the invalid image.
93 | # Such image should be replaced or removed from the calibration dataset to ensure a good calibration.
94 | calibration_flags = cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv2.fisheye.CALIB_CHECK_COND + cv2.fisheye.CALIB_FIX_SKEW
95 | term_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6)
96 | # Logical coordinates of chessboard corners
97 | obj_p = np.zeros((1, CHESSBOARD_SIZE[0]*CHESSBOARD_SIZE[1], 3), np.float32)
98 | obj_p[0, :, :2] = np.mgrid[0:CHESSBOARD_SIZE[0], 0:CHESSBOARD_SIZE[1]].T.reshape(-1, 2)
99 |
100 | img_ref_shape = None
101 | obj_points = [] # 3d point in real world space
102 | img_points = [] # 2d points in image plane.
103 |
104 | # Iterate through all images in the folder
105 | images = glob.glob(img_folder + '/*.png')
106 | for filename in images:
107 | # Chessboard detection
108 | ret, corners, img_shape = detect_chessboard(filename, save_detection)
109 |
110 | if img_ref_shape == None:
111 | img_ref_shape = img_shape
112 | else:
113 | assert img_ref_shape == img_shape, "All images must share the same size."
114 |
115 | # If found, add object points, image points (after refining them)
116 | if ret == True:
117 | obj_points.append(obj_p)
118 | img_points.append(corners)
119 | print('Image ' + filename + ' is valid for calibration')
120 |
121 | k = np.zeros((3, 3))
122 | d = np.zeros((4, 1))
123 | dims = img_shape[::-1]
124 | valid_img_count = len(obj_points)
125 |
126 | if valid_img_count > 0:
127 | rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(valid_img_count)]
128 | tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(valid_img_count)]
129 |
130 | print('Beginning calibration process...')
131 | rms, _, _, _, _ = cv2.fisheye.calibrate(
132 | obj_points,
133 | img_points,
134 | img_shape,
135 | k,
136 | d,
137 | rvecs,
138 | tvecs,
139 | calibration_flags,
140 | term_criteria
141 | )
142 |
143 | print("Calibration done!")
144 | print("Found " + str(valid_img_count) + " valid images for calibration")
145 | return k, d, dims
146 |
147 |
148 | # Save calibration to file -> use of pickle serialization
149 | def save_calibration(path, dims, k, d ):
150 | with open(path, 'wb') as f:
151 | pickle.dump(dims, f)
152 | pickle.dump(k, f)
153 | pickle.dump(d, f)
154 |
155 |
156 | # Load calibration from file -> use of pickle serialization
157 | def load_calibration(path):
158 | k = np.zeros((3, 3))
159 | d = np.zeros((4, 1))
160 | dims = np.zeros(2)
161 |
162 | with open(path, 'rb') as f:
163 | k = pickle.load(f)
164 | d = pickle.load(f)
165 | dims = pickle.load(f)
166 |
167 | return k, d, dims
168 |
169 |
170 | # Undistort FishEye image with given calibration parameters
171 | def undistort(src_image, k, d, dims):
172 | dim1 = src_image.shape[:2][::-1] # dim1 is the dimension of input image to un-distort
173 | assert dim1[0] / dim1[1] == dims[0] / dims[1], "Image to undistort needs to have same aspect ratio as the ones used in calibration"
174 | map1, map2 = cv2.fisheye.initUndistortRectifyMap(k, d, np.eye(3), k, dims, cv2.CV_16SC2)
175 | undistorted_img = cv2.remap(src_image, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
176 | return undistorted_img
177 |
178 |
179 | # Undistort FishEye image (from a file) with given calibration parameters
180 | def undistort_from_file(img_path, k, d, dims):
181 | img = cv2.imread(img_path)
182 | return undistort(img, k, d, dims)
183 |
184 |
185 | # Undistort FishEye image (from a file) with given calibration parameters
186 | # This function has few others parameters to set the field of view of the undistorted image
187 | # Not suitable for large FOV FishEye camera (near 180°)
188 | def undistort_beta(img_path, k, d, dims, balance=0.0, dim2=None, dim3=None):
189 | img = cv2.imread(img_path)
190 | dim1 = img.shape[:2][::-1] # dim1 is the dimension of input image to un-distort
191 | assert dim1[0] / dim1[1] == dims[0] / dims[1], "Image to undistort needs to have same aspect ratio as the ones used in calibration"
192 |
193 | if not dim2:
194 | dim2 = dim1
195 |
196 | if not dim3:
197 | dim3 = dim1
198 |
199 | scaled_k = k * dim1[0] / dims[0] # The values of K is to scale with image dimension.
200 | scaled_k[2][2] = 1.0 # Except that K[2][2] is always 1.0
201 |
202 | # This is how scaled_K, dim2 and balance are used to determine the final K used to un-distort image
203 | new_k = cv2.fisheye.estimateNewCameraMatrixForUndistortRectify(scaled_k, d, dim2, np.eye(3), None, balance, None, 1.0)
204 | map1, map2 = cv2.fisheye.initUndistortRectifyMap(scaled_k, d, np.eye(3), new_k, dim3, cv2.CV_16SC2)
205 | undistorted_img = cv2.remap(img, map1, map2, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
206 | return undistorted_img
207 |
208 |
209 | # Tool function to save image to disk at the given path
210 | def save_image(image, image_path):
211 | cv2.imwrite(image_path, image)
212 |
213 |
214 | # Tool function to save image to disk with automatic path definition (from the original image path)
215 | # -> use for test only
216 | def save_undistort_image(undistorted_img, img_path, suffix):
217 | folder = os.path.dirname(img_path)
218 | save_folder = folder + '/Undistort/'
219 |
220 | if not os.path.exists(save_folder):
221 | os.makedirs(save_folder)
222 |
223 | filename = os.path.basename(img_path)
224 | filename, file_extension = os.path.splitext(filename)
225 | cv2.imwrite(save_folder + filename + suffix + file_extension, undistorted_img)
226 |
227 | # Tool function to test chessboard detection on all images in subfolder "CalibImages"
228 | def test_chessboard_detection():
229 | images = glob.glob('CalibImages/*.png')
230 | for filename in images:
231 | ret, _, _ = detect_chessboard(filename, True)
232 | if ret:
233 | print('Chessboard detected correctly in image ' + filename)
234 | else:
235 | print('Chessboard not detected in image ' + filename)
236 |
237 | # Tool function to test calibration function on all images in subfolder "CalibImages"
238 | def test_calibration():
239 | k, d, dims = calibrate('CalibImages')
240 | print('----- Calibration results -----')
241 | print("Dimensions =" + str(dims))
242 | print("K = np.array(" + str(k.tolist()) + ")")
243 | print("D = np.array(" + str(d.tolist()) + ")")
244 |
245 | save_calibration('calibration.txt', k, d, dims)
246 | print("Calibration file saved successfully")
247 |
248 | k, d, dims = load_calibration('calibration.txt')
249 | print("Calibration file loaded successfully")
250 | print("Dimensions =" + str(dims))
251 | print("K = np.array(" + str(k.tolist()) + ")")
252 | print("D = np.array(" + str(d.tolist()) + ")")
253 |
254 | # Tool function to test undistortion
255 | # Calibration file "calibration.txt" must be in the current folder
256 | def test_undistort():
257 | k, d, dims = load_calibration('calibration.txt')
258 |
259 | # filename = 'SrcImages/Natuition/pos1.jpg'
260 | # undistorted_img = undistort_beta(filename, k, d, dims)
261 | # save_undistort_image(undistorted_img, filename, '_simple')
262 | # undistorted_img = undistort_beta(filename, k, d, dims, balance=0.0, dim2=(2000, 1500))
263 | # save_undistort_image(undistorted_img, filename, '_b0.0')
264 | # undistorted_img = undistort_beta(filename, k, d, dims, balance=0.5, dim2=(2000, 1500))
265 | # save_undistort_image(undistorted_img, filename, '_b0.5')
266 | # undistorted_img = undistort_beta(filename, k, d, dims, balance=1.0, dim2=(2000, 1500))
267 | # save_undistort_image(undistorted_img, filename, '_b1.0')
268 |
269 | images = glob.glob('SrcImages/Natuition/*.jpg')
270 | for filename in images:
271 | undistorted_img = undistort_from_file(filename, k, d, dims)
272 | save_undistort_image(undistorted_img, filename, '')
273 |
274 | images = glob.glob('SrcImages/Ikomia/*.png')
275 | for filename in images:
276 | undistorted_img = undistort_from_file(filename, k, d, dims)
277 | save_undistort_image(undistorted_img, filename, '')
278 |
279 | print("Undistort process finished successfully")
280 |
281 | # Tool function to test deformation measure on a specific image
282 | # Calibration file "calibration.txt" must be in the current folder
283 | def test_measure(img_path):
284 | src_image = cv2.imread(img_path)
285 | k, d, dims = load_calibration('calibration.txt')
286 | undistorted_img = undistort(src_image, k, d, dims)
287 | deformation_measure(src_image, undistorted_img)
288 |
289 | if __name__ == '__main__':
290 | #test_measure('CalibImages/img14.png')
291 | test_chessboard_detection()
292 | #test_calibration()
293 | #test_undistort()
294 |
295 |
296 |
--------------------------------------------------------------------------------
/calibrate.py:
--------------------------------------------------------------------------------
1 | from UndistortFishEye import fisheye_module
2 | import argparse
3 |
4 |
5 | # Python script to launch calibration process from terminal
6 | # Usage: calibrate.py "folder_of_calibration_images" "output_path"
7 | if __name__ == '__main__':
8 | parser = argparse.ArgumentParser()
9 | parser.add_argument(
10 | "image_folder",
11 | help="Folder where the calibration images are stored"
12 | )
13 |
14 | parser.add_argument(
15 | "-o",
16 | dest="output_file_path",
17 | default="calibration.txt",
18 | help="Path where the calibration file will be saved"
19 | )
20 |
21 | parser.add_argument(
22 | "-s",
23 | dest="save_detection",
24 | default=False,
25 | help="Path where the calibration file will be saved"
26 | )
27 |
28 | args = parser.parse_args()
29 | if not args.image_folder:
30 | raise RuntimeError("Folder of calibration images is required")
31 |
32 | k, d, dims = fisheye_module.calibrate(args.image_folder, args.save_detection)
33 | print('----- Calibration results -----')
34 | print("Dimensions =" + str(dims))
35 | print("K = np.array(" + str(k.tolist()) + ")")
36 | print("D = np.array(" + str(d.tolist()) + ")")
37 |
38 | fisheye_module.save_calibration(args.output_file_path, k, d, dims)
39 | print("Calibration file saved successfully")
40 |
41 |
--------------------------------------------------------------------------------
/evaluate.py:
--------------------------------------------------------------------------------
1 | from UndistortFishEye import fisheye_module
2 | import argparse
3 |
4 | # Python script to launch evaluate process from terminal
5 | # Usage: evaluate.py "source_image_path" -c "calibration_file_path"
6 | if __name__ == '__main__':
7 | parser = argparse.ArgumentParser()
8 | parser.add_argument(
9 | "source_image_path",
10 | help="Path of the source image to undistort"
11 | )
12 |
13 | parser.add_argument(
14 | "-c",
15 | dest="calibration_file_path",
16 | default="calibration.txt",
17 | help="Path of the calibration file"
18 | )
19 |
20 | args = parser.parse_args()
21 |
22 | if not args.source_image_path:
23 | raise RuntimeError("Source image path could not be empty")
24 |
25 | if not args.calibration_file_path:
26 | raise RuntimeError("Calibration file path could not be empty")
27 |
28 | fisheye_module.evaluate(args.source_image_path, args.calibration_file_path)
29 | print("Evaluate process finished successfully")
30 |
--------------------------------------------------------------------------------
/undistort.py:
--------------------------------------------------------------------------------
1 | from UndistortFishEye import fisheye_module
2 | import argparse
3 |
4 | # Python script to launch undistort process from terminal
5 | # Usage: undistort.py "source_image_path" "calibration_file_path" "output_image_path"
6 | if __name__ == '__main__':
7 | parser = argparse.ArgumentParser()
8 | parser.add_argument(
9 | "source_image_path",
10 | help="Path of the source image to undistort"
11 | )
12 |
13 | parser.add_argument(
14 | "-c",
15 | dest="calibration_file_path",
16 | default="calibration.txt",
17 | help="Path of the calibration file"
18 | )
19 |
20 | parser.add_argument(
21 | "-o",
22 | dest="output_image_path",
23 | default="undistort.png",
24 | help="Path where the result undistorted image will be saved"
25 | )
26 |
27 | args = parser.parse_args()
28 |
29 | if not args.source_image_path:
30 | raise RuntimeError("Source image path could not be empty")
31 |
32 | if not args.calibration_file_path:
33 | raise RuntimeError("Calibration file path could not be empty")
34 |
35 | k, d, dims = fisheye_module.load_calibration(args.calibration_file_path)
36 | undistorted_img = fisheye_module.undistort_from_file(args.source_image_path, k, d, dims)
37 | fisheye_module.save_image(undistorted_img, args.output_image_path)
38 | print("Undistort process finished successfully")
39 |
--------------------------------------------------------------------------------