import glob
import os
import cv2 as cv
import numpy as np
import requests
import re
import errno
from py2vision.image_process.frame_decorator import Frame
from py2vision.image_process.resize import Resize
from py2vision.image_process.rotate import Rotate
[docs]class Camera():
"""An emulation of a real world camera with his relevant parameters, like camera matrix, extrinsics and intrinsics parameters.
Args:
id: A string to identify our camera.
source: When you use webcam you need to put (int) 0, if you want to use videos or streaming, you will need to put his URL or path even you can put in a video file or a image path.
"""
def __init__(self, id, source):
self.id = id
self.source = source
# define type of source
regex = '((http|https)://)(www.)?' + '[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\\.[a-z]' + '{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*)'
regular_exp = re.compile(regex)
if type(self.source) is str:
if re.search(regular_exp, self.source):
self.type_source = 'stream'
else:
self.type_source = 'other'
else:
self.type_source = 'webcam'
[docs] def take_photos(self, num_photos=15, save_dir="images", prefix_name="photo", rotate=0, resize=False, resize_dim=(640,480)):
""" A simple way to take photos in console and save in a folder.
Args:
num_photos: Number of photos to take, 15 by default.
save_dir: Directory name where the photos will be saved.
prefix_name: A prefix for the names of the photos.
rotate: rotate input image around his center, 0 grades by default.
resize: To resize input image.
resize_dim: resize input image dimensions (width, height), its default is (640, 480).
Raises:
OSError: if folder exist.
"""
# initial directory
cwd = os.getcwd()
# make directory
try:
os.mkdir(save_dir)
os.chdir(save_dir)
except OSError as error:
print(error)
if error.errno == errno.EEXIST:
os.chdir(save_dir)
if self.type_source == 'other' or self.type_source == 'webcam':
cap = cv.VideoCapture(self.source)
if not cap.isOpened():
print("Cannot open camera")
exit()
for i in range(num_photos):
print("Please press enter to take a photo")
while True:
if self.type_source == 'stream':
img_resp = requests.get(self.source)
img_arr = np.array(bytearray(img_resp.content), dtype=np.uint8)
frame = cv.imdecode(img_arr, -1)
if rotate != 0:
transf_frame = Frame(frame)
frame = Rotate(transf_frame).apply(90)
frame = cv.flip(frame, 0)
if resize:
transf_frame = Frame(frame)
frame = Resize(transf_frame).apply(resize_dim[0], resize_dim[1])
cv.namedWindow("streaming: {}".format(self.id), cv.WINDOW_NORMAL)
cv.imshow("streaming: {}".format(self.id), frame)
elif self.type_source == 'other' or 'webcam':
_, frame = cap.read()
if rotate != 0:
transf_frame = Frame(frame)
frame = Rotate(transf_frame).apply(90)
frame = cv.flip(frame, -1)
if resize:
transf_frame = Frame(frame)
frame = Resize(transf_frame).apply(resize_dim[0], resize_dim[1])
cv.imshow("webcam: {}".format(self.id), frame)
input_key = cv.waitKey(1)
if input_key == 32:
#space pressed
cv.imwrite('{}_{}.png'.format(prefix_name, i + 1), frame)
print("saved as {}_{}.png".format(prefix_name, i + 1))
print("{} photos left".format(num_photos - i - 1))
break
if input_key == 27:
#esc pressed
print("Escape hit, closing...")
break
if input_key == 27:
#esc pressed
break
cv.destroyAllWindows()
# restore to initial directory
os.chdir(cwd)
[docs] def calibrate(self, images_path='', pattern_type='chessboard', pattern_size=(8,5), export_file=True, show=True, fish_eye=True):
""" Compute camera parameters.
Args:
images_path: folder where is saved calibration pattern photos.
pattern_type: It can be "circles" pattern or "chessboard" pattern (default).
pattern_size: If pattern_type is "chessboard" this the Number of inner corners per a chessboard row and column. But If pattern_type is "circles" this will be the number of circles per row and column.
export_file: To export camera parameters on xml file.
show: if is true it show corners or centers found by calibration algorithm at each iteration.
fish_eye: A boolean if is true it will calibrate with cv2.fisheye.calibrate if no it'll use normal calibration, fish eye is recomended when cameras has an field of view > 160.
Returns:
RMS calibration's error.
Raises:
OSError: If didn't find photos on images_path folder.
ValueError: If pattern_type is different of 'chessboard' or 'circles' or when
fish_eye isn't a boolean.
"""
valid_patterns = ["chessboard", "circles"]
if pattern_type not in valid_patterns:
raise ValueError("pattern_type only can be 'chessboard' or 'circles'")
if type(fish_eye) != bool:
raise ValueError("fish_eye has to be a boolean")
# adjust image path
images_path, _ = os.path.splitext(images_path)
# available formats
formats = ['*.svg', '*.png', '*.jpg', '*.bmp', '*.jpeg', '*.raw']
# get files names
files_grabbed = [glob.glob((images_path + '/' + e) if len(images_path) > 0 else e) for e in formats]
# flatting files list
images = [item for sublist in files_grabbed for item in sublist]
# termination criteria
if fish_eye == False:
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
else:
criteria = (cv.TERM_CRITERIA_EPS+cv.TERM_CRITERIA_MAX_ITER, 30, 0.1)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
if fish_eye == False:
objp = np.zeros((pattern_size[1]*pattern_size[0],3), np.float32)
objp[:,:2] = np.mgrid[0:pattern_size[0],0:pattern_size[1]].T.reshape(-1,2)
else:
objp = np.zeros((pattern_size[1]*pattern_size[0], 1, 3), np.float32)
objp[:,0, :2] = np.mgrid[0:pattern_size[0],0:pattern_size[1]].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
try:
if len(images) == 0:
raise OSError
for fname in images:
img = cv.imread(fname)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Find the chess board corners
if pattern_type == 'chessboard' and fish_eye == False:
ret, corners = cv.findChessboardCorners(gray, pattern_size, None)
elif pattern_type == 'circles' and fish_eye == False:
ret, corners = cv.findCirclesGrid(gray, pattern_size, None)
elif pattern_type == 'chessboard' and fish_eye == True:
ret, corners = cv.findChessboardCorners(gray, pattern_size, flags=cv.CALIB_CB_ADAPTIVE_THRESH+cv.CALIB_CB_FAST_CHECK+cv.CALIB_CB_NORMALIZE_IMAGE)
elif pattern_type == 'circles' and fish_eye == True:
ret, corners = cv.findCirclesGrid(gray, pattern_size, cv.CALIB_CB_ADAPTIVE_THRESH+cv.CALIB_CB_FAST_CHECK+cv.CALIB_CB_NORMALIZE_IMAGE)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
if fish_eye == False:
corners = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
else:
corners = cv.cornerSubPix(gray, corners, (3,3), (-1,-1), criteria)
imgpoints.append(corners)
if show:
# Draw and display the corners
cv.drawChessboardCorners(img, pattern_size, corners, ret)
cv.namedWindow(fname, cv.WINDOW_NORMAL)
cv.imshow(fname, img)
cv.waitKey(3000)
cv.destroyAllWindows()
if fish_eye:
flags = cv.fisheye.CALIB_RECOMPUTE_EXTRINSIC + cv.fisheye.CALIB_FIX_SKEW
k = np.zeros((3, 3))
d = np.zeros((4, 1))
rvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(len(objpoints))]
tvecs = [np.zeros((1, 1, 3), dtype=np.float64) for i in range(len(objpoints))]
if fish_eye == False:
error, self.matrix, self.dist_coeffs, self.rvecs, self.tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
height, width = img.shape[:2]
self.matrix, self.roi = cv.getOptimalNewCameraMatrix(self.matrix, self.dist_coeffs, (width, height), 1, (width, height))
else:
error, self.matrix, self.dist_coeffs, self.rvecs, self.tvecs = cv.fisheye.calibrate(objpoints, imgpoints, gray.shape[::-1], k, d, rvecs, tvecs, flags, (cv.TERM_CRITERIA_EPS+cv.TERM_CRITERIA_MAX_ITER, 30, 1e-6))
print("camera error {}".format(error))
# export an xml with camera parameters
if export_file:
print("Saving parameters!")
cv_file = cv.FileStorage('{}_parameters.xml'.format(self.id), cv.FILE_STORAGE_WRITE)
cv_file.write('camera_mtx', self.matrix)
cv_file.write('distortion_coeff', self.dist_coeffs)
for i, rvec in enumerate(self.rvecs):
cv_file.write('rotation_mtx_col_{}'.format(i), rvec)
num_rvecs = i + 1
cv_file.write('num_rvecs', num_rvecs)
for i, tvec in enumerate(self.tvecs):
cv_file.write('translation_mtx_col_{}'.format(i), tvec)
num_tvecs = i + 1
cv_file.write('num_tvecs', num_tvecs)
if hasattr(self, "roi"):
cv_file.write('ROI', self.roi)
cv_file.release()
print("Saved as {}_parameters.xml".format(self.id))
except OSError:
print("Could not find any image in {}".format(images_path))
return error
[docs] def get_parameters(self, path):
""" Loads camera parameters from a xml file
Args:
path: a path where is saved xml file. (Note: if you don't have any parameters you can use calibrate method first and then pass true in export file argument)
Raises:
OSError: when path is wrong.
"""
# file storage read
cv_file = cv.FileStorage(path, cv.FILE_STORAGE_READ)
if not cv_file.isOpened():
raise OSError("Looks like {} doesn't exist!".format(path))
self.matrix = cv_file.getNode('camera_mtx').mat()
self.dist_coeffs = cv_file.getNode('distortion_coeff').mat()
num_rvecs = int(cv_file.getNode('num_rvecs').real())
self.rvecs = [cv_file.getNode('rotation_mtx_col_{}'.format(i)).mat() for i in range(num_rvecs)]
num_tvecs = int(cv_file.getNode('num_tvecs').real())
self.tvecs = [cv_file.getNode('translation_mtx_col_{}'.format(i)).mat() for i in range(num_tvecs)]
if hasattr(self, "roi"):
self.roi = tuple(cv_file.getNode('ROI').mat().astype(int).reshape(1, -1)[0])
cv_file.release()