Skip to content

Instantly share code, notes, and snippets.

@Amir22010
Forked from myounus96/convert_voc_to_yolo.md
Created February 22, 2020 22:01
Show Gist options
  • Save Amir22010/a99f18ca19112bc7db0872a36a03a1ec to your computer and use it in GitHub Desktop.
Save Amir22010/a99f18ca19112bc7db0872a36a03a1ec 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)
@paritosh-101
Copy link

Line 44.
difficult = obj.find('difficult').text

What is "difficult" in a PascalVOC .xml file?
Code works fine when all of the "difficult" parts are commented/removed.

@mh2129
Copy link

mh2129 commented Jul 18, 2021

It is not working for me. I managed to get the txt file with all the images, but within the yolo folder all the .txt files are empty

@colinmcgovern
Copy link

colinmcgovern commented Aug 19, 2021

Almost there but this is awesome thank you!

@NickosKal
Copy link

This is awesome thank you!

Hello, how did you made it work because I am trying and I am getting a weird error. Thanks in advance

@Phantonym21
Copy link

Any instructions to run this on Windows 10 because i am unable to do so... the text files are created but they are blank... Any solution??

@ShinHodu
Copy link

That was really usesul, thanks! Can I run this on Colab while mounted with Google drive?

@ShinHodu
Copy link

This is awesome thank you!

Hello, how did you made it work because I am trying and I am getting a weird error. Thanks in advance

what's your error?

@harinduravin
Copy link

Useful and quick

@Hedenir
Copy link

Hedenir commented Nov 4, 2021

Thank you!

@Bellahmer-hacene
Copy link

Bellahmer-hacene commented Nov 5, 2021

Thanks, really helpfull !
You should add some code to add "classes.txt" file in the "yolo" folder in order to open the labeled images in labelimg for exemple.

file = open(full_dir_path +"/yolo/classes.txt", "w")
file.write(< classes >)
file.close()

the < classes > parameter should contain the classes that you used to label yours images :
< classes > = "class1 \nclass2 \nclass3"

@samida22
Copy link

@Yogiwolf did you solve it ? I am getting same error. Thanks

@Shubhs0411
Copy link

This code is not working

@Hedenir
Copy link

Hedenir commented Nov 25, 2021 via email

@Shubhs0411
Copy link

Shubhs0411 commented Nov 25, 2021 via email

@Hedenir
Copy link

Hedenir commented Nov 25, 2021 via email

@iaverypadberg
Copy link

You're script is FIRE

@c-pineau
Copy link

Hi !
Thank you very much for this script

I had to change a few things so I'll just list them behind in case anyone has the same issues.

  • my files were jpeg, but the extension is case sensitive in the script, so check your files extension, i went from .jpg to .JPG:
for filename in glob.glob(dir_path + '/*.JPG'):

(It's on line 13)

  • I had some images that weren't labellised (because i couldn't find anything on them), but I still need to keep them in my folder, so I added an if statement to mak sure it'd just go over my non labelled images. My getImagesInDir function looks like this now:
def getImagesInDir(dir_path):
    image_list = []
    for filename in glob.glob(dir_path + '/*.JPG'):
        xml_filename = os.path.splitext(os.path.basename(filename))[0] + '.xml'
        if (os.path.exists(dir_path + '/' + xml_filename)):
            image_list.append(filename)

    return image_list

@mhamza19
Copy link

Please help. I have uploaded my entire dataset to google drive and mounted it to Google Colab. I am trying to run this script on Colab. My dataset directory is as follows: There are two separate folders for Images and Annotations. Both of these folders reside in a folder named MyDataset. I am compiling this script in MyDataset folder but I am getting an empty yolo folder and .txt file. Also, I don't have train and validation folders of my dataset yet. Any help would be highly appreciated!

@NguyenDuyHung99
Copy link

work well. thanks

@imadgohar
Copy link

@c-pineau Thanks for your post. I had the same issue but I am not getting the output yolo file. I am wonder about the directory structure. I am working with colab. Also my data have not train and val split just like @mhamza19 commented above. What to do in this situation? Your guidance will be appreciated.

@mhamza19
Copy link

mhamza19 commented Sep 1, 2022

@imadgohar What do you mean by Yolo file? Also, I would suggest you to use Roboflow as It will save you a lot of time while automating all this data-related stuff in a few clicks.

@imadgohar
Copy link

@imadgohar What do you mean by Yolo file? Also, I would suggest you to use Roboflow as It will save you a lot of time while automating all this data-related stuff in a few clicks.

I am done with it thanks.

@ipiyushvaghela
Copy link

ipiyushvaghela commented Nov 8, 2022

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

dirs = ['train', 'test']
classes = ['apple', 'banana', 'orange']


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):
    if size[0] == 0:
        dw = 1./(size[0]+0.00001)
    else:
        dw = 1./(size[0])
        
    if size[0] == 0:
        dh = 1./(size[1]+0.00001)
    else:
        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()
print('not working')
for dir_path in dirs:
    full_dir_path = cwd + '\\' + dir_path
    output_path = full_dir_path +'\\yolo\\'

    if not os.path.exists(output_path):
        print(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:
        print(image_path)
        list_file.write(image_path + '\n')
        convert_annotation(full_dir_path, output_path, image_path)
    list_file.close()

    print("Finished processing: " + dir_path)
print('gihub.com/ipiyushvaghela')

For Windows users...

Suppose the DATASET folder contains the train and test folders...
then just create a .py file inside the DATASET folder and paste the above code. and run that py file your .txt will be created inside train --> yolo.

@BayanFatayer
Copy link

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

dirs = ['train', 'test']
classes = ['apple', 'banana', 'orange']


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):
    if size[0] == 0:
        dw = 1./(size[0]+0.00001)
    else:
        dw = 1./(size[0])
        
    if size[0] == 0:
        dh = 1./(size[1]+0.00001)
    else:
        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()
print('not working')
for dir_path in dirs:
    full_dir_path = cwd + '\\' + dir_path
    output_path = full_dir_path +'\\yolo\\'

    if not os.path.exists(output_path):
        print(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:
        print(image_path)
        list_file.write(image_path + '\n')
        convert_annotation(full_dir_path, output_path, image_path)
    list_file.close()

    print("Finished processing: " + dir_path)
print('gihub.com/ipiyushvaghela')

For Windows users...

Suppose the DATASET folder contains the train and test folders... then just create a .py file inside the DATASET folder and paste the above code. and run that py file your .txt will be created inside train --> yolo.

it didn't work for me

@abdollah-semej
Copy link

abdollah-semej commented Aug 8, 2023

very nice
It worked for me
tnks bro

@carlosgomez1987
Copy link

Very nice post!!! It worked well for me.

@DungHD-1997
Copy link

very nice, thank you for share!

@onur-unsoy
Copy link

it didn't work for me. i got this error.
runfile('D:/bit dataset file/BITVehicle_Dataset/from_xml_to_yolo_convert_label_script.py', wdir='D:/bit dataset file/BITVehicle_Dataset')
Traceback (most recent call last):

File ~.conda\envs\thesis_two\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec
exec(code, globals, locals)

File d:\bit dataset file\bitvehicle_dataset\from_xml_to_yolo_convert_label_script.py:75
convert_annotation(full_dir_path, output_path, image_path)

File d:\bit dataset file\bitvehicle_dataset\from_xml_to_yolo_convert_label_script.py:47 in convert_annotation
w = int(size.find('width').text)

ValueError: invalid literal for int() with base 10: '[[1920]]'

Because my labeling files consist like that size.

[[1920]]
[[1080]]
3
How can i change this code? Thank you.

@DungHD-1997
Copy link

it didn't work for me. i got this error. runfile('D:/bit dataset file/BITVehicle_Dataset/from_xml_to_yolo_convert_label_script.py', wdir='D:/bit dataset file/BITVehicle_Dataset') Traceback (most recent call last):

File ~.conda\envs\thesis_two\lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec exec(code, globals, locals)

File d:\bit dataset file\bitvehicle_dataset\from_xml_to_yolo_convert_label_script.py:75 convert_annotation(full_dir_path, output_path, image_path)

File d:\bit dataset file\bitvehicle_dataset\from_xml_to_yolo_convert_label_script.py:47 in convert_annotation w = int(size.find('width').text)

ValueError: invalid literal for int() with base 10: '[[1920]]'

Because my labeling files consist like that size. [[1920]] [[1080]] 3 How can i change this code? Thank you.

This code worked for me but I used another code for more convenience.
https://github.com/Ryo-Kawanami/xml2yolo/tree/master

@Wuito
Copy link

Wuito commented Feb 20, 2024

The author's code works. In VOC dataset, tag xml files and jpg images are stored separately. You need to place your xml tag file and jpg image in the same folder and then run the program to get the result of the conversion. Ensuring file paths as:
├──convert_voc_to_yolo.py
└──train
└── xxx0.xml
└── xxx0.jpg
└──test
└── xxx1.xml
└── xxx1.jpg

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