def image_draw_mask(input_image, input_image_mask, transparency=0.3, warning=True, debug=True): ''' draw a mask on top of an image with certain transparency parameters: input_image: a pil or numpy image input_image_mask: a pil or numpy image transparency: transparency factor outputs: masked_image: uint8 numpy image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) np_image_mask, _ = safe_image(input_image_mask, warning=warning, debug=debug) if debug: assert isscalar(transparency), 'the transparency should be a scalar' assert np_image.shape == np_image_mask.shape, 'the shape of mask should be equal to the shape of input image' if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if isfloatimage(np_image_mask): np_image_mask = (np_image_mask * 255.).astype('uint8') pil_image, pil_image_mask = Image.fromarray(np_image), Image.fromarray( np_image_mask) masked_image = np.array( Image.blend(pil_image, pil_image_mask, alpha=transparency)) return masked_image
def test_safe_image(): image_path = '../lena.jpg' # test when the input image is pil image img_pil = Image.open(image_path) img_bak = np.array(img_pil).copy() copy_image, isnan = safe_image(img_pil) assert CHECK_EQ_NUMPY( copy_image, np.asarray(img_pil)), 'the original image should be equal to the copy' copy_image += 1 assert not CHECK_EQ_NUMPY( copy_image, np.asarray(img_pil)), 'the original image should be equal to the copy' assert CHECK_EQ_NUMPY(img_bak, np.asarray( img_pil)), 'the original image should be equal to the backup version' assert not isnan # test when the input image is a numpy image img_numpy = np.asarray(img_pil) # read only img_bak = img_numpy.copy() copy_image, isnan = safe_image(img_numpy) assert CHECK_EQ_NUMPY( copy_image, img_numpy), 'the original image should be equal to the copy' copy_image += 1 assert not CHECK_EQ_NUMPY( copy_image, img_numpy), 'the original image should be equal to the copy' assert CHECK_EQ_NUMPY( img_bak, img_numpy), 'the original image should be equal to the backup version' assert not isnan print('\n\nDONE! SUCCESSFUL!!\n')
def gray2rgb(input_image, with_color=True, cmap='jet', warning=True, debug=True): ''' convert a grayscale image (1-channel) to a rgb image parameters: input_image: an pil or numpy image with_color: add false colormap output: rgb_image: an uint8 rgb numpy image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if debug: assert isgrayimage_dimension( np_image), 'the input numpy image is not correct: {}'.format( np_image.shape) assert isuintimage( np_image ), 'the input numpy image should be uint8 image in order to use opencv' if with_color: if cmap == 'jet': rgb_image = cv2.applyColorMap(np_image, cv2.COLORMAP_JET) else: assert False, 'cmap %s is not supported' % cmap else: rgb_image = cv2.cvtColor(np_image, cv2.COLOR_GRAY2RGB) return rgb_image
def image_clahe(input_image, clip_limit=2.0, grid_size=8, warning=True, debug=True): ''' do contrast limited adative histogram equalization for an image: could be a color image or gray image the color space used for histogram equalization is LAB parameters: input_image: a pil or numpy image outputs: clahe_image: an uint8 numpy image (rgb or gray) ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image.astype('float32') * 255.).astype('uint8') if debug: assert isuintimage(np_image), 'the input image should be a uint8 image' clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(grid_size, grid_size)) if iscolorimage_dimension(np_image): lab_image = rgb2lab(np_image, warning=warning, debug=debug) input_data = lab_image[:, :, 0] # extract the value channel clahe_lab_image = clahe.apply(input_data) lab_image[:, :, 0] = clahe_lab_image clahe_image = lab2rgb(lab_image, warning=warning, debug=debug) elif isgrayimage_dimension(np_image): clahe_image = clahe.apply(np_image) else: assert False, 'the input image is neither a color image or a grayscale image' return clahe_image
def image_hist_equalization_hsv(input_image, warning=True, debug=True): ''' do histogram equalization for an image: could be a color image or gray image the color space used for histogram equalization is HSV parameters: input_image: a pil or numpy image outputs: equalized_image: an uint8 numpy image (rgb or gray) ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isuintimage(np_image): np_image = np_image.astype('float32') / 255. if debug: assert isfloatimage( np_image), 'the input image should be a float image' if iscolorimage_dimension(np_image): hsv_image = rgb2hsv(np_image, warning=warning, debug=debug) input_data = hsv_image[:, :, 2] # extract the value channel equalized_hsv_image = ( hist_equalization(input_data, num_bins=256, debug=debug) * 255.).astype('uint8') hsv_image[:, :, 2] = equalized_hsv_image equalized_image = hsv2rgb(hsv_image, warning=warning, debug=debug) elif isgrayimage_dimension(np_image): equalized_image = ( hist_equalization(np_image, num_bins=256, debug=debug) * 255.).astype('uint8') else: assert False, 'the input image is neither a color image or a grayscale image' return equalized_image
def image_pad_around(input_image, pad_rect, pad_value=0, warning=True, debug=True): ''' this function is to pad given value to an image in provided region, all images in this function are floating images parameters: input_image: an pil or numpy image pad_rect: a list of 4 non-negative integers, describing how many pixels to pad. The order is [left, top, right, bottom] pad_value: an intger between [0, 255] outputs: img_padded: an uint8 numpy image with padding ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if len(np_image.shape) == 2: np_image = np.expand_dims(np_image, axis=2) # extend the third channel if the image is grayscale if debug: assert isuintimage(np_image), 'the input image is not an uint8 image' assert isinteger(pad_value) and pad_value >= 0 and pad_value <= 255, 'the pad value should be an integer within [0, 255]' assert islistofnonnegativeinteger(pad_rect) and len(pad_rect) == 4, 'the input pad rect is not a list of 4 non-negative integers' im_height, im_width, im_channel = np_image.shape[0], np_image.shape[1], np_image.shape[2] # calculate the padded size of image pad_left, pad_top, pad_right, pad_bottom = pad_rect[0], pad_rect[1], pad_rect[2], pad_rect[3] new_height = im_height + pad_top + pad_bottom new_width = im_width + pad_left + pad_right # padding img_padded = np.zeros([new_height, new_width, im_channel]).astype('uint8') img_padded.fill(pad_value) img_padded[pad_top : new_height - pad_bottom, pad_left : new_width - pad_right, :] = np_image if img_padded.shape[2] == 1: img_padded = img_padded[:, :, 0] return img_padded
def convolve(self, input_image): ''' convolve the kernel with the input image, whatever the input image format is. If the input image is a color image, the filter is expanded to a 3D shape parameters: input_image: an pil or numpy, gray or color image outputs: filtered_image: a float32 numpy image, shape is same as before ''' np_image, _ = safe_image(input_image, warning=self.warning, debug=self.debug) if isuintimage(np_image): np_image = np_image.astype('float32') / 255. if self.debug: assert isfloatimage( np_image), 'the input image should be a float image' self.weights is not None, 'the kernel is not defined yet' if iscolorimage_dimension(np_image): self.weights = self.expand_3d( ) # expand the filter to 3D for color image elif isgrayimage_dimension(np_image): np_image = np_image.reshape( np_image.shape[0], np_image.shape[1]) # squeeze the image dimension to 2 filtered_image = ndimage.filters.convolve(np_image, self.weights) return filtered_image
def image_hwc2chw(input_image, warning=True, debug=True): ''' this function transpose the channels of an image from HWC to CHW parameters: input_image: a pil or numpy HWC image outputs: np_image: a numpy CHW image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if debug: assert np_image.ndim == 3 and np_image.shape[2] == 3, 'the input numpy image does not have a good dimension: {}'.format(np_image.shape) return np.transpose(np_image, (2, 0, 1))
def image_rgb2bgr(input_image, warning=True, debug=True): ''' this function converts a rgb image to a bgr image parameters: input_image: a pil or numpy rgb image outputs: np_image: a numpy bgr image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if debug: assert iscolorimage(np_image), 'the input image is not a color image' np_image = np_image[:, :, ::-1] # convert RGB to BGR return np_image
def hsv2rgb(input_image, warning=True, debug=True): ''' convert a hsv image to a rgb image using opencv package parameters: input_image: an pil or numpy image output: rgb_img: an uint8 rgb numpy image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if debug: assert iscolorimage(np_image), 'the input image should be a rgb image' assert isuintimage(np_image), 'the input numpy image should be uint8 image in order to use opencv' rgb_img = cv2.cvtColor(np_image, cv2.COLOR_HSV2RGB) return rgb_img
def image_resize(input_image, resize_factor=None, target_size=None, interp='bicubic', warning=True, debug=True): ''' resize the image given a resize factor (e.g., 0.25), or given a target size (height, width) e.g., the input image has 600 x 800: 1. given a resize factor of 0.25 -> results in an image with 150 x 200 2. given a target size of (300, 400) -> results in an image with 300 x 400 note that: resize_factor and target_size cannot exist at the same time parameters: input_image: an pil or numpy image resize_factor: a scalar target_size: a list of tuple or numpy array with 2 elements, representing height and width interp: interpolation methods: bicubic or bilinear outputs: resized_image: a numpy uint8 image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if debug: assert interp in ['bicubic', 'bilinear'], 'the interpolation method is not correct' assert (resize_factor is not None and target_size is None) or (resize_factor is None and target_size is not None), 'resize_factor and target_size cannot co-exist' if target_size is not None: if debug: assert isimsize(target_size), 'the input target size is not correct' target_width, target_height = int(round(target_size[1])), int(round(target_size[0])) elif resize_factor is not None: if debug: assert isscalar(resize_factor), 'the resize factor is not a scalar' height, width = np_image.shape[:2] target_width, target_height = int(round(resize_factor * width)), int(round(resize_factor * height)) else: assert False, 'the target_size and resize_factor do not exist' if interp == 'bicubic': resized_image = cv2.resize(np_image, (target_width, target_height), interpolation = cv2.INTER_CUBIC) elif interp == 'bilinear': resized_image = cv2.resize(np_image, (target_width, target_height), interpolation = cv2.INTER_LINEAR) else: assert False, 'interpolation is wrong' return resized_image
def image_rotate(input_image, input_angle, warning=True, debug=True): ''' rotate the image given an angle in degree (e.g., 90). The rotation is counter-clockwise parameters: input_image: an pil or numpy image input_angle: a scalar outputs: rotated_image: a numpy uint8 image ''' if debug: assert isscalar(input_angle), 'the input angle is not a scalar' rotation_angle = safe_angle(input_angle, warning=warning, debug=True) # ensure to be in [-180, 180] np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') pil_image = Image.fromarray(np_image) pil_image = pil_image.rotate(rotation_angle, expand=True) rotated_image = np.array(pil_image).astype('uint8') return rotated_image
def rgb2hsv_v2(input_image, warning=True, debug=True): ''' convert a rgb image to a hsv image, using PIL package, not compatible with opencv package parameters: input_image: an pil or numpy image output: hsv_image: an uint8 hsv numpy image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if debug: assert iscolorimage(np_image), 'the input image should be a rgb image' assert isuintimage(np_image), 'the input numpy image should be uint8 image in order to use PIL' pil_rgb_img = Image.fromarray(np_image) pil_hsv_img = pil_rgb_img.convert('HSV') hsv_img = np.array(pil_hsv_img) return hsv_img
def image_crop_center(input_image, center_rect, pad_value=0, warning=True, debug=True): ''' crop the image around a specific center with padded value around the empty area when the crop width/height are even, the cropped image has 1 additional pixel towards left/up parameters: center_rect: a list contains [center_x, center_y, (crop_width, crop_height)] pad_value: scalar within [0, 255] outputs: img_cropped: an uint8 numpy image crop_bbox: numpy array with shape of (1, 4), user-attempted cropping bbox, might out of boundary crop_bbox_clipped: numpy array with shape of (1, 4), clipped bbox within the boundary ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if len(np_image.shape) == 2: np_image = np.expand_dims(np_image, axis=2) # extend the third channel if the image is grayscale # center_rect and pad_value are checked in get_crop_bbox and pad_around functions if debug: assert isuintimage(np_image), 'the input image is not an uint8 image' im_height, im_width = np_image.shape[0], np_image.shape[1] # calculate crop rectangles crop_bbox = get_center_crop_bbox(center_rect, im_width, im_height, debug=debug) crop_bbox_clipped = clip_bboxes_TLWH(crop_bbox, im_width, im_height, debug=debug) x1, y1, x2, y2 = crop_bbox_clipped[0, 0], crop_bbox_clipped[0, 1], crop_bbox_clipped[0, 0] + crop_bbox_clipped[0, 2], crop_bbox_clipped[0, 1] + crop_bbox_clipped[0, 3] img_cropped = np_image[y1 : y2, x1 : x2, :] # if original image is not enough to cover the crop area, we pad value around outside after cropping xmin, ymin, xmax, ymax = crop_bbox[0, 0], crop_bbox[0, 1], crop_bbox[0, 0] + crop_bbox[0, 2], crop_bbox[0, 1] + crop_bbox[0, 3] if (xmin < 0 or ymin < 0 or xmax > im_width or ymax > im_height): pad_left = max(0 - xmin, 0) pad_top = max(0 - ymin, 0) pad_right = max(xmax - im_width, 0) pad_bottom = max(ymax - im_height, 0) pad_rect = [pad_left, pad_top, pad_right, pad_bottom] img_cropped = image_pad_around(img_cropped, pad_rect=pad_rect, pad_value=pad_value, debug=debug) if len(img_cropped.shape) == 3 and img_cropped.shape[2] == 1: img_cropped = img_cropped[:, :, 0] return img_cropped, crop_bbox, crop_bbox_clipped
def rgb2hsv(input_image, warning=True, debug=True): ''' convert a rgb image to a hsv image using opencv package parameters: input_image: an pil or numpy image output: hsv_image: an uint8 hsv numpy image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if debug: assert iscolorimage_dimension( np_image), 'the input image should be a rgb image' assert isuintimage( np_image ), 'the input numpy image should be uint8 image in order to use opencv' hsv_img = cv2.cvtColor(np_image, cv2.COLOR_RGB2HSV) return hsv_img
def rgb2gray(input_image, warning=True, debug=True): ''' convert a color image to a grayscale image (1-channel) parameters: input_image: an pil or numpy image output: gray_image: an uint8 HW gray numpy image ''' np_image, _ = safe_image(input_image, warning=warning, debug=debug) if isfloatimage(np_image): np_image = (np_image * 255.).astype('uint8') if debug: assert iscolorimage_dimension( np_image), 'the input numpy image is not correct: {}'.format( np_image.shape) assert isuintimage( np_image ), 'the input numpy image should be uint8 image in order to use opencv' gray_image = cv2.cvtColor(np_image, cv2.COLOR_RGB2GRAY) return gray_image