def apply_kernel(image, kernel): """ Performs convolution between the given image and kernel """ if utils.is_color(image): result_b = convolve2d(image[:, :, 0], kernel, mode='same', fillvalue=np.median(image[:, :, 0])) result_g = convolve2d(image[:, :, 1], kernel, mode='same', fillvalue=np.median(image[:, :, 1])) result_r = convolve2d(image[:, :, 2], kernel, mode='same', fillvalue=np.median(image[:, :, 2])) channels_list = [] # Trim values lower than 0 or higher than 255 and convert to uint8 for openCV compatibility for channel in 'bgr': underflow_mask = locals()['result_' + channel] < 0 result_temp = np.where(underflow_mask, 0, locals()['result_' + channel]) result_temp = np.where(result_temp > 255, 255, result_temp) result_temp = result_temp.astype(np.uint8) channels_list.append(result_temp) filtered_image = utils.merge_channels(channels_list) else: # Trim values lower than 0 or higher than 255 and convert to uint8 for openCV compatibility filtered_image = convolve2d(image, kernel, mode='same') filtered_image = np.where(filtered_image < 0, 0, filtered_image) filtered_image = np.where(filtered_image > 255, 255, filtered_image) filtered_image = filtered_image.astype(np.uint8) return filtered_image
def lines_shuffle(channels, state): """ Shuffles the lines of an image (The pixels on each line are left unchanged) """ # Shuffle the lines in each channel, with the same permutation for channel in channels: np.random.shuffle(channel) # Reset the state so that next shuffle is same permutation np.random.set_state(state) shuffled_image = utils.merge_channels(channels) return shuffled_image
def columns_shuffle(channels, state): """ Shuffles the columns of an image (The pixels on each column are left unchanged) """ # Shuffle the columns in each channel, with the same permutation for channel in channels: transpose = np.transpose(channel) np.random.shuffle(transpose) channel = np.transpose(transpose) # Reset the state so that next shuffle is same permutation np.random.set_state(state) shuffled_image = utils.merge_channels(channels) return shuffled_image
def pixels_shuffle(channels, state): """ Shuffles the pixels of an image """ # Shuffle the pixels in each channel, with the same permutation for channel in channels: pixel_list = np.ravel( channel) # Flattens the channel matrix to an array np.random.shuffle(pixel_list) channel = np.reshape(pixel_list, channel.shape) # Reset the state so that next shuffle is same permutation np.random.set_state(state) shuffled_image = utils.merge_channels(channels) return shuffled_image
def split_channels(image, extra_inputs, parameters): """ Splits an image into its channels and returns them. Arguments: *image* (NumPy array) -- the image to be split *extra_inputs* (dictionary) -- a dictionary holding any extra inputs for the call (empty) *parameters* (dictionary) -- a dictionary containing following keys: *spectrum* (str, optional) -- the spectrum in which the channels will be represented; possible values are *grayscale* and *color*; default value is *color* Returns: list of NumPy array uint8 -- list containing the channels of the image """ if utils.is_color(image): b = image[:, :, 0] g = image[:, :, 1] r = image[:, :, 2] if 'spectrum' in parameters: spectrum = parameters['spectrum'] else: spectrum = 'color' if spectrum == 'color': zeros = np.zeros((image.shape[:2]), dtype=np.uint8) b = utils.merge_channels([b, zeros, zeros]) g = utils.merge_channels([zeros, g, zeros]) r = utils.merge_channels([zeros, zeros, r]) return [b, g, r] return [image]
def channels_shuffle(channels): """ Shuffles the channels of an image """ # Keep shuffling the indices list until a non-stationary permutation has # been obtained, as to ensure the output image is not the same as the input indices = np.arange(len(channels)) while (np.arange(len(channels)) == indices).all() and len(channels) != 1: np.random.shuffle(indices) # Build the output image by merging the channels in the shuffled order shuffled_channels = [] for i in indices: shuffled_channels.append(channels[i]) shuffled_image = utils.merge_channels(shuffled_channels) return shuffled_image
def sepia(image, extra_inputs={}, parameters={}): """ Applies a **Sepia Filter** onto an image. \n Arguments: *image* (NumPy array) -- the image to be filtered *extra_inputs* (dictionary) -- a dictionary holding any extra inputs for the call (empty) *parameters* (dictionary) -- a dictionary holding parameter values (empty) Returns: list of NumPy array uint8 -- list containing the filtered image """ # Apply the Sepia formulas if utils.is_color(image): result_red = image[:, :, 2] * 0.393 + image[:, :, 1] * 0.769 + image[:, :, 0] * 0.189 result_green = image[:, :, 2] * 0.349 + image[:, :, 1] * 0.686 + image[:, :, 0] * 0.168 result_blue = image[:, :, 2] * 0.272 + image[:, :, 1] * 0.534 + image[:, :, 0] * 0.131 else: result_red = image * 0.393 + image * 0.769 + image * 0.189 result_green = image * 0.349 + image * 0.686 + image * 0.168 result_blue = image * 0.272 + image * 0.534 + image * 0.131 # Trim values greater than 255 result_red = np.where(result_red > 255, 255, result_red) result_green = np.where(result_green > 255, 255, result_green) result_blue = np.where(result_blue > 255, 255, result_blue) # Round the values and convert to int result_red = np.uint8(np.rint(result_red)) result_green = np.uint8(np.rint(result_green)) result_blue = np.uint8(np.rint(result_blue)) sepia_image = utils.merge_channels([result_blue, result_green, result_red]) return [sepia_image]
def build_mosaic(image, texture, technique, alpha, resolution, redundancy): """ Helper function which actually builds the mosaic """ if utils.is_grayscale(image): image = utils.merge_channels([image, image, image]) else: image = image[:, :, :3] # Load the appropriate image library information database_path = os.path.join(project_path, 'backend', 'miscellaneous', 'database') pickle_name = texture + '_' + resolution + '.pickle' pickle_path = os.path.join(database_path, pickle_name) with open(pickle_path, 'rb') as p: tiles_height, tiles_width = pickle.load(p) tiles_averages_dict = pickle.load(p) # Initialise necessary variables tiles_averages = np.zeros((len(tiles_averages_dict), 3)) tiles_averages_values = list(tiles_averages_dict.values()) tiles_averages_keys = list(tiles_averages_dict.keys()) for i in range(len(tiles_averages_dict)): tiles_averages[i] = np.array(tiles_averages_values[i]) image_height, image_width = image.shape[:2] mosaic_lines = int(image_height / tiles_height) mosaic_columns = int(image_width / tiles_width) mosaic_image = np.zeros( (mosaic_lines * tiles_height, mosaic_columns * tiles_width, 3), dtype=np.uint8) tile_usage_grid = np.ones( (mosaic_lines, mosaic_columns), dtype=np.uint8) * -1 # For each block: # Compute the average r, g, b values of the block and put them in a vector # Compute the distances between the avg vector of the block and each of the avg vectors of the tiles # Replace the current block with the tile located at the shortest distance from the block block_averages = np.zeros((1, 3)) for line in range(mosaic_lines): for column in range(mosaic_columns): block = image[line * tiles_height:(line + 1) * tiles_height, column * tiles_width:(column + 1) * tiles_width] block_averages[0] = np.array( (np.mean(block[:, :, 0]), np.mean(block[:, :, 1]), np.mean(block[:, :, 2]))) distances = cdist(block_averages, tiles_averages) # Determine the index of the closest compatible tile if redundancy: closest_tile_index = np.where( distances == np.min(distances))[1][0] else: closest_tile_index = get_closest_usable_tile_index( distances, tile_usage_grid, line, column) closest_tile_name = tiles_averages_keys[closest_tile_index] closest_tile_path = os.path.join(database_path, texture + '_' + resolution, closest_tile_name) closest_tile = cv2.imread(closest_tile_path, cv2.IMREAD_UNCHANGED) mosaic_image[line * tiles_height:(line + 1) * tiles_height, column * tiles_width:(column + 1) * tiles_width] = closest_tile tile_usage_grid[line][column] = closest_tile_index if technique == 'alternative': # Overlay the mosaic on the input image image_copy = utils.resize_dimension(image, *mosaic_image.shape[:2]) mosaic_image = image_copy * (1 - alpha) + mosaic_image * alpha return mosaic_image
def high_pass(image, extra_inputs, parameters): """Applies a **High Pass Filter** on an image. \n The image is converted into the frequency domain (using the *Fast Fourier Transform*) and only the frequencies higher than the cutoff frequency are let through. *High-frequency emphasis* can be achieved by providing an *offset* greater than 0 (0 is default) and a *multiplier* greater than 1 (1 is default). The filter is then transformed by the equation: emphasisFilter = offset + multiplier * highpassFilter Arguments: *image* (NumPy array) -- the image on which the filter is to be applied *extra_inputs* (dictionary) -- a dictionary holding any extra inputs for the call (empty) *parameters* (dictionary) -- a dictionary containing following keys: *cutoff* (int) -- the minimum frequency to be let through by the filter *offset* (int, optional) -- number used for avoiding the reduction of the DC term to 0; default value is 0, which does not prevent reduction of the DC term *multiplier* (int, optional) -- number used for emphasizing frequencies; default value is 1, which does not have any effect *type* (str, optional) -- the type of high-pass filter to be applied; possible values are: *ideal*, *butterworth*, *gaussian*; default value is *gaussian* *order* (int, optional) -- the order used for Butterworth filtering; default value is 2 *filename* (str, optional) -- the name of the image file to be filtered, used for checking whether the corresponding FFT(s) are serialized on the server or not Returns: list of NumPy array uint8 -- list containing the filtered image """ # Parameter validation and assignment if 'offset' in parameters: offset = parameters['offset'] else: offset = 0 if 'multiplier' in parameters: multiplier = parameters['multiplier'] else: multiplier = 1 if 'type' in parameters: filter_type = parameters['type'] else: filter_type = 'gaussian' if 'order' in parameters: order = parameters['order'] else: order = 2 if 'filename' in parameters: filename = parameters['filename'] else: filename = '' image_h, image_w = image.shape[:2] # Take image dimensions # Compute the cutoff frequency as a percentage from the smaller dimension of the image cutoff_dimension = image_h if image_h < image_w else image_w cutoff = parameters['cutoff'] / 100 * cutoff_dimension padded_h, padded_w = 2 * image_h, 2 * image_w # Obtain the padding parameters # Check whether the FFTs of the image have been serialized or not deserializing, file_not_found = False, False pickles_path = os.path.join(project_path, 'webui', 'static', 'tempdata') if filename != '': filename, extension = filename.split('.') if utils.is_color(image): files_to_check = [filename + '_' + c + '_fft.pickle' for c in 'bgr'] else: files_to_check = [filename + '_fft.pickle'] for file in files_to_check: if not os.path.isfile(os.path.join(pickles_path, file)): file_not_found = True if not file_not_found: deserializing = True # Deserialize the FFTs if possible if deserializing: padded_image_FFTs = [] for file in files_to_check: f = open(os.path.join(pickles_path, file), 'rb') padded_image_FFTs.append(pickle.load(f)) f.close() print('Deserialized', file) else: # Create padded image if utils.is_color(image): padded_image = np.zeros((padded_h, padded_w, len(utils.get_channels(image))), np.uint8) padded_image[0:image_h, 0:image_w, :] = image else: padded_image = np.zeros((padded_h, padded_w), np.uint8) padded_image[0:image_h, 0:image_w] = image # Take the FFTs of the padded image channels padded_image_FFTs = utils.get_FFTs(padded_image) # Compute the filter image if filter_type == 'ideal': filter_image = ideal_filter('high', (padded_h, padded_w), cutoff) elif filter_type == 'butterworth': filter_image = butterworth_filter('high', (padded_h, padded_w), cutoff, order) else: filter_image = gaussian_filter('high', (padded_h, padded_w), cutoff) # Perform High-frequency emphasis if multiplier == 1: filter_image = offset + filter_image else: filter_image = offset + np.multiply(multiplier, filter_image) # Apply the filter to the FFTs filtered_FFTs = [np.multiply(channelFFT, filter_image) for channelFFT in padded_image_FFTs] # Take the inverse FFT of the filtered padded image FFT components result_components = [np.real(np.fft.ifft2(np.fft.ifftshift(filteredComponent))) for filteredComponent in filtered_FFTs] # Obtain the result image if len(result_components) == 1: result_image = result_components[0] else: result_image = utils.merge_channels(result_components) # Trim values lower than 0 or higher than 255 result_image = np.where(result_image > 255, 255, result_image) result_image = np.where(result_image < 0, 0, result_image) # Round the values and unpad the image result_image = np.uint8(np.rint(result_image)) if len(result_components) == 1: result_image = result_image[0:image_h, 0:image_w] else: result_image = result_image[0:image_h, 0:image_w, :] return [result_image]
def visible_watermark(image, extra_inputs, parameters): """ Embeds a watermark image over a host image, using the visible watermarking technique; the watermark is scaled to the selected size and is embedded into the selected location, with the selected transparency Arguments: *image* (NumPy array) -- the image to be watermarked *extra_inputs* (dictionary) -- a dictionary holding any extra inputs for the call *Watermark* (NumPy array) -- the image to be embedded on top of the host image *parameters* (dictionary) -- a dictionary containing following keys: *transparency* (str, optional) -- specifies how will the watermark image be overlayed over the host image; possible values are: *opaque*, *transparent* and *very transparent*; default value is *transparent* *location* (str, optional) -- specifies the location where the watermark image will be embedded; possible values are: *bottom right*, *bottom left*, *top right*, *top left*, *center*, *everywhere*; default value is *bottom right* *size* (int, optional) -- specifies the maximum width of the watermark image, as a percentage of the width of the host image; possible values are: 10 to 90, with increments of 10; default value is 20 (%) Returns: list of NumPy array uint8 -- list containing the watermarked image Observations: If the watermark image's width is already smaller than the maximum watermark width computed from *size*, the watermark image will be left unchanged. If the height of the watermark image (after width adjustment) is greater than the height of the host image, the watermark image will be rescaled as to fit the host image """ # Load the extra parameter, the watermark image watermark = extra_inputs['Watermark'] # Parameters extraction if 'transparency' in parameters: mode = parameters['transparency'] else: mode = 'transparent' if 'location' in parameters: location = parameters['location'] else: location = 'bottom right' if 'size' in parameters: size = parameters['size'] else: size = 20 # Check if the watermark image needs rescaling image_h, image_w = image.shape[:2] watermark_h, watermark_w = watermark.shape[:2] maximum_watermark_width = int(size / 100 * image_w) if watermark_w > maximum_watermark_width: # Resize watermark image by new width watermark = utils.resize_dimension(watermark, new_width=maximum_watermark_width) # The watermark dimensions need to be updated watermark_h, watermark_w = watermark.shape[:2] if watermark_h > image_h: # Resize watermark image by height of host image watermark = utils.resize_dimension(watermark, new_height=image_h) # If any of the images are grayscale, convert them to the color equivalent if utils.is_grayscale(image): image = utils.merge_channels([image, image, image]) if utils.is_grayscale(watermark): watermark = utils.merge_channels([watermark, watermark, watermark]) # Remove the alpha channel from both images (if present) if image.shape[2] == 4: image = image[:, :, :3] if watermark.shape[2] == 4: watermark = watermark[:, :, :3] # Apply the watermark over the host image; alpha blending technique is used # result = background * (1 - alpha) + foreground * alpha watermarked_image = image.copy() # Compute the alpha level needed for alpha blending if mode == 'opaque': alpha = 1 elif mode == 'transparent': alpha = 170 / 255 else: alpha = 85 / 255 # Compute the region of interest, based on the location specified by user if location == 'top left': line_start = 10 line_end = watermark_h + 10 column_start = 10 column_end = watermark_w + 10 elif location == 'top right': line_start = 10 line_end = watermark_h + 10 column_start = image_w - watermark_w - 10 column_end = image_w - 10 elif location == 'bottom left': line_start = image_h - watermark_h - 10 line_end = image_h - 10 column_start = 10 column_end = watermark_w + 10 elif location == 'bottom right': line_start = image_h - watermark_h - 10 line_end = image_h - 10 column_start = image_w - watermark_w - 10 column_end = image_w - 10 elif location == 'center': line_start = int(image_h / 2 - watermark_h / 2) line_end = int(image_h / 2 + watermark_h / 2) column_start = int(image_w / 2 - watermark_w / 2) column_end = int(image_w / 2 + watermark_w / 2) elif location == 'everywhere': # Compute number of horizontal and vertical repeats, respectively repeat_x = image_w // watermark_w repeat_y = image_h // watermark_h for x in range(repeat_x): for y in range(repeat_y): line_start = watermark_h * y line_end = watermark_h * (y + 1) column_start = watermark_w * x column_end = watermark_w * (x + 1) watermarked_image[line_start: line_end, column_start: column_end, : 3] = \ image[line_start: line_end, column_start: column_end, : 3] * (1 - alpha) + \ watermark[:, :, : 3] * alpha return [watermarked_image] else: raise ValueError("'location' parameter value not allowed") # Overlay the watermark on the host image watermarked_image[line_start: line_end, column_start: column_end, : 3] = \ image[line_start: line_end, column_start: column_end, : 3] * (1 - alpha) + \ watermark[:, :, : 3] * alpha return [watermarked_image]