def linear_adjustment(a_img: ImageImpl) -> ImageImpl: """ Given a matrix image representation apply, if necessary, linear transformation to bring values in the pixel value range (0, 255). :param a_img: numpy array of 2 or 3 dimensions. :return: np.ndarray of same shape with values in range """ min_value = a_img.min_value() max_value = a_img.max_value() if MAX_PIXEL_VALUE >= max_value and MIN_PIXEL_VALUE <= min_value: # values are in range return a_img # pixels should be ints no floats # if values are out of range, adjust based on current values if max_value == min_value: # a transformation should only shift values in this case slope = 0 else: slope = (MAX_PIXEL_VALUE - MIN_PIXEL_VALUE) / (max_value - min_value) if max_value == min_value: if max_value > MAX_PIXEL_VALUE: constant = MAX_PIXEL_VALUE elif min_value < MIN_PIXEL_VALUE: constant = MIN_PIXEL_VALUE else: constant = max_value else: # as we want the tranformation to map MIN_PIXEL_VALUE to the min_value found # we just solve y = mx + b for known x, y and m constant = MIN_PIXEL_VALUE - slope * min_value return a_img.mul_scalar(slope).add_scalar(constant)
def bilateral_filter( a_img: ImageImpl, kernel_size: int, sigma_s: float, sigma_r: float ) -> ImageImpl: """ Given an Image instance, apply the bilateral filter kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :param sigma_s: sigma_s value :param sigma_r: sigma_r value :return: transformed image """ img_in = a_img.get_array() if a_img.channels == 1: img_in = img_in[:, :, 0] gaussian = ( lambda r2, sigma: (np.exp(-0.5 * r2 / sigma ** 2) * 3).astype(int) * 1.0 / 3.0 ) # define the window width to be the 3 time the spatial std. dev. to # be sure that most of the spatial kernel is actually captured win_width = int(kernel_size) # initialize the results and sum of weights to very small values for # numerical stability. not strictly necessary but helpful to avoid # wild values with pathological choices of parameters reg_constant = 1e-8 wgt_sum = np.ones(img_in.shape) * reg_constant result = img_in * reg_constant # accumulate the result by circularly shifting the image across the # window in the horizontal and vertical directions. within the inner # loop, calculate the two weights and accumulate the weight sum and # the unnormalized result image for shft_x in range(-win_width, win_width + 1): for shft_y in range(-win_width, win_width + 1): # compute the spatial weight w = gaussian(shft_x ** 2 + shft_y ** 2, sigma_s) # shift by the offsets off = np.roll(img_in, [shft_y, shft_x], axis=[0, 1]) # compute the value weight tw = w * gaussian((off - img_in) ** 2, sigma_r) # accumulate the results result += off * tw wgt_sum += tw # normalize the result and return out = result / wgt_sum dims = len(out.shape) out = np.expand_dims(out, axis=dims) if dims == 2 else out return ImageImpl.from_array(out)
def hough_circle_detector( image: ImageImpl, threshold: int = 0.4, min_radius: int = 10, max_radius: int = 40, steps: int = 100, high_threshold: float = 75, low_threshold: float = 35, ) -> ImageImpl: img = Image.fromarray(image.to_rgb().get_array()) img = img.resize((300, 200), Image.ANTIALIAS) image = ImageImpl(np.array(img)) border_image = border_detection.canny_detection(image, 3, 10, high_threshold, low_threshold, 10) edge_pixels = np.where( border_image.get_array()[..., 0] == constants.MAX_PIXEL_VALUE) rmin = min_radius rmax = max_radius points = [] for r in range(rmin, rmax + 1): for t in range(steps): points.append((r, int(r * cos(2 * pi * t / steps)), int(r * sin(2 * pi * t / steps)))) coordinates = list(zip(edge_pixels[0], edge_pixels[1])) acc = defaultdict(int) for y, x in coordinates: for r, dx, dy in points: a = x - dx b = y - dy acc[(a, b, r)] += 1 circles = [] iterator = sorted(acc.items(), key=lambda i: -i[1]) for k, v in iterator: x, y, r = k if v / steps >= threshold and all( (x - xc)**2 + (y - yc)**2 > rc**2 for xc, yc, rc in circles): print(v / steps, x, y, r) circles.append((x, y, r)) result = image.to_rgb() img = Image.fromarray(result.get_array()) draw_result = ImageDraw.Draw(img) for x, y, r in circles: draw_result.ellipse((x - r, y - r, x + r, y + r), outline=(255, 0, 0, 0)) result = ImageImpl(np.array(img)) return result
def draw_circle(image: ImageImpl, a: float, b: float, radius: float, epsilon: float) -> ImageImpl: x_start = int(a - radius) x_end = int(a + radius) y_start = int(b - radius) y_end = int(b + radius) for y in range(y_start, y_end): for x in range(x_start, x_end): if 0 <= x < image.width and 0 <= y < image.height: x_difference = pow(x - a, 2) y_difference = pow(y - b, 2) squared_radius = pow(radius, 2) value = abs(x_difference + y_difference - squared_radius) if value <= epsilon: image.array[y, x, 0] = 0 image.array[y, x, 1] = constants.MAX_PIXEL_VALUE image.array[y, x, 2] = 0 for i in range(1, 4): if (0 <= x - 1 and x + 1 < image.width and 0 <= y - 1 and y + 1 < image.height): image.array[y + i, x, 1] = constants.MAX_PIXEL_VALUE image.array[y - i, x, 1] = constants.MAX_PIXEL_VALUE image.array[y, x + i, 1] = constants.MAX_PIXEL_VALUE image.array[y, x - i, 1] = constants.MAX_PIXEL_VALUE image.array[y, x, 1] = constants.MAX_PIXEL_VALUE return image
def weighted_median_filter(a_img: ImageImpl, kernel_size: int) -> ImageImpl: """ Given an Image instance, apply the weighted median filter using a square kernel of size kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :return: transformed image """ a_img.convolution( kernel_size, lambda window: _weighted_median(window, kernel_size), ) return a_img
def mean_filter_fast(a_img: ImageImpl, kernel_size: int) -> ImageImpl: """ Given an Image instance, apply the mean filter using a square kernel of size kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :return: transformed image """ kernel_size = int(kernel_size) a_img.convolution_fast( np.ones((kernel_size, kernel_size)), ) return a_img
def high_filter(a_img: ImageImpl, kernel_size: int) -> ImageImpl: """ Given a Image instance, apply high value filter. :param a_img: :param kernel_size: :return: transformed Image instance """ a_img.convolution( kernel_size, lambda window: apply_high_filter(window, int(kernel_size)), ) return a_img
def gaussian_filter(a_img: ImageImpl, kernel_size: int, sigma: float) -> ImageImpl: """ Given an Image instance, apply the weighted gaussian filter using a square kernel of size kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :param sigma: sigma value :return: transformed image """ a_img.convolution( kernel_size, lambda window: _apply_gaussian_filter(window, int(kernel_size), sigma), ) return a_img
def load_image(filename: str) -> ImageImpl: image = ImageIO.load_image(filename) image_matrix = np.array(image) dims = len(image_matrix.shape) image_matrix = (np.expand_dims(image_matrix, axis=dims) if dims == 2 else image_matrix) return ImageImpl(image_matrix)
def has_border_neighbours_without_thresholds(image: ImageImpl, i: int, j: int, four_neighbours: bool): height, width = image.height, image.width image_array = image.get_array()[..., 0] if four_neighbours: increments = [[0, -1], [1, 0], [0, 1], [-1, 0]] else: increments = [ [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], ] for k in range(0, len(increments)): new_i = i + increments[k][0] new_j = j + increments[k][1] if (0 <= new_i < width and 0 <= new_j < height and image_array[new_i, new_j] == constants.MAX_PIXEL_VALUE): return True return False
def sobel_detector(a_img: ImageImpl, return_all: bool = False) -> List[ImageImpl]: """ Given an image object, return the gradient filter (border detection) as denoted by the sobel operator. :param return_all: :param a_img: image matrix representation :return: image gradient filter matrix representation """ horizontal_kernel = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]]) # calcualte vertical gradient vertical_kernel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) horizontal_grad = copy(a_img) horizontal_grad.convolution_fast(horizontal_kernel) vertical_grad = copy(a_img) vertical_grad.convolution_fast(vertical_kernel) # finally the module of the value is calculated as the sqrt([dFx ** 2 + dFy ** 2]) grad_img = ImageImpl( np.sqrt(horizontal_grad.array**2 + vertical_grad.array**2)) result = [grad_img] if return_all: result.append(horizontal_grad) result.append(vertical_grad) return result
def anisodiff(a_img: ImageImpl, steps, b) -> ImageImpl: """ Given an image implementation, calculates the ansiotropic filter as described in the paper by Perona- Malik et al. :param a_img: image matrix representation :param steps: the number of iterations to apply the mask :param b: the leclerc coefficient hyper parameter :return: ImageImpl """ lam = 0.25 # convert to int steps = int(steps) im = a_img.array im_new = np.zeros(im.shape, dtype=im.dtype) for t in range(steps): # calculate the gradient with respect to each cardinal direction # the sneaky way is to get get the far right sub matrix (without the last # row and subtract the far left one, and the do the same for all other directions. dn = im[:-2, 1:-1] - im[1:-1, 1:-1] ds = im[2:, 1:-1] - im[1:-1, 1:-1] de = im[1:-1, 2:] - im[1:-1, 1:-1] dw = im[1:-1, :-2] - im[1:-1, 1:-1] im_new[1:-1, 1:-1] = im[1:-1, 1:-1] + lam * ( leclerc_coef(dn, b) * dn + leclerc_coef(ds, b) * ds + leclerc_coef(de, b) * de + leclerc_coef(dw, b) * dw ) im = im_new return ImageImpl(im)
def high_filter_fast(a_img: ImageImpl, kernel_size: int) -> ImageImpl: """ Given a Image instance, apply high value filter. :param a_img: :param kernel_size: :return: transformed Image instance """ kernel_size = int(kernel_size) kernel = np.ones((kernel_size, kernel_size)) * -1 # augment the center value kernel[int(kernel_size / 2), int(kernel_size / 2)] = kernel_size a_img.convolution_fast( kernel, ) return a_img
def gaussian_filter_fast(a_img: ImageImpl, kernel_size: int, sigma: float) -> ImageImpl: """ Given an Image instance, apply the weighted gaussian filter using a square kernel of size kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :param sigma: sigma value :return: transformed image """ kernel_size = int(kernel_size) kernel = gaussian_kernel(kernel_size, sigma) * MAX_PIXEL_VALUE a_img.convolution_fast( (1 / kernel_size ** 2) * kernel, ) return a_img
def threshold_filter(a_img: ImageImpl, threshold: float) -> ImageImpl: """ Given a matrix image representation, apply treshold transformation and return a binary matrix which follows this transformation: F(img[i,j]):| max_pixel_value if img[i, j] >= threshold | 0 else :param a_img: matrix image representation :param threshold: float between [0, max pixel value] :return: transformed matrix """ # applies if element wise a_img.array = a_img.array >= threshold # transform a boolean matrix binary and scales a_img.array = a_img.array.astype(int) * MAX_PIXEL_VALUE return a_img
def umbralization_with_two_thresholds(a_img: ImageImpl, high_threshold: float, low_threshold: float) -> ImageImpl: image_array = a_img.get_array() res = np.zeros_like(image_array) weak = np.int32(constants.MAX_PIXEL_VALUE / 2) strong = np.int32(constants.MAX_PIXEL_VALUE) strong_i, strong_j, strong_k = np.where(image_array >= high_threshold) weak_i, weak_j, weak_k = np.where((image_array <= high_threshold) & (image_array >= low_threshold)) res[strong_i, strong_j, strong_k] = strong res[weak_i, weak_j, weak_k] = weak return ImageImpl.from_array(res)
def generate_image_with_border(mask_array: np.ndarray, image: ImageImpl) -> ImageImpl: border_image = np.zeros((image.height, image.width, 3), dtype=np.uint8) image_array = image.get_array() for y in range(0, image.height): for x in range(0, image.width): if mask_array[y, x] == -1 or mask_array[y, x] == 1: border_image[y, x, 0] = np.uint8(0) border_image[y, x, 1] = np.uint8(255) border_image[y, x, 2] = np.uint8(0) else: border_image[y, x, 0] = image_array[y, x, 0] border_image[y, x, 1] = image_array[y, x, 1] border_image[y, x, 2] = image_array[y, x, 2] return ImageImpl.from_array(border_image)
def median_filter(a_img: ImageImpl, kernel_size: int) -> ImageImpl: """ Given an Image instance, apply the median filter using a square kernel of size kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :return: transformed image """ a_img.convolution( kernel_size, lambda window: np.median( window.reshape( -1, ) ), ) return a_img
def global_thresholding(image: ImageImpl) -> (ImageImpl, list): img_array = image.get_array() t = [] img_binary = [] for c in range(image.channels): current_t = int(np.mean(img_array[:, :, c])) last_t = 0 while abs(last_t - current_t) >= 0.5: last_t = current_t current_t = _calculate_global_threshold(img_array[:, :, c], last_t) img_binary.append( _array_binarize(img_array[:, :, c], current_t) * constants.MAX_PIXEL_VALUE) t.append(int(current_t)) return ImageImpl.from_array(np.moveaxis(np.array(img_binary), 0, 2)), t
def otsu_thresholding(image: ImageImpl): hists = image.df() img_array = image.get_array() t = [] img_binary = [] for c in range(image.channels): prob = np.array(hists[c]) / len(img_array[:, :, c]) prob_sumcum = np.array(ImageImpl._cdf_hist(hists[c])) / 255 means = _compute_means(prob) global_means = _compute_global_mean(prob) variances = _compute_variance(global_means, means, prob_sumcum) threshold = _get_threshold_from_variance(variances) img_binary.append( _array_binarize(img_array[:, :, c], threshold) * constants.MAX_PIXEL_VALUE) t.append(threshold) return ImageImpl.from_array(np.moveaxis(np.array(img_binary), 0, 2)), t
def negative_img_fun(a_img: ImageImpl) -> ImageImpl: """ Given an image matrix representation, invert pixel values. Following the function: F: PixelDomain -> PixelDomain/ F(r) = -r + Max_Pixel_value :param a_img: matrix image representation :return: transformed matrix """ return a_img.mul_scalar(-1).add_scalar(MAX_PIXEL_VALUE)
def hysteresis( image: ImageImpl, four_neighbours: bool = True, weak: int = int(constants.MAX_PIXEL_VALUE / 2), strong: int = constants.MAX_PIXEL_VALUE, ) -> ImageImpl: height, width = image.height, image.width image_array = image.get_array()[..., 0] border_image = np.array(image_array) for i in range(1, width - 1): for j in range(1, height - 1): if border_image[i, j] == weak: if has_border_neighbours_without_thresholds( image, i, j, four_neighbours): border_image[i, j] = strong else: border_image[i, j] = 0 return ImageImpl(border_image[:, :, np.newaxis])
def canny_detection( a_img: ImageImpl, kernel_size: int, sigma_s: float, sigma_r: float, high_threshold: float, low_threshold: float, four_neighbours: bool = True, ) -> ImageImpl: """ apply caddy mask and border detection :param four_neighbours: :param a_img: :param kernel_size: :param sigma_s: :param sigma_r: :return: """ gray_image = a_img.to_gray() filtered_image = bilateral_filter(gray_image, kernel_size, sigma_s, sigma_r) border_images = sobel_detector(filtered_image, True) synthesized_image = linear_adjustment(border_images[0]) horizontal_image = border_images[1] vertical_image = border_images[2] # this calcualtes the direction in radians angle_matrix = np.arctan2(horizontal_image.array[..., 0], vertical_image.array[..., 0]) # now we convert to degrees angle_matrix = np.rad2deg(angle_matrix) angle_matrix[angle_matrix < 0] += 180 suppressed_image = suppress_false_maximums2(synthesized_image, angle_matrix) # high_threshold = np.amax(suppressed_image.array) * 0.15 # low_threshold = np.amax(suppressed_image.array) * 0.05 umbralized_image = umbralization_with_two_thresholds( suppressed_image, high_threshold, low_threshold) border_image = hysteresis(umbralized_image, bool(four_neighbours)) # import cv2 # edges = cv2.Canny(filtered_image.get_array()[..., 0], 100, 200) # border_image = ImageImpl.from_array(edges[:, :, np.newaxis]) return linear_adjustment(border_image)
def draw_lines(image: ImageImpl, rho: float, theta: float) -> ImageImpl: a = np.cos(theta) b = np.sin(theta) x0 = a * rho y0 = b * rho x1 = int(x0 + 1000 * (-b)) y1 = int(y0 + 1000 * (a)) x2 = int(x0 - 1000 * (-b)) y2 = int(y0 - 1000 * (a)) if x1 == x2: for y in range(0, image.width): image.array[y, int(x0), 0] = constants.MAX_PIXEL_VALUE image.array[y, int(x0), 1] = 0 image.array[y, int(x0), 2] = 0 else: slope = (y2 - y1) / (x2 - x1) origin_ordenate = y0 - slope * x0 for x in range(0, image.width): y = int(slope * x + origin_ordenate) # y = int(- ((np.cos(theta) / np.sin(theta)) * x) + rho / np.sin(theta)) if 0 <= y < image.height: image.array[y, x, 0] = constants.MAX_PIXEL_VALUE image.array[y, x, 1] = 0 image.array[y, x, 2] = 0 return image
def susan_detection(a_img: ImageImpl, threshold: int, low_filter: float, high_filter: float, color: int) -> ImageImpl: """ :param a_img: :param kernel_size: :param threshold: :return: """ # circ_kernel = circular_kernel(7) circ_kernel = np.array([ [0, 0, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1, 0], [0, 0, 1, 1, 1, 0, 0], ]) original = ImageImpl(a_img.get_array()) a_img.apply_filter( circ_kernel, lambda m: _calculate_c_for_susan(m, circ_kernel, threshold)) # a_img.array = np.uint8(a_img.array > 0.75) border_img = np.uint8(a_img.array > low_filter) b_img = border_img - np.uint8(a_img.array > high_filter) border_img = np.zeros((border_img.shape[0], border_img.shape[1], 3)) # because we only have 3 channels color = color % 3 border_img[:, :, color] = b_img[:, :, 0] border_img = ImageImpl(border_img) result = border_img.mul_scalar(255) original = original.to_rgb() # result.add_image(original) return original.add(result)
def get_object_color( mask_array: np.ndarray, image: ImageImpl, top_left_vertex_x: int, top_left_vertex_y: int, bottom_right_vertex_x: int, bottom_right_vertex_y: int, lin: dict, lout: dict, ) -> np.ndarray: color_sum = np.zeros(image.channels) image_array = image.get_array() square_height = (bottom_right_vertex_y - top_left_vertex_y) + 1 square_width = (bottom_right_vertex_x - top_left_vertex_x) + 1 square_size = square_height * square_width for y in range(top_left_vertex_y, bottom_right_vertex_y + 1): for x in range(top_left_vertex_x, bottom_right_vertex_x + 1): mask_array[y, x] = -3 for k in range(0, image.channels): color_sum[k] += image_array[y, x, k] for y in range(top_left_vertex_y, bottom_right_vertex_y + 1): mask_array[y, top_left_vertex_x - 1] = -1 lin[(top_left_vertex_x - 1, y)] = -1 mask_array[y, bottom_right_vertex_x + 1] = -1 lin[(bottom_right_vertex_x + 1, y)] = -1 mask_array[y, top_left_vertex_x - 2] = 1 lout[(top_left_vertex_x - 2, y)] = 1 mask_array[y, bottom_right_vertex_x + 2] = 1 lout[(bottom_right_vertex_x + 2, y)] = 1 for x in range(top_left_vertex_x, bottom_right_vertex_x + 1): mask_array[top_left_vertex_y - 1, x] = -1 lin[(x, top_left_vertex_y - 1)] = -1 mask_array[bottom_right_vertex_y + 1, x] = -1 lin[(x, bottom_right_vertex_y + 1)] = -1 mask_array[top_left_vertex_y - 2, x] = 1 lout[(x, top_left_vertex_y - 2)] = 1 mask_array[bottom_right_vertex_y + 2, x] = 1 lout[(x, bottom_right_vertex_y + 2)] = 1 mask_array[top_left_vertex_y - 1, top_left_vertex_x - 1] = 1 lout[(top_left_vertex_x - 1, top_left_vertex_y - 1)] = 1 mask_array[bottom_right_vertex_y + 1, top_left_vertex_x - 1] = 1 lout[(top_left_vertex_x - 1, bottom_right_vertex_y + 1)] = 1 mask_array[bottom_right_vertex_y + 1, bottom_right_vertex_x + 1] = 1 lout[(bottom_right_vertex_x + 1, bottom_right_vertex_y + 1)] = 1 mask_array[top_left_vertex_y - 1, bottom_right_vertex_x + 1] = 1 lout[(bottom_right_vertex_x + 1, top_left_vertex_y - 1)] = 1 return color_sum / square_size
def salt_and_pepper_apply(image: ImageImpl, p0: float, p1=None) -> ImageImpl: if p1 is None: p1 = 1 - p0 mask = np.random.uniform(low=0.0, high=1.0, size=image.array.shape) mask_p0 = np.where(mask > p0, 1.0, 0.0) mask_p1 = np.where(mask < p1, 1.0, 0.0) mask_p1_add = np.where(mask > p1, 1.0, 0.0) result = image.array * mask_p0 result = result * mask_p1 + mask_p1_add * constants.MAX_PIXEL_VALUE return ImageImpl.from_array(result)
def gamma_fun(a_img: ImageImpl, gamma: float) -> ImageImpl: """ Given a matrix image representation, apply gamma filter :param a_img: image matrix representation :param gamma: gamma value, should be positive value :return: image matrix after filter transformation """ # remember T(img) = C^(1 - gamma) * img ^ (gamma) # calculate C value c = MAX_PIXEL_VALUE # element wise power function img = a_img.apply(lambda x: np.power(x, gamma)) return img.apply(lambda x: x.dot(c ** (1 - gamma)))
def median_filter_fast(a_img: ImageImpl, kernel_size: int) -> ImageImpl: """ Given an Image instance, apply the median filter using a square kernel of size kernel_size. :param a_img: Image instance :param kernel_size: kernel size int :return: transformed image """ # build kernel kernel_size = int(kernel_size) filtered = ndimage.median_filter(a_img.array, size=kernel_size) a_img.array = filtered return a_img
def histogram_equalization(a_img: ImageImpl) -> ImageImpl: """ Given a matrix representation of an image, apply histogram equalization as given by: T(Rk) = sum from 0 to k of Nj/N where: - Rk: k-th grey value in the scale from o - max pixel value - Nj: number of pixel with j-th grey value in the matrix - N: total number of pixels. :param a_img: image matrix representation :return: transformed matrix """ return a_img.equalize_image()