Source code for openalea.eartrack.binarisation

# coding: utf-8

import numpy
import cv2


[docs]def dilate(binary_image, kshape='MORPH_CROSS', ksize=3, iterations=1): """ Dilate an image Dilate an image using opencv dilate method :param binary_image: numpy.ndarray 2-D array :param kshape: str, opt See opencv documentation :param ksize: int, opt See opencv documentation :param iterations: int, opt Number of iteration of dilatation :return: dilated : numpy.ndarray 2-D image """ kshape = getattr(cv2, kshape) element = cv2.getStructuringElement(kshape, (ksize, ksize)) dilated = cv2.dilate(binary_image, element, iterations=iterations) return dilated
[docs]def open(binary_image, kshape='MORPH_CROSS', ksize=3, iterations=1): """ Open an image Perform morphology opening algorithm on image using opencv method :param binary_image: numpy.ndarray 2-D array :param kshape: str, opt See opencv documentation :param ksize: int, opt See opencv documentation :param iterations: int, opt Number of iteration :return: opened : numpy.ndarray 2-D image """ kshape = getattr(cv2, kshape) element = cv2.getStructuringElement(kshape, (ksize, ksize)) opened = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, element, iterations=iterations) return opened
[docs]def close(binary_image, kshape='MORPH_CROSS', ksize=3, iterations=1): """ Close an image Perform morphology closing algorithm on image using opencv method :param binary_image: numpy.ndarray 2-D array :param kshape: str, opt See opencv documentation :param ksize: int, opt See opencv documentation :param iterations: int, opt Number of iteration :return: closed : numpy.ndarray 2-D image """ kshape = getattr(cv2, kshape) element = cv2.getStructuringElement(kshape, (ksize, ksize)) closed = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, element, iterations=iterations) return closed
[docs]def erode_dilate(binary_image, kernel_shape=(3, 3), iterations=1, mask=None): """ Applied a morphology (erode & dilate) on binary_image on mask ROI. Parameters ---------- binary_image : numpy.ndarray 2-D array kernel_shape: (N, M) of integers, optional kernel shape of (erode & dilate) applied to binary_image iterations: int, optional number of successive iteration of (erode & dilate) mask : numpy.ndarray, optional Array of same shape as `image`. Only points at which mask == True will be processed. Returns ------- out : numpy.ndarray Binary Image """ # ========================================================================== # Check Parameters if not isinstance(binary_image, numpy.ndarray): raise TypeError('binary_image must be a numpy.ndarray') if binary_image.ndim != 2: raise ValueError('binary_image must be 2D array') if mask is not None: if not isinstance(mask, numpy.ndarray): raise TypeError('mask must be a numpy.ndarray') if mask.ndim != 2: raise ValueError('mask must be 2D array') # ========================================================================== if mask is not None: out = cv2.bitwise_and(binary_image, mask) else: out = binary_image.copy() element = cv2.getStructuringElement(cv2.MORPH_CROSS, kernel_shape) out = cv2.erode(out, element, iterations=iterations) out = cv2.dilate(out, element, iterations=iterations) if mask is not None: res = cv2.subtract(binary_image, mask) out = cv2.add(res, out) return out
[docs]def threshold_hsv(image, hsv_min, hsv_max, mask=None): """ Binarize HSV image with hsv_min and hsv_max parameters. => cv2.inRange(hsv_image, hsv_min, hsv_max) If mask is not None : => cv2.bitwise_and(binary_hsv_image, mask) Parameters ---------- image : numpy.ndarray of integers 3-D array of image RGB hsv_min : tuple of integers HSV value of minimum range hsv_max : tuple of integers HSV value of maximum range mask : numpy.ndarray, optional Array of same shape as `image`. Only points at which mask == True will be thresholded. Returns ------- out : numpy.ndarray Thresholded binary image See Also -------- threshold_meanshift """ # ========================================================================== # Check Parameters if not isinstance(image, numpy.ndarray): raise TypeError('image should be a numpy.ndarray') if image.ndim != 3: raise ValueError('image should be 3D array') if not isinstance(hsv_min, tuple): raise TypeError('hsv_min should be a Tuple') if len(hsv_min) != 3: raise ValueError('hsv_min should be of size 3') for value in hsv_min: if not isinstance(value, int): raise ValueError('hsv_min value should be a integer') if not isinstance(hsv_max, tuple): raise TypeError('hsv_max should be a Tuple') if len(hsv_max) != 3: raise ValueError('hsv_max should be of size 3') for value in hsv_max: if not isinstance(value, int): raise ValueError('hsv_max value should be a integer') if mask is not None: if not isinstance(mask, numpy.ndarray): raise TypeError('mask should be a numpy.ndarray') if mask.ndim != 2: raise ValueError('mask should be 2D array') if image.shape[0:2] != mask.shape: raise ValueError('image and mask should have the same shape') # ========================================================================== out = cv2.inRange(image, hsv_min, hsv_max) if mask is not None: out = cv2.bitwise_and(out, mask) return out
[docs]def threshold_meanshift(image, mean_image, threshold=0.3, mask=None): """ Threshold pixels in numpy array such as:: image / mean <= (1.0 - threshold) If reverse is True (Inequality is reversed):: image / mean <= (1.0 + threshold Parameters ---------- image : numpy.ndarray of integers 3-D array mean_image : numpy.ndarray of the same shape as 'image' 3-D array 'mean_image' threshold : float, optional Threshold value. Must between 0.0 and 1.0 reverse : bool, optional If True reverse inequality mask : numpy.ndarray, optional Array of same shape as `image`. Only points at which mask == True will be thresholded. Returns ------- out : numpy.ndarray Thresholded binary image See Also -------- get_mean_image, threshold_hsv """ # ========================================================================== # Check Parameters if not isinstance(image, numpy.ndarray): raise TypeError('image should be a numpy.ndarray') if not isinstance(mean_image, numpy.ndarray): raise TypeError('mean should be a numpy.ndarray') if image.ndim != 3: raise ValueError('image should be 3D array') if mean_image.ndim != 3: raise ValueError('mean should be 3D array') if image.shape != mean_image.shape: raise ValueError('image and mean must have equal sizes') if not (0.0 <= threshold <= 1.0): raise ValueError('threshold must be between 0.0 and 1.0') if mask is not None: if not isinstance(mask, numpy.ndarray): raise TypeError('mask should be a numpy.ndarray') if mask.ndim != 2: raise ValueError('mask should be 2D array') if image.shape[0:2] != mask.shape: raise ValueError('mask and image must have equal sizes') # ========================================================================== with numpy.errstate(divide='ignore', invalid='ignore'): img = numpy.divide(numpy.float32(image), numpy.float32(mean_image)) img[~ numpy.isfinite(img)] = 0 # Take min value of RGB tuple img = img.min(2) out = img <= (1. - threshold) out = numpy.uint8(out) if mask is not None: out = cv2.bitwise_and(out, mask) del img return out * 255
[docs]def mean_shift_hsv(image, mean_img, threshold=0.3, hsv_min=(30, 11, 0), hsv_max=(129, 254, 141), iterations_clean_noise=3, iterations=1, mask_mean_shift=None, mask_hsv=None, mask_clean_noise=None): """ Segmentation using mean shift method Compute segmentation of an object in image using a combination of meanshift method and hsv threshold :param image: numpy.ndarray of integers 3-D array :param mean_img: numpy.ndarray of integers (same shape as 'image') 3-D array :param threshold: float, optional Threshold value. Must between 0.0 and 1.0 :param hsv_min: tuple of 3 int, optional Minimum values to threshold hsv image. Values must be between 0 and 255 :param hsv_max: tuple of 3 int, optional Maximum values to threshold hsv image. Values must be between 0 and 255 :param iterations_clean_noise: int, optional Number of iterations to clean noise on binary result image under mask :param iterations: int, optional Number of iterations to clean noise on binary result image :param mask_mean_shift: numpy.ndarray, optional Array 2-D of same shape as `image`. Only points at which mask == True will be calculated in meanshift method. :param mask_hsv: numpy.ndarray, optional Array 2-D of same shape as `image`. Only points at which mask == True will be calculated with hsv method. :param mask_clean_noise: numpy.ndarray, optional Array 2-D of same shape as `image`. Only points at which mask == True will be cleaned :return: result: numpy.ndarray 2-D of same shape as `image` Binary image representing plant segmentation of 'image' """ hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) binary_hsv_image = threshold_hsv(hsv_image, hsv_min, hsv_max, mask_hsv) binary_mean_shift_image = threshold_meanshift( image, mean_img, threshold, mask_mean_shift) result = cv2.add(binary_hsv_image, binary_mean_shift_image) if mask_clean_noise is not None and iterations_clean_noise > 0: result = erode_dilate(result, iterations=iterations_clean_noise, mask=mask_clean_noise) if iterations > 0: result = erode_dilate(result, iterations=iterations) return result
[docs]def mean_image(images): """ Compute the mean of a image list. Parameters ---------- images : [ numpy.ndarray of integers ] list of 3-D array Returns ------- out : numpy.ndarray Mean of the list image See Also -------- threshold_meanshift """ # ========================================================================== # Check Parameters if not isinstance(images, list): raise TypeError('images is not a list') if not images: raise ValueError('images is empty') shape_image_ref = None for image in images: if not isinstance(image, numpy.ndarray): raise TypeError('image in list images is not a ndarray') if shape_image_ref is None: shape_image_ref = numpy.shape(image) elif numpy.shape(image) != shape_image_ref: raise ValueError('Shape of ndarray image in list is different') # ========================================================================== length = len(images) weight = 1. / length start = cv2.addWeighted(images[0], weight, images[1], weight, 0) return reduce(lambda x, y: cv2.addWeighted(x, 1, y, weight, 0), images[2:], start)
[docs]def color_tree(bgr, cabin=None, mask_pot=None, mask_rails=None, empty_img=None): """ Segmentation using decision tree and mask Platform specific method, masks and decision trees depend on imagery cabin :param bgr: numpy.ndarray of integers 3-D array :param cabin: string, 2 possible values : cabin-1 or cabin-2 :param mask_pot: mask_mean_shift: numpy.ndarray, optional Array 2-D of same shape as `bgr` representing pot position on image :param mask_rails: mask_mean_shift: numpy.ndarray, optional Array 2-D of same shape as `bgr` representing rails position :param empty_img: numpy.ndarray of integers 3-D array of empty cabin (without plant) :return: result : numpy.ndarray 2-D of same shape as `bgr` Binary image representing plant segmentation of 'bgr' """ if cabin == "cabin-1": image_bin = decision_tree_threshold_phenoarch_1(bgr) elif cabin == "cabin-2": image_bin = decision_tree_threshold_phenoarch_2(bgr) else: image_bin = numpy.zeros(bgr.shape[0:2], 'uint8') if mask_pot is None: mask_pot = numpy.zeros(bgr.shape[0:2], 'uint8') if mask_rails is None: mask_rails = numpy.zeros(bgr.shape[0:2], 'uint8') # Using reference image corrects error out of the mask (pot and rails) if empty_img is not None: # Getting mask pot and rails "hand-made" mask = numpy.bitwise_or(mask_pot, mask_rails) # Making an extended mask to correct possible human error mask_extend = dilate(mask, iterations=3) # Calculating threshold on the extended mask only # We keep the extended mask because human error delete pixel in diff image_bin_threshold_pot = numpy.bitwise_and(image_bin, mask_extend) # Calculating diff between reference image and image with plant image_bin_diff = mean_shift_hsv(bgr, empty_img, mask_hsv=numpy.zeros(bgr.shape[0: 2], 'uint8')) # Out of the mask, keeping only pixels in both diff and threshold image_bin_diff = numpy.bitwise_and( numpy.bitwise_and(image_bin_diff, image_bin), numpy.bitwise_not(mask)) result = numpy.add(image_bin_threshold_pot, image_bin_diff) else: result = image_bin return open(result, iterations=3)
# TODO auto-generate these 2 functions from decisions trees description
[docs]def decision_tree_threshold_phenoarch_1(bgr): """ Implementation of a decision tree Platform specific method, for top image in cabin 1 of Phenoarch :param bgr: numpy.ndarray of integers 3-D array :return: result : numpy.ndarray 2-D of same shape as `bgr` Binary image representing True or False value of each pixel threw decision tree """ hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV) luv = cv2.cvtColor(bgr, cv2.COLOR_BGR2LUV) lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB) # hls = cv2.cvtColor(bgr, cv2.COLOR_BGR2HLS) xyz = cv2.cvtColor(bgr, cv2.COLOR_BGR2XYZ) yuv = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV) image_bin_seuil = numpy.uint8( numpy.bitwise_or( numpy.bitwise_and(lab[:, :, 1] >= 120.5, numpy.bitwise_or( \ numpy.bitwise_and(lab[:, :, 2] < 139.5, \ numpy.bitwise_or( \ numpy.bitwise_and( lab[:, :, 1] >= 122.5, \ numpy.bitwise_and( lab[:, :, 1] < 123.5, \ numpy.bitwise_and( bgr[:, :, 0] < 91.5, \ numpy.bitwise_and( hsv[ :, :, 1] >= 28.5, \ numpy.bitwise_and( yuv[ :, :, 0] >= 52.5, \ numpy.bitwise_or( luv[ :, :, 1] < 94.5, \ numpy.bitwise_and( luv[ :, :, 1] >= 94.5, bgr[ :, :, 2] >= 82.5) \ ) \ ) \ ) \ ) \ ) \ ), numpy.bitwise_and( lab[:, :, 1] < 122.5, \ numpy.bitwise_or( xyz[:, :, 2] < 103.5, \ numpy.bitwise_and( xyz[:, :, 2] >= 103.5, \ numpy.bitwise_and( xyz[ :, :, 2] < 114.5, lab[ :, :, 1] < 121.5) \ ) \ ) \ ) \ ) \ ), \ numpy.bitwise_and(lab[:, :, 2] >= 139.5, \ numpy.bitwise_or( \ numpy.bitwise_and( hsv[:, :, 1] < 55.5, \ numpy.bitwise_and( bgr[:, :, 0] < 143.5, bgr[:, :, 2] < 110.5) \ ), \ numpy.bitwise_and( hsv[:, :, 1] >= 55.5, \ numpy.bitwise_and( xyz[:, :, 2] >= 56.5, \ numpy.bitwise_or( \ numpy.bitwise_and( hsv[ :, :, 1] < 69.5, \ numpy.bitwise_or( hsv[ :, :, 0] >= 32.5, \ numpy.bitwise_and( hsv[ :, :, 0] < 32.5, \ numpy.bitwise_and( hsv[ :, :, 1] >= 61.5, \ numpy.bitwise_or( numpy.bitwise_and( hsv[ :, :, 0] <= 20.5, xyz[ :, :, 2] < 138.5), \ numpy.bitwise_and( hsv[ :, :, 0] < 20.5, \ numpy.bitwise_or( lab[ :, :, 1] < 121.5, \ numpy.bitwise_and( lab[ :, :, 1] >= 121.5, \ numpy.bitwise_or( luv[ :, :, 1] < 97.5, \ numpy.bitwise_and( luv[ :, :, 1] >= 97.5, \ numpy.bitwise_and( yuv[ :, :, 1] >= 134.5, yuv[ :, :, 1] < 137.5) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ), \ numpy.bitwise_and( hsv[ :, :, 1] >= 69.5, \ numpy.bitwise_or( \ numpy.bitwise_and( bgr[ :, :, 1] < 84.5, \ numpy.bitwise_or( yuv[ :, :, 1] < 129.5, yuv[ :, :, 1] >= 135.5) \ ), \ numpy.bitwise_and( bgr[ :, :, 1] >= 84.5, \ numpy.bitwise_or( \ numpy.bitwise_and( hsv[ :, :, 1] < 85.5, yuv[ :, :, 1] < 143.5), \ numpy.bitwise_and( hsv[ :, :, 1] >= 85.5, lab[ :, :, 1] < 151.5) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ), \ numpy.bitwise_and(lab[:, :, 1] < 120.5, \ numpy.bitwise_or(bgr[:, :, 0] < 127.5, \ numpy.bitwise_and( bgr[:, :, 0] >= 127.5, \ numpy.bitwise_and( hsv[:, :, 1] >= 49.5, yuv[:, :, 1] < 205.5) \ ) \ ) \ ) \ ) * 255) return image_bin_seuil
[docs]def decision_tree_threshold_phenoarch_2(bgr): """ Implementation of a decision tree Platform specific method, for top image in cabin 1 of Phenoarch :param bgr: numpy.ndarray of integers 3-D array :return: result : numpy.ndarray 2-D of same shape as `bgr` Binary image representing True or False value of each pixel threw decision tree """ hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV) luv = cv2.cvtColor(bgr, cv2.COLOR_BGR2LUV) lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB) # hls = cv2.cvtColor(bgr, cv2.COLOR_BGR2HLS) xyz = cv2.cvtColor(bgr, cv2.COLOR_BGR2XYZ) yuv = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV) image_bin_seuil = numpy.uint8( \ numpy.bitwise_or( \ numpy.bitwise_and(lab[:, :, 1] >= 121.5, \ numpy.bitwise_or( \ numpy.bitwise_and(lab[:, :, 2] < 146.5, \ numpy.bitwise_or( \ numpy.bitwise_and( lab[:, :, 1] >= 122.5, \ numpy.bitwise_or( \ numpy.bitwise_and( luv[:, :, 1] >= 94.5, \ numpy.bitwise_and( hsv[ :, :, 1] >= 38.5, \ numpy.bitwise_or( \ numpy.bitwise_and( lab[ :, :, 1] >= 124.5, \ numpy.bitwise_and( luv[ :, :, 2] >= 143.5, \ numpy.bitwise_and( hsv[ :, :, 1] >= 57.5, \ numpy.bitwise_and( hsv[ :, :, 0] >= 22.5, yuv[ :, :, 2] < 116.5) \ ) \ ) \ ), \ numpy.bitwise_and( lab[ :, :, 1] < 124.5, \ numpy.bitwise_and( bgr[ :, :, 0] < 119.5, \ numpy.bitwise_or( \ numpy.bitwise_and( hsv[ :, :, 1] < 47.5, \ numpy.bitwise_and( lab[ :, :, 1] < 123.5, bgr[ :, :, 0] < 103.5) \ ), \ numpy.bitwise_and( hsv[ :, :, 1] >= 47.5, bgr[ :, :, 1] >= 56.5) \ ) \ ) \ ) \ ) \ ) \ ), \ numpy.bitwise_and( luv[:, :, 1] < 94.5, \ numpy.bitwise_and( bgr[ :, :, 0] < 110.5, \ numpy.bitwise_and( lab[ :, :, 1] < 123.5, \ numpy.bitwise_or( numpy.bitwise_and( bgr[ :, :, 0] < 63.5, bgr[ :, :, 1] >= 56.5), \ numpy.bitwise_and( bgr[ :, :, 0] >= 63.5, \ numpy.bitwise_and( xyz[ :, :, 0] < 96.5, \ numpy.bitwise_or( luv[ :, :, 2] >= 143.5, \ numpy.bitwise_and( luv[ :, :, 2] < 143.5, \ numpy.bitwise_and( luv[ :, :, 1] < 93.5, bgr[ :, :, 0] < 93.5) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ), \ numpy.bitwise_and( lab[:, :, 1] < 122.5, \ numpy.bitwise_or( \ numpy.bitwise_and( bgr[:, :, 0] >= 112.5, \ numpy.bitwise_and( hsv[ :, :, 1] >= 48.5, bgr[ :, :, 0] < 130.5) \ ), \ numpy.bitwise_or( bgr[:, :, 0] < 98.5, \ numpy.bitwise_and( bgr[ :, :, 0] < 112.5, \ numpy.bitwise_or( hsv[ :, :, 1] >= 38.5, \ numpy.bitwise_and( hsv[ :, :, 1] < 38.5, \ numpy.bitwise_and( bgr[ :, :, 0] < 105.5, hsv[ :, :, 0] < 73.5) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ), \ numpy.bitwise_and(lab[:, :, 2] >= 146.5, bgr[:, :, 0] < 161.5) \ ) \ ), \ numpy.bitwise_and(lab[:, :, 1] < 121.5, \ numpy.bitwise_or( \ numpy.bitwise_and(bgr[:, :, 0] >= 126.5, \ numpy.bitwise_and( hsv[:, :, 1] >= 49.5, \ numpy.bitwise_or( \ numpy.bitwise_and( hsv[:, :, 1] < 56.5, \ numpy.bitwise_and( bgr[:, :, 0] < 165.5, \ numpy.bitwise_or( luv[ :, :, 2] >= 163.5, \ numpy.bitwise_and( luv[ :, :, 2] < 163.5, xyz[ :, :, 0] >= 157.5) \ ) \ ) \ ), \ numpy.bitwise_and( hsv[:, :, 1] >= 56.5, bgr[:, :, 0] < 169.5) \ ) \ ) \ ), \ numpy.bitwise_or(bgr[:, :, 0] < 108.5, \ numpy.bitwise_and( bgr[:, :, 0] < 126.5, \ numpy.bitwise_and( bgr[:, :, 0] >= 108.5, \ numpy.bitwise_or( hsv[:, :, 1] >= 53.5, \ numpy.bitwise_and( hsv[:, :, 1] < 53.5, \ numpy.bitwise_and( luv[ :, :, 2] >= 146.5, \ numpy.bitwise_or( bgr[ :, :, 0] < 116.5, \ numpy.bitwise_and( bgr[ :, :, 0] >= 116.5, hsv[ :, :, 1] >= 35.5) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) * 255) return image_bin_seuil