예제 #1
0
def get_annotation_data(image_base_name: str, path_to_images: str,
                        ann_format: str, label_mapping: pd.DataFrame,
                        class_mapping: pd.DataFrame) -> pd.DataFrame:
    """
    Calculates the annotation data related to the labels of the given image.

    :param image_base_name: the base name of the image (without extension)
    :param path_to_images: the path where the images relative to the dataset are stored
    :param ann_format: the annotation format, which can be either YOLOv2 or darkflow
    :param label_mapping: the image-labels mapping
    :param class_mapping: the class string to class number mapping
    :return: the annotation of the current image as a dataframe
    """

    # Get all the labels of the image as a string
    try:
        labels = label_mapping.loc[image_base_name, 'labels']
    except KeyError:
        labels = ''

    # Convert the string of labels to list
    labels = [line[:-1] for line in re.findall(r"(?:\S*\s){5}", str(labels))]

    # Get the width and height of the image
    img_width, img_height = Image.open(
        os.path.join(path_to_images, to_file_name(image_base_name))).size

    convert_to = {
        'YOLOv2': convert_to_yolov2,
        'darkflow': convert_to_darkflow,
        'frcnn': convert_to_frcnn,
    }

    # Create a list of lists to store the annotation data
    img_path = os.path.join(path_to_images, image_base_name)
    annotation_data = [
        convert_to[ann_format](label=label,
                               class_mapping=class_mapping,
                               image_width=img_width,
                               image_height=img_height,
                               image_path=img_path) for label in labels
    ]

    # If the image has no labels, insert a default row. For frcnn 'bg' is special background class
    if not annotation_data:
        annotation_data = [['', '', '', '', '', img_width, img_height]] \
            if ann_format != 'frcnn' else [[to_file_name(img_path), 0, 0, img_width, img_height, 'bg']]

    data_format = {
        'YOLOv2': ['class', 'x_c', 'y_c', 'bb_width', 'bb_height'],
        'darkflow':
        ['class', 'xmin', 'ymin', 'xmax', 'ymax', 'img_width', 'img_height'],
        'frcnn': ['filepath', 'xmin', 'ymin', 'xmax', 'ymax', 'class_name']
    }

    # Create a dataframe to store the whole annotation
    annotation = pd.DataFrame(annotation_data, columns=data_format[ann_format])

    return annotation
def write_as_darkflow(annotation: pd.DataFrame, path_to_annotations: str,
                      image_id: str):
    """
    Writes an annotation on file according to the darkflow format.

    :param annotation: the annotation data
    :param path_to_annotations: the path to the annotations files
    :param image_id: the base name of the image (without file extension)
    """

    file_name = to_file_name(image_id, 'jpg').replace('-', '_')

    # Create a writer object for the image
    writer = Writer(path=os.path.join(path_to_annotations, file_name),
                    filename=os.path.join('..', 'images', file_name),
                    width=annotation['img_width'][0],
                    height=annotation['img_height'][0],
                    database='training')

    # Add the data related to each row (label) of the dataframe
    for _, row in annotation.iterrows():
        writer.addObject(name=row['class'],
                         xmin=row['xmin'],
                         ymin=row['ymin'],
                         xmax=row['xmax'],
                         ymax=row['ymax'])

    # Set the path to the XML annotation
    path_to_xml = os.path.join(path_to_annotations,
                               to_file_name(image_id, 'xml'))

    # Write the data to an XML file
    writer.save(path_to_xml)

    # If the XML file is empty because the image has no objects
    if not annotation.loc[0]['class']:
        # Get the XML tree of the annotation
        tree = ElementTree.parse(path_to_xml)

        # Get the root of the annotation
        root = tree.getroot()

        # Delete the object tag
        obj = root.getchildren()[-1]
        root.remove(obj)
        ElementTree.dump(root)

        tree.write(path_to_xml)
예제 #3
0
def visualize_training_data(path_to_train_images: str, image_id: str,
                            mapping: pd.DataFrame):
    """
    Takes an image_id and return an image with bounding boxes around each character
    :param path_to_train_images: the path to the images of the training set
    :param mapping: the image-labels mapping
    :param image_id: an image base name (i.e. without the file extension)
    """

    print(
        '\nDisplaying the image {} from the training set with bounding boxes...'
        .format(image_id))

    # Get all the characters and the position of the bounding boxes for the image
    mapping.set_index('image_id', inplace=True)
    labels = mapping.loc[image_id, 'labels']
    labels = np.array(labels.split(" ")).reshape(-1, 5)

    plt = display_image(os.path.join(path_to_train_images,
                                     to_file_name(image_id)),
                        show=False)
    ax = plt.gca()

    for label in labels:
        ax = draw_box_and_text(ax, label)

    plt.show()
예제 #4
0
def visualize_null_images(path_to_train_images: str, mapping: pd.DataFrame):
    """
    Visualizes some of the images which have no characters.
    :param path_to_train_images: the path to the images of the training set
    :param mapping: the image-labels mapping
    """

    # Configure the plot
    rows, columns = 2, 2
    fig = plt.figure(figsize=(20, 20))

    # Get all the images with no characters
    images_nan_labels = mapping[mapping.isna().labels]['image_id'].tolist()

    # Plot some of the images with no characters
    for i in range(1, rows * columns + 1):
        rnd_img_nan_nbr = random.randint(0, len(images_nan_labels) - 1)
        img_nan = Image.open(
            os.path.join(path_to_train_images,
                         to_file_name(images_nan_labels[rnd_img_nan_nbr])))
        fig.add_subplot(rows, columns, i)
        plt.imshow(img_nan, aspect='equal')

    print('\nDisplaying some images with no characters...')

    plt.show()
예제 #5
0
def resize_dataset(size: int,
                   path_to_images: str,
                   path_to_annotations: str = None):
    """
    Resizes the dataset to the given size deleting all the exceeding items
    and corresponding annotations.

    :param size: the new size of the dataset
    :param path_to_images: the path to the images of the dataset
    :param path_to_annotations: the path to the annotations of the dataset
    """

    print('Resizing the dataset from {old_n} to {new_n} items...'.format(
        old_n=len(os.listdir(path_to_images)), new_n=size))

    # Iterate over all the dataset items (i.e. names of images)
    for i, img_file_name in enumerate(sorted(os.listdir(path_to_images))):

        # Get the base name of the image (without extension)
        img_id = to_id(img_file_name)

        # Remove all the exceeding items
        if i >= size:
            # Delete the image
            os.remove(os.path.join(path_to_images, to_file_name(img_id,
                                                                'jpg')))

            # If annotations have been specified
            if path_to_annotations:

                path_to_annotation = os.path.join(path_to_annotations,
                                                  to_file_name(img_id, 'xml'))

                # Delete the annotation
                if os.path.isfile(path_to_annotation):
                    os.remove(path_to_annotation)

    print('Resizing Complete!')
    print('The dataset now has:')
    print('* {} images'.format(len(os.listdir(path_to_images))))
    if path_to_annotations:
        print('* {} annotations'.format(len(os.listdir(path_to_annotations))))
def convert_to_frcnn(label: [], image_path: str, **_) -> []:
    """
    Converts the label data to the darkflow data format (i.e. PASCAL VOC XML).

    The dataset is provided with labels in the format: <unicode> <x_bl> <y_bl> <abs_bb_width> <abs_bb_height>

    Where:
    - <unicode>        : is the class label as a unicode
    - <x_bl>           : is the bottom-left corner x coordinate of the bounding box
    - <y_bl>           : is the bottom-left corner y coordinate of the bounding box
    - <abs_bb_width>   : is the width of the bounding box
    - <abs_bb_height>  : is the height of the bounding box

    The darkflow data format is: <class_name> <xmin> <ymin> <xmax> <ymax> <img_width> <img_height>

    Where:
    - <class_name> : is the name of the class object as string
    - <img_width>  : is the width of the image
    - <img_height> : is the height of the image
    - <xmin>       : is the bottom-left x coordinate of the bounding box
    - <ymin>       : is the bottom-left y coordinate of the bounding box
    - <xmax>       : is the top-right x coordinate of the bounding box
    - <ymax>       : is the top-right y coordinate of the bounding box

    :param image_path: the path to the image, including image extension
    :param label: the label for one character in the image
    :return: a list with the converted data
    """

    # Get the label data in the dataset format
    unicode, xmin, ymin, abs_bb_width, abs_bb_height = label.split()

    # Make sure the class is a string
    class_name = str(unicode)

    # Cast each data to int
    xmin = int(xmin)
    ymin = int(ymin)
    abs_bb_width = int(abs_bb_width)
    abs_bb_height = int(abs_bb_height)

    # Calculate the top-right coordinates of the bounding box
    xmax = xmin + abs_bb_width
    ymax = ymin + abs_bb_height

    return [to_file_name(image_path, 'jpg'),
            xmin,
            ymin,
            xmax,
            ymax,
            class_name]
def write_as_yolov2(annotation: pd.DataFrame, path_to_annotations: str,
                    image_id: str):
    """
    Writes an annotation on file according to YOLOv2 format.

    :param annotation: the annotation data
    :param path_to_annotations: the path to the annotations files
    :param image_id: the base name of the image (without file extension)
    """

    annotation.to_csv(os.path.join(path_to_annotations,
                                   to_file_name(image_id, 'txt')),
                      header=None,
                      index=None,
                      sep=' ',
                      mode='a')
예제 #8
0
def visualize_bounding_boxes(path_to_images: str, image_id: str, labels: []):
    """
    Takes an image_id and return an image with bounding boxes around each character.

    :param path_to_images: the path to the images of the training set
    :param labels: the labels of the image to be tested
    :param image_id: an image base name (i.e. without the file extension)
    """

    plt = display_image(os.path.join(path_to_images, to_file_name(image_id)),
                        show=False)
    ax = plt.gca()

    for label in labels:
        ax = draw_box_and_text(ax, label)

    plt.show()
예제 #9
0
def visualize_random_image(path_to_train_images: str):
    """
    Visualizes a random image from the training set
    :param path_to_train_images: the path to the images of the training set
    """

    # Get a random index for the selection of the image
    rnd_nbr = random.randint(0, len(os.listdir(path_to_train_images)))

    # Get the random image base name
    rnd_img = os.listdir(path_to_train_images)[rnd_nbr]

    # Get the full path to the random image
    path_to_rnd_image = os.path.join(path_to_train_images,
                                     to_file_name(rnd_img))

    print('\nDisplaying the random image {}...'.format(rnd_img))

    # Display the random image
    display_image(path_to_rnd_image)
예제 #10
0
def restore_dataset_from_backup(path_to_images: str, path_to_annotations: str,
                                path_to_backup: str):
    """
    Copies all the images from the backup to the main folder and deletes the old files.

    :param path_to_images: the path to the old images
    :param path_to_annotations: the path to the old annotations
    :param path_to_backup: the path to the backup images
    """

    print('Restoring the dataset from backup...\n'
          'The dataset now counts:\n'
          '* {n_img} images\n'
          '* {n_ann} annotations'.format(n_img=len(os.listdir(path_to_images)),
                                         n_ann=len(
                                             os.listdir(path_to_annotations))))

    # Delete the current images and annotations
    for img_file_name in os.listdir(path_to_images):
        # Delete the image
        os.remove(os.path.join(path_to_images, img_file_name))

        # Delete the corresponding annotation
        path_to_annotation = os.path.join(
            path_to_annotations, to_file_name(to_id(img_file_name), 'xml'))

        if os.path.isfile(path_to_annotation):
            os.remove(path_to_annotation)

    # Copy the backup images into the main folder
    [
        copy(os.path.join(path_to_backup, image), path_to_images)
        for image in os.listdir(path_to_backup)
    ]

    print('Restoring performed successfully!\n'
          'The dataset now counts:\n'
          '* {n_img} images\n'
          '* {n_ann} annotations'.format(n_img=len(os.listdir(path_to_images)),
                                         n_ann=len(
                                             os.listdir(path_to_annotations))))
예제 #11
0
def visualize_from_training(path_to_images: str, path_to_annotations: str,
                            image_id: str):
    """
    Visualizes the image from the current training set.

    :param path_to_images: the path to the backup images
    :param path_to_annotations: the path to the annotations
    :param image_id: the id of the image (without file extension)
    """

    print('\nRetrieving generated annotation...')

    xml_doc = minidom.parse(
        os.path.join(path_to_annotations, to_file_name(image_id, 'xml')))
    objects = xml_doc.getElementsByTagName('object')

    labels = []

    for obj in objects:
        obj_class = obj.getElementsByTagName('name')[0].firstChild.nodeValue

        bounding_box = obj.getElementsByTagName('bndbox')[0]
        xmin = bounding_box.getElementsByTagName(
            'xmin')[0].firstChild.nodeValue
        ymin = bounding_box.getElementsByTagName(
            'ymin')[0].firstChild.nodeValue
        xmax = bounding_box.getElementsByTagName(
            'xmax')[0].firstChild.nodeValue
        ymax = bounding_box.getElementsByTagName(
            'ymax')[0].firstChild.nodeValue

        label_data = [
            obj_class, xmin, ymin,
            str(int(xmax) - int(xmin)),
            str(int(ymax) - int(ymin))
        ]

        labels.append(label_data)

    visualize_bounding_boxes(path_to_images, image_id, labels)