def load_image_gt(dataset, config, image_id, augment=False, augmentation=None, use_mini_mask=False): image = dataset.load_image(image_id) mask, class_ids = dataset.load_mask(image_id) original_shape = image.shape image, window, scale, padding, crop = utils.resize_image( image, min_dim=config.IMAGE_MIN_DIM, min_scale=config.IMAGE_MIN_SCALE, max_dim=config.IMAGE_MAX_DIM, mode=config.IMAGE_RESIZE_MODE) mask = utils.resize_mask(mask, scale, padding, crop) if augment: logging.warning("'augment' is deprecated. Use 'augmentation' instead.") if random.randint(0, 1): image = np.fliplr(image) mask = np.fliplr(mask) if augmentation: import imgaug MASK_AUGMENTERS = ["Sequential", "SomeOf", "OneOf", "Sometimes", "Fliplr", "Flipud", "CropAndPad", "Affine", "PiecewiseAffine"] def hook(images, augmenter, parents, default): """Determines which augmenters to apply to masks.""" return augmenter.__class__.__name__ in MASK_AUGMENTERS image_shape = image.shape mask_shape = mask.shape det = augmentation.to_deterministic() image = det.augment_image(image) mask = det.augment_image(mask.astype(np.uint8), hooks=imgaug.HooksImages(activator=hook)) assert image.shape == image_shape, "Augmentation shouldn't change image size" assert mask.shape == mask_shape, "Augmentation shouldn't change mask size" mask = mask.astype(np.bool) _idx = np.sum(mask, axis=(0, 1)) > 0 mask = mask[:, :, _idx] class_ids = class_ids[_idx] bbox = utils.extract_bboxes(mask) active_class_ids = np.zeros([dataset.num_classes], dtype=np.int32) source_class_ids = dataset.source_class_ids[dataset.image_info[image_id]["source"]] active_class_ids[source_class_ids] = 1 if use_mini_mask: mask = utils.minimize_mask(bbox, mask, config.MINI_MASK_SHAPE) image_meta = compose_image_meta(image_id, original_shape, image.shape, window, scale, active_class_ids) return image, image_meta, class_ids, bbox, mask
def load_image_gt(dataset, config, image_id, augment=False, use_mini_mask=False): """ Load and return ground truth data for an image (image, mask, bounding boxes). Inputs: -------- augment: If true, apply random image augmentation. Currently, only horizontal flipping is offered. use_mini_mask: If False, returns full-size masks that are the same height and width as the original image. These can be big, for example 1024x1024x100 (for 100 instances). Mini masks are smaller, typically, 224x224 and are generated by extracting the bounding box of the object and resizing it to MINI_MASK_SHAPE. Returns: --------- image: [height, width, 3] shape: the original shape of the image before resizing and cropping. class_ids: [instance_count] Integer class IDs bbox: [instance_count, (y1, x1, y2, x2)] mask: [height, width, instance_count]. The height and width are those of the image unless use_mini_mask is True, in which case they are defined in MINI_MASK_SHAPE. """ # Load image and mask # print('=========================') # print(' Load Image GT: ', image_id) # print('=========================') image = dataset.load_image(image_id) mask, class_ids = dataset.load_mask(image_id) # print(mask.shape, class_ids.shape) # for i in range( class_ids.shape[-1]) : # print( 'mask ',i, ' class_id :', class_ids[i], mask[:,:,i].shape) # print() # print(np.array2string(np.where(mask[:,:,i],1,0),max_line_width=134, separator = '')) shape = image.shape image, window, scale, padding = utils.resize_image( image, min_dim=config.IMAGE_MIN_DIM, max_dim=config.IMAGE_MAX_DIM, padding=config.IMAGE_PADDING) mask = utils.resize_mask(mask, scale, padding) # print('after resize_mask shape is :',mask.shape) # Random horizontal flips. if augment: if random.randint(0, 1): image = np.fliplr(image) mask = np.fliplr(mask) # Bounding boxes. Note that some boxes might be all zeros # if the corresponding mask got cropped out. # bbox: [num_instances, (y1, x1, y2, x2)] bbox = utils.extract_bboxes(mask) # print('boxes are: \n', bbox) ## Active classes # Different datasets have different classes, so track the # classes supported in the dataset of this image. active_class_ids = np.zeros([dataset.num_classes], dtype=np.int32) source_class_ids = dataset.source_class_ids[dataset.image_info[image_id] ["source"]] active_class_ids[source_class_ids] = 1 # Resize masks to smaller size to reduce memory usage if use_mini_mask: mask = utils.minimize_mask(bbox, mask, config.MINI_MASK_SHAPE) # print('after use_mini_mask shape is :',mask.shape) # Image meta data image_meta = utils.compose_image_meta(image_id, shape, window, active_class_ids) return image, image_meta, class_ids, bbox, mask
def load_image_gt(dataset, config, image_id, augment=False, augmentation=None, use_mini_mask=False): """Load and return ground truth data for an image (image, mask, bounding boxes). augment: (deprecated. Use augmentation instead). If true, apply random image augmentation. Currently, only horizontal flipping is offered. augmentation: Optional. An imgaug (https://github.com/aleju/imgaug) augmentation. For example, passing imgaug.augmenters.Fliplr(0.5) flips images right/left 50% of the time. use_mini_mask: If False, returns full-size masks that are the same height and width as the original image. These can be big, for example 1024x1024x100 (for 100 instances). Mini masks are smaller, typically, 224x224 and are generated by extracting the bounding box of the object and resizing it to MINI_MASK_SHAPE. Returns: image: [height, width, 3] shape: the original shape of the image before resizing and cropping. class_ids: [instance_count] Integer class IDs bbox: [instance_count, (y1, x1, y2, x2)] mask: [height, width, instance_count]. The height and width are those of the image unless use_mini_mask is True, in which case they are defined in MINI_MASK_SHAPE. """ # Load image and mask image = dataset.load_image(image_id) mask, class_ids = dataset.load_mask(image_id) original_shape = image.shape image, window, scale, padding, crop = utils.resize_image( image, min_dim=config.IMAGE_MIN_DIM, min_scale=config.IMAGE_MIN_SCALE, max_dim=config.IMAGE_MAX_DIM, mode=config.IMAGE_RESIZE_MODE) mask = utils.resize_mask(mask, scale, padding, crop) # Random horizontal flips. # TODO: will be removed in a future update in favor of augmentation if augment: logging.warning("'augment' is deprecated. Use 'augmentation' instead.") if random.randint(0, 1): image = np.fliplr(image) mask = np.fliplr(mask) # Augmentation # This requires the imgaug lib (https://github.com/aleju/imgaug) if augmentation: import imgaug # Augmenters that are safe to apply to masks # Some, such as Affine, have settings that make them unsafe, so always # test your augmentation on masks MASK_AUGMENTERS = [ "Sequential", "SomeOf", "OneOf", "Sometimes", "Fliplr", "Flipud", "CropAndPad", "Affine", "PiecewiseAffine" ] def hook(images, augmenter, parents, default): """Determines which augmenters to apply to masks.""" return augmenter.__class__.__name__ in MASK_AUGMENTERS # Store shapes before augmentation to compare image_shape = image.shape mask_shape = mask.shape # Make augmenters deterministic to apply similarly to images and masks det = augmentation.to_deterministic() image = det.augment_image(image) # Change mask to np.uint8 because imgaug doesn't support np.bool mask = det.augment_image(mask.astype(np.uint8), hooks=imgaug.HooksImages(activator=hook)) # Verify that shapes didn't change assert image.shape == image_shape, "Augmentation shouldn't change image size" assert mask.shape == mask_shape, "Augmentation shouldn't change mask size" # Change mask back to bool mask = mask.astype(np.bool) # Note that some boxes might be all zeros if the corresponding mask got cropped out. # and here is to filter them out _idx = np.sum(mask, axis=(0, 1)) > 0 mask = mask[:, :, _idx] class_ids = class_ids[_idx] # Bounding boxes. Note that some boxes might be all zeros # if the corresponding mask got cropped out. # bbox: [num_instances, (y1, x1, y2, x2)] bbox = utils.extract_bboxes(mask) # Active classes # Different datasets have different classes, so track the # classes supported in the dataset of this image. active_class_ids = np.zeros([dataset.num_classes], dtype=np.int32) source_class_ids = dataset.source_class_ids[dataset.image_info[image_id] ["source"]] active_class_ids[source_class_ids] = 1 # Resize masks to smaller size to reduce memory usage if use_mini_mask: mask = utils.minimize_mask(bbox, mask, config.MINI_MASK_SHAPE) # Image meta data image_meta = compose_image_meta(image_id, original_shape, image.shape, window, scale, active_class_ids) return image, image_meta, class_ids, bbox, mask
def cleanImage(datasetPath: str, imageName: str, cleaningClasses: str, excludeClasses=None, imageFormat="jpg", cleanMasks=False, minAreaThreshold=300, config: Config = None): """ Creating the full_images directory and cleaning the base image by removing non-cleaning-class areas :param excludeClasses: :param datasetPath: the dataset that have been wrapped :param imageName: the image name :param cleaningClasses: the class to use to clean the image :param cleanMasks: if true, will clean masks based on the cleaning-class-mask :param imageFormat: the image format to use to save the image :param minAreaThreshold: remove mask if its area is smaller than this threshold :param config: config object :return: None """ assert cleaningClasses is not None and cleaningClasses != "", "Cleaning class is required." if type(cleaningClasses) is str: cleaningClasses = [cleaningClasses] if type(excludeClasses) is str: excludeClasses = [excludeClasses] # Getting the base image path = os.path.join(datasetPath, imageName, '{folder}', f"{imageName}.{imageFormat}") imagePath = path.format(folder='images') fullImagePath = path.format(folder='full_images') image = cv2.imread(imagePath) # Fusing all the cleaning-class masks and then cleaning the image and if needed the masks cleaningClassMasks = gatherClassesMasks(datasetPath, imageName, image.shape, cleaningClasses) if excludeClasses is None: excludedClassMasks = None else: excludedClassMasks = gatherClassesMasks(datasetPath, imageName, image.shape, excludeClasses) if cleaningClassMasks is not None or excludedClassMasks is not None: if cleaningClassMasks is None: cleaningClassMasks = np.ones_like(image)[..., 0] * 255 if excludedClassMasks is not None: excludedClassMasks = cv2.bitwise_not(excludedClassMasks) cleaningClassMasks = cv2.bitwise_and(cleaningClassMasks, excludedClassMasks) # Copying the full image into the correct directory os.makedirs(os.path.dirname(fullImagePath), exist_ok=True) shutil.copy2(imagePath, fullImagePath) # Cleaning the image and saving it image = cv2.bitwise_and( image, np.repeat(cleaningClassMasks[:, :, np.newaxis], 3, axis=2)) cv2.imwrite(imagePath, image, CV2_IMWRITE_PARAM) # Cleaning masks so that they cannot exist elsewhere if cleanMasks: folderToRemove = [] for folder in os.listdir(os.path.join(datasetPath, imageName)): folderPath = os.path.join(datasetPath, imageName, folder) # Checking only for the other classes folder skipClasses = ["images", "full_images"] skipClasses.extend(cleaningClasses) skipClasses.extend(excludeClasses) if os.path.isdir(folderPath) and folder not in skipClasses: # For each mask of the folder for maskImageFileName in os.listdir(folderPath): maskImagePath = os.path.join(folderPath, maskImageFileName) mask = loadSameResImage(maskImagePath, image.shape) areaBefore = getBWCount(mask)[1] # If mask is not empty if areaBefore > 0: # Cleaning it with the cleaning-class masks mask = cv2.bitwise_and(mask, cleaningClassMasks) areaAfter = getBWCount(mask)[1] else: areaAfter = areaBefore # If mask was empty or too small after cleaning, we remove it if areaBefore == 0 or areaAfter < minAreaThreshold: os.remove(maskImagePath) elif areaBefore != areaAfter: # If mask has is different after cleaning, we replace the original one try: try: idMask = int( maskImageFileName.split('.')[0].split( '_')[1]) except ValueError: # If we could not retrieve the original mask ID, give it a unique one idMask = int(time()) # If mini-mask are enabled, we minimize it before saving it bbox_coordinates = "" if config is not None and config.is_using_mini_mask( ): bbox = extract_bboxes(mask) mask = minimize_mask( bbox, mask, config.get_mini_mask_shape()) mask = mask.astype(np.uint8) * 255 y1, x1, y2, x2 = bbox bbox_coordinates = f"_{y1}_{x1}_{y2}_{x2}" # Saving cleaned mask outputName = f"{imageName}_{idMask:03d}{bbox_coordinates}.{imageFormat}" cv2.imwrite( os.path.join(folderPath, outputName), mask, CV2_IMWRITE_PARAM) if outputName != maskImageFileName: # Remove former mask if not the same name os.remove(maskImagePath) except Exception: print(f"Error on {maskImagePath} update") if len(os.listdir(folderPath)) == 0: folderToRemove.append(folderPath) for folderPath in folderToRemove: shutil.rmtree(folderPath, ignore_errors=True) pass
def createMask(imgName: str, imgShape, idMask: int, ptsMask, datasetName: str = 'dataset_train', maskClass: str = 'masks', imageFormat="jpg", config: Config = None): """ Create the mask image based on its polygon points :param imgName: name w/o extension of the base image :param imgShape: shape of the image :param idMask: the ID of the mask, a number not already used for that image :param ptsMask: array of [x, y] coordinates which are all the polygon points representing the mask :param datasetName: name of the output dataset :param maskClass: name of the associated class of the current mask :param imageFormat: output format of the masks' images :param config: config object :return: None """ # https://www.programcreek.com/python/example/89415/cv2.fillPoly # Formatting coordinates matrix to get int ptsMask = np.double(ptsMask) ptsMask = np.matrix.round(ptsMask) ptsMask = np.int32(ptsMask) bbox_coordinates = "" if config is not None and config.is_using_mini_mask(): bbox = get_bbox_from_points(ptsMask) if get_bboxes_intersection(bbox, [0, 0, *imgShape[:2]]) <= 0: return kept_bbox = [0, 0, 0, 0] for i in range(4): kept_bbox[i] = min(max(0, bbox[i]), imgShape[i % 2]) y1, x1, y2, x2 = kept_bbox bbox_coordinates = f"_{y1}_{x1}_{y2}_{x2}" shiftedBbox = shift_bbox(bbox) shift = bbox[:2] mask = np.uint8(np.zeros((shiftedBbox[2], shiftedBbox[3]))) cv2.fillPoly(mask, [ptsMask - shift[::-1]], 255) shifted_kept_bbox = shift_bbox(kept_bbox, customShift=shift) y1, x1, y2, x2 = shifted_kept_bbox mask = mask[y1:y2, x1:x2] # Creating black matrix with same size than original image and then drawing the mask mask = minimize_mask(shiftedBbox, mask, config.get_mini_mask_shape()) mask = mask.astype(np.uint8) * 255 else: # Creating black matrix with same size than original image and then drawing the mask mask = np.uint8(np.zeros((imgShape[0], imgShape[1]))) cv2.fillPoly(mask, [ptsMask], 255) # Saving result image maskClass = maskClass.lower().strip(' ').replace(" ", "_") output_directory = os.path.join(datasetName, imgName, maskClass) if not os.path.exists(output_directory): os.makedirs(output_directory) output_name = f"{imgName}_{idMask:03d}{bbox_coordinates}.{imageFormat}" cv2.imwrite(os.path.join(output_directory, output_name), mask, CV2_IMWRITE_PARAM)
# Run object detection results = model.detect([image], verbose=1) ax = get_ax(1) r = results[0] visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'], dataset.class_names, r['scores'], ax=ax, title="Predictions") mini_new_mask = utils.minimize_mask(r['rois'], r['masks'], config.MINI_MASK_SHAPE) #display_images([mini_new_mask[:,:,i] for i in range(min(mini_new_mask.shape[-1], 14))]) print(len(gt_bbox)) # saving test data information #SAV_DIR = os.path.join(ROOT_DIR, 'samples/pig/crop_mask_data/pig_data_test_'+str(sav_dir_str_index+i+1)) # saving the training data informations SAV_DIR = os.path.join( ROOT_DIR, 'crop_mask_data/pig_data_train_' + str(sav_dir_str_index + i + 1)) # saving the actual output of mask rcnn informations #SAV_DIR = os.path.join(ROOT_DIR, 'samples/pig/crop_mask_data/pig_data_act_out_'+str(sav_dir_str_index+i+1)) os.mkdir(SAV_DIR) print(SAV_DIR) for index in range(len(gt_bbox)):