Source code for Moildev.Moildev

"""
This module provides classes and functions for working with MOIL camera calibration data and image processing.

The Moildev class provides methods for initializing the camera parameters, generating maps for remapping fisheye images,
and transforming images to create different views.

The draw_polygon function is used to draw a polygon on an image given mapX and mapY coordinates.
"""

import ctypes
import os
import platform
import cv2
import json
import math
import numpy as np

dir_path = os.path.dirname(os.path.realpath(__file__))
if platform.system() == "Windows":
    path = os.path.join(dir_path, "moildev.dll")
    shared_lib_path = path
else:
    path = os.path.join(dir_path, "moildev.so")
    shared_lib_path = path

try:
    lib = ctypes.cdll.LoadLibrary(shared_lib_path)
except Exception as e:
    print(e)


[docs] def draw_polygon(image, mapX, mapY): """ Draw polygon from mapX and mapY given in the original image. Args: image: Original image mapX: map image X from anypoint process mapY: map image Y from anypoint process return: image: """ hi, wi = image.shape[:2] X1 = [] Y1 = [] X2 = [] Y2 = [] X3 = [] Y3 = [] X4 = [] Y4 = [] x = 0 while x < wi: a = mapX[0, ] b = mapY[0, ] ee = mapX[-1, ] f = mapY[-1, ] if a[x] == 0. or b[x] == 0.: pass else: X1.append(a[x]) Y1.append(b[x]) if f[x] == 0. or ee[x] == 0.: pass else: Y3.append(f[x]) X3.append(ee[x]) x += 10 y = 0 while y < hi: c = mapX[:, 0] d = mapY[:, 0] g = mapX[:, -1] h = mapY[:, -1] # eliminate the value 0 for map X if d[y] == 0. or c[y] == 0.: # or d[y] and c[y] == 0.0: pass else: Y2.append(d[y]) X2.append(c[y]) # eliminate the value 0 for map Y if h[y] == 0. or g[y] == 0.: pass else: Y4.append(h[y]) X4.append(g[y]) # render every 10 times, it will be like 1, 11, 21 and so on. y += 10 p = np.array([X1, Y1]) q = np.array([X2, Y2]) r = np.array([X3, Y3]) s = np.array([X4, Y4]) points = p.T.reshape((-1, 1, 2)) points2 = q.T.reshape((-1, 1, 2)) points3 = r.T.reshape((-1, 1, 2)) points4 = s.T.reshape((-1, 1, 2)) # Draw polyline on original image cv2.polylines(image, np.int32([points]), False, (0, 255, 0), 10) cv2.polylines(image, np.int32([points2]), False, (0, 255, 0), 10) cv2.polylines(image, np.int32([points3]), False, (0, 255, 0), 10) cv2.polylines(image, np.int32([points4]), False, (0, 255, 0), 10) return image
[docs] class Moildev(object): def __init__(self, parameter_path, camera_type): """ This is the initial configuration that you need provide the parameter. The camera parameter is the result from calibration camera by MOIL laboratory. before the successive functions can work correctly,configuration is necessary in the beginning of program. Args: parameter_path (): camera_type (): for more detail, please reference https://github.com/MoilOrg/moildev """ """ This is the initial configuration that you need provide the parameter. The camera parameter is the result from calibration camera by MOIL laboratory. before the successive functions can work correctly,configuration is necessary in the beginning of program. Args: . camera_name - A string to describe this camera . sensor_width - Camera sensor width (cm) . sensor_height - Camera Sensor Height (cm) . Icx - image center X coordinate(pixel) . Icy - image center Y coordinate(pixel) . ratio : Sensor pixel aspect ratio. . imageWidth : Input image width . imageHeight : Input image height . parameter0 .. parameter5 : calibration's parameters . calibrationRatio : input image with/ calibrationRatio image width for more detail, please reference https://github.com/MoilOrg/moildev """ super(Moildev, self).__init__() self.__PI = 3.1415926 self.__alphaToRho_Table = [] self.__rhoToAlpha_Table = [] self.__moildev = None if parameter_path is None: pass else: with open(parameter_path) as f: data = json.load(f) if camera_type in data.keys(): self.__camera = data[camera_type]["cameraName"] self.__sensor_width = data[camera_type]['cameraSensorWidth'] self.__sensor_height = data[camera_type]['cameraSensorHeight'] self.__Icx = data[camera_type]['iCx'] self.__Icy = data[camera_type]['iCy'] self.__ratio = data[camera_type]['ratio'] self.__imageWidth = data[camera_type]['imageWidth'] self.__imageHeight = data[camera_type]['imageHeight'] self.__calibrationRatio = data[camera_type]['calibrationRatio'] self.__parameter0 = data[camera_type]['parameter0'] self.__parameter1 = data[camera_type]['parameter1'] self.__parameter2 = data[camera_type]['parameter2'] self.__parameter3 = data[camera_type]['parameter3'] self.__parameter4 = data[camera_type]['parameter4'] self.__parameter5 = data[camera_type]['parameter5'] self.__import_moildev() self.__initAlphaRho_Table() else: print( "Error 1: camera parameter not available, please check your camera type") def __initAlphaRho_Table(self): """ Create and calculate a list for initial alpha proportionate with rho image (height image). Returns: Initial alpha proportionate with rho image table. """ for i in range(1800): alpha = i / 10 * 3.1415926 / 180 self.__alphaToRho_Table.append( (self.__parameter0 * alpha * alpha * alpha * alpha * alpha * alpha + self.__parameter1 * alpha * alpha * alpha * alpha * alpha + self.__parameter2 * alpha * alpha * alpha * alpha + self.__parameter3 * alpha * alpha * alpha + self.__parameter4 * alpha * alpha + self.__parameter5 * alpha) * self.__calibrationRatio) i += 1 i = 0 index = 0 while i < 1800: while index < self.__alphaToRho_Table[i]: self.__rhoToAlpha_Table.append(i) index += 1 i += 1 while index < 3600: self.__rhoToAlpha_Table.append(i) index += 1 def __import_moildev(self): """ Create Moildev instance from Moildev SDK share object library. Returns: """ lib.moildev_new.argtypes = [ ctypes.c_char_p, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double] lib.moildev_new.restype = ctypes.c_void_p self.__moildev = lib.moildev_new( self.__camera.encode('utf-8'), self.__sensor_width, self.__sensor_height, self.__Icx, self.__Icy, self.__ratio, self.__imageWidth, self.__imageHeight, self.__parameter0, self.__parameter1, self.__parameter2, self.__parameter3, self.__parameter4, self.__parameter5, self.__calibrationRatio) self.__map_x = np.zeros( (self.__imageHeight, self.__imageWidth), dtype=np.float32) self.__map_y = np.zeros( (self.__imageHeight, self.__imageWidth), dtype=np.float32) self.__res = self.__create_map_result_image() def __create_map_result_image(self): """ Create Maps image from zeroes matrix for result image Returns: Zeroes matrix. """ size = self.__imageHeight, self.__imageWidth, 3 return np.zeros(size, dtype=np.uint8)
[docs] def test(self): """ Test to link with Moildev share library Returns: Hello from C++ """ if self.__moildev is not None: if platform.system() == "Windows": lib.test.argtypes = [ctypes.c_void_p] lib.test.restype = ctypes.c_char_p print((lib.test(self.__moildev)).decode()) else: lib.test.argtypes = [ctypes.c_void_p] lib.test.restype = ctypes.c_void_p return lib.test(self.__moildev) else: return None
[docs] def clean(self): """ clean the memory of pointer. Returns: None """ lib.cleanup_moildev.argtypes = [ctypes.c_void_p] lib.cleanup_moildev.restype = ctypes.c_void_p return lib.cleanup_moildev(self.__moildev)
[docs] def get_Icx(self): """ Get center image from width image (x axis). Returns: """ return self.__Icx
[docs] def get_Icy(self): """ Get center image from height image (y axis). Returns: """ return self.__Icy
[docs] def get_imageWidth(self): """ Get image width. Returns: """ return self.__imageWidth
[docs] def get_imageHeight(self): """Get image height. :return: image height :rtype: int """ return self.__imageHeight
[docs] def getAnypointMaps(self, alpha, beta, zoom, mode=1): """The purpose is to generate a pair of X-Y Maps for the specified alpha, beta and zoom parameters, the result X-Y Maps can be used later to remap the original fisheye image to the target angle image. Args: :param alpha: alpha :type alpha: float :param beta: beta :type beta: float :param zoom: decimal zoom factor, normally 1..12 :type zoom: int :param mode: selection anypoint mode(1 or 2) :type mode: int Return: :return: map_x, map_y :rtype: float Examples: please reference: https://github.com/MoilOrg/moildev """ if self.__moildev is not None: if mode == 1: if beta < 0: beta = beta + 360 if alpha < -90 or alpha > 90 or beta < 0 or beta > 360: alpha = 0 beta = 0 else: alpha = -90 if alpha < -90 else alpha alpha = 90 if alpha > 90 else alpha beta = 0 if beta < 0 else beta beta = 360 if beta > 360 else beta lib.moil_anypointM.argtypes = [ ctypes.c_void_p, ctypes.POINTER( ctypes.c_float), ctypes.POINTER( ctypes.c_float), ctypes.c_int, ctypes.c_int, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double] lib.moil_anypointM.restype = None mapX = self.__map_x.ctypes.data_as( ctypes.POINTER(ctypes.c_float)) mapY = self.__map_y.ctypes.data_as( ctypes.POINTER(ctypes.c_float)) lib.moil_anypointM( self.__moildev, mapX, mapY, self.__imageWidth, self.__imageHeight, alpha, beta, zoom, self.__ratio) else: if alpha < - 90 or alpha > 90 or beta < -90 or beta > 90: alpha = 0 beta = 0 else: alpha = -90 if alpha < -90 else alpha alpha = 90 if alpha > 90 else alpha beta = -90 if beta < -90 else beta beta = 90 if beta > 90 else beta lib.moil_anypointM2.argtypes = [ ctypes.c_void_p, ctypes.POINTER( ctypes.c_float), ctypes.POINTER( ctypes.c_float), ctypes.c_int, ctypes.c_int, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double] lib.moil_anypointM2.restype = None mapX = self.__map_x.ctypes.data_as( ctypes.POINTER(ctypes.c_float)) mapY = self.__map_y.ctypes.data_as( ctypes.POINTER(ctypes.c_float)) lib.moil_anypointM2( self.__moildev, mapX, mapY, self.__imageWidth, self.__imageHeight, alpha, beta, zoom, self.__ratio) return self.__map_x, self.__map_y else: return None, None
[docs] def getPanoramaMaps(self, alpha_min, alpha_max): """ 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: :param alpha_min: alpha min :type alpha_min: float :param alpha_max: alpha max :type alpha_max: float Return: :return: pair maps x-y :rtype: array Examples: please reference: https://github.com/MoilOrg/moildev """ lib.moil_panoramaX.argtypes = [ ctypes.c_void_p, ctypes.POINTER( ctypes.c_float), ctypes.POINTER( ctypes.c_float), ctypes.c_int, ctypes.c_int, ctypes.c_double, ctypes.c_double, ctypes.c_double] lib.moil_panoramaX.restype = None mapX = self.__map_x.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) mapY = self.__map_y.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) lib.moil_panoramaX( self.__moildev, mapX, mapY, self.__imageWidth, self.__imageHeight, self.__ratio, alpha_max, alpha_min) return self.__map_x, self.__map_y
[docs] def anypoint(self, image, alpha, beta, zoom, mode=1): """Generate anypoint view.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: :param image: source image :type image: array :param alpha: alpha :type alpha: float :param beta: beta :type beta: float :param zoom: zoom :type zoom: int :param mode: mode anypoint view :type mode: int Return: :return: anypoint view :rtype: array Examples: please reference: https://github.com/MoilOrg/moildev """ map_x, map_y = self.getAnypointMaps(alpha, beta, zoom, mode) image = cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC) return image
[docs] def panorama(self, image, alpha_min, alpha_max): """The panorama image centered at the 3D direction with alpha = iC_alpha_degree and beta = iC_beta_degree Args: :param image: Original image :type image: array :param alpha_min: min of alpha. by default it 10 degree. :type alpha_min: float :param 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. :type alpha_max: float Returns: :return: panorama image :rtype: array Examples: please reference: https://github.com/MoilOrg/moildev """ if alpha_min < 10: print( "Oops! That was no valid number on alpha_min. the value must equal or more than 10") return None else: map_x, map_y = self.getPanoramaMaps(alpha_min, alpha_max) image = cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC) return image
def __PanoramaM_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: . mapX : memory pointer of result X-Map . mapY : memory pointer of result Y-Map . w : width of the Map (both mapX and mapY) . h : height of the Map (both mapX and mapY) . magnification : input imageWidth / sensor_width, m_ratio is normally equal to 1. . 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: New mapX and mapY. Examples: please reference: https://github.com/MoilOrg/moildev """ lib.moil_panoramaM_Rt.argtypes = [ ctypes.c_void_p, ctypes.POINTER( ctypes.c_float), ctypes.POINTER( ctypes.c_float), ctypes.c_int, ctypes.c_int, ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_double] lib.moil_panoramaM_Rt.restype = None mapX = self.__map_x.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) mapY = self.__map_y.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) lib.moil_panoramaM_Rt( self.__moildev, mapX, mapY, self.__imageWidth, self.__imageHeight, self.__ratio, alpha_max, iC_alpha_degree, iC_beta_degree) return self.__map_x, self.__map_y
[docs] def reverse_image(self, image, alpha_max, alpha, beta): """To generate the image reverse image from panorama that can change the focus direction from the original images. The panorama reverse image centered at the 3D direction with alpha_max = max of alpha and beta = iC_beta_degree. Args: :param image: source image :type image: array :param alpha_max: alpha max :type alpha_max: float :param alpha: alpha :type alpha: float :param beta: beta :type beta: float Return: :return: reverse view image :rtype: array Examples: please reference: https://github.com/MoilOrg/moildev """ map_x, map_y = self.__PanoramaM_Rt(alpha_max, alpha, beta) panorama_image = cv2.remap(image, map_x, map_y, cv2.INTER_CUBIC) if platform.system() == "Windows": print("revPanorama available at Moildev library version 1.3.0 \n" "make sure you have install OpenCV and Visual Studio code") else: lib.moil_revPanorama.argtypes = [ ctypes.c_void_p, ctypes.POINTER( ctypes.c_void_p), ctypes.POINTER( ctypes.c_void_p), ctypes.c_int, ctypes.c_int, ctypes.c_double, ctypes.c_double, ] lib.moil_revPanorama.restype = None panoramaImage = panorama_image.ctypes.data_as( ctypes.POINTER(ctypes.c_void_p)) res = self.__res.ctypes.data_as(ctypes.POINTER(ctypes.c_void_p)) lib.moil_revPanorama( self.__moildev, panoramaImage, res, self.__imageWidth, self.__imageHeight, alpha_max, beta) return self.__res
[docs] def getAlphaFromRho(self, rho): """Get the alpha from rho image. Args: :param rho: rho image :type rho: int Return: :return: alpha :rtype: float Examples: please reference: https://github.com/MoilOrg/moildev """ if rho >= 0: return self.__rhoToAlpha_Table[rho] / 10 else: return -self.__rhoToAlpha_Table[-rho] / 10
[docs] def getRhoFromAlpha(self, alpha): """Get rho image from alpha given. Args: :param alpha:alpha :type alpha: float Return: :return: rho image :rtype: int Examples: please reference: https://github.com/MoilOrg/moildev """ return self.__alphaToRho_Table[round(alpha * 10)]
[docs] def get_alpha_beta(self, coordinateX, coordinateY, mode=1): """Get the alpha beta from specific coordinate image. Args: :param mode: the anypoint mode. :type mode: int :param coordinateX: the coordinate point X axis. :type coordinateX: int :param coordinateY: the coordinate point Y axis. :type coordinateY: int Return: :return: alpha, beta :rtype: float Examples: please reference: https://github.com/MoilOrg/moildev """ if self.__moildev is not None: delta_x = round(coordinateX - self.__imageWidth * 0.5) delta_y = round(- (coordinateY - self.__imageHeight * 0.5)) if mode == 1: r = round( math.sqrt( math.pow( delta_x, 2) + math.pow( delta_y, 2))) alpha = self.getAlphaFromRho(r) if delta_x == 0: angle = 0 else: angle = (math.atan2(delta_y, delta_x) * 180) / self.__PI beta = 90 - angle else: alpha = self.getAlphaFromRho(delta_y) beta = self.getAlphaFromRho(delta_x) return alpha, beta else: return None, None