Arquivo main.py
O main.py
é um script Python que lê arquivos de anotação no formato Pascal VOC (Visual Object Classes) e converte essas anotações em arquivos de texto no formato YOLO (You Only Look Once), que é um formato popular para treinamento de modelos de detecção de objetos em imagens.
O script lê arquivos de anotação xml e, para cada anotação, extrai informações sobre as caixas delimitadoras dos objetos na imagem, bem como os rótulos desses objetos. Ele então converte essas informações em um formato YOLO compatível e grava um arquivo de texto correspondente. Além disso, ele cria um arquivo de texto com a lista de classes presentes nas anotações, que é útil para treinar modelos de detecção de objetos.
O código depende de algumas bibliotecas Python, incluindo os
, xml.etree.ElementTree
, lxml
, cv2
e glob
. Além disso, ele usa algumas variáveis globais, como o caminho para os arquivos de anotação XML, o caminho para as imagens, o caminho de saída para os arquivos de texto e o formato das imagens. O código também lida com a leitura de um arquivo de texto opcional que lista as classes presentes nas anotações.
Bibliotecas necessárias
os
para interagir com o sistema operacional;
xml.etree.ElementTree
para ler e analisar arquivos XML;
lxml.etree
para processar e analisar XMLs de forma mais eficiente;
cv2
para manipular imagens usando OpenCV;
glob
para encontrar arquivos em um diretório.
typing
fornece recursos para trabalhar com tipos de dados, anotações de tipo e geração de classes genéricas.
PascalVocConverter
A classe PascalVocConverter
converte anotações de objetos em imagens do formato Pascal VOC para um formato de anotação de detecção de objetos diferente. Para isso, a classe recebe o caminho dos arquivos xml com as anotações, o caminho das imagens correspondentes, o caminho de saída para os arquivos de anotação convertidos, um arquivo de texto com a lista de classes e uma extensão de arquivo (default ".jpg").
Source code in firevision/main.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 | class PascalVocConverter:
"""
A classe `PascalVocConverter` converte anotações de objetos em imagens do formato Pascal VOC para um formato de anotação de detecção de objetos diferente. Para isso, a classe recebe o caminho dos arquivos xml com as anotações, o caminho das imagens correspondentes, o caminho de saída para os arquivos de anotação convertidos, um arquivo de texto com a lista de classes e uma extensão de arquivo (default ".jpg").
"""
def __init__(self, parentpath, addxmlpath, addimgpath, outputpath, classes_txt, ext=".jpg"):
"""
O método `__init__()` é o construtor da classe e inicializa o objeto com os caminhos dos diretórios dos arquivos XML, das imagens, a pasta de saída para salvar os arquivos txt gerados, um arquivo de texto que contém os nomes das classes e uma extensão padrão .jpg.
"""
self.parentpath = parentpath
self.addxmlpath = addxmlpath
self.addimgpath = addimgpath
self.outputpath = outputpath
self.classes_txt = classes_txt
self.classes = dict()
self.num_classes = 0
self.ext = ext
def run(self):
"""
O método `run()` realiza a conversão para o novo formato de anotação e grava os resultados em arquivos de texto separados. Cada arquivo de anotação de detecção de objeto possui uma linha para cada objeto anotado, indicando a classe do objeto e as coordenadas normalizadas do retângulo delimitador que contém o objeto na imagem. Além disso, a classe cria um arquivo de texto classes.txt contendo a lista de todas as classes detectadas nas anotações de entrada.
"""
if os.path.isfile(self.classes_txt):
with open(self.classes_txt, "r") as f:
class_list = f.read().strip().split()
self.classes = {k: v for (v, k) in enumerate(class_list)}
xmlPaths = glob(self.addxmlpath + "/*.xml")
for xmlPath in xmlPaths:
tVocParseReader = PascalVocReader(xmlPath)
shapes = tVocParseReader.getShapes()
with open(self.outputpath + "/" + os.path.basename(xmlPath)[:-4] + ".txt", "w") as f:
for shape in shapes:
class_name = shape[0]
box = shape[1]
filename = os.path.splitext(
self.addimgpath + "/" + os.path.basename(xmlPath)[:-4])[0] + self.ext
if class_name not in self.classes.keys():
self.classes[class_name] = self.num_classes
self.num_classes += 1
class_idx = self.classes[class_name]
(height, width, _) = cv2.imread(filename).shape
coord_min = box[0]
coord_max = box[2]
xcen = float((coord_min[0] + coord_max[0])) / 2 / width
ycen = float((coord_min[1] + coord_max[1])) / 2 / height
w = float((coord_max[0] - coord_min[0])) / width
h = float((coord_max[1] - coord_min[1])) / height
f.write("%d %.06f %.06f %.06f %.06f\n" %
(class_idx, xcen, ycen, w, h))
print(class_idx, xcen, ycen, w, h)
with open(self.parentpath + "classes.txt", "w") as f:
for key in self.classes.keys():
f.write("%s\n" % key)
print(key)
|
__init__(parentpath, addxmlpath, addimgpath, outputpath, classes_txt, ext='.jpg')
O método __init__()
é o construtor da classe e inicializa o objeto com os caminhos dos diretórios dos arquivos XML, das imagens, a pasta de saída para salvar os arquivos txt gerados, um arquivo de texto que contém os nomes das classes e uma extensão padrão .jpg.
Source code in firevision/main.py
87
88
89
90
91
92
93
94
95
96
97
98 | def __init__(self, parentpath, addxmlpath, addimgpath, outputpath, classes_txt, ext=".jpg"):
"""
O método `__init__()` é o construtor da classe e inicializa o objeto com os caminhos dos diretórios dos arquivos XML, das imagens, a pasta de saída para salvar os arquivos txt gerados, um arquivo de texto que contém os nomes das classes e uma extensão padrão .jpg.
"""
self.parentpath = parentpath
self.addxmlpath = addxmlpath
self.addimgpath = addimgpath
self.outputpath = outputpath
self.classes_txt = classes_txt
self.classes = dict()
self.num_classes = 0
self.ext = ext
|
run()
O método run()
realiza a conversão para o novo formato de anotação e grava os resultados em arquivos de texto separados. Cada arquivo de anotação de detecção de objeto possui uma linha para cada objeto anotado, indicando a classe do objeto e as coordenadas normalizadas do retângulo delimitador que contém o objeto na imagem. Além disso, a classe cria um arquivo de texto classes.txt contendo a lista de todas as classes detectadas nas anotações de entrada.
Source code in firevision/main.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 | def run(self):
"""
O método `run()` realiza a conversão para o novo formato de anotação e grava os resultados em arquivos de texto separados. Cada arquivo de anotação de detecção de objeto possui uma linha para cada objeto anotado, indicando a classe do objeto e as coordenadas normalizadas do retângulo delimitador que contém o objeto na imagem. Além disso, a classe cria um arquivo de texto classes.txt contendo a lista de todas as classes detectadas nas anotações de entrada.
"""
if os.path.isfile(self.classes_txt):
with open(self.classes_txt, "r") as f:
class_list = f.read().strip().split()
self.classes = {k: v for (v, k) in enumerate(class_list)}
xmlPaths = glob(self.addxmlpath + "/*.xml")
for xmlPath in xmlPaths:
tVocParseReader = PascalVocReader(xmlPath)
shapes = tVocParseReader.getShapes()
with open(self.outputpath + "/" + os.path.basename(xmlPath)[:-4] + ".txt", "w") as f:
for shape in shapes:
class_name = shape[0]
box = shape[1]
filename = os.path.splitext(
self.addimgpath + "/" + os.path.basename(xmlPath)[:-4])[0] + self.ext
if class_name not in self.classes.keys():
self.classes[class_name] = self.num_classes
self.num_classes += 1
class_idx = self.classes[class_name]
(height, width, _) = cv2.imread(filename).shape
coord_min = box[0]
coord_max = box[2]
xcen = float((coord_min[0] + coord_max[0])) / 2 / width
ycen = float((coord_min[1] + coord_max[1])) / 2 / height
w = float((coord_max[0] - coord_min[0])) / width
h = float((coord_max[1] - coord_min[1])) / height
f.write("%d %.06f %.06f %.06f %.06f\n" %
(class_idx, xcen, ycen, w, h))
print(class_idx, xcen, ycen, w, h)
with open(self.parentpath + "classes.txt", "w") as f:
for key in self.classes.keys():
f.write("%s\n" % key)
print(key)
|
PascalVocReader
A classe PascalVocReader
é responsável pela leitura de arquivos no formato Pascal VOC.
Source code in firevision/main.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 | class PascalVocReader:
"""
A classe `PascalVocReader` é responsável pela leitura de arquivos no formato Pascal VOC.
"""
def __init__(self, filepath):
"""
O método `__init__()` é o construtor da classe e inicializa as variáveis necessárias, como a lista de formas (`shapes`), o caminho do arquivo (`filepath`) e uma variável booleana para verificar se o arquivo foi verificado (verified). Também chama a função `parseXML()` para fazer a análise do arquivo xml.
"""
self.shapes = []
self.filepath = filepath
self.verified = False
try:
self.parseXML()
except:
pass
def getShapes(self):
"""
O método `getShapes()` retorna a lista de formas (`shapes`) encontradas no arquivo xml.
"""
return self.shapes
def addShape(self, label, bndbox, filename, difficult):
"""
O método `addShape()` é responsável por extrair informações sobre uma forma, como rótulo (`label`), coordenadas do retângulo delimitador (`bndbox`), nome do arquivo (`filename`) e se a forma é difícil de ser detectada (`difficult`), e adicioná-las à lista de formas. A partir das coordenadas do retângulo delimitador, o método calcula os pontos dos quatro vértices do retângulo e adiciona-os à lista de formas.
"""
xmin = int(bndbox.find("xmin").text)
ymin = int(bndbox.find("ymin").text)
xmax = int(bndbox.find("xmax").text)
ymax = int(bndbox.find("ymax").text)
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
self.shapes.append((label, points, filename, difficult))
def parseXML(self):
"""
O método `parseXML()` é responsável por analisar o arquivo XML e extrair informações sobre cada objeto (anotação de objeto) encontrado no arquivo. Ele usa a biblioteca `ElementTree` para analisar o arquivo XML e extrair o caminho da imagem e a variável de verificação. Em seguida, itera sobre cada objeto encontrado no arquivo, extrai informações sobre a forma e chama o método addShape para adicioná-la à lista de formas.
"""
assert self.filepath.endswith(XML_EXT), "Unsupport file format"
parser = etree.XMLParser(encoding=ENCODE_METHOD)
xmltree = ElementTree.parse(self.filepath, parser=parser).getroot()
path = xmltree.find("path").text
try:
verified = xmltree.attrib["verified"]
if verified == "yes":
self.verified = True
except KeyError:
self.verified = False
for object_iter in xmltree.findall("object"):
bndbox = object_iter.find("bndbox")
label = object_iter.find("name").text
difficult = False
if object_iter.find("difficult") is not None:
difficult = bool(int(object_iter.find("difficult").text))
self.addShape(label, bndbox, path, difficult)
return True
|
__init__(filepath)
O método __init__()
é o construtor da classe e inicializa as variáveis necessárias, como a lista de formas (shapes
), o caminho do arquivo (filepath
) e uma variável booleana para verificar se o arquivo foi verificado (verified). Também chama a função parseXML()
para fazer a análise do arquivo xml.
Source code in firevision/main.py
25
26
27
28
29
30
31
32
33
34
35
36 | def __init__(self, filepath):
"""
O método `__init__()` é o construtor da classe e inicializa as variáveis necessárias, como a lista de formas (`shapes`), o caminho do arquivo (`filepath`) e uma variável booleana para verificar se o arquivo foi verificado (verified). Também chama a função `parseXML()` para fazer a análise do arquivo xml.
"""
self.shapes = []
self.filepath = filepath
self.verified = False
try:
self.parseXML()
except:
pass
|
addShape(label, bndbox, filename, difficult)
O método addShape()
é responsável por extrair informações sobre uma forma, como rótulo (label
), coordenadas do retângulo delimitador (bndbox
), nome do arquivo (filename
) e se a forma é difícil de ser detectada (difficult
), e adicioná-las à lista de formas. A partir das coordenadas do retângulo delimitador, o método calcula os pontos dos quatro vértices do retângulo e adiciona-os à lista de formas.
Source code in firevision/main.py
44
45
46
47
48
49
50
51
52
53 | def addShape(self, label, bndbox, filename, difficult):
"""
O método `addShape()` é responsável por extrair informações sobre uma forma, como rótulo (`label`), coordenadas do retângulo delimitador (`bndbox`), nome do arquivo (`filename`) e se a forma é difícil de ser detectada (`difficult`), e adicioná-las à lista de formas. A partir das coordenadas do retângulo delimitador, o método calcula os pontos dos quatro vértices do retângulo e adiciona-os à lista de formas.
"""
xmin = int(bndbox.find("xmin").text)
ymin = int(bndbox.find("ymin").text)
xmax = int(bndbox.find("xmax").text)
ymax = int(bndbox.find("ymax").text)
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
self.shapes.append((label, points, filename, difficult))
|
getShapes()
O método getShapes()
retorna a lista de formas (shapes
) encontradas no arquivo xml.
Source code in firevision/main.py
| def getShapes(self):
"""
O método `getShapes()` retorna a lista de formas (`shapes`) encontradas no arquivo xml.
"""
return self.shapes
|
parseXML()
O método parseXML()
é responsável por analisar o arquivo XML e extrair informações sobre cada objeto (anotação de objeto) encontrado no arquivo. Ele usa a biblioteca ElementTree
para analisar o arquivo XML e extrair o caminho da imagem e a variável de verificação. Em seguida, itera sobre cada objeto encontrado no arquivo, extrai informações sobre a forma e chama o método addShape para adicioná-la à lista de formas.
Source code in firevision/main.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 | def parseXML(self):
"""
O método `parseXML()` é responsável por analisar o arquivo XML e extrair informações sobre cada objeto (anotação de objeto) encontrado no arquivo. Ele usa a biblioteca `ElementTree` para analisar o arquivo XML e extrair o caminho da imagem e a variável de verificação. Em seguida, itera sobre cada objeto encontrado no arquivo, extrai informações sobre a forma e chama o método addShape para adicioná-la à lista de formas.
"""
assert self.filepath.endswith(XML_EXT), "Unsupport file format"
parser = etree.XMLParser(encoding=ENCODE_METHOD)
xmltree = ElementTree.parse(self.filepath, parser=parser).getroot()
path = xmltree.find("path").text
try:
verified = xmltree.attrib["verified"]
if verified == "yes":
self.verified = True
except KeyError:
self.verified = False
for object_iter in xmltree.findall("object"):
bndbox = object_iter.find("bndbox")
label = object_iter.find("name").text
difficult = False
if object_iter.find("difficult") is not None:
difficult = bool(int(object_iter.find("difficult").text))
self.addShape(label, bndbox, path, difficult)
return True
|
main()
A função main()
é o construtor da classe e inicializa o objeto com o caminho do arquivo xml a ser lido e cria algumas variáveis como uma lista vazia de formas (shapes
) encontradas no arquivo, o caminho do arquivo e uma variável de verificação de integridade do arquivo.
Source code in firevision/main.py
148
149
150
151
152
153
154
155
156
157
158
159
160 | def main():
"""
A função `main()` é o construtor da classe e inicializa o objeto com o caminho do arquivo xml a ser lido e cria algumas variáveis como uma lista vazia de formas (`shapes`) encontradas no arquivo, o caminho do arquivo e uma variável de verificação de integridade do arquivo.
"""
parent_path = "./"
addxmlpath = parent_path + "data/training/annotations"
addimgpath = parent_path + "data/training/images"
output_path = parent_path + "labels"
classes_txt = "./fire_classes.txt"
converter = PascalVocConverter(
parent_path, addxmlpath, addimgpath, output_path, classes_txt)
converter.run()
|