def setUp(self): img_path = os.path.join(os.getcwd(), "../../cars_dataset/images/cars0.png") annotation_path = os.path.join( os.getcwd(), "../../cars_dataset/annotations/xmls/cars0.xml") assert os.path.isfile(img_path) assert os.path.isfile(annotation_path) # Image. self.frame = cv2.imread(img_path) self.annotation = annotation_path imgAnt = ImageAnnotation(path=self.annotation) self.bndboxes = imgAnt.propertyBoundingBoxes self.names = imgAnt.propertyNames # Augmenters. self.augmenter = BoundingBoxAugmenters() # Testing options. self.visualize = True self.waitTime = 100 self.windowSize = (800, 800)
def applyDataAugmentation(self, configurationFile=None, outputImageDirectory=None, outputAnnotationDirectory=None, threshold=None): """ Applies one or multiple data augmentation methods to the dataset. Args: configurationFile: A string with a path to a json file that contains the configuration of the data augmentation methods. outputImageDirectory: A string that contains the path to the directory where images will be saved. outputAnnotationDirectory: A string that contains the path the directory where annotations will be saved. threshold: A float that contains a number between 0 and 1. Returns: None """ # Assertions if (configurationFile == None): raise ValueError("ERROR: Augmenter parameter cannot be empty.") else: if (not os.path.isfile(configurationFile)): raise Exception("ERROR: Path to json file ({}) does not exist."\ .format(configurationFile)) jsonConf = AugmentationConfigurationFile(file=configurationFile) typeAugmentation = jsonConf.runAllAssertions() if (outputImageDirectory == None): outputImageDirectory = os.getcwd() Util.create_folder(os.path.join(outputImageDirectory, "images")) outputImageDirectory = os.path.join(os.getcwd(), "images") if (not (os.path.isdir(outputImageDirectory))): raise Exception("ERROR: Path to output directory does not exist. {}"\ .format(outputImageDirectory)) if (outputAnnotationDirectory == None): outputAnnotationDirectory = os.getcwd() Util.create_folder( os.path.join(outputAnnotationDirectory, "annotations")) Util.create_folder( os.path.join(outputAnnotationDirectory, "annotations", "xmls")) outputAnnotationDirectory = os.path.join(os.getcwd(), "annotations", "xmls") if (not (os.path.isdir(outputAnnotationDirectory))): raise Exception("ERROR: Path to output annotation directory does not exist. {}"\ .format(outputAnnotationDirectory)) if (threshold == None): threshold = 0.5 if (type(threshold) != float): raise TyperError( "ERROR: threshold parameter must be of type float.") if ((threshold > 1) or (threshold < 0)): raise ValueError("ERROR: threshold paramater should be a number between" +\ " 0-1.") # Load configuration data. f = open(configurationFile) data = json.load(f) f.close() # Iterate over the images. for img in tqdm(os.listdir(self.imagesDirectory)): # Get the extension extension = Util.detect_file_extension(filename=img) if (extension == None): raise Exception("ERROR: Your image extension is not valid." +\ "Only jpgs and pngs are allowed.") # Extract name. filename = os.path.split(img)[1].split(extension)[0] # Create xml and img name. imgFullPath = os.path.join(self.imagesDirectory, filename + extension) xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml") imgAnt = ImageAnnotation(path=xmlFullPath) boundingBoxes = imgAnt.propertyBoundingBoxes names = imgAnt.propertyNames # Apply augmentation. if (typeAugmentation == 0): for i in data["bounding_box_augmenters"]: if (i == "Sequential"): # Prepare data for sequence frame = cv2.imread(imgFullPath) bndboxes = boundingBoxes # Read elements of vector assert type(data["bounding_box_augmenters"] [i]) == list, "Not list" for k in range(len( data["bounding_box_augmenters"][i])): # Extract information augmentationType = list( data["bounding_box_augmenters"][i] [k].keys())[0] if (not jsonConf.isValidBoundingBoxAugmentation( augmentation=augmentationType)): raise Exception( "ERROR: {} is not valid.".format( augmentationType)) parameters = data["bounding_box_augmenters"][i][k][ augmentationType] # Save? saveParameter = jsonConf.extractSavingParameter( parameters=parameters) frame, bndboxes = applyBoundingBoxAugmentation( frame=frame, boundingBoxes=bndboxes, augmentationType=augmentationType, #j, parameters=parameters) if (saveParameter == True): # Generate a new name. newName = Util.create_random_name( name=self.databaseName, length=4) imgName = newName + extension xmlName = newName + ".xml" # Save image. Util.save_img( frame=frame, img_name=imgName, output_image_directory=outputImageDirectory ) # Save annotation. Util.save_annotation( filename=imgName, path=os.path.join(outputImageDirectory, imgName), database_name=self.databaseName, frame_size=frame.shape, data_augmentation_type=augmentationType, bounding_boxes=bndboxes, names=names, origin=imgFullPath, output_directory=os.path.join( outputAnnotationDirectory, xmlName)) else: parameters = data["bounding_box_augmenters"][i] # Save? saveParameter = jsonConf.extractSavingParameter( parameters=parameters) frame, bndboxes = applyBoundingBoxAugmentation( frame=cv2.imread(imgFullPath), boundingBoxes=boundingBoxes, augmentationType=i, parameters=parameters) # Save frame if (saveParameter == True): # Generate a new name. newName = Util.create_random_name( name=self.databaseName, length=4) imgName = newName + extension xmlName = newName + ".xml" # Save image. Util.save_img( frame=frame, img_name=imgName, output_image_directory=outputImageDirectory) # Save annotation. Util.save_annotation( filename=imgName, path=os.path.join(outputImageDirectory, imgName), database_name=self.databaseName, frame_size=frame.shape, data_augmentation_type=augmentationType, bounding_boxes=bndboxes, names=names, origin=imgFullPath, output_directory=os.path.join( outputAnnotationDirectory, xmlName)) elif (typeAugmentation == 1): # Geometric data augmentations raise ValueError("Image geometric data augmentations are not " +\ "supported for bounding boxes. Use bounding box " +\ "augmentation types.") elif (typeAugmentation == 2): # Color data augmentations for i in data["image_color_augmenters"]: if (i == "Sequential"): # Prepare data for sequence frame = cv2.imread(imgFullPath) # Read elements of vector assert type(data["image_color_augmenters"] [i]) == list, "Not list" for k in range(len(data["image_color_augmenters"][i])): # Extract information augmentationType = list( data["image_color_augmenters"][i][k].keys())[0] if (not jsonConf.isValidColorAugmentation( augmentation=augmentationType)): raise Exception( "ERROR: {} is not valid.".format( augmentationType)) parameters = data["image_color_augmenters"][i][k][ augmentationType] # Save? saveParameter = jsonConf.extractSavingParameter( parameters=parameters) # Apply augmentation frame = applyColorAugmentation( frame=frame, augmentationType=augmentationType, #j, parameters=parameters) if (saveParameter == True): # Generate a new name. newName = Util.create_random_name( name=self.databaseName, length=4) imgName = newName + extension xmlName = newName + ".xml" # Save image. Util.save_img( frame=frame, img_name=imgName, output_image_directory=outputImageDirectory ) # Save annotation. Util.save_annotation( filename=imgName, path=os.path.join(outputImageDirectory, imgName), database_name=self.databaseName, frame_size=frame.shape, data_augmentation_type=augmentationType, bounding_boxes=bndboxes, names=names, origin=imgFullPath, output_directory=os.path.join( outputAnnotationDirectory, xmlName)) else: parameters = data["image_color_augmenters"][i] # Save? saveParameter = jsonConf.extractSavingParameter( parameters=parameters) frame = applyColorAugmentation( frame=cv2.imread(imgFullPath), augmentationType=i, parameters=parameters) # Save frame if (saveParameter == True): # Generate a new name. newName = Util.create_random_name( name=self.databaseName, length=4) imgName = newName + extension xmlName = newName + ".xml" # Save image. Util.save_img( frame=frame, img_name=imgName, output_image_directory=outputImageDirectory) # Save annotation. Util.save_annotation( filename=imgName, path=os.path.join(outputImageDirectory, imgName), database_name=self.databaseName, frame_size=frame.shape, data_augmentation_type=augmentationType, bounding_boxes=bndboxes, names=names, origin=imgFullPath, output_directory=os.path.join( outputAnnotationDirectory, xmlName)) elif (typeAugmentation == 3): # Assert sequential follows multiple_image_augmentations. if (not ("Sequential" in data["multiple_image_augmentations"])): raise Exception( "ERROR: Data after multiple_image_augmentations is not recognized." ) # Multiple augmentation configurations, get a list of hash maps of all the confs. list_of_augmenters_confs = data[ "multiple_image_augmentations"]["Sequential"] # Assert list_of_augmenters_confs is a list. if (not (type(list_of_augmenters_confs) == list)): raise TypeError( "ERROR: Data inside [multiple_image_augmentations][Sequential] must be a list." ) # Prepare data for sequence. frame = cv2.imread(imgFullPath) bndboxes = boundingBoxes # print("\n*", list_of_augmenters_confs, "\n") for k in range(len(list_of_augmenters_confs)): # Get augmenter type ("bounding_box_augmenter" or "color_augmenter") position # in the list of multiple augmentations. augmentationConf = list( list_of_augmenters_confs[k].keys())[0] if (not (jsonConf.isBndBxAugConfFile( keys=[augmentationConf]) or jsonConf.isColorConfFile( keys=[augmentationConf]))): raise Exception( "{} is not a valid configuration.".format( augmentationConf)) # Get sequential information from there. This information is a list of # the types of augmenters that belong to augmentationConf. list_of_augmenters_confs_types = list_of_augmenters_confs[ k][augmentationConf]["Sequential"] # Assert list_of_augmenters_confs is a list if (not (type(list_of_augmenters_confs_types) == list)): raise TypeError("Data inside [multiple_image_augmentations][Sequential][{}][Sequential] must be a list."\ .format(augmentationConf)) # Iterate over augmenters inside sequential of type. for l in range(len(list_of_augmenters_confs_types)): # Get augmentation type and its parameters. augmentationType = list( list_of_augmenters_confs_types[l].keys())[0] # Assert augmentation is valid. if (not (jsonConf.isValidBoundingBoxAugmentation( augmentation=augmentationType) or jsonConf.isValidColorAugmentation( augmentation=augmentationType))): raise Exception("ERROR: {} is not valid.".format( augmentationType)) parameters = list_of_augmenters_confs_types[l][ augmentationType] # Save? saveParameter = jsonConf.extractSavingParameter( parameters=parameters) # Restart frame to original? restartFrameParameter = jsonConf.extractRestartFrameParameter( parameters=parameters) # Probability of augmentation happening. randomEvent = jsonConf.randomEvent( parameters=parameters, threshold=threshold) # print(augmentationType, parameters) # Apply augmentation. if (augmentationConf == "image_color_augmenters"): # print(augmentationConf, augmentationType, parameters) if (randomEvent == True): frame = applyColorAugmentation( frame=frame, augmentationType=augmentationType, parameters=parameters) elif (augmentationConf == "bounding_box_augmenters"): # print(augmentationConf, augmentationType, parameters) if (randomEvent == True): frame, bndboxes = applyBoundingBoxAugmentation( frame=frame, boundingBoxes=bndboxes, augmentationType=augmentationType, #j, parameters=parameters) # Save? if ((saveParameter == True) and (randomEvent == True)): # Generate a new name. newName = Util.create_random_name( name=self.databaseName, length=4) imgName = newName + extension xmlName = newName + ".xml" # Save image. Util.save_img( frame=frame, img_name=imgName, output_image_directory=outputImageDirectory) # Save annotation. Util.save_annotation( filename=imgName, path=os.path.join(outputImageDirectory, imgName), database_name=self.databaseName, frame_size=frame.shape, data_augmentation_type=augmentationType, bounding_boxes=bndboxes, names=names, origin=imgFullPath, output_directory=os.path.join( outputAnnotationDirectory, xmlName)) # Restart frame? if (restartFrameParameter == True): frame = cv2.imread(imgFullPath) bndboxes = boundingBoxes else: raise Exception( "Type augmentation {} not valid.".format(typeAugmentation))
def reduceImageDataPointByRoi(self, imagePath=None, annotationPath=None, offset=None, outputImageDirectory=None, outputAnnotationDirectory=None): """ Group an image's bounding boxes into Rois and create smaller images. Args: imagePath: A string that contains the path to an image. annotationPath: A string that contains the path to an annotation. offset: An int that contains the offset. outputImageDirectory: A string that contains the path where the images will be stored. outputAnnotationDirectory: A string that contains the path where the annotations will be stored. Returns: None Example: Given an image and its bounding boxes, create ROIs of size offset that enclose the maximum possible amount of bounding boxes. --------------------------------- -------------------------------- | | | | | --- | | Roi0------ | | | | | | | | | | | --- | | |--- | | | | | | --- | | | --- | -> | | | | | | | | | | | | --- | | | --- | | ------Roi0 | | | | | | | | | | | | | | --- | | Roi1---- | | | | | | | | | | --- | | | | | | | | | --- | | | | | | | | | | | | | | --- | | | | | ----Roi1 | --------------------------------- --------------------------------- Then, the rois are saved with their respective annotations. """ # Assertions if (imagePath == None): raise ValueError( "ERROR: Path to imagePath parameter cannot be empty.") if (annotationPath == None): raise ValueError( "ERROR: Path to annotation parameter cannot be empty.") if (not os.path.isfile(imagePath)): raise ValueError( "ERROR: Path to image does not exist {}.".format(imagePath)) if (not os.path.isfile(annotationPath)): raise ValueError( "ERROR: Path to annotation does not exist {}.".format( annotationPath)) if (offset == None): raise ValueError("ERROR: Offset parameter cannot be empty.") if (not (os.path.isdir(outputImageDirectory))): raise ValueError("ERROR: Output image directory does not exist.") if (not (os.path.isdir(outputAnnotationDirectory))): raise ValueError( "ERROR: Output annotation directory does not exist.") # Load image annotation. annotation = ImageAnnotation(path=annotationPath) height, width, depth = annotation.propertySize names = annotation.propertyNames objects = annotation.propertyObjects boundingBoxes = annotation.propertyBoundingBoxes # Create a list of classes with the annotations. annotations = [] index = 0 for boundingBox, name in zip(boundingBoxes, names): # Compute the module ix, iy, x, y = boundingBox module = VectorOperations.compute_module(vector=[ix, iy]) annotations.append(Annotation(name = name, bndbox = boundingBox, \ module = module, corePoint = True)) index += 1 # Sort the list of Annotations by its module from lowest to highest. for i in range(len(annotations)): for j in range(len(annotations) - 1): module0 = annotations[j].propertyModule module1 = annotations[j + 1].propertyModule if (module0 >= module1): # Swap Annotation aux = annotations[j + 1] annotations[j + 1] = annotations[j] annotations[j] = aux # Debug # for each in annotations: # print(each.propertyName, each.propertyModule) # print("\n") # Work on the points. for i in range(len(annotations)): # Ignore non-core points. if (annotations[i].propertyCorePoint == False): pass else: # Center the core point in an allowed image space. RoiXMin, RoiYMin, \ RoiXMax, RoiYMax = prep.adjustImage(frameHeight = height, frameWidth = width, boundingBoxes = [annotations[i].propertyBndbox], offset = offset) # Find the annotations that can be included in the allowed image space. for j in range(len(annotations)): # Get bounding box. ix, iy, x, y = annotations[j].propertyBndbox # Check current bounding box is inside the allowed space. if ((ix >= RoiXMin) and (x <= RoiXMax)) and \ ((iy >= RoiYMin) and (y <= RoiYMax)): # Disable point from being a core point. Check it is not the # current point of reference. if (not (annotations[i].propertyBndbox == annotations[j].propertyBndbox)): annotations[j].propertyCorePoint = False # Include the corresponding bounding boxes in the region of interest. newBoundingBoxes, \ newNames = prep.includeBoundingBoxes(edges = [RoiXMin, RoiYMin, RoiXMax, RoiYMax], boundingBoxes = boundingBoxes, names = names) if (len(newBoundingBoxes) == 0): print(boundingBoxes) print(RoiXMin, RoiYMin, RoiXMax, RoiYMax) raise Exception( "ERROR: No bounding boxes: {}. Please report this problem." .format(imagePath)) # Read image. frame = cv2.imread(imagePath) extension = Util.detect_file_extension(filename=imagePath) if (extension == None): raise Exception("Your image extension is not valid. " +\ "Only jpgs and pngs are allowed. {}".format(extension)) # Generate a new name. newName = Util.create_random_name(name=self.databaseName, length=4) imgName = newName + extension xmlName = newName + ".xml" # Save image. Util.save_img(frame=frame[RoiYMin:RoiYMax, RoiXMin:RoiXMax, :], img_name=imgName, output_image_directory=outputImageDirectory) # Save annotation. Util.save_annotation( filename=imgName, path=os.path.join(outputImageDirectory, imgName), database_name=self.databaseName, frame_size=frame[RoiYMin:RoiYMax, RoiXMin:RoiXMax, :].shape, data_augmentation_type="Unspecified", bounding_boxes=newBoundingBoxes, names=newNames, origin=imagePath, output_directory=os.path.join(outputAnnotationDirectory, xmlName))
def saveBoundingBoxes(self, outputDirectory=None, filterClasses=None): """ Saves the bounding boxes as images of each image in the dataset. Args: outputDirectory: A string that contains the directory where the images will be saved. filterClasses: A list of Strings that contains names of the classes to be filtered and saved. Returns: None """ # Assertions if (outputDirectory == None): raise ValueError("outputDirectory cannot be empty") if (type(outputDirectory) != str): raise TyperError("outputDirectory must be a string.") if (not (os.path.isdir(outputDirectory))): raise FileNotFoundError( "outputDirectory's path does not exist: ".format( outputDirectory)) if (filterClasses == None): filterClasses = [] if (type(filterClasses) != list): raise TyperError("filterClasses must be of type list.") # Local variables images = [ os.path.join(self.imagesDirectory, i) for i in os.listdir(self.imagesDirectory) ] # Logic for img in tqdm(images): # Get extension extension = Util.detect_file_extension(filename=img) if (extension == None): raise Exception("ERROR: Your image extension is not valid." +\ "Only jpgs and pngs are allowed.") # Extract name filename = os.path.split(img)[1].split(extension)[0] # Create xml and img name imgFullPath = os.path.join(self.imagesDirectory, filename + extension) xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml") # Load annotation. annt = ImageAnnotation(path=xmlFullPath) # Get bounding boxes. boundingBoxes = annt.propertyBoundingBoxes names = annt.propertyNames # Save image. frame = cv2.imread(img) # Save bounding boxes as png images. for name, boundingBox in zip(names, boundingBoxes): if ((len(filterClasses) == 0) or (name in filterClasses)): ix, iy, x, y = boundingBox # Detect extension. extension = Util.detect_file_extension(filename=img) if (extension == None): raise Exception("Your image extension is not valid. " +\ "Only jpgs and pngs are allowed. {}".format(extension)) # Generate a new name. newName = Util.create_random_name(name=self.databaseName, length=4) imgName = newName + extension # Check bounding box does not get out of boundaries. if (x == frame.shape[1]): x -= 1 if (y == frame.shape[0]): y -= 1 # Check bounding boxes are ok. if (((y-iy) == 0) or ((x - ix) == 0) or \ ((ix < 0) or (iy < 0)) or \ ((x > frame.shape[1]) or (y > frame.shape[0]))): print(img) print(ix, iy, x, y) raise Exception("Bounding box does not exist.") # Save image. Util.save_img(frame=frame[iy:y, ix:x, :], img_name=imgName, output_image_directory=outputDirectory)
def computeBoundingBoxStats(self, saveDataFrame=None, outputDirDataFrame=None): """ Compute basic stats for the dataset's bounding boxes. Args: saveDataFrame: A boolean that defines whether to save the dataframe or not. outputDirDataFrame: A string that contains the path where the dataframe will be saved. Returns: None """ # Assertions if (saveDataFrame == None): saveDataFrame = False else: if (type(saveDataFrame) == bool): if (outputDirDataFrame == None): raise ValueError( "Parameter directory dataframe cannot be empty.") else: raise TypeError("saveDataFrame must be of type bool.") # Local variables namesFrequency = {} files = os.listdir(self.imagesDirectory) columns = [ "path", "name", "width", "height", "xmin", "ymin", "xmax", "ymax" ] paths = [] names = [] widths = [] heights = [] boundingBoxesLists = [] # Logic for file in tqdm(files): extension = Util.detect_file_extension(filename=file) if (extension == None): raise Exception("ERROR: Your image extension is not valid: {}".format(extension) +\ " Only jpgs and pngs are allowed.") # Extract name. filename = os.path.split(file)[1].split(extension)[0] # Create xml and img name. imgFullPath = os.path.join(self.imagesDirectory, filename + extension) xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml") # Create an object of ImageAnnotation. annt = ImageAnnotation(path=xmlFullPath) # Check if it is empty. boundingBoxes = annt.propertyBoundingBoxes names = annt.propertyNames height, width, depth = annt.propertySize for i in range(len(names)): if (not (names[i] in namesFrequency)): namesFrequency[names[i]] = 0 else: namesFrequency[names[i]] += 1 paths.append(file) names.append(names[i]) widths.append(width) heights.append(height) boundingBoxesLists.append(boundingBoxes[i]) # Print stats print("Total number of bounding boxes: {}"\ .format(sum([i for i in namesFrequency.values()]))) print("Unique classes: {}".format(namesFrequency)) # Save data? if (saveDataFrame): Util.save_lists_in_dataframe( columns=columns, data=[paths, names, widths, heights, boundingBoxesLists], output_directory=outputDirDataFrame)
def findEmptyOrWrongAnnotations(self, removeEmpty=None): """ Find empty or irregular annotations in the annotation files. An empty annotation is an annotation that includes no objects. And a irregular annotation is an annotation that has a bounding box with coordinates that are off the image's boundaries. Args: removeEmpty: A boolean that if True removes the annotation and image that are empty. Returns: None Raises: - Exception: when the extension of the image is not allowed. Only jpgs and pngs are allowed. - Exception: when an annotation file is empty. - Exception: when a coordinate is not valid. Either less than zero or greater than image's size. """ # Assertions if (removeEmpty == None): removeEmpty = False # Local variables emptyAnnotations = [] files = os.listdir(self.imagesDirectory) # Logic for file in tqdm(files): # In case a folder is found, report it. if (os.path.isdir(file)): continue # Otherwise, continue. extension = Util.detect_file_extension(filename=file) if (extension == None): raise Exception("ERROR: Your image extension is not valid: {}".format(extension) +\ " Only jpgs and pngs are allowed.") # Extract name filename = os.path.split(file)[1].split(extension)[0] # Create xml and img name imgFullPath = os.path.join(self.imagesDirectory, filename + extension) xmlFullPath = os.path.join(self.annotationsDirectory, filename + ".xml") # Create an object of ImageAnnotation. annt = ImageAnnotation(path=xmlFullPath) # Check if it is empty. if (len(annt.propertyBoundingBoxes) == 0): emptyAnnotations.append(file) print("WARNING: Annotation {} does not have any annotations.". format(xmlFullPath)) # Check if we need to remove this annotation. if (removeEmpty == True): #os.remove(imgFullPath) os.remove(xmlFullPath) # Check if it is irregular height, width, depth = annt.propertySize for each in annt.propertyBoundingBoxes: ix, iy, x, y = each if (ix < 0): raise ValueError( "ERROR: Negative coordinate found in {}".format(file)) if (iy < 0): raise ValueError( "ERROR: Negative coordinate found in {}".format(file)) if (x > width): raise ValueError("ERROR: Coordinate {} bigger than width {} found in {}"\ .format(x, width, file)) if (y > height): raise ValueError("ERROR: Coordinate {} bigger than height {} found in {}"\ .format(y, height, file)) # Return empty annotations return emptyAnnotations