import math
import json
import cv2
import numpy as np
import os
import sys
import warnings
sys.path.append(os.path.dirname(__file__))
from dll import MoilCV
[docs]class Moildev(object):
def __init__(self, file_camera_parameter=None, camera_type=None, **kwarg):
"""
Before the ensuing functions can function properly,
the camera parameter must be configured at the start of the program.
The camera parameter is the outcome of the MOIL laboratory's calibration camera.
Args:
file_camera_parameter: *.json file
camera_type : the name of the camera type used (use if yore pass the parameter using *.json file)
cameraName : the name of the camera used
cameraFov : camera field of view (FOV)
sensor_width : size of sensor width
sensor_height : size of sensor height
Icx : center image in x-axis
Icy : center image in y-axis
ratio : the value of the ratio image
imageWidth : the size of width image
imageHeight : the size of height image
calibrationRatio : the value of calibration ratio
parameter0 .. parameter5 : intrinsic fisheye camera parameter get from calibration
.. code-block :: markdown
for more detail, please reference `https://github.com/perseverance-tech-tw/moildev`
"""
super(Moildev, self).__init__()
self.__alpha_to_rho_table = []
self.__rho_to_alpha_table = []
self.__camera_name = None
self.__camera_fov = None
self.__sensor_width = None
self.__sensor_height = None
self.__icx = None
self.__icy = None
self.__ratio = None
self.__image_width = None
self.__image_height = None
self.__calibration_ratio = None
self.__parameter_0 = None
self.__parameter_1 = None
self.__parameter_2 = None
self.__parameter_3 = None
self.__parameter_4 = None
self.__parameter_5 = None
if file_camera_parameter is None:
if kwarg == {}:
print("Pass the argument with camera parameter file with json extension (*.json),\n"
"see detail documentation here `https://github.com/perseverance-tech-tw/moildev`")
else:
for key, value in kwarg.items():
if key == "camera_name":
self.__camera_name = value
elif key == "camera_fov":
self.__camera_fov = value
elif key == "sensor_width":
self.__sensor_width = value
elif key == "sensor_height":
self.__sensor_height = value
elif key == "icx":
self.__icx = value
elif key == "icy":
self.__icy = value
elif key == "ratio":
self.__ratio = value
elif key == "image_width":
self.__image_width = value
elif key == "image_height":
self.__image_height = value
elif key == "calibration_ratio":
self.__calibration_ratio = value
elif key == "parameter_0":
self.__parameter_0 = value
elif key == "parameter_1":
self.__parameter_1 = value
elif key == "parameter_2":
self.__parameter_2 = value
elif key == "parameter_3":
self.__parameter_3 = value
elif key == "parameter_4":
self.__parameter_4 = value
elif key == "parameter_5":
self.__parameter_5 = value
if self.__camera_name is None or \
self.__camera_fov is None or \
self.__sensor_width is None or \
self.__sensor_height is None or \
self.__icx is None or \
self.__icy is None or \
self.__ratio is None or \
self.__image_width is None or \
self.__image_height is None or \
self.__calibration_ratio is None or \
self.__parameter_0 is None or \
self.__parameter_1 is None or \
self.__parameter_2 is None or \
self.__parameter_3 is None or \
self.__parameter_4 is None or \
self.__parameter_5 is None:
warnings.warn("You're not passing the complete parameter. please refer to the documentation here "
"`https://github.com/perseverance-tech-tw/moildev` ")
else:
self.__init_alpha_rho_table()
self.__import_moildev()
else:
self.__setCamera_parameter(file_camera_parameter, camera_type)
if self.__camera_name is not None:
self.__init_alpha_rho_table()
self.__import_moildev()
def __setCamera_parameter(self, parameter, cameraType):
"""
Set up the configuration of the camera parameter
Args:
parameter: the *.json file
cameraType: type of the camera
Returns:
None
"""
with open(parameter) as f:
data = json.load(f)
if cameraType in data.keys():
self.__camera_name = data[cameraType]["cameraName"]
self.__camera_fov = data[cameraType]["cameraFov"] if "cameraFov" in data[cameraType].keys() else 220
self.__sensor_width = data[cameraType]['cameraSensorWidth']
self.__sensor_height = data[cameraType]['cameraSensorHeight']
self.__icx = data[cameraType]['iCx']
self.__icy = data[cameraType]['iCy']
self.__ratio = data[cameraType]['ratio']
self.__image_width = data[cameraType]['imageWidth']
self.__image_height = data[cameraType]['imageHeight']
self.__calibration_ratio = data[cameraType]['calibrationRatio']
self.__parameter_0 = data[cameraType]['parameter0']
self.__parameter_1 = data[cameraType]['parameter1']
self.__parameter_2 = data[cameraType]['parameter2']
self.__parameter_3 = data[cameraType]['parameter3']
self.__parameter_4 = data[cameraType]['parameter4']
self.__parameter_5 = data[cameraType]['parameter5']
else:
if "cameraName" in data.keys() and cameraType is None:
with open(parameter) as file:
data = json.load(file)
self.__camera_name = data["cameraName"]
self.__camera_fov = data["cameraFov"] if "cameraFov" in data.keys() else 220
self.__sensor_width = data['cameraSensorWidth']
self.__sensor_height = data['cameraSensorHeight']
self.__icx = data['iCx']
self.__icy = data['iCy']
self.__ratio = data['ratio']
self.__image_width = data['imageWidth']
self.__image_height = data['imageHeight']
self.__calibration_ratio = data['calibrationRatio']
self.__parameter_0 = data['parameter0']
self.__parameter_1 = data['parameter1']
self.__parameter_2 = data['parameter2']
self.__parameter_3 = data['parameter3']
self.__parameter_4 = data['parameter4']
self.__parameter_5 = data['parameter5']
else:
warnings.warn("Please Check your parameter file. \n"
"If inside the file has multiple camera parameter, typing the name of camera!!\n"
"see detail documentation here `https://github.com/perseverance-tech-tw/moildev`\n"
"or you can email to 'perseverance.tech.tw@gmail.com'")
def __init_alpha_rho_table(self):
"""
Create list for initial corresponding alpha to rho(height image).
Returns:
Initial alpha and rho table.
"""
for i in range(1800):
alpha = i / 10 * math.pi / 180
self.__alpha_to_rho_table.append(
(self.__parameter_0 * alpha * alpha * alpha * alpha * alpha * alpha +
self.__parameter_1 * alpha * alpha * alpha * alpha * alpha +
self.__parameter_2 * alpha * alpha * alpha * alpha +
self.__parameter_3 * alpha * alpha * alpha +
self.__parameter_4 * alpha * alpha +
self.__parameter_5 * alpha) * self.__calibration_ratio)
i += 1
i = 0
index = 0
while i < 1800:
while index < self.__alpha_to_rho_table[i]:
self.__rho_to_alpha_table.append(i)
index += 1
i += 1
while index < 3600:
self.__rho_to_alpha_table.append(i)
index += 1
def __import_moildev(self):
"""
Create moildev instance from Moildev SDK share object library.
Returns:
Moildev object (private attribute for this class)
"""
self.__create_maps()
self.__moildev = MoilCV.MoilCV(
self.__camera_name,
self.__sensor_width,
self.__sensor_height,
self.__icx,
self.__icy,
self.__ratio,
self.__image_width,
self.__image_height,
self.__calibration_ratio,
self.__parameter_0,
self.__parameter_1,
self.__parameter_2,
self.__parameter_3,
self.__parameter_4,
self.__parameter_5)
def __create_maps(self):
"""
Create Maps image from zeroes matrix.
Returns:
Zeroes matrix same size with original image.
"""
self.__map_x = np.zeros((self.__image_height, self.__image_width), dtype=np.float32)
self.__map_y = np.zeros((self.__image_height, self.__image_width), dtype=np.float32)
size = self.__image_height, self.__image_width, 3
self.__res = np.zeros(size, dtype=np.uint8)
[docs] @classmethod
def version(cls):
"""
Showing the information of the version moildev library.
Returns:
Moildev version information
"""
MoilCV.version()
@property
def camera_name(self):
"""
Get camera name used.
Returns:
Camera name (string)
"""
return self.__camera_name
@property
def camera_fov(self):
"""
Get Field of View (FoV) from camera used.
Returns:
FoV camera (int)
"""
return self.__camera_fov
@property
def icx(self):
"""
Get center image x-axis from camera used.
Returns:
Image center X (int)
"""
return self.__icx
@property
def icy(self):
"""
Get center image y-axis from camera used.
Returns:
Image center Y(int)
"""
if self.__icy is not None:
return self.__icy
@property
def image_width(self):
"""
Get the width of the image used.
Returns:
image width(int)
"""
return self.__image_width
@property
def image_height(self):
"""
Get the height of the image used.
Returns:
image height(int)
"""
return self.__image_height
@property
def param_0(self):
"""
Get the value of calibration parameter_0 from camera used.
Returns:
Parameter_0 (float)
"""
return self.__parameter_0
@property
def param_1(self):
"""
Get the value of calibration parameter_1 from camera used.
Returns:
Parameter_1 (float)
"""
return self.__parameter_1
@property
def param_2(self):
"""
Get the value of calibration parameter_2 from camera used.
Returns:
Parameter_2 (float)
"""
return self.__parameter_2
@property
def param_3(self):
"""
Get the value of calibration parameter_3 from camera used.
Returns:
Parameter_3 (float)
"""
return self.__parameter_3
@property
def param_4(self):
"""
Get the value of calibration parameter_4 from camera used.
Returns:
Parameter_4 (float)
"""
return self.__parameter_4
@property
def param_5(self):
"""
Get the value of calibration parameter_5 from camera used.
Returns:
Parameter_5 (float)
"""
return self.__parameter_5
[docs] def maps_anypoint_mode1(self, alpha, beta, zoom):
"""
Generate a pair of X-Y Maps for the specified alpha, beta and zoom parameters,
and then utilize the resulting X-Y Maps to remap the original fisheye image to the target angle image.
This function has 2 mode to generate maps anypoint, mode 1 is for tube application and
mode 2 usually for car application
Args:
alpha: value of zenith distance(float).
beta: value of azimuthal distance based on cartography system(float)
zoom: value of zoom(float)
Returns:
mapX: the mapping matrices X
mapY: the mapping matrices Y
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
if beta < 0:
beta = beta + 360
if alpha < -110 or alpha > 110 or beta < 0 or beta > 360:
alpha = 0
beta = 0
else:
alpha = -110 if alpha < -110 else alpha
alpha = 110 if alpha > 110 else alpha
beta = 0 if beta < 0 else beta
beta = 360 if beta > 360 else beta
self.__moildev.AnypointM(self.__map_x, self.__map_y, alpha, beta, zoom)
return self.__map_x, self.__map_y
[docs] def maps_anypoint_mode2(self, pitch, yaw, roll, zoom):
"""
Generate a pair of X-Y Maps for the specified pitch, yaw, and roll also zoom parameters,
and then utilize the resulting X-Y Maps to remap the original fisheye image to the target image.
Args:
pitch: pitch rotation (from -110 to 110 degree)
yaw: yaw rotation (from -110 to 110 degree)
roll: roll rotation (from -110 to 110 degree)
zoom: zoom scale (1 - 20)
Returns:
mapX: the mapping matrices X
mapY: the mapping matrices Y
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
if pitch < - 110 or pitch > 110 or yaw < -110 or yaw > 110 or roll < -110 or roll > 110:
pitch = 0
yaw = 0
roll = 0
else:
pitch = -110 if pitch < -110 else pitch
pitch = 110 if pitch > 110 else pitch
yaw = -110 if yaw < -110 else yaw
yaw = 110 if yaw > 110 else yaw
roll = 110 if roll > 110 else roll
roll = 110 if roll > 110 else roll
self.__moildev.AnypointCar(self.__map_x, self.__map_y, pitch, yaw, roll, zoom)
return self.__map_x, self.__map_y
[docs] def maps_panorama_tube(self, alpha_min, alpha_max, flip_h=False):
"""
To generate a pair of X-Y Maps for alpha within 0 ... alpha_max degree, the result X-Y Maps can be used later
to generate a panorama image from the original fisheye image.
Args:
alpha_min: the minimum alpha degree given
alpha_max: the maximum alpha degree given. The recommended value is half of camera FOV. For example, use
90 for a 180 degree fisheye images and use 110 for a 220 degree fisheye images.
flip_h: Flip horizontal axis (boolean True or False)
Returns:
mapX: the mapping matrices X
mapY: the mapping matrices Y
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
if alpha_min < 5:
alpha_min = 5
self.__moildev.PanoramaTube(self.__map_x, self.__map_y, alpha_min, alpha_max, flip_h)
return self.__map_x, self.__map_y
[docs] def maps_panorama_car(self, alpha_max, iC_alpha_degree, iC_beta_degree, flip_h=False):
"""
To generate a pair of X-Y Maps for alpha within 0 alpha_max degree, the result X-Y Maps can be used later
to generate a panorama image from the original fisheye image. The panorama image centered at the 3D
direction with alpha = iC_alpha_degree and beta = iC_beta_degree.
Args:
alpha_max : max of alpha. The recommended value is half of camera FOV. For example, use
90 for a 180 degree fisheye images and use 110 for a 220 degree fisheye images.
iC_alpha_degree : alpha angle of panorama center.
iC_beta_degree : beta angle of panorama center.
flip_h: Flip horizontal axis (boolean True or False)
Returns:
mapX
mapY
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
self.__moildev.PanoramaCar(self.__map_x, self.__map_y, alpha_max, iC_alpha_degree,
iC_beta_degree, flip_h)
return self.__map_x, self.__map_y
[docs] def maps_panorama_rt(self, alpha_max, iC_alpha_degree, iC_beta_degree):
"""
To generate a pair of X-Y Maps for alpha within 0..alpha_max degree, the result X-Y Maps can be used later
to generate a panorama image from the original fisheye image. The panorama image centered at the 3D
direction with alpha = iC_alpha_degree and beta = iC_beta_degree.
Args:
alpha_max : max of alpha. The recommended value is half of camera FOV. For example, use
90 for a 180 degree fisheye images and use 110 for a 220 degree fisheye images.
iC_alpha_degree : alpha angle of panorama center.
iC_beta_degree : beta angle of panorama center.
Returns:
mapX
mapY
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
self.__moildev.PanoramaM_Rt(self.__map_x, self.__map_y, alpha_max, iC_alpha_degree, iC_beta_degree)
return self.__map_x, self.__map_y
[docs] def maps_recenter(self, alpha_max, beta_degree):
"""
Create maps for reverse image. this can work using input panorama rotation image
Args:
alpha_max: max of alpha. The recommended value is half of camera FOV. For example, use
90 for a 180 degree fisheye images and use 110 for a 220 degree fisheye images.
beta_degree : beta angle.
Returns:
maps_x_reverse, maps_y_reverse
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
self.__moildev.revPanoramaMaps(self.__map_x, self.__map_y, alpha_max, beta_degree)
return self.__map_x, self.__map_y
[docs] def anypoint_mode1(self, image, alpha, beta, zoom):
"""
Generate anypoint view image. for mode 1, the result rotation is betaOffset degree rotation around the
Z-axis(roll) after alphaOffset degree rotation around the X-axis(pitch). for mode 2, The result rotation
is thetaY degree rotation around the Y-axis(yaw) after thetaX degree rotation around the X-axis(pitch).
Args:
image: source image given
alpha: the alpha offset that corespondent to the pitch rotation
beta: the beta offset that corespondent to the yaw rotation
zoom: decimal zoom factor, normally 1..12
Returns:
anypoint image
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
map_x, map_y = self.maps_anypoint_mode1(alpha, beta, zoom)
image = cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC)
return image
[docs] def anypoint_mode2(self, image, pitch, yaw, roll, zoom):
"""
Generate anypoint view image. for mode 1, the result rotation is betaOffset degree rotation around the
Z-axis(roll) after alphaOffset degree rotation around the X-axis(pitch). for mode 2, The result rotation
is thetaY degree rotation around the Y-axis(yaw) after thetaX degree rotation around the X-axis(pitch).
Args:
image: source image given
pitch: the alpha offset that corespondent to the pitch rotation
yaw: the beta offset that corespondent to the yaw rotation
roll: the beta offset that corespondent to the yaw rotation
zoom: decimal zoom factor, normally 1..12
Returns:
anypoint image
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
map_x, map_y = self.maps_anypoint_mode2(pitch, yaw, roll, zoom)
image = cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC)
return image
[docs] def panorama_tube(self, image, alpha_min, alpha_max, flip_h=False):
"""
The panorama image
Args:
image: image source given
alpha_min:
alpha_max:
flip_h: Flip horizontal axis (boolean True or False)
Returns:
Panorama view image
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
map_x, map_y = self.maps_panorama_tube(alpha_min, alpha_max, flip_h)
image = cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC)
return image
[docs] def panorama_car(self, image, alpha_max, alpha, beta, left, right, top, bottom, flip_h=False):
"""
The function that generate a moil dash panorama image from fisheye camera.
the image can control by alpha to change the pitch direction and beta for yaw direction.
in order to select the roi, we can control by the parameter such as left, right, top, and bottom.
Args:
image: input fisheye image
alpha_max:
alpha: change the pitch direction(0 ~ 180)
beta: change the yaw direction(-90 ~ 90)
left: crop the left image by scale(0 ~ 1)
right: crop the right image by scale(0 ~ 1)
top: crop the top image by scale(0 ~ 1)
bottom: crop the bottom image by scale(0 ~ 1)
flip_h: Flip horizontal axis (boolean True or False)
Returns:
Panorama image
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
map_x, map_y = self.maps_panorama_car(alpha_max, alpha, beta, flip_h)
image = cv2.resize(cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC),
(image.shape[1] * 2, image.shape[0]))
image = image[round(image.shape[0] * top):
round(image.shape[0] * top) + round(image.shape[0] * bottom),
round(image.shape[1] * left):
round(image.shape[1] * left) + round(image.shape[1] * (right - left))]
return image
[docs] def recenter(self, image, alpha_max, iC_alpha_degree, iC_beta_degree):
"""
Change the optical point of fisheye image.
Args:
image: input image
alpha_max: max of alpha. The recommended value is half of camera FOV. For example, use
90 for a 180 degree fisheye images and use 110 for a 220 degree fisheye images.
iC_alpha_degree: alpha angle of panorama center
iC_beta_degree: beta angle of panorama center
Returns:
reverse image
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
self.__moildev.PanoramaM_Rt(self.__map_x, self.__map_y, alpha_max, iC_alpha_degree, iC_beta_degree)
result = cv2.remap(image, self.__map_x, self.__map_y, cv2.INTER_CUBIC)
self.__moildev.revPanorama(result, self.__res, alpha_max, iC_beta_degree)
return self.__res
[docs] def get_alpha_from_rho(self, rho):
"""
Get the alpha from rho image.
Args:
rho: the value of rho given
Returns:
alpha
"""
if rho >= 0:
return self.__rho_to_alpha_table[rho] / 10
else:
return -self.__rho_to_alpha_table[-rho] / 10
[docs] def get_rho_from_alpha(self, alpha):
"""
Get rho image from alpha given.
Args:
alpha: the value of alpha given
Returns:
rho image
"""
return self.__alpha_to_rho_table[round(alpha * 10)]
[docs] def get_alpha_beta(self, coordinateX, coordinateY, mode=1):
"""
Get the alpha beta from specific coordinate image.
Args:
coordinateX:
coordinateY:
mode:
Returns:
alpha, beta (if you get none, the coordinate is out of range that can cover)
.. code-block :: markdown
please reference: `https://github.com/perseverance-tech-tw/moildev`
"""
delta_x = coordinateX - self.__icx
delta_y = -(coordinateY - self.__icy)
range_left = coordinateX - (self.__icx - self.get_rho_from_alpha(110) - 4)
range_right = self.image_width - (self.__icx - self.get_rho_from_alpha(110) + 4)
if range_left >= 0 and coordinateX <= range_right:
if mode == 1:
r = round(math.sqrt(math.pow(delta_x, 2) + math.pow(delta_y, 2)))
alpha = self.get_alpha_from_rho(r)
if coordinateX == self.__icy:
angle = 0
else:
angle = (math.atan2(delta_y, delta_x) * 180) / math.pi
beta = 90 - angle
else:
alpha = self.get_alpha_from_rho(delta_y)
beta = self.get_alpha_from_rho(delta_x)
else:
alpha, beta = None, None
return alpha, beta