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)
@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