Introduction to detectron2 learning 2: realize the conversion between Mask and coco format of FruitsNut fruit and nut segmentation task data set

Learning objectives:

Convert the data set into Mask, coco annotation and other different formats

1, The coco annotation and Mask of a single instance are converted to each other:

There are many ways of mask conversion. One mask is generated for each instance or one mask is generated for each picture. For beginners, it is better to use the existing tools for the conversion of single instance coco data sets. pycococreator is used for the conversion:
pycococreator project file address:
https://github.com/waspinator/pycococreator
Since the official installation method is on linux, windows can choose to download the project file and open the command prompt in the current project file. If the running environment is in the virtual environment, activate the virtual environment, and then enter the following command:

python setup.py install

For relevant instructions, please refer to the website:
https://patrickwasp.com/create-your-own-coco-style-dataset/
To verify the successful installation, you can enter the following commands:

from pycococreatortools import pycococreatortools

If no error is reported.

1.1 convert each instance into a gray mask

Generate the instance mask according to the marked data.
According to the example of pycococreator, the mask is named as follows:

Here, the naming type of mask generation directly adopts its required naming format, so that the subsequent conversion can be carried out directly.
Relevant codes and notes are shown in the figure:

def objectSingleMask(annFile, classes_names, savePath):
    """
    From dimension file annFile Extract compliance category classes_names And save it
    Since each image has only one instance, it defaults to grayscale image
    :param annFile: json Annotate Document 
    :param classes_names: The class alias of the mask needs to be extracted
    :param savePath: Save path
    :return:
    """
    # Get COCO_json data
    coco = COCO(annFile)
    # Get the id of all the required image data - what is the id of the categories I need
    classes_ids = coco.getCatIds(catNms=classes_names)
    # Take all picture IDs of the union of all categories
    # If you want to intersect, you don't need a loop. Directly enter all categories as parameters to get the pictures contained in all categories
    imgIds_list = []
    # Loop out the pictures corresponding to each category id and obtain the id number of the picture
    for idx in classes_ids:
        imgidx = coco.getImgIds(catIds=idx)  # Put all the picture IDs of this category into a list
        imgIds_list += imgidx
        print("search id... ", imgidx)
    # Remove duplicate images
    imgIds_list = list(set(imgIds_list))  # Merge the same picture id corresponding to multiple categories

    # Get the information of all images at one time
    image_info_list = coco.loadImgs(imgIds_list)
    # Generate a mask for each instance of each picture
    annotation_id = 1
    for imageinfo in image_info_list:
        for singleClassIdIndex in range(len(classes_ids)):
            singleClassId = classes_ids[singleClassIdIndex]
            # Find the label id of the mask
            singleClassAnnId = coco.getAnnIds(imgIds=imageinfo['id'], catIds=[singleClassId], iscrowd=None)
            # Extract the segmentation data of the mask
            singleClassAnnList = coco.loadAnns(singleClassAnnId)
            for singleItem in singleClassAnnList:
                # Find every instance
                singleItemMask = coco.annToMask(singleItem)
                # Convert the instance to uint8 of 0 ~ 255 and save it
                singleItemMask = (singleItemMask * 255).astype(np.uint8)
                # <image_id>_<object_class_name>_<annotation_id>.png
                file_name = savePath + '/' + imageinfo['file_name'][:-4] + '_' + \
                            classes_names[singleClassIdIndex] + \
                            '_' + '%d' % annotation_id + '.png'
                cv2.imwrite(file_name, singleItemMask)
                print("Saved mask picture: ", file_name)
                annotation_id = annotation_id + 1

Enter directly when the program is running:

    # Label file address
    jsondir = "data/trainval.json"
    # Single mask
    singleMaskDir = "mydata/singleMask"
    mkr(singleMaskDir)
    objectSingleMask(jsondir, ['date', 'fig', 'hazelnut'], singleMaskDir)

Just.
Folder display:

1.2 regenerate the coco annotation file according to the mask of a single instance

# Convert a single mask to a json file
# https://patrickwasp.com/create-your-own-coco-style-dataset/
import datetime
import json
import os
import re
import fnmatch
from PIL import Image
import numpy as np
from pycococreatortools import pycococreatortools


