How to obtain the detection index APs of small targets? ——Dataset Yolo format generates json file

It’s such a simple thing, but I worked on it for several days for no reason. I was very depressed, so I just started without saying much.
First, you must have a data set in YoLo format. Starting from the simplest, you need image data and corresponding txt tag data of the yolo type. (The first folder in the screenshot below stores pictures, and the naming rule uses pure numbers. The latter folder is a txt file, corresponding to the detection label of each picture).

After having these two folders, the first step is to divide the data set. Divide the image dataset into 8:2. The code used is as follows:

import os
import shutil
import random
from tqdm import tqdm

"""
The annotation file is in yolo format (txt file)
Training set: Validation set (8:2)
"""


def split_img(img_path, label_path, split_list):
    try: #Create dataset folder
        Data = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets'
        #os.mkdir(Data)

        train_img_dir = Data + '/images/train'
        val_img_dir = Data + '/images/val'
        # test_img_dir = Data + '/images/test'

        train_label_dir = Data + '/labels/train'
        val_label_dir = Data + '/labels/val'
        # test_label_dir = Data + '/labels/test'

        #Create folder
        os.makedirs(train_img_dir)
        os.makedirs(train_label_dir)
        os.makedirs(val_img_dir)
        os.makedirs(val_label_dir)
        #os.makedirs(test_img_dir)
        # os.makedirs(test_label_dir)

    except:
        print('The file directory already exists')

    train, val = split_list
    all_img = os.listdir(img_path)
    all_img_path = [os.path.join(img_path, img) for img in all_img]
    # all_label = os.listdir(label_path)
    # all_label_path = [os.path.join(label_path, label) for label in all_label]
    train_img = random.sample(all_img_path, int(train * len(all_img_path)))
    train_img_copy = [os.path.join(train_img_dir, img.split('')[-1]) for img in train_img]
    train_label = [toLabelPath(img, label_path) for img in train_img]
    train_label_copy = [os.path.join(train_label_dir, label.split('')[-1]) for label in train_label]
    for i in tqdm(range(len(train_img)), desc='train ', ncols=80, unit='img'):
        _copy(train_img[i], train_img_dir)
        _copy(train_label[i], train_label_dir)
        all_img_path.remove(train_img[i])
    val_img = all_img_path
    val_label = [toLabelPath(img, label_path) for img in val_img]
    for i in tqdm(range(len(val_img)), desc='val ', ncols=80, unit='img'):
        _copy(val_img[i], val_img_dir)
        _copy(val_label[i], val_label_dir)


def _copy(from_path, to_path):
    shutil.copy(from_path, to_path)


def toLabelPath(img_path, label_path):
    img = img_path.split('')[-1]
    label = img.split('.tiff')[0] + '.txt'
    return os.path.join(label_path, label)


if __name__ == '__main__':
    #Change to your own image path
    img_path = 'D:\datasets\small-object-detection-datasets/aluminum_1103\JPEGImages'
    #Modify to your own label path
    label_path = 'D:\datasets\small-object-detection-datasets/aluminum_1103\labels'
    split_list = [0.8, 0.2] # Data set division ratio [train:val]
    split_img(img_path, label_path, split_list)

ok, now there is an additional folder

The structure inside is as follows:

imageSets
images
train The images of the training set are stored in it
val Inside Store images of the verification set
labels
train The labels of the training set are stored in it
val The tags that store the verification set

Next, train.txt and val.txt are generated (these two files are the files where yolov5 reads the image path). The code is as follows:

import os
#TODO: What needs to be changed is the three path addresses. Because train.txt and val.txt are to be generated, they need to be executed twice.

#Specify the path to the B folder
folder_B = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets\images/train'

# Get all image files in folder B
image_files = [f for f in os.listdir(folder_B) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '. tiff'))]

#Create the train.txt file and write the image file name
with open('D:\datasets\small-object-detection-datasets\Solar_1103/train.txt', 'w') as file:
    for image_file in image_files:
        # image_files_myhouzhui = image_file[:6] + ".txt" #Split suffix
        file.write("D:\datasets\small-object-detection-datasets\Solar_1103\JPEGImages/" + image_file + '\
')

print('train.txt file has been created and the image file name has been written')

After this you have five files:

The solution is the last step to generate the json file, run the yolo to COCO script, the code is as follows:

import os
import json
import random
import time
from PIL import Image
import csv
#TODO: The following four paths need to be modified accordingly. In addition, the contents of the class_names list should be modified to the categories of your own data.
coco_format_save_path = 'D:\datasets\small-object-detection-datasets\Solar_1103' # The folder where the standard coco format labels to be generated are located
yolo_format_annotation_path = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets\labels/val' # The folder where the yolo format labels are located
img_pathDir = 'D:\datasets\small-object-detection-datasets\Solar_1103\JPEGImages' # Folder where the picture is located
val_img_pathDir = 'D:\datasets\small-object-detection-datasets\Solar_1103\ImageSets\images/val'
# Category settings
categories = []
class_names = ['bl', 'ce', 'ch', 'cr', 'hi', 'ir', 'oi', 'pe'] #Need to change to your own category
for label in class_names:
    categories.append({'id': class_names.index(label), 'name': label, 'supercategory': ""})

write_json_context = dict() # Write a large dictionary to the .json file
write_json_context['licenses'] = [{'name': "", 'id': 0, 'url': ""}]
write_json_context['info'] = {'contributor': "", 'date_created': "", 'description': "", 'url': "", 'version': "", 'year': ""}
write_json_context['categories'] = categories
write_json_context['images'] = []
write_json_context['annotations'] = []

# The following code mainly adds the key values of 'images' and 'annotations'
imageFileList = os.listdir(img_pathDir)
#Sort by numbers
def sort_list_by_number(strings):
    def extract_number(s):
        # Extract the numeric part from the string
        return int(''.join(filter(str.isdigit, s)))

        # Sort the string list using a custom sort key
    sorted_strings = sorted(strings, key=extract_number)

    return sorted_strings
imageFileList = sort_list_by_number(imageFileList)
'''
    Create a new list with only val folders for secondary judgment! !
'''
val_imageFile = os.listdir(val_img_pathDir)
val_imageFile = sort_list_by_number(val_imageFile)
# Traverse all files in the folder and add all file names to the list
img_id = 0 #Image number
anno_id = 0 # mark the label
for i, imageFile in enumerate(imageFileList):
    if '_' not in imageFile:
        img_id + = 1
        if imageFile in val_imageFile:
            imagePath = os.path.join(img_pathDir, imageFile) # Get the absolute path of the image
            image = Image.open(imagePath) # Read the image
            W, H = image.size # Get the height and width of the image
            img_context = {} # Use a dictionary to store the image information
            # img_name=os.path.basename(imagePath)
            img_context['id'] = img_id # Unique ID index for each image
            img_context['width'] = W
            img_context['height'] = H
            img_context['file_name'] = imageFile
            img_context['license'] = 0
            img_context['flickr_url'] = ""
            img_context['color_url'] = ""
            img_context['date_captured'] = ""

            write_json_context['images'].append(img_context) # Add the image information to the 'image' list

            txtFile = imageFile.split('.')[0] + '.txt' # Get the txt file obtained by the image
            with open(os.path.join(yolo_format_annotation_path, txtFile), 'r') as fr:
                lines = fr.readlines() # Read each line of data from the txt file. lines2 is a list that contains all the annotation information of an image.

            for j, line in enumerate(lines):
                anno_id + = 1 # The annotated id starts from 1
                bbox_dict = {} # Store each bounding box information in the dictionary

                class_id, x, y, w, h = line.strip().split(' ') # Get detailed information of each label box
                class_id, x, y, w, h = int(class_id), float(x), float(y), float(w), float(h) # Convert string type to calculable int and float types

                # Coordinate conversion
                xmin = (x - w / 2) * W
                ymin = (y - h / 2) * H
                xmax = (x + w / 2) * W
                ymax = (y + h / 2) * H
                w = w * W
                h = h * H
                height, width = abs(ymax - ymin), abs(xmax - xmin)

                #Coordinate information of bounding box
                bbox_dict['id'] = anno_id #The index of each annotation information
                bbox_dict['image_id'] = img_id # ID index of the current image
                bbox_dict['category_id'] = class_id #Category information
                bbox_dict['segmentation'] = [[xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax]]
                bbox_dict['area'] = height * width
                bbox_dict['bbox'] = [xmin, ymin, w, h] # Note that the target category needs to be increased by one
                bbox_dict['iscrowd'] = 0
                bbox_dict['attributes'] = ""

                write_json_context['annotations'].append(bbox_dict) # Add each bounding box information stored in the dictionary to the 'annotations' list

name = os.path.join(coco_format_save_path, "annotations" + '.json')
with open(name, 'w') as fw: # Write dictionary information into .json file
    json.dump(write_json_context, fw, indent=4, ensure_ascii=False)

This way you can get the json file.

Explain this file and explain why this file is needed.

The json file is a description of the validation set in the data set (that is, the picture in the val folder, and the description of its coordinates – there are other things, but it is enough to know this for small target detection indicators).

This file is needed because when you use the val.py file to evaluate APs indicators, a best.json will be generated. That json file contains your detection information for the images in the verification set, which is actually the detection frame you generated. Information, compare best.json and annotations.json to get the detection indicators of APs.

Post a successful picture:

——Note that when calling val.py to execute COCO detection indicators, you need to modify three codes in val.py. I don’t have time today, so I’ll leave it for now (search it yourself, it’s easy to search), and wait until you have time. I’ll post the link, but I’m too lazy to look for it now.

——After the image is annotated, it will be a file in json format. To generate a txt file of yolo, you can refer to my blog to record the txt format label of yolo generated after labelme is annotated – CSDN Blog

——Also note that the folder of ImageSets can be deleted after generating the json file. You do not need to use it if you use yolo to run the detection.