from PIL import Image, ImageDraw import numpy as np # Return a mask based off the specified color # Color should have a shape of (r, g, b) and a float value from 0.0 to 1.0 # Colored should be the image in array form, with expanded dimensions of axis=0, # and the array has values from 0.0 to 1.0, same as color array. def get_mask(colored, color): mask = np.ones(colored.shape, np.uint8) i, j = np.where(np.all(colored[0] == color, axis=-1)) mask[0, i, j] = 0 return mask def image_to_array(image): array = np.asarray(image) return np.array(array / 255.0) # Find all the regions of the masked picture # All marked regions should have the value 0, all else should be 1 def region_by_mask(mask): # Gets all of the coordinates where the mask exists i, j = np.where(np.all(mask[0], axis=-1) == 0) if len(i) == 0: return [] # Creates a tuple of the coordinates coords = [coord for coord in zip(j, i)] # Creates a dictionary with the coordinates as both key and value. neighbors = dict((y, {y}) for y in coords) for x, y in neighbors: candidates = (x + 1, y), (x, y + 1) for candidate in candidates: if candidate in neighbors: neighbors[x, y].add(candidate) neighbors[candidate].add((x, y)) closed_list = set() def connected_component(pixel): region = set() open_list = {pixel} while open_list: pixel = open_list.pop() closed_list.add(pixel) open_list |= neighbors[pixel] - closed_list region.add(pixel) return region regions = [] for pixel in neighbors: if pixel not in closed_list: regions.append(connected_component(pixel)) regions.sort(key=len, reverse=True) return regions # Expand the box surrounding the region area. def expand_bounding_mk2(img, region, expand_factor=1.50, min_size=256, max_size=512): # expand bounding box to capture more context if not isinstance(region, set) and len(region) == 4: min_x, min_y, max_x, max_y = region[0], region[1], region[2], region[3] else: x, y = zip(*region) min_x, min_y, max_x, max_y = min(x), min(y), max(x), max(y) width, height = img.size bb_width = max_x - min_x bb_height = max_y - min_y x_center = (min_x + max_x)//2 y_center = (min_y + max_y)//2 current_size = max(bb_width, bb_height) current_size = int(current_size * expand_factor) bb_width = int(bb_width * expand_factor) bb_height = int(bb_height * expand_factor) if bb_width > max_size: bb_width = max_size if bb_width < min_size: bb_width = min_size if bb_height > max_size: bb_height = max_size if bb_height < min_size: bb_height = min_size x1 = x_center - bb_width//2 x2 = x_center + bb_width//2 y1 = y_center - bb_height//2 y2 = y_center + bb_height//2 x1_square = x1 y1_square = y1 x2_square = x2 y2_square = y2 if (y1_square < 0 or y2_square > (height - 1)) and (x1_square < 0 or x2_square > (width - 1)): # conservative square region if x1_square < 0 and y1_square < 0: x1_square = 0 y1_square = 0 x2_square = bb_width y2_square = bb_height elif x2_square > (width - 1) and y1_square < 0: x1_square = width - bb_width - 1 y1_square = 0 x2_square = width - 1 y2_square = bb_height elif x1_square < 0 and y2_square > (height - 1): x1_square = 0 y1_square = height - bb_height - 1 x2_square = bb_width y2_square = height - 1 elif x2_square > (width - 1) and y2_square > (height - 1): x1_square = width - bb_width - 1 y1_square = height - bb_height - 1 x2_square = width - 1 y2_square = height - 1 else: x1_square = x1 y1_square = y1 x2_square = x2 y2_square = y2 else: if x1_square < 0: difference = x1_square x1_square -= difference x2_square -= difference if x2_square > (width - 1): difference = x2_square - width + 1 x1_square -= difference x2_square -= difference if y1_square < 0: difference = y1_square y1_square -= difference y2_square -= difference if y2_square > (height - 1): difference = y2_square - height + 1 y1_square -= difference y2_square -= difference # If the box is out of bounds, return to the original coordinates if x2_square > width or y2_square > height: print("bounding box out of bounds!") print(x1_square, y1_square, x2_square, y2_square) x1_square, y1_square, x2_square, y2_square = min_x, min_y, max_x, max_y return int(x1_square), int(y1_square), int(x2_square), int(y2_square) # Creates a new big box around the specified box. # Size is the size of the picture, and not the box, which is meant to limit the coordinates of the box # as to not go outside of the picture. # Index 0 of the return array is a boolean mentioning if the box is too big to fit in the picture (False if too big). def bounding_box_check_mk2(size, box, target_size=(512, 512)): inside = True # Get the boundaries of the box minX = min(box[0], box[2]) maxX = max(box[0], box[2]) minY = min(box[1], box[3]) maxY = max(box[1], box[3]) # If the box has a width less than or equal to the target size (512, 512) # a bigger box is created, centered around it b_width = maxX - minX if b_width <= target_size[0]: dist_left = target_size[0] - b_width if dist_left != 0: minX = round(minX - dist_left/2) maxX = round(maxX + dist_left/2) else: print('Censor region is too big') inside = False b_height = maxY - minY if b_height <= target_size[1]: dist_left = target_size[1] - b_height if dist_left != 0: minY = round(minY - dist_left/2) maxY = round(maxY + dist_left/2) else: print('Censor region is too big') inside = False # This shifts the box so that it does not go outside of the picture minX, minY = shift(size, minX, minY) # Shrink/move the box horizontally if it is bigger than the picture if (minX < 0 or minX + target_size[0] > size[0]): print("No possible box of size 512,512 possible") maxX = size[0] else: maxX = minX + target_size[0] minX = max(0, minX) # Shrink/move the box vertically if it is bigger than the picture if (minY < 0 or minY + target_size[1] > size[1]): maxY = size[1] else: maxY = minY + target_size[1] minY = max(0, minY) return [inside, (minX, minY, maxX, maxY)] # Check if box1 fits into big_box by checking if it is within range of all other boxes. # Size is the size of the picture and not the size of the box. def big_bound_check_mk2(size, big_box, box1, boxes, target_size=(512, 512)): inside = True # Initialize maxX,maxY,minX,minY maxX = max(box1[0], box1[2]) maxY = max(box1[1], box1[3]) minX = min(box1[0], box1[2]) minY = min(box1[1], box1[3]) # Check all the boxes for the correct min/max values for box in boxes: maxX = max(maxX, box[0], box[2]) maxY = max(maxY, box[1], box[3]) minX = min(minX, box[0], box[2]) minY = min(minY, box[1], box[3]) # Get the total width of the new big box b_width = maxX - minX # If the new big box has a width less than or equal to the target size (512, 512) # the new box fits into the group, and the big box can be moved accordingly if b_width <= target_size[0]: dist_left = target_size[0] - b_width if dist_left != 0: minX = round(minX - dist_left/2) maxX = round(maxX + dist_left/2) else: inside = False # Same deal for the height as for the width above b_height = maxY - minY if b_height <= target_size[1]: dist_left = target_size[1] - b_height if dist_left != 0: minY = round(minY - dist_left/2) maxY = round(maxY + dist_left/2) else: inside = False # Shift the box if it's close to the edge minX, minY = shift(size, minX, minY) # print("MaxX: {maxx}, MinX: {minx}, MaxY: {maxy}, MinY: {miny}".format(maxx=maxX, minx=minX, maxy=maxY, miny=minY)) # Shrink/move the box horizontally if it is bigger than the picture if (minX < 0 or minX + target_size[0] > size[0]): print("No possible box of size 512,512 possible") maxX = size[0] minX = size[0] - target_size[0] if target_size[0] >= size[0]: minX = 0 else: maxX = minX + target_size[0] # Shrink/move the box vertically if it is bigger than the picture if (minY < 0 or minY + target_size[1] > size[1]): maxY = size[1] minY = size[1] - target_size[1] if target_size[1] >= size[1]: minY = 0 else: maxY = minY + target_size[1] return [inside, (minX, minY, maxX, maxY)] # Shifts the x and y if it's too close to the edge for target_size not to be applicable # Does not reshape the box if the target_size is greater than size of the picture def shift(size, x, y, target_size=(512, 512)): if x < 0: x = 0 if (x + target_size[0]) > size[0]: x = size[0] - target_size[0] if y < 0: y = 0 if (y + target_size[1]) > size[1]: y = size[1] - target_size[1] return x, y # Draws boxes around the found censor regions. if __name__ == '__main__': image = Image.open(r'') no_alpha_image = image.convert('RGB') draw = ImageDraw.Draw(no_alpha_image) ### Original # for region in find_regions(no_alpha_image, [0, 255, 0]): # draw.rectangle(expand_bounding_mk2(no_alpha_image, region), outline=(0, 255, 0)) # no_alpha_image.show() ### END OF ORIGINAL ### With new mask region finder # ori_array = np.asarray(no_alpha_image) # ori_array = np.array(ori_array / 255.0) # ori_array = np.expand_dims(ori_array, axis=0) # mask = get_mask(ori_array, [0.0, 1.0, 0.0]) # regions = region_by_mask(mask) # for region in regions: # draw.rectangle(expand_bounding_mk2(no_alpha_image, region), outline=(0, 255, 0)) # no_alpha_image.show() ### END OF NEW MASK REGION FINDER ### Using big boxes and new region finder width, height = no_alpha_image.size ori_array = np.asarray(no_alpha_image) ori_array = np.array(ori_array / 255.0) ori_array = np.expand_dims(ori_array, axis=0) mask = get_mask(ori_array, [0.0, 1.0, 0.0]) # Find all the masked regions regions = region_by_mask(mask) box_bounds = [] # Group boxes into a bigger box that has size target_size (default: 512x512) for region_counter, region in enumerate(regions, 1): bounding_box = expand_bounding_mk2(no_alpha_image, region, expand_factor=1.25, min_size=64) if (len(box_bounds) == 0): boxCheck = bounding_box_check_mk2((width, height), bounding_box, target_size=(512, 512)) if (boxCheck[0]): box_bounds.append([boxCheck[1], [bounding_box], [region]]) else: print("Censor region exceeds boundary: {}".format(bounding_box)) continue for i in range(len(box_bounds)): boxCheck = big_bound_check_mk2((width, height), box_bounds[i][0], bounding_box, box_bounds[i][1], (512, 512)) if (boxCheck[0]): box_bounds[i][0] = boxCheck[1] box_bounds[i][1].append(bounding_box) box_bounds[i][2].append(region) break else: if (i == len(box_bounds) - 1): boxCheck = bounding_box_check_mk2((width, height), bounding_box, target_size=(512, 512)) if (boxCheck[0]): box_bounds.append([boxCheck[1], [bounding_box], [region]]) else: print("Censor region exceeds boundary: {}".format(bounding_box)) # Iterate over the big boxes and draw them to the picture for indx, region in enumerate(box_bounds): # Alternate colors c = (255 * (0 if (indx) % 3 == 0 else 1), 255 * (0 if (indx + 1) % 3 == 0 else 1), 255 * (0 if (indx + 2) % 3 == 0 else 1)) # print(indx, c) # Display the big box draw.rectangle(region[0], outline=c, width=5) # Display all the boxes that are grouped in the big box, uncomment to show for box in region[1]: draw.rectangle(expand_bounding_mk2(no_alpha_image, box, expand_factor=1.25), outline=c, width=3) no_alpha_image.show() ### END OF BIG BOXES + REGION FINDER