Source code for EdiHeadyTrack.camera

# **************************************************************************** #
#                                                                              #
#                                                         :::      ::::::::    #
#    camera.py                                          :+:      :+:    :+:    #
#                                                     +:+ +:+         +:+      #
#    By: taston <thomas.aston@ed.ac.uk>             +#+  +:+       +#+         #
#                                                 +#+#+#+#+#+   +#+            #
#    Created: 2023/04/25 15:41:23 by taston            #+#    #+#              #
#    Updated: 2023/09/01 13:41:21 by taston           ###   ########.fr        #
#                                                                              #
# **************************************************************************** #

import numpy as np
import cv2 
from datetime import datetime
from .video import Video

[docs] class Camera: """ A class used to represent a Camera. ... Attributes ---------- focal_length : float float representing the focal length of the Camera in mm internal_matrix : ndarray array representing the Camera's intrinsic parameters distortion_matrix : ndarray array representing the Camera's lens distortion parameters calibrator : Calibrator Calibrator object used for camera calibration calibrated : bool bool for quick checking if camera has been calibrated video : Video Video object where the footage has been shot using this Camera Methods ------- calibrate(checkerboard=(9,6), video=Video()): Performs calibration on the camera """ def __init__(self): """ Parameters ---------- ... """ width = 1280 height = 720 self.focal_length = height * 1.28 # self.focal_length = 5000 self.internal_matrix = np.array([[self.focal_length, 0, width/2], [0, self.focal_length, height/2], [0, 0, 1]]) self.distortion_matrix = np.zeros((4, 1), dtype=np.float64) self.calibrated = False
[docs] def calibrate(self, checkerboard=(9,6), video=Video(), show=True): """Creates a calibrator object and calibrates the Camera. If arguments checkerboard and video aren't passed in, the default checkerboard pattern and an empty video are used. Parameters ---------- checkerboard : tuple, optional Checkerboard pattern used in camera calibration (default is 9x6) video : Video, optional Video used to calibrate camera """ self.video = video self.calibrator = Calibrator(checkerboard, self.video, show) self.calibrated = True self.internal_matrix, self.distortion_matrix = self.calibrator.matrix, self.calibrator.distortion return self
[docs] class Checkerboard: """ A class used to represent a calibration Checkerboard ... Attributes ---------- dimensions : tuple tuple of checkerboard pattern dimensions min_points : int integer threshold of minimum detected points for checkerboard to be considered found objectp3d : ndarray array of checkerboard points in three dimensions threedpoints : list list of checkerboard points in three dimensions for each frame where a checkerboard is found twodpoints : list list of detected checkerboard points in two dimensions for each frame Methods ------- get_corners(gray_frame) Finds checkerboard corners in a given grayscale frame """ def __init__(self, dimensions = (9,6)): """ Parameters ---------- dimensions : tuple, optional Checkerboard pattern used in camera calibration (default is 9x6) """ print('Checkerboard created') self.dimensions = dimensions self.min_points = 50 self.twodpoints = [] self.threedpoints = [] self.objectp3d = np.zeros((1, self.dimensions[0] * self.dimensions[1], 3), np.float32) self.objectp3d[0, :, :2] = np.mgrid[0:self.dimensions[0], 0:self.dimensions[1]].T.reshape(-1, 2)
[docs] def get_corners(self, gray_frame): """ Looks for checkerboard corners in a given grayscale video frame. Parameters ---------- gray_frame : ndarray ndarray representing grayscale frame from video Returns ------- ret : bool bool representing if corner search was successful corners : ndarray ndarray containing coordinates of corners """ ret, corners = cv2.findChessboardCorners( gray_frame, self.dimensions, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE) return ret, corners
[docs] class Calibrator: """ A class used to represent a camera Calibrator ... Attributes ---------- checkerboard : Checkerboard criteria : tuple tuple of criteria for successful camera calibration distortion : ndarray ndarray of distortion parameters frame : ndarray ndarray representing video frame gray_frame : ndarray ndarray representing grayscale video frame matrix : ndarray ndarray representing camera intrinsic matrix r_vecs : ndarray ndarray of rotational vectors t_vecs : ndarray ndarray of translation vectors Methods ------- calibrate() Perform camera calibration process draw_corners(corners) Draw checkerboard corners on video frame save_outputs() Save camera parameters to csv files """ def __init__(self, checkerboard, video=Video(), show=True): """ Parameters ---------- checkerboard : tuple tuple representing Checkerboard pattern video : Video, optional Video used for camera calibration. If no video specified an empty video will be attempted. """ timestamp = datetime.now().strftime("%H:%M:%S") self.show = show self.video = video print('-'*120) print('{:<100} {:>19}'.format(f'Creating Calibrator object for video {self.video.filename}:', timestamp)) print('-'*120) print(self.video) self.checkerboard = Checkerboard(checkerboard) print(f'Checkerboard dimensions: {self.checkerboard.dimensions[0]} x {self.checkerboard.dimensions[1]}') self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) self.calibrate() # self.save_outputs() timestamp = datetime.now().strftime("%H:%M:%S") print('-'*120) print('{:<100} {:>19}'.format(f'Calibrator object complete!', timestamp)) print('-'*120)
[docs] def calibrate(self): """ Performs the camera calibration procedure outlined here: https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html """ timestamp = datetime.now().strftime("%H:%M:%S") self.video.create_writer() print('Displaying video...') while True: ret, self.frame = self.video.cap.read() frame_number = int(self.video.cap.get(cv2.CAP_PROP_POS_FRAMES)) self.gray_frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY) ret, corners = self.checkerboard.get_corners(self.gray_frame) if ret: complete, image = self.draw_corners(corners) if complete: break if self.show == True: cv2.imshow('Calibrating...', self.frame) self.video.writer.write(self.frame) k = cv2.waitKey(1) if k == 27: self.video.cap.release() self.video.writer.release() cv2.destroyAllWindows() break h, w = image.shape[:2] # Perform camera calibration by given threedpoints and twodpoints ret, self.matrix, self.distortion, self.r_vecs, self.t_vecs = cv2.calibrateCamera(self.checkerboard.threedpoints, self.checkerboard.twodpoints, self.gray_frame.shape[::-1], None, None) print(f'Number of frames used for calibration: {frame_number}') return self
[docs] def draw_corners(self, corners): ''' Draws corners of checkerboard onto frame to verify calibration is working Parameters ---------- corners : ndarray ndarray of the corners found for a given frame Returns ------- complete : bool bool representing whether search for corners is complete frame : ndarray new video frame with corners drawn ''' complete = False self.checkerboard.threedpoints.append(self.checkerboard.objectp3d) # Refining pixel coordinates for given 2d points. corners2 = cv2.cornerSubPix( self.gray_frame, corners, self.checkerboard.dimensions, (-1, -1), self.criteria) self.checkerboard.twodpoints.append(corners2) # When we have minimum number of data points, stop: if len(self.checkerboard.twodpoints) > self.checkerboard.min_points: self.video.cap.release() self.video.writer.release() cv2.destroyAllWindows() complete=True # Draw and display the corners: frame = cv2.drawChessboardCorners(self.frame, self.checkerboard.dimensions, corners2, True) return complete, frame
[docs] def save_outputs(self): """ Saves matrices to csv """ timestamp = datetime.now().strftime("%H:%M:%S") print('Saving outputs...') from numpy import savetxt savetxt('camera_matrix.csv', self.matrix, delimiter=',') savetxt('camera_distortion.csv', self.distortion, delimiter=',') return