Skip to content

Instantly share code, notes, and snippets.

@myounus96
Forked from vdalv/convert_voc_to_yolo.md
Last active February 3, 2022 22:27
Show Gist options
  • Save myounus96/ceaf66e11a9c0f555b66a75d5b557465 to your computer and use it in GitHub Desktop.
Save myounus96/ceaf66e11a9c0f555b66a75d5b557465 to your computer and use it in GitHub Desktop.
convert pascal voc dataset to yolo format

Convert PascalVOC Annotations to YOLO

This script reads PascalVOC xml files, and converts them to YOLO txt files.

Note: This script was written and tested on Ubuntu. YMMV on other OS's.

Disclaimer: This code is a modified version of Joseph Redmon's voc_label.py

Instructions:

  1. Place the convert_voc_to_yolo.py file into your data folder.
  2. Edit the dirs array (line 8) to contain the folders where your images and xmls are located. Note: this script assumes all of your images are .jpg's (line 13).
  3. Edit the classes array (line 9) to contain all of your classes.
  4. Run the script. Upon running the script, each of the given directories will contain a 'yolo' folder that contains all of the YOLO txt files. A text file containing all of the image paths will be created in the cwd, for each given directory.

Make sure to put images and xml files in the root of train.Like this(image is in comment),here my folder name is VOCData and yolo folder is generated by script.

convert_voc_to_yolo.py:

import glob
import os
import pickle
import xml.etree.ElementTree as ET
from os import listdir, getcwd
from os.path import join

dirs = ['train', 'val']
classes = ['person', 'car']

def getImagesInDir(dir_path):
    image_list = []
    for filename in glob.glob(dir_path + '/*.jpg'):
        image_list.append(filename)

    return image_list

def convert(size, box):
    dw = 1./(size[0])
    dh = 1./(size[1])
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)

def convert_annotation(dir_path, output_path, image_path):
    basename = os.path.basename(image_path)
    basename_no_ext = os.path.splitext(basename)[0]

    in_file = open(dir_path + '/' + basename_no_ext + '.xml')
    out_file = open(output_path + basename_no_ext + '.txt', 'w')
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult)==1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

cwd = getcwd()

for dir_path in dirs:
    full_dir_path = cwd + '/' + dir_path
    output_path = full_dir_path +'/yolo/'

    if not os.path.exists(output_path):
        os.makedirs(output_path)

    image_paths = getImagesInDir(full_dir_path)
    list_file = open(full_dir_path + '.txt', 'w')

    for image_path in image_paths:
        list_file.write(image_path + '\n')
        convert_annotation(full_dir_path, output_path, image_path)
    list_file.close()

    print("Finished processing: " + dir_path)
@myounus96
Copy link
Author

image

@myounus96
Copy link
Author

To remove the data of extra classes,check this gist
https://gist.github.com/M-Younus/4de69a641d67ac8bcef6874efaf08392

@jubaer-ad
Copy link

Thanks a lot brother.

@vasyllyashkevych
Copy link

Thanks! Finally I see working example!

Copy link

ghost commented Mar 8, 2020

Thanks bro!!! The only voc2yolo converter that works!

@MakAbdel
Copy link

MakAbdel commented Apr 3, 2020

Hi, i want to convert xml file to yolo format, but i have this following error message:

difficult = obj.find('difficult').text
AttributeError: 'NoneType' object has no attribute 'text'

@808brick
Copy link

Works like a charm, thanks!

@hamzehmirhaji
Copy link

Hi,
I run this script without error but I don't receive any data.
could you help me?

@hamzehmirhaji
Copy link

Hi,
I run this script without error but I don't receive any data.
could you help me?

@hamzehmirhaji
Copy link

Hi,
I run this script without error but I don't receive any data.
could you help me?

@hamzehmirhaji
Copy link

Hi,
I run this script without error but I don't receive any data.
could you help me?

@Maziar-Kasaei
Copy link

Hi,
I run this script without error but I don't receive any data.
could you help me?

Are you sure your images are in jpg format? If not, you should change the code to whatever format your images are.

@Sequential-circuits
Copy link

Thanks but this software works poorly: it raises no errors. Trash

@technopharmer2000
Copy link

I did a control by opening a VOC labelled .jpg and saving it as yolo format, but when I run this script the values for the same .jpg are slightly off. Whats happening here?

Control:
2 0.365234 0.458623 0.119358 0.107060
2 0.699653 0.371238 0.099826 0.092593
1 0.730035 0.497830 0.058594 0.073785
0 0.515191 0.452257 0.130208 0.263310
0 0.600694 0.271123 0.129774 0.151042

voc_to_yolo.py:
2 0.3641493055555555 0.4548611111111111 0.11935763888888888 0.10706018518518517
2 0.6994357638888888 0.37094907407407407 0.09982638888888888 0.09259259259259259
1 0.7298177083333333 0.49754050925925924 0.05859375 0.07378472222222222
0 0.5149739583333333 0.45196759259259256 0.13020833333333331 0.2633101851851852
0 0.6004774305555555 0.2708333333333333 0.12977430555555555 0.15104166666666666

@technopharmer2000
Copy link

I did a control by opening a VOC labelled .jpg and saving it as yolo format, but when I run this script the values for the same .jpg are slightly off. Whats happening here?

Control:
2 0.365234 0.458623 0.119358 0.107060
2 0.699653 0.371238 0.099826 0.092593
1 0.730035 0.497830 0.058594 0.073785
0 0.515191 0.452257 0.130208 0.263310
0 0.600694 0.271123 0.129774 0.151042

voc_to_yolo.py:
2 0.3641493055555555 0.4548611111111111 0.11935763888888888 0.10706018518518517
2 0.6994357638888888 0.37094907407407407 0.09982638888888888 0.09259259259259259
1 0.7298177083333333 0.49754050925925924 0.05859375 0.07378472222222222
0 0.5149739583333333 0.45196759259259256 0.13020833333333331 0.2633101851851852
0 0.6004774305555555 0.2708333333333333 0.12977430555555555 0.15104166666666666

Found that changing the convert func to the following resolves the issue:
def convert(size, box):
dw = 1./(size[0])
dh = 1./(size[1])
x = (box[0] + box[1])/2.0
y = (box[2] + box[3])/2.0
w = box[1] - box[0]
h = box[3] - box[2]
x = round(xdw, 6)
w = round(w
dw, 6)
y = round(ydh, 6)
h = round(h
dh, 6)
return (x,y,w,h)

@xXMiltonTGXx
Copy link

It really worked, thanks.

@myounus96
Copy link
Author

myounus96 commented May 26, 2020

Hi, i want to convert xml file to yolo format, but i have this following error message:

difficult = obj.find('difficult').text
AttributeError: 'NoneType' object has no attribute 'text'

This script converts pascal format to yolo not every XML format to yolo.

@myounus96
Copy link
Author

myounus96 commented May 26, 2020

Hi,
I run this script without error but I don't receive any data.
could you help me?

Please make sure directory structure is according to 1st comment, and this script converts pascal format to yolo not every XML format to yolo.

@emmexx
Copy link

emmexx commented Jun 9, 2020

If the xml file has negative values, e.g.:

	<object>
		<name>bike</name>
		<pose>Unspecified</pose>
		<truncated>1</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>169</xmin>
			<ymin>1</ymin>
			<xmax>190</xmax>
			<ymax>-33</ymax>
		</bndbox>
	</object>

the generated line in yolo format has negative values too and darknet yolo doesn't like negative values:

Wrong annotation: x = 0, y = 0, < 0 or > 1, file: myfile.txt

@emmexx
Copy link

emmexx commented Jun 9, 2020

Since not all images in a folder have a corresponding .xml file, the script stops with an error.
I suggest to add a check before executing the function that converts to yolo:

  for image_path in image_paths:
     #check first if xml file exists fot the image
       basename = os.path.basename(image_path)
       basename_no_ext = os.path.splitext(basename)[0]
       if os.path.exists(dir_path + '/' + basename_no_ext + '.xml'):
           list_file.write(image_path + '\n')
           convert_annotation(full_dir_path, output_path, image_path)
   list_file.close()

   print("Finished processing: " + dir_path)

@anjilshakya
Copy link

anjilshakya commented Nov 29, 2020

the txt files in yolo folder ,that got converted ,is empty
all of those txt files are empty
what must have went wrong?

@ludwigwittgenstein2
Copy link

not sure -- it would be nice if there's additional details to this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment