Source code for ovl.visions.vision

from functools import reduce
import math
import types
import cv2
import numpy as np
from typing import List, Union, Tuple, Any

from ..helpers.vision_detector_arguments import arguments_to_detector
from ..thresholds.threshold import Threshold
from ..detectors.detector import Detector
from ..exceptions.exceptions import InvalidCustomFunctionError, CameraError
from ..camera.camera import Camera
from ..partials.filter_applier import filter_applier
from ..connections.connection import Connection
from ..directions.director import Director
from ..camera_.camera_settings import CameraSettings
from ..connections.network_location import NetworkLocation
from ..directions.directing_functions import center_directions


[docs]class Vision: """ Vision object represents a computer vision pipeline. The pipeline consists of 4 main stages: 1. processing - 'apply_all_image_filters' which uses a list of image_filter functions 2. detection - 'detect' which comes from Detector objects and detects objects (contours, bounding rectangles..) 3. filtering - 'apply_target_filters' which is uses filter_functions like contour_filters 4. conversion & usage - 'direct' which comes from director objects Each functionality can be used to easily create a complex yet modular Additional capabilities and tuning options are: Image filters (Blurs, rotations, cropping), Morphological functions, Ovl color HSVCalibration, 1. camera handling 2. connection clean-up and sending Vision can also be used as a part of a more complex pipeline. MultiVision can contain multiple vision objects and switch between pipelines, allowing for very versatile logic that can fit multiple needs. Ambient Vision is another option for using 2 different Vision objects and alternate between the 2. """ def __init__(self, detector: Detector = None, threshold: Threshold = None, morphological_functions: List[types.FunctionType] = None, target_filters: List[types.FunctionType] = None, director: Director = None, width=320, height=240, connection: Connection = None, camera: Union[int, str, Camera, cv2.VideoCapture, Any] = None, camera_settings: CameraSettings = None, image_filters: List[types.FunctionType] = None, ovl_camera: bool = False, haar_classifier: str = None): """ :param detector: a Detector object responsible for detecting targets :param threshold: threshold object, creates the binary mask from a given image, this cannot be passed together with detector :param target_filters: the list of contour_filter functions that remove contours that aren't the target(s) :param director: a functions that receives a list or a single contour and returns director :param width: the width (in pixels) of images taken with the camera :param height: the height (in pixels) :param connection: a connection object that passes the result to the connection target :param camera: a Camera object (cv2.VideoCapture, ovl.Camera) or source from which to open a camera :param camera_settings: Special camera settings like calibration or offset used for image correction and various direction calculations. :param image_filters: a list of image altering functions that are applied on the image. :param ovl_camera: a boolean that makes the camera opened to be ovl.Camera instead of cv2.VideoCapture :param haar_classifier: """ mutually_exclusive_arguments = {"threshold": (threshold, morphological_functions), "detector": (detector,), "haar_cascade": (haar_classifier,)} detector = arguments_to_detector(mutually_exclusive_arguments) self.detector = detector self.width = width self.height = height self.target_filters = target_filters or [] self.director = director or Director(center_directions, failed_detection=9999, target_amount=1) self.connection = connection self.image_filters = image_filters or [] self.camera = None self.camera_port = None self.camera_settings = camera_settings if isinstance(camera, (cv2.VideoCapture, Camera)): self.camera = camera elif camera is None: pass else: self.camera_setup(camera, width, height, ovl_camera=ovl_camera) def __repr__(self): return str(self) def __str__(self): filters = [filter_function.__name__ for filter_function in self.target_filters] return "Vision: \n Detector: {} \n Filters: {}".format(self.detector, filters) @property def target_amount(self): """ The wanted amount of targets Determined by self.director (0 None or math.inf if there is no limit, 1 if 1 target is wanted etc.) """ if self.director is None: return math.inf return self.director.target_amount
[docs] def send(self, data: Any, *args, **kwargs) -> Any: """ Sends data to the destination using self.connection :param data: The data to send to the Connection :param args: any other arguments for the send function in your connection :param kwargs: any other named arguments for the connection object :return: Depends on the connection object used, returns its result """ return self.connection.send(*args, **kwargs, data=data)
[docs] def send_to_location(self, data: Any, network_location: NetworkLocation, *args, **kwargs): """ A function that sends data to a specific NetworkLocation :param data: the data to be sent :param network_location: information used to send the data to a specific 'location' in the network :return: Depends on the connection object """ return self.connection.send_to_location(data, network_location, *args, **kwargs)
[docs] def get_image(self) -> np.ndarray: """ Gets an image from self.camera and applies image filters :return: the image, false if failed to get it """ if self.camera is None: raise ValueError("No camera given, (Camera is None)") if not self.camera.isOpened(): raise CameraError("Camera given is not open (Has it been closed or disconnected?)") output = self.camera.read() if len(output) == 2: ret, image = output return image if ret else False else: return output
[docs] def apply_target_filter(self, filter_function, contours, verbose=False): """ Applies a filter function on the contour list, this is used to remove contours that do not match desired features NOTE: Vision.detect is mainly used for full object detection and filtering, refer to it for common use of Vision :param filter_function: Filter functions are function with a contour list variable that apply some sort of filter on the contours, thus removing ones that don't fit the limit given by the filter. for example: straight_rectangle_filter removes contours that are not rectangles that are parallel to the frame of the picture :param contours: the contours on which the filter should be applied (list of numpy.ndarrays) :param verbose: if true_shape does not print anything :return: returns the output of the filter function. """ if verbose: print('Before "{}": {}'.format(filter_function.__name__, len(contours))) filter_function_output = filter_function(contours) if isinstance(filter_function_output, tuple): if len(filter_function_output) == 2: filtered_contours, ratio = filter_function_output[0], filter_function_output[1] else: raise InvalidCustomFunctionError('Filter function must return between 1 and 2 lists.' 'Please refer to the Documentation: ' 'https://github.com/1937Elysium/Ovl-Python') elif isinstance(filter_function_output, list): filtered_contours, ratio = filter_function_output, [] else: raise TypeError('The contour list must be a list or tuple of 2 lists (contours and ratios)') return filtered_contours, ratio
[docs] def apply_target_filters(self, targets: List[np.ndarray], verbose=False ) -> Tuple[List[np.ndarray], List[float]]: """ Applies all of the filters on a list of contours, one after the other. Applies the first filter and passes the output to the second filter, :param targets: List of targets (numpy arrays or bounding boxes) to :param verbose: prints out information about filtering process if true (useful for debugging) :return: a list of all of the ratios given by the filter function in order. """ ratios = [] for filter_func in self.target_filters: targets, ratio = self.apply_target_filter(filter_func, targets, verbose=verbose) ratios.append(ratio) if verbose: print("After all filters: {}".format(len(targets))) return targets, ratios
[docs] def apply_image_filters(self, image: np.ndarray) -> np.ndarray: """ Applies all given image filters to the given image This is used to apply various image filters on your image in a pipeline, like blurs, image cropping, contrasting, sharpening, rotations, translations etc. :param image: the image that the image filters should be applied on (numpy array) :return: the image with the filters applied """ return reduce(filter_applier, self.image_filters, image)
[docs] def get_directions(self, contours: List[np.ndarray], image: np.ndarray, sorter=None): """ Calculates the directions, based on contours found in the given image :param contours: final contours after filtering :param image: the image from which to find the contours :param sorter: optional parameter, applies a sorter on the given contours :return: a string of the director (output of the director function), length depends on the director function """ return self.director.direct(contours, image, sorter=sorter)
[docs] def camera_setup(self, source=0, image_width=None, image_height=None, ovl_camera=False): """ Opens up the camera reference and sets a given width and height to all images taken :param image_width: the width of the images to be taken, 0 does not set a width :param image_height: the height of the images to be taken, 0 does not set a height :param source: the location from which to open the camera string for network connections int for local USB connections. :param ovl_camera: if the camera object should be ovl.Camera :return: the camera object, also sets self.camera to the object. """ image_height = image_height or self.height image_width = image_width or self.width self.camera_port = source if ovl_camera: camera = Camera(source=source, image_width=image_width, image_height=image_height) else: camera = cv2.VideoCapture(source) if image_width != -1: camera.set(3, image_width) if image_height != -1: camera.set(4, image_height) if not camera.isOpened(): raise CameraError("Camera did not open correctly! Camera source: {}".format(self.camera_port)) self.camera = camera return camera
[docs] def detect(self, image, verbose=False, *args, **kwargs): """ This is the function that performs processing detection and filtering on a given image, essentially passing the image through the detection related part of the pipeline detect applies image filters, detects objects in the filtered images (using the passed/created detector object) and finally applies all of the target_filters on the image. args and kwargs are passed to the detect function (passed to the detect method of the detector) :param verbose: passes verbose to apply_target_filters, which prints out information about the target filtering. :param image: image in which the vision should detect an object :return: contours and the filtered image and the ratios if return_ratios is true """ image = self.apply_image_filters(image) targets = self.detector.detect(image, verbose, *args, **kwargs) return self.apply_target_filters(targets, verbose)