Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
notes
prototypes
*.png
yolo
camera1photos
camera2photos
trained_model
__pycache__
224 changes: 224 additions & 0 deletions camera_calibrator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import cv2
import numpy as np
import os
import glob

CHESSBOARD_HEIGHT = 6 # TODO: change if needed
CHESSBOARD_WIDTH = 7 # TODO: change if needed
CHESSBOARD_DIM = (CHESSBOARD_WIDTH, CHESSBOARD_HEIGHT)
MIN_IMAGES = 20 # the minimum amount of images needed to calibrate
CRITERIA = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
PHOTO_DIRECTORY = "calibration_photos"

# creates the world points in relation to the chessboard itself
objp = np.zeros(((CHESSBOARD_WIDTH * CHESSBOARD_HEIGHT), 3), np.float32)
objp[:,:2] = np.mgrid[0:CHESSBOARD_WIDTH, 0:CHESSBOARD_HEIGHT].T.reshape(-1,2)

class CameraCalibrator:

imgSize = None

def __init__(self, camDescriptor):
self.descriptor = camDescriptor
self.objpoints = [] # array to store world points
self.imgpoints = [] # array to store 2d points
self.totalNumImages = 0 # total number of images found with the required pattern
# self.imgSize = None # size of image used to calibrate the camera
self.camera = None

# components from calibrating the camera
self.retVal = None
self.cameraMatrix = None
self.distCoeff = None
self.rvecs = None
self.tvecs = None
self.reprojectionError = None

self.photoDirectory = f"camera{self.descriptor}photos"

"""
This function will initialize a directory for the photos that were able to find the required pattern in
"""
def initializePhotoDirectory(self):

os.mkdir(self.photoDirectory)

"""
Checks whether or not a directory exists for holding the photos that were able to find the required pattern in
"""
def checkExistingDirectory(self):

pass

# Initializes the camera with the specified descriptor

def initializeCamera(self):

if not self.camera:
self.camera = cv2.VideoCapture(self.descriptor, cv2.CAP_DSHOW)

if not self.camera.isOpened():
print(f'Unable to open camera {self.descriptor}')
exit()
# raise Exception("")


def findPatternCameras(self, img):

ret, corners = cv2.findChessboardCorners(img, CHESSBOARD_DIM, None)

# ret is True if the algorithm was able to find the corners in the frame, else try again. also if minimum number of images is not reached yet keep capturing pictures
if ret:

self.totalNumImages += 1

print(f"{self.photoDirectory}/photo{self.totalNumImages}.png")

# write image to directory
cv2.imwrite(f"{self.photoDirectory}/photo{self.totalNumImages}.png", img)

print(f'Successfully captured, total: {self.totalNumImages}')

CameraCalibrator.imgSize = img.shape
print(img.shape)

# self.imgSize = img.shape # TODO: will this work? i mean it updates everytime

self.objpoints.append(objp)

# this basically improves the accuracy of the corners
corners2 = cv2.cornerSubPix(img, corners, (11, 11), (-1, -1), CRITERIA)
self.imgpoints.append(corners2)

# just draws the corners found in the frame
cv2.drawChessboardCorners(img, CHESSBOARD_DIM, corners2, ret)
cv2.imshow("found pattern", img)
else:
print("Unsuccessful capture")

def findPatternImages(self, img):

ret, corners = cv2.findChessboardCorners(img, CHESSBOARD_DIM, None)

# ret is True if the algorithm was able to find the corners in the frame, else try again. also if minimum number of images is not reached yet keep capturing pictures
if ret:

self.totalNumImages += 1

print(f'Successfully captured, total: {self.totalNumImages}')

CameraCalibrator.imgSize = img.shape
print(img.shape)

# self.imgSize = img.shape # TODO: will this work? i mean it updates everytime

self.objpoints.append(objp)

# this basically improves the accuracy of the corners
corners2 = cv2.cornerSubPix(img, corners, (11, 11), (-1, -1), CRITERIA)
self.imgpoints.append(corners2)

# just draws the corners found in the frame
cv2.drawChessboardCorners(img, CHESSBOARD_DIM, corners2, ret)
cv2.imshow("found pattern", img)
else:
print("Unsuccessful capture")

def calculateReprojectionError(self):
mean_error = 0

# reproject each set of image points for each image
for i in range(len(self.objpoints)):
imgpoints2, _ = cv2.projectPoints(self.objpoints[i], self.rvecs[i], self.tvecs[i], self.cameraMatrix, self.distCoeff)
error = cv2.norm(imgpoints2, self.imgpoints[i], cv2.NORM_L2)
mean_error += error

self.reprojectionError = (mean_error / len(self.objpoints))
# print(f'Reprojection error for camera {self.descriptor} is {mean_error / len(self.objpoints)}')

def printInternals(self):
print(f'Results from calibration for camera {self.descriptor}')

print(f'ret: {self.retVal}')
print(f'Camera matrix: {self.cameraMatrix}')
print(f'Distortion coefficients: {self.distCoeff}')
print(f'Rotation vectors: {self.rvecs}')
print(f'Translation vectors: {self.tvecs}')

print(f'Reprojection error: {self.reprojectionError}')

def calibrateUsingCameras(self):

while True:

# read in the next frame
ret, frame = self.camera.read()

if not ret:
print("Read unsuccessful")

if self.totalNumImages >= MIN_IMAGES:
break

# frame is inverted so flip TODO: is this right? should it be kept unflipped?
# frame = cv2.flip(frame, 1)
cv2.imshow("frame", frame)

key = cv2.waitKey(1)

# capture image
if key == ord('c'):

# turn the image grayscale for finding the corners
capturedImage = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

self.findPatternCameras(capturedImage)

return capturedImage.shape[::-1]

def calibrateUsingImages(self):

# Note: this assumes the photo directory exists

# walk through the directory and read the image
for img in glob.glob(f"{self.photoDirectory}/*.png"):
frame = cv2.imread(img)

capturedImage = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

self.findPatternImages(capturedImage)
# pass image to self.findPattern(image)

return capturedImage.shape[::-1]


def calibrate(self):

self.initializeCamera()

if not os.path.exists(self.photoDirectory):
self.initializePhotoDirectory()
capturedImageShape = self.calibrateUsingCameras()
else:
capturedImageShape = self.calibrateUsingImages()

# Note: when we get here we should have the minumum amount of required images
print("Minimum requirement reached")

# calibrate the camera
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(self.objpoints, self.imgpoints, capturedImageShape, None, None)

self.retVal = ret
self.cameraMatrix = mtx
self.distCoeff = dist
self.rvecs = rvecs
self.tvecs = tvecs

self.calculateReprojectionError()

self.printInternals()

# TODO: probably shouldn't release?
# self.camera.release()
cv2.destroyAllWindows()

50 changes: 50 additions & 0 deletions camera_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import cv2

# laptopCam = cv2.VideoCapture(0)
camLeft = cv2.VideoCapture(1, cv2.CAP_DSHOW)
camRight = cv2.VideoCapture(2, cv2.CAP_DSHOW)

print("Opened cameras")

while(True):

if not camLeft:
print("Camera is not available")
exit()

ret, frame = camLeft.read()

cv2.imshow("laptop", frame)

key = cv2.waitKey(1)

if key == ord('q'):
break

# while(True):

# if not camLeft:
# print("Camera is not available")
# exit()

# ret, frame = camLeft.read()

# key = cv2.waitKey(0)

# if key == ord('q'):
# break

while(True):

if not camRight:
print("Camera is not available")
exit()

ret, frame = camRight.read()

cv2.imshow("cam", frame)

key = cv2.waitKey(1)

if key == ord('q'):
break
Loading