- refactored calibration into separate class
- load/store camera calibration for physical camera using device name - load/store camera calibration for synthetic images using project name
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
import numpy as np
|
||||||
|
import cv2 as cv
|
||||||
|
|
||||||
|
|
||||||
|
class Calibration:
|
||||||
|
def __init__(self):
|
||||||
|
self.mtx = None
|
||||||
|
self.dist = None
|
||||||
|
self.obj_points_list = []
|
||||||
|
self.img_points_list = []
|
||||||
|
|
||||||
|
def calibration_clear(self):
|
||||||
|
self.mtx = None
|
||||||
|
self.dist = None
|
||||||
|
self.obj_points_list = []
|
||||||
|
self.img_points_list = []
|
||||||
|
|
||||||
|
def is_calibrated(self):
|
||||||
|
return self.mtx is not None and self.dist is not None
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self.mtx, self.dist
|
||||||
|
|
||||||
|
def set(self, mtx, dist):
|
||||||
|
self.mtx = mtx
|
||||||
|
self.dist = dist
|
||||||
|
|
||||||
|
def load(self, filename: str):
|
||||||
|
try:
|
||||||
|
with np.load(filename) as X:
|
||||||
|
mtx, dist = [X[i] for i in ('mtx', 'dist')]
|
||||||
|
self.set(mtx, dist)
|
||||||
|
print(f"Loaded {filename} successfully!")
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save(self, filename: str):
|
||||||
|
mtx, dist = self.get()
|
||||||
|
np.savez(f"{filename}", mtx=mtx, dist=dist)
|
||||||
|
calib = {'mtx': mtx.tolist(), 'dist': dist.tolist()}
|
||||||
|
print(f"Saved {filename} successfully!")
|
||||||
|
|
||||||
|
def process(self, image: np.ndarray, r_vecs=None, t_vecs=None, flags=0):
|
||||||
|
success = False
|
||||||
|
if len(self.obj_points_list) > 0 and len(self.img_points_list) > 0:
|
||||||
|
ret, mtx, dist, r_vecs, t_vecs = cv.calibrateCamera(self.obj_points_list, self.img_points_list, image.shape, self.mtx, self.dist,
|
||||||
|
rvecs=r_vecs, tvecs=t_vecs, flags=flags)
|
||||||
|
|
||||||
|
if ret < 3:
|
||||||
|
success = True
|
||||||
|
self.mtx = mtx
|
||||||
|
self.dist = dist
|
||||||
|
|
||||||
|
return success
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import abc
|
import abc
|
||||||
import cv2 as cv
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from calibration import Calibration
|
||||||
|
|
||||||
RESULTS_FOLDER = "results"
|
RESULTS_FOLDER = "results"
|
||||||
|
|
||||||
@@ -9,11 +9,7 @@ class BoardDetector(abc.ABC):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def __init__(self, name: str, board_id: str, n_syms: int):
|
def __init__(self, name: str, board_id: str, n_syms: int):
|
||||||
self.mtx = None
|
|
||||||
self.dist = None
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.obj_points_list = []
|
|
||||||
self.img_points_list = []
|
|
||||||
self.board_id = board_id
|
self.board_id = board_id
|
||||||
|
|
||||||
# Create IDs
|
# Create IDs
|
||||||
@@ -46,42 +42,14 @@ class BoardDetector(abc.ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def process(self, image: np.ndarray, draw_marker_box=True, draw_marker_id=False, draw_marker_axis=False, pose_center: bool = True) -> (bool, np.ndarray, np.ndarray):
|
def process(self, image: np.ndarray, calibration: Calibration, draw_marker_box=True, draw_marker_id=False, draw_marker_axis=False, pose_center: bool = True) -> (bool, np.ndarray, np.ndarray):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def is_calibrated(self):
|
def collect_points(self, image: np.ndarray, calibration: Calibration) -> bool:
|
||||||
return self.mtx is not None and self.dist is not None
|
|
||||||
|
|
||||||
def collect_points(self, image: np.ndarray) -> bool:
|
|
||||||
success, obj_points, img_points = self.match_points(image)
|
success, obj_points, img_points = self.match_points(image)
|
||||||
if success:
|
if success:
|
||||||
self.obj_points_list.append(obj_points)
|
calibration.obj_points_list.append(obj_points)
|
||||||
self.img_points_list.append(img_points)
|
calibration.img_points_list.append(img_points)
|
||||||
|
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def calibration_clear(self):
|
|
||||||
self.mtx = None
|
|
||||||
self.dist = None
|
|
||||||
self.obj_points_list = []
|
|
||||||
self.img_points_list = []
|
|
||||||
|
|
||||||
def calibration_set(self, mtx, dist):
|
|
||||||
self.mtx = mtx
|
|
||||||
self.dist = dist
|
|
||||||
|
|
||||||
def calibration_get(self):
|
|
||||||
return self.mtx, self.dist
|
|
||||||
|
|
||||||
def calibrate_camera(self, image: np.ndarray, r_vecs=None, t_vecs=None, flags=0):
|
|
||||||
success = False
|
|
||||||
if len(self.obj_points_list) > 0 and len(self.img_points_list) > 0:
|
|
||||||
ret, mtx, dist, r_vecs, t_vecs = cv.calibrateCamera(self.obj_points_list, self.img_points_list, image.shape, self.mtx, self.dist,
|
|
||||||
rvecs=r_vecs, tvecs=t_vecs, flags=flags)
|
|
||||||
|
|
||||||
if ret < 3:
|
|
||||||
success = True
|
|
||||||
self.mtx = mtx
|
|
||||||
self.dist = dist
|
|
||||||
|
|
||||||
return success
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import cv2 as cv
|
import cv2 as cv
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from ar_tag_pose.detector_board import BoardDetector
|
from ar_tag_pose.detector_board import BoardDetector
|
||||||
|
from ar_tag_pose.calibration import Calibration
|
||||||
from ar_tag_pose.aruco_types import ARUCO_DICT
|
from ar_tag_pose.aruco_types import ARUCO_DICT
|
||||||
|
|
||||||
def factory(dict_type: int, board_size: cv.typing.Size, marker_length: float=100.0, marker_sep=10, ids: np.array=None):
|
def factory(dict_type: int, board_size: cv.typing.Size, marker_length: float=100.0, marker_sep=10, ids: np.array=None):
|
||||||
@@ -48,7 +50,7 @@ class BoardDetectorAruco(BoardDetector):
|
|||||||
|
|
||||||
return success, obj_points, img_points
|
return success, obj_points, img_points
|
||||||
|
|
||||||
def process(self, image: np.ndarray, draw_marker_box=True, draw_marker_id=False, draw_marker_axis=False, pose_center: bool = True):
|
def process(self, image: np.ndarray, calibration: Calibration, draw_marker_box=True, draw_marker_id=False, draw_marker_axis=False, pose_center: bool = True):
|
||||||
pose = False
|
pose = False
|
||||||
r_vec = None
|
r_vec = None
|
||||||
t_vec = None
|
t_vec = None
|
||||||
@@ -66,15 +68,15 @@ class BoardDetectorAruco(BoardDetector):
|
|||||||
if draw_marker_id:
|
if draw_marker_id:
|
||||||
cv.aruco.drawDetectedMarkers(image, corners, ids)
|
cv.aruco.drawDetectedMarkers(image, corners, ids)
|
||||||
|
|
||||||
if not self.is_calibrated():
|
if not calibration.is_calibrated():
|
||||||
return pose, None, None
|
return pose, None, None
|
||||||
|
|
||||||
# Draw marker axis
|
# Draw marker axis
|
||||||
if draw_marker_axis:
|
if draw_marker_axis:
|
||||||
_r_vecs, _t_vecs, _ = cv.aruco.estimatePoseSingleMarkers(corners, markerLength=0.1, cameraMatrix=self.mtx, distCoeffs=self.dist)
|
_r_vecs, _t_vecs, _ = cv.aruco.estimatePoseSingleMarkers(corners, markerLength=0.1, cameraMatrix=calibration.mtx, distCoeffs=calibration.dist)
|
||||||
if _r_vecs is not None:
|
if _r_vecs is not None:
|
||||||
for i in range(len(_r_vecs)):
|
for i in range(len(_r_vecs)):
|
||||||
cv.drawFrameAxes(image, self.mtx, self.dist, _r_vecs[i], _t_vecs[i], 0.05)
|
cv.drawFrameAxes(image, calibration.mtx, calibration.dist, _r_vecs[i], _t_vecs[i], 0.05)
|
||||||
|
|
||||||
# Estimate pose of each marker and return the values r_vec and t_vec---(different from those of camera coefficients)
|
# Estimate pose of each marker and return the values r_vec and t_vec---(different from those of camera coefficients)
|
||||||
obj_points, img_points = self.board.matchImagePoints(corners, ids)
|
obj_points, img_points = self.board.matchImagePoints(corners, ids)
|
||||||
@@ -82,10 +84,10 @@ class BoardDetectorAruco(BoardDetector):
|
|||||||
offset_w = self.board.getGridSize()[0] * (self.board.getMarkerLength() + self.board.getMarkerSeparation())
|
offset_w = self.board.getGridSize()[0] * (self.board.getMarkerLength() + self.board.getMarkerSeparation())
|
||||||
offset_h = self.board.getGridSize()[1] * (self.board.getMarkerLength() + self.board.getMarkerSeparation())
|
offset_h = self.board.getGridSize()[1] * (self.board.getMarkerLength() + self.board.getMarkerSeparation())
|
||||||
obj_points -= [offset_w/2, offset_h/2, 0]
|
obj_points -= [offset_w/2, offset_h/2, 0]
|
||||||
pose, r_vec, t_vec = cv.solvePnP(obj_points, img_points, self.mtx, self.dist, rvec=None, tvec=None)
|
pose, r_vec, t_vec = cv.solvePnP(obj_points, img_points, calibration.mtx, calibration.dist, rvec=None, tvec=None)
|
||||||
if pose:
|
if pose:
|
||||||
# Draw Axis
|
# Draw Axis
|
||||||
cv.drawFrameAxes(image, self.mtx, self.dist, r_vec, t_vec, self.board.getMarkerLength()*1.5)
|
cv.drawFrameAxes(image, calibration.mtx, calibration.dist, r_vec, t_vec, self.board.getMarkerLength()*1.5)
|
||||||
|
|
||||||
return pose, r_vec, t_vec
|
return pose, r_vec, t_vec
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import cv2 as cv
|
import cv2 as cv
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from ar_tag_pose.detector_board import BoardDetector
|
from ar_tag_pose.detector_board import BoardDetector
|
||||||
|
from ar_tag_pose.calibration import Calibration
|
||||||
from ar_tag_pose.aruco_types import ARUCO_DICT
|
from ar_tag_pose.aruco_types import ARUCO_DICT
|
||||||
|
|
||||||
def factory(dict_type: int, board_size: cv.typing.Size, square_length: float = 5.0, marker_length: float = 3.0, ids: np.array=None):
|
def factory(dict_type: int, board_size: cv.typing.Size, square_length: float = 5.0, marker_length: float = 3.0, ids: np.array=None):
|
||||||
@@ -49,7 +51,7 @@ class BoardDetectorCharuco(BoardDetector):
|
|||||||
|
|
||||||
return success, obj_points, img_points
|
return success, obj_points, img_points
|
||||||
|
|
||||||
def process(self, image: np.ndarray, draw_marker_box=True, draw_marker_id=False, draw_marker_axis=False, pose_center: bool = True):
|
def process(self, image: np.ndarray, calibration: Calibration, draw_marker_box=True, draw_marker_id=False, draw_marker_axis=False, pose_center: bool = True):
|
||||||
pose = False
|
pose = False
|
||||||
r_vec = None
|
r_vec = None
|
||||||
t_vec = None
|
t_vec = None
|
||||||
@@ -68,11 +70,11 @@ class BoardDetectorCharuco(BoardDetector):
|
|||||||
cv.aruco.drawDetectedCornersCharuco(image, charuco_corners, charuco_ids)
|
cv.aruco.drawDetectedCornersCharuco(image, charuco_corners, charuco_ids)
|
||||||
|
|
||||||
# Draw marker axis
|
# Draw marker axis
|
||||||
if draw_marker_axis and self.is_calibrated():
|
if draw_marker_axis and calibration.is_calibrated():
|
||||||
_r_vecs, _t_vecs, _ = cv.aruco.estimatePoseSingleMarkers(marker_corners, markerLength=0.1,
|
_r_vecs, _t_vecs, _ = cv.aruco.estimatePoseSingleMarkers(marker_corners, markerLength=0.1,
|
||||||
cameraMatrix=self.mtx, distCoeffs=self.dist)
|
cameraMatrix=calibration.mtx, distCoeffs=calibration.dist)
|
||||||
for i in range(len(_r_vecs)):
|
for i in range(len(_r_vecs)):
|
||||||
cv.drawFrameAxes(image, self.mtx, self.dist, _r_vecs[i], _t_vecs[i], 0.05)
|
cv.drawFrameAxes(image, calibration.mtx, calibration.dist, _r_vecs[i], _t_vecs[i], 0.05)
|
||||||
|
|
||||||
if len(charuco_ids) >= len(self.board.getIds()):
|
if len(charuco_ids) >= len(self.board.getIds()):
|
||||||
obj_points, img_points = self.board.matchImagePoints(charuco_corners, charuco_ids)
|
obj_points, img_points = self.board.matchImagePoints(charuco_corners, charuco_ids)
|
||||||
@@ -80,12 +82,12 @@ class BoardDetectorCharuco(BoardDetector):
|
|||||||
offset_w = self.board.getChessboardSize()[0] * self.board.getSquareLength()
|
offset_w = self.board.getChessboardSize()[0] * self.board.getSquareLength()
|
||||||
offset_h = self.board.getChessboardSize()[1] * self.board.getSquareLength()
|
offset_h = self.board.getChessboardSize()[1] * self.board.getSquareLength()
|
||||||
obj_points -= [offset_w/2, offset_h/2, 0]
|
obj_points -= [offset_w/2, offset_h/2, 0]
|
||||||
if self.is_calibrated():
|
if calibration.is_calibrated():
|
||||||
pose, r_vec, t_vec = cv.solvePnP(obj_points, img_points, self.mtx, self.dist, useExtrinsicGuess=False,
|
pose, r_vec, t_vec = cv.solvePnP(obj_points, img_points, calibration.mtx, calibration.dist, useExtrinsicGuess=False,
|
||||||
rvec=None, tvec=None, flags=cv.SOLVEPNP_ITERATIVE)
|
rvec=None, tvec=None, flags=cv.SOLVEPNP_ITERATIVE)
|
||||||
if pose:
|
if pose:
|
||||||
# Draw
|
# Draw
|
||||||
cv.drawFrameAxes(image, self.mtx, self.dist, r_vec, t_vec, 10)
|
cv.drawFrameAxes(image, calibration.mtx, calibration.dist, r_vec, t_vec, 10)
|
||||||
|
|
||||||
return pose, r_vec, t_vec
|
return pose, r_vec, t_vec
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import mimetypes
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ar_tag_pose.utils import to_tf, to_euler, draw_text
|
from ar_tag_pose.utils import to_tf, to_euler, draw_text
|
||||||
|
from calibration import Calibration
|
||||||
from detector_board import BoardDetector
|
from detector_board import BoardDetector
|
||||||
from detector_board_aruco import BoardDetectorAruco
|
from detector_board_aruco import BoardDetectorAruco
|
||||||
from detector_board_charuco import BoardDetectorCharuco
|
from detector_board_charuco import BoardDetectorCharuco
|
||||||
@@ -15,7 +16,8 @@ from cv2_enumerate_cameras import enumerate_cameras
|
|||||||
RESULTS_FOLDER = "results"
|
RESULTS_FOLDER = "results"
|
||||||
|
|
||||||
class Detector(abc.ABC):
|
class Detector(abc.ABC):
|
||||||
def __init__(self, params: dict, source: str, detector: list[BoardDetector], out_file: str|None = None, rotation_order: str=''):
|
def __init__(self, name: str, params: dict, source: str, detector: list[BoardDetector], out_file: str|None = None, rotation_order: str=''):
|
||||||
|
self.name = name
|
||||||
self.params = params
|
self.params = params
|
||||||
self.detector = detector
|
self.detector = detector
|
||||||
self.out_file = out_file
|
self.out_file = out_file
|
||||||
@@ -51,7 +53,10 @@ class Detector(abc.ABC):
|
|||||||
elif self.src_images is not None:
|
elif self.src_images is not None:
|
||||||
# Open image file sequence
|
# Open image file sequence
|
||||||
self.cap = cv.VideoCapture(self.src_images, cv.CAP_IMAGES)
|
self.cap = cv.VideoCapture(self.src_images, cv.CAP_IMAGES)
|
||||||
|
self.camera_name = self.name
|
||||||
|
|
||||||
|
self.calibration = Calibration()
|
||||||
|
self.calibration.load(f"{RESULTS_FOLDER}/{self.camera_name}_cal.npz")
|
||||||
|
|
||||||
def params_save(self):
|
def params_save(self):
|
||||||
with open(f"{RESULTS_FOLDER}/{self.params["name"]}.json", 'w') as fp:
|
with open(f"{RESULTS_FOLDER}/{self.params["name"]}.json", 'w') as fp:
|
||||||
@@ -98,34 +103,6 @@ class Detector(abc.ABC):
|
|||||||
name = det.get_board_name()
|
name = det.get_board_name()
|
||||||
cv.imwrite(f"{RESULTS_FOLDER}/{name}.png", image)
|
cv.imwrite(f"{RESULTS_FOLDER}/{name}.png", image)
|
||||||
|
|
||||||
def calibrate_save(self, det: BoardDetector):
|
|
||||||
filename = f"{RESULTS_FOLDER}/{det.get_board_name()}_cal.npz"
|
|
||||||
mtx, dist = det.calibration_get()
|
|
||||||
np.savez(f"{filename}", mtx=mtx, dist=dist)
|
|
||||||
calib = {'mtx': mtx.tolist(), 'dist': dist.tolist()}
|
|
||||||
self.params['calib'][self.camera_name][str(det)] = calib
|
|
||||||
self.params_save()
|
|
||||||
print(f"Saved {filename} successfully!")
|
|
||||||
|
|
||||||
def calibrate_load(self):
|
|
||||||
calib = {}
|
|
||||||
for det in self.detector:
|
|
||||||
filename = f"{RESULTS_FOLDER}/{det.get_board_name()}_cal.npz"
|
|
||||||
try:
|
|
||||||
with np.load(filename) as X:
|
|
||||||
mtx, dist = [X[i] for i in ('mtx', 'dist')]
|
|
||||||
calib[str(det)] = {'mtx': mtx.tolist(), 'dist': dist.tolist()}
|
|
||||||
det.calibration_set(mtx, dist)
|
|
||||||
self.params['calib'] = calib
|
|
||||||
self.params_save()
|
|
||||||
print(f"Loaded {filename} successfully!")
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.params['calib'] = {self.camera_name: calib}
|
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
cap = self.cap
|
cap = self.cap
|
||||||
if cap is None or not cap.isOpened():
|
if cap is None or not cap.isOpened():
|
||||||
@@ -162,25 +139,23 @@ class Detector(abc.ABC):
|
|||||||
elif k == ord('p'):
|
elif k == ord('p'):
|
||||||
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||||
for det in self.detector:
|
for det in self.detector:
|
||||||
success = det.collect_points(gray)
|
success = det.collect_points(gray, self.calibration)
|
||||||
if success:
|
if success:
|
||||||
print(f"{det}: Points matching success")
|
print(f"{det}: Points matching success")
|
||||||
elif k == ord('c'):
|
elif k == ord('c'):
|
||||||
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||||
for det in self.detector:
|
if self.calibration.process(gray):
|
||||||
if det.calibrate_camera(gray):
|
print(f"{self.name}: Camera calibration success")
|
||||||
print(f"{det}: Camera calibration success")
|
self.calibration.save(f"{RESULTS_FOLDER}/{self.camera_name}_cal.npz")
|
||||||
self.calibrate_save(det)
|
|
||||||
elif k == ord('x'):
|
elif k == ord('x'):
|
||||||
for det in self.detector:
|
self.calibration.calibration_clear()
|
||||||
det.calibration_clear()
|
|
||||||
elif k == ord('f'):
|
elif k == ord('f'):
|
||||||
pose_center = not pose_center
|
pose_center = not pose_center
|
||||||
|
|
||||||
k = 800 / img.shape[1]
|
k = 800 / img.shape[1]
|
||||||
y_pos = 30
|
y_pos = 30
|
||||||
for det in self.detector:
|
for det in self.detector:
|
||||||
success, r_vec, t_vec = det.process(img, True, False, False, pose_center)
|
success, r_vec, t_vec = det.process(img, self.calibration, True, False, False, pose_center)
|
||||||
if success:
|
if success:
|
||||||
if len(self.rotation_order) == 3:
|
if len(self.rotation_order) == 3:
|
||||||
rot = to_euler(to_tf(r_vec, t_vec), rotation_order=self.rotation_order)
|
rot = to_euler(to_tf(r_vec, t_vec), rotation_order=self.rotation_order)
|
||||||
@@ -287,9 +262,8 @@ def main():
|
|||||||
out_file = f"{var_args["name"]}.mp4"
|
out_file = f"{var_args["name"]}.mp4"
|
||||||
|
|
||||||
# Create Detector app
|
# Create Detector app
|
||||||
detector = Detector(params, var_args["source"], board_detector, out_file=out_file, rotation_order=var_args["rotation_order"])
|
detector = Detector(var_args["name"], params, var_args["source"], board_detector, out_file=out_file, rotation_order=var_args["rotation_order"])
|
||||||
detector.board_image_generate(margin_length=var_args["margin_length"])
|
detector.board_image_generate(margin_length=var_args["margin_length"])
|
||||||
detector.calibrate_load()
|
|
||||||
detector.process()
|
detector.process()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user