def filter_for_jpeg(root, files):
    """
    Extract the picture files in the folder that match the relevant suffix
    :param root: File path
    :param files: File list under file path
    :return: Files that meet the naming criteria
    """
    file_types = ['*.jpeg', '*.jpg', '*.png']
    file_types = r'|'.join([fnmatch.translate(x) for x in file_types])
    files = [os.path.join(root, f) for f in files]
    files = [f for f in files if re.match(file_types, f)]
    return files


def filter_for_annotations(root, files, image_filename):
    """
    Extract the files in the folder that match the relevant suffix coco Note file,
    because png The picture is non-destructive, so it is generally used png Format as mask format
    :param root: File path
    :param files: File list under file path
    :param image_filename: coco Picture file corresponding to annotation file
    :return: 
    """
    file_types = ['*.png']
    file_types = r'|'.join([fnmatch.translate(x) for x in file_types])
    basename_no_extension = os.path.splitext(os.path.basename(image_filename))[0]
    file_name_prefix = basename_no_extension + '.*'
    files = [os.path.join(root, f) for f in files]
    files = [f for f in files if re.match(file_types, f)]
    files = [f for f in files if re.match(file_name_prefix, os.path.splitext(os.path.basename(f))[0])]
    return files


if __name__ == "__main__":
    # Path setting
    ROOT_DIR = 'data'
    IMAGE_DIR = os.path.join(ROOT_DIR, "images")
    ANNOTATION_DIR = "mydata/singleMask"
    SAVE_PATH_DIR = "myjson/singleJson.json"
    # Relevant information of the note file is optional
    INFO = {
        "description": "FruitsNuts Dataset",
        "url": "https://github.com/fenglingbai/FruitsNutsHandle",
        "version": "0.1.0",
        "year": 2022,
        "contributor": "fenglingbai",
        "date_created": datetime.datetime.utcnow().isoformat(' ')
    }

    LICENSES = [
        {
            "id": 1,
            "name": "None",
            "url": "None"
        }
    ]

    # Add categories according to your needs
    CATEGORIES = [
        {
            "supercategory": "date",
            "id": 1,
            "name": "date"
        },
        {
            "supercategory": "fig",
            "id": 2,
            "name": "fig"
        },
        {
            "supercategory": "hazelnut",
            "id": 3,
            "name": "hazelnut"
        }
    ]
    # Output Dictionary of json file
    coco_output = {
        "info": INFO,
        "licenses": LICENSES,
        "categories": CATEGORIES,
        "images": [],
        "annotations": []
    }
    # Initialization of annotation label
    image_id = 0
    segmentation_id = 1

    # Original drawing file of filtered data
    for root, _, files in os.walk(IMAGE_DIR):
        image_files = filter_for_jpeg(root, files)

        # Traverse each original drawing file
        for image_filename in image_files:
            image = Image.open(image_filename)
            image_info = pycococreatortools.create_image_info(
                image_id, os.path.basename(image_filename), image.size)
            coco_output["images"].append(image_info)

            # Filter the annotation file corresponding to the original drawing file
            for root, _, files in os.walk(ANNOTATION_DIR):
                annotation_files = filter_for_annotations(root, files, image_filename)

                # Traverse each annotation file
                for annotation_filename in annotation_files:

                    print(annotation_filename)
                    class_id = [x['id'] for x in CATEGORIES if x['name'] in annotation_filename][0]

                    category_info = {'id': class_id, 'is_crowd': 'crowd' in image_filename}
                    binary_mask = np.asarray(Image.open(annotation_filename)
                                             .convert('1')).astype(np.uint8)

                    annotation_info = pycococreatortools.create_annotation_info(
                        segmentation_id, image_id, category_info, binary_mask,
                        image.size, tolerance=2)

                    if annotation_info is not None:
                        coco_output["annotations"].append(annotation_info)

                    segmentation_id = segmentation_id + 1

            image_id = image_id + 1
    # Store json annotation files
    with open(SAVE_PATH_DIR, 'w') as output_json_file:
        json.dump(coco_output, output_json_file)
    print('ok')

The run json file can be found in the file path of the corresponding setting.

2, The coco label of a single picture and Mask are converted to each other:

2.1 mask conversion of single picture

def objectMultyMask(annFile, classes_names, savePath, type="color"):
    """
    From dimension file annFile Extract compliance category classes_names And save it
    :param annFile: Label file
    :param classes_names: Class alias to be extracted
    :param savePath: Mask save path
    :param type: Saved color selection,
        color use matplot Save as color by default,
        gray use cv2 Save as grayscale
    :return:
    """
    # Get COCO_json data
    coco = COCO(annFile)
    # Get the id of all the required image data - what is the id of the categories I need
    classes_ids = coco.getCatIds(catNms=classes_names)
    # Take all picture IDs of the union of all categories
    # If you want to intersect, you don't need a loop. Directly enter all categories as parameters to get the pictures contained in all categories
    imgIds_list = []
    # Loop out the pictures corresponding to each category id and obtain the id number of the picture
    for idx in classes_ids:
        imgidx = coco.getImgIds(catIds=idx)  # Put all the picture IDs of this category into a list
        imgIds_list += imgidx
        print("search id... ", imgidx)
    # Remove duplicate images
    imgIds_list = list(set(imgIds_list))  # Merge the same picture id corresponding to multiple categories

    # Get the information of all images at one time
    image_info_list = coco.loadImgs(imgIds_list)

    # Generate a mask for each picture
    for imageinfo in image_info_list:
        mask_pic = np.zeros(shape=(imageinfo['height'], imageinfo['width']))
        for singleClassId in classes_ids:
            # Each type of marker generates a mask
            singleClassMask = np.zeros(shape=(imageinfo['height'], imageinfo['width']))
            # Find the label id of the mask
            singleClassAnnId = coco.getAnnIds(imgIds=imageinfo['id'], catIds=[singleClassId], iscrowd=None)
            # Extract the segmentation data of the mask
            singleClassAnnList = coco.loadAnns(singleClassAnnId)
            for singleItem in singleClassAnnList:
                # Stack each instance
                singleItemMask = coco.annToMask(singleItem)
                singleClassMask += singleItemMask
            # Finally, this kind of mask information is superimposed on the original image
            singleClassMask[singleClassMask > 0] = 1
            # The overlapping part uses the mask information of the latter
            mask_pic[singleClassMask == 1] = 0
            mask_pic = mask_pic + singleClassMask * singleClassId
        # Save picture
        mask_pic = mask_pic.astype(np.uint8)
        file_name = savePath + '/' + imageinfo['file_name'][:-4] + '.png'
        if type == "color":
            plt.imsave(file_name, mask_pic)
        else:
            cv2.imwrite(file_name, mask_pic)
        print("Saved mask picture: ", file_name)

When saving an image, the value of each pixel represents the category to which the pixel belongs. Therefore, if you need visual display, you can select "color" in the "type" attribute, otherwise select "gary". When running the program directly:

    multyMaskGrayDir = "mydata/multyMaskGray"
    mkr(multyMaskGrayDir)
    objectMultyMask(jsondir, ['date', 'fig', 'hazelnut'], multyMaskGrayDir, type="gray")
    multyMaskColorDir = "mydata/multyMaskColor"
    mkr(multyMaskColorDir)
    objectMultyMask(jsondir, ['date', 'fig', 'hazelnut'], multyMaskColorDir, type="color")

The color display of multi mask is shown in the figure:

Grayscale image display because the pixel values are 0, 1, 2 and 3, which are displayed in 8-bit images, all are black, which is difficult to recognize by the naked eye, so it will not be displayed here.

2.2 coco annotation file conversion with single picture mask

In the above, we chose the pycococreator tool, which can also be implemented by the tools in cv2

import cv2
import os
import numpy as np
import json

if __name__ == '__main__':
    # Initialize the corresponding class alias and id
    my_label = {"background": 0,
                "date": 1,
                "fig": 2,
                "hazelnut": 3
                }
    # Initialize the coco dictionary to be saved
    coco = dict()
    coco['images'] = []
    coco['type'] = 'instances'
    coco['annotations'] = []
    coco['categories'] = []

    # Initialization related variables and Tags
    image_set = set()
    category_item_id = 0
    annotation_id = 0
    # 1. Add categories of coco
    my_label = sorted(my_label.items(), key=lambda item: item[1])
    for val in my_label:
        category_item = dict()
        category_item['supercategory'] = 'none'
        category_item_id = val[1]
        if 0 == category_item_id:
            continue
        category_item['id'] = category_item_id
        category_item['name'] = val[0]
        coco['categories'].append(category_item)

    # 2. Add coco's images
    IMAGE_DIR = "data/images/"
    ANNOTATION_DIR = "mydata/multyMaskGray/"
    SAVE_JSON_DIR = "myjson/multyMaskGray.json"
    # Load picture information
    imageListFile = os.listdir(IMAGE_DIR)
    imageListFile.sort(key=lambda x: int(x[:-4]))

    annotationListFile = os.listdir(ANNOTATION_DIR)
    annotationListFile.sort(key=lambda x: int(x[:-4]))
    assert len(imageListFile) == len(annotationListFile)

    for imageId in range(len(imageListFile)):
        assert imageListFile[imageId][0:-4] == annotationListFile[imageId][0:-4]

        annotationPath = ANNOTATION_DIR + annotationListFile[imageId]
        annotationGray = cv2.imread(annotationPath, -1)
        if len(annotationGray.shape) == 3:
            annotationGray = cv2.cvtColor(annotationGray, cv2.COLOR_BGR2GRAY)

        image_item = dict()
        image_item['id'] = imageId
        image_item['file_name'] = imageListFile[imageId]
        image_item['width'] = annotationGray.shape[1]  # size['width']
        image_item['height'] = annotationGray.shape[0]  # size['height']
        coco['images'].append(image_item)
        image_set.add(imageListFile[imageId])

        # 3. Add annotations of coco
        for current_category_id in range(1, len(my_label)):
            img_bi = np.zeros(annotationGray.shape, dtype='uint8')
            img_bi[annotationGray == current_category_id] = 255
            my_contours, _ = cv2.findContours(img_bi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
            for c in my_contours:
                area_t = cv2.contourArea(c)
                # The threshold is set here for filtering
                if 0 == len(c) or area_t < 20:
                    continue
                L_pt = c
                # x,y,w,h
                bbox = cv2.boundingRect(L_pt)
                x1, y1, w1, h1 = bbox
                # If the mark exceeds the limit of the original drawing, ignore it
                if x1 < 0 or y1 < 0 or x1 + w1 > annotationGray.shape[1] or y1 + h1 > annotationGray.shape[0]:
                    continue
                seg = []
                for val in L_pt:
                    x = val[0][0]
                    y = val[0][1]
                    seg.append(int(x))
                    seg.append(int(y))

                bbox = list(bbox)

                annotation_item = dict()
                annotation_item['segmentation'] = []
                annotation_item['segmentation'].append(seg)

                annotation_item['area'] = area_t
                annotation_item['iscrowd'] = 0
                annotation_item['ignore'] = 0
                annotation_item['image_id'] = imageId
                annotation_item['bbox'] = bbox
                annotation_item['category_id'] = current_category_id
                annotation_id += 1
                annotation_item['id'] = annotation_id
                coco['annotations'].append(annotation_item)

    json.dump(coco, open(SAVE_JSON_DIR, 'w'))
    print('ok')

The run json file can be found in the file path of the corresponding setting.

3, Display of operation results:

3.1 single instance annotation file training prediction display:

3.2 single picture annotation file training prediction display:


It can be seen that the training effect of single instance annotation file is better than that of single image annotation file. This is because there are two instances connected or blocked in the mask of single image. In this case, it is difficult to separate different instances, so that the training data is trained as a whole. At the same time, after NMS processing, the prediction results will also determine the adjacent objects as a single instance, so the effect is poor.

Keywords: Computer Vision Deep Learning Object Detection

Added by secret007 on Sat, 05 Feb 2022 04:02:13 +0200