Skip to content
This repository was archived by the owner on Aug 22, 2024. It is now read-only.
Merged
Prev Previous commit
Next Next commit
bugfix to how rms radians is computed and more quality of image checks
  • Loading branch information
Kyle Rendon committed Mar 2, 2021
commit 652f400991003f21b50bfad6d02829f799f2a3d4
4 changes: 0 additions & 4 deletions examples/calibration_registration/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,3 @@ pylint = "*"
[packages]
numpy = ">=1.19.2"
opencv-contrib-python = ">=4.4.0.46"
pefile = ">=2019.4.18"
pipenv = ">=2020.8.13"
virtualenv = ">=20.0.31"
virtualenv-clone = ">=0.5.4"
8 changes: 4 additions & 4 deletions examples/calibration_registration/calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
# ------------------------------------------------------------------------------
def parse_args():
"""
Get arguments for running the carlibration.
Get arguments for running the calibration.

Returns:
args -- Return a list of command line arguments for running calibartion.
args -- Return a list of command line arguments for running calibration.
"""

parser = argparse.ArgumentParser(description="Generate calibration.")
parser.add_argument("-d", "--dataset-dir",required=True,
parser.add_argument("-d", "--dataset-dir", required=True,
help="Full path to all captures from camera.")
parser.add_argument("-t", "--template", required=True,
help="Full path to Charuco board template file.")
Expand All @@ -33,7 +33,7 @@ def parse_args():
if __name__ == "__main__":
args = parse_args()

rms, calib_matrix, dist_coeffs, img_size, rvecs, tvecs = \
rms, calib_matrix, dist_coeffs, img_size, rvecs, tvecs, num_good_images = \
calibrate_camera(args.dataset_dir,
args.template,
init_calfile=args.calib_file)
Expand Down
66 changes: 46 additions & 20 deletions examples/calibration_registration/camera_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def write_opencv_calfile(calfile:str,
Args:
calfile (str): Full path of calibration file.
k_matrix (np.array): the camera intrinsics matrix.
dist8 (List): The distorition coefficients.
dist8 (List): The distortion coefficients.
img_size (np.array): 2d array of the image size, in the order width, height.
"""
fs_calib = cv2.FileStorage(calfile, cv2.FILE_STORAGE_WRITE)
Expand Down Expand Up @@ -507,25 +507,29 @@ def registration_error(array_a:np.array,
# calculated points.
num_points = pts.shape[0]
squares = [elem**2 for elem in angles]
rms_radians = math.sqrt(sum(squares))/num_points
rms_radians = math.sqrt(sum(squares)/num_points)
print(f"RMS (Radians): {rms_radians}")

rms_pixels = np.sqrt(np.sum((corners_a-pts)**2)/num_points)
return rms_pixels, rms_radians

#-------------------------------------------------------------------------------
def calibrate_camera(imdir:str,
template:str,
init_calfile:str = None,
rms_thr:float = 1.0,
postfix:str = "",
min_detections:int = 30,
min_images:int = 30) -> Tuple[float,
np.array,
np.array,
np.array,
List,
List]:
def calibrate_camera(
imdir:str,
template:str,
init_calfile:str = None,
rms_thr:float = 1.0,
postfix:str = "",
min_detections:int = 30,
min_images:int = 30,
min_quality_images:int = 30,
per_view_threshold:int = 1
) -> Tuple[float,
np.array,
np.array,
np.array,
List,
List]:
""" Calibrate a camera using charuco detector and opencv bundler.

Args:
Expand All @@ -536,11 +540,16 @@ def calibrate_camera(imdir:str,
postfix (str, optional): Calibration filename suffix. Defaults to "".
min_detections (int, optional): Required minimum number of detections.
Defaults to 30.
postfix (int, optional): Required minimum number of images. Defaults to 30.
min_images (int, optional): Minimum number of images. Defaults to 30.
min_quality_images (int, optional): Minimum number of images with sufficient
reprojection quality. Defaults to 30.
per_view_threshold (int, optional): RMS reprojection error threshold to
distinguish quality images. Defaults to 1.

Raises:
CalibrationError: Not enough images for calibration.
CalibrationError: Not enough detections for calibration.
CalibrationError: Not enough images with low rms for calibration.

Returns:
Tuple[float, np.array, np.array, np.array, List, List]:
Expand Down Expand Up @@ -573,7 +582,7 @@ def calibrate_camera(imdir:str,
ccorners_all, cids_all, p3d, img_size, board = \
detect_markers_many_images(imgnames, template)

# check number of board detections
# check number of times corners were successfully detected.
num_det = len(ccorners_all)
if num_det < min_detections:
msg = f"Insufficent detections. {num_det} found, {min_detections} required."
Expand All @@ -593,7 +602,7 @@ def calibrate_camera(imdir:str,
criteria = cv2.TERM_CRITERIA_COUNT + cv2.TERM_CRITERIA_EPS, 100, 1e-6

start = time.perf_counter()
rms, k_matrix, dist, rvecs, tvecs, _, _, _ = \
rms, k_matrix, dist, rvecs, tvecs, stdDeviationsIntrinsics, stdDeviationsExtrinsics, perViewErrors = \
aruco.calibrateCameraCharucoExtended(ccorners_all,
cids_all,
board,
Expand All @@ -603,17 +612,34 @@ def calibrate_camera(imdir:str,
flags=flags,
criteria=criteria)

# Check Quality of each image.
n_low_rms = [err[0] for err in perViewErrors if err[0] <= per_view_threshold]
num_good_images = len(n_low_rms)

# Report which indexes are failing in perViewErros.
failing_idxs = []
[failing_idxs.append(str(index)) for (index, err) in enumerate(perViewErrors) if err[0] > per_view_threshold]
warning_failing_indexes = "Failing image indices: " + ", ".join(failing_idxs)

if len(failing_idxs) != 0:
warnings.warn(warning_failing_indexes)

if num_good_images < min_quality_images:
msg = f"Insufficent number of quality images. " \
f"{num_good_images} found, {min_quality_images} required."
raise CalibrationError(msg)

dist8 = dist[:8, :]
img_size_as_array = np.array([np.array([img_size[0]]),
np.array([img_size[1]])])
if rms < rms_thr:
print("calibrateCamera took {} sec".format(time.perf_counter()-start))
print("calibrate_camera took {} sec".format(time.perf_counter()-start))
write_opencv_calfile(calfile, k_matrix, dist8, img_size_as_array)
write_json(os.path.join(imdir, "report.json"), {"RMS_pixels":rms})
else:
print("calibrateCamera failed \n")
print("calibrate_camera failed \n")

return rms, k_matrix, dist, img_size, rvecs, tvecs
return rms, k_matrix, dist, img_size, rvecs, tvecs, num_good_images

#-------------------------------------------------------------------------------
def register(img_a: str,
Expand Down