【T-Rex Label基础教程】全新的自动化图片标注工具/软件 提高效率必备 可在线

斑斓GORGEOUS 2024-10-27 08:37:01 阅读 95

文章目录

0 数据准备1 图片导入2 数据标注3 标注导出4 格式转换

<code>T-Rex Label是一种自动化辅助图片标注的工具,它可以使人们脱离繁琐的传统标注流程(如LabelImg),极大地提高标注的效率。

在这里插入图片描述

话不多说,先来看一看自动化辅助标注的震撼效果:

那么该如何获取呢? T-Rex Label Github官网提供了在线和本地两种方式,这里为了避免繁琐的环境配置过程,选择T-Rex Label在线工具进行说明。(点击即可跳转)

以下从数据准备、图片导入、数据标注、标注导出、格式转换五个小节来说明<code>T-Rex Label的使用流程。

0 数据准备

人们总是对例子情有独钟,为了帮助你更好的理解操作流程,我将通过一个案例来说明。

我们手中有如下的数据目录,Annotations_voc存放VOC格式的xml标注文件,Annotations_yolo存放YOLO格式的txt标注文件,当然目前还没有开始标注,他们都是空的,而pic中存放的是此次要标注的5张图片。

我们的目的是在<code>T-Rex Label在线工具上对pic进行标注(把所有人头head标注出来),获得相应的Annotations_yolo标注文件(因为目前只支持导出COCO和YOLO格式),再使用yolo2voc.py(后续会提供)将YOLO格式转化为VOC格式,从而获取Annotations_voc 标注文件。

1 图片导入

打开在线工具链接后,在以下界面选择要导入的图片即可,目前最多支持一次导入200张图片并对其进行标注。

可以看到导入图片后如下,接着点击开始标注即可。

2 数据标注

下面用动图演示标注过程如下:

{\color{#E16B8C}{①}}

① 过程中因为预先没有定义分类,先按A键进入智能标注模式时,会提示你创建一个分类(e.g. head);

{\color{#E16B8C}{②}}

② 创建完成后,接着左键划标注框

\rightarrow

Enter确定,这样一张图片的标注就完成了;

{\color{#E16B8C}{③}}

③ 过程中如果有划错的标注框,可以按Ctrl+Z回撤,也可以按D键进入选择模式,点击要删除的标定框后按Del键即可;

{\color{#E16B8C}{④}}

④ 过程中如果有AI漏标的对象,可以按R键进入手动标注模式进行框定;

{\color{#E16B8C}{⑤}}

⑤ 另外可以用小键盘上的左右方向键切换图片;

{\color{#E16B8C}{⑥}}

⑥ 建议先用智能标注完成一遍所有导入图片的粗标注,再用手动标注的方式进行精标注。

3 标注导出

所有图片都标注完成后导出标注即可。这里只可选择COCO或YOLO格式,我们先选择YOLO格式。

接着你会得到一个压缩包,解压后的文件目录如下,其中images是上传的原始图片,labels是YOLO格式的标注,classes是定义的分类。

在这里插入图片描述

到这儿,如果你需要的是YOLO或COCO格式,那么标注过程就完成了,如果你需要VOC格式,请看格式转换一节。

4 格式转换

首先创建一个如下图所示的项目目录:

在这里插入图片描述

{\color{#E16B8C}{①}}

data图片导入一节中已经讲解过了;

{\color{#E16B8C}{②}}

② 将下载解压得到的images中的图片放入pic中,将labels中的文件放入Annotations_yolo中,此时Annotations_voc仍为空;

{\color{#E16B8C}{③}}

③ 接着创建yolo2voc.py的转换函数(主要更改__main__的部分),运行即可在Annotations_voc中看到VOC格式的标注文件;

<code># yolo2voc.py文件的内容

from xml.dom.minidom import Document

import os

import cv2

# 可以修改makexml定制化要写入xml的内容

def makexml(picPath, txtPath, xmlPath,

folder_name="pic", #图片文件建的名称code>

dirpath=".\\data\\pic\\", #图片目录code>

):

"""此函数用于将yolo格式txt标注文件转换为voc格式xml标注文件

"""

dic = { '0': "head"} # 创建字典用来对类型进行转换

files = os.listdir(txtPath)

for name in files:

xmlBuilder = Document()

annotation = xmlBuilder.createElement("annotation")

xmlBuilder.appendChild(annotation)

txtFile = open(os.path.join(txtPath, name))

txtList = txtFile.readlines()

img = cv2.imread(os.path.join(picPath, name[0:-4] + ".jpg"))

Pheight, Pwidth, Pdepth = img.shape

folder = xmlBuilder.createElement("folder")

foldercontent = xmlBuilder.createTextNode(folder_name)

folder.appendChild(foldercontent)

annotation.appendChild(folder)

filename = xmlBuilder.createElement("filename")

filenamecontent = xmlBuilder.createTextNode(name[0:-4] + ".jpg")

filename.appendChild(filenamecontent)

annotation.appendChild(filename)

path = xmlBuilder.createElement("path")

pathcontent = xmlBuilder.createTextNode(dirpath + name[0:-4] + ".jpg")

path.appendChild(pathcontent)

annotation.appendChild(path)

source = xmlBuilder.createElement("source")

annotation.appendChild(source)

database = xmlBuilder.createElement("database")

databasecontent = xmlBuilder.createTextNode("Unknown")

database.appendChild(databasecontent)

source.appendChild(database)

size = xmlBuilder.createElement("size")

width = xmlBuilder.createElement("width")

widthcontent = xmlBuilder.createTextNode(str(Pwidth))

width.appendChild(widthcontent)

size.appendChild(width)

height = xmlBuilder.createElement("height")

heightcontent = xmlBuilder.createTextNode(str(Pheight))

height.appendChild(heightcontent)

size.appendChild(height)

depth = xmlBuilder.createElement("depth")

depthcontent = xmlBuilder.createTextNode(str(Pdepth))

depth.appendChild(depthcontent)

size.appendChild(depth)

annotation.appendChild(size)

segmented = xmlBuilder.createElement("segmented")

segmentedcontent = xmlBuilder.createTextNode("0")

segmented.appendChild(segmentedcontent)

annotation.appendChild(segmented)

for j in txtList:

oneline = j.strip().split(" ")

object = xmlBuilder.createElement("object")

picname = xmlBuilder.createElement("name")

namecontent = xmlBuilder.createTextNode(dic[oneline[0]])

picname.appendChild(namecontent)

object.appendChild(picname)

pose = xmlBuilder.createElement("pose")

posecontent = xmlBuilder.createTextNode("Unspecified")

pose.appendChild(posecontent)

object.appendChild(pose)

truncated = xmlBuilder.createElement("truncated")

truncatedContent = xmlBuilder.createTextNode("0")

truncated.appendChild(truncatedContent)

object.appendChild(truncated)

difficult = xmlBuilder.createElement("difficult")

difficultcontent = xmlBuilder.createTextNode("0")

difficult.appendChild(difficultcontent)

object.appendChild(difficult)

bndbox = xmlBuilder.createElement("bndbox")

xmin = xmlBuilder.createElement("xmin")

mathData = int(((float(oneline[1])) * Pwidth + 1) - (float(oneline[3])) * 0.5 * Pwidth)

xminContent = xmlBuilder.createTextNode(str(mathData))

xmin.appendChild(xminContent)

bndbox.appendChild(xmin)

ymin = xmlBuilder.createElement("ymin")

mathData = int(((float(oneline[2])) * Pheight + 1) - (float(oneline[4])) * 0.5 * Pheight)

yminContent = xmlBuilder.createTextNode(str(mathData))

ymin.appendChild(yminContent)

bndbox.appendChild(ymin)

xmax = xmlBuilder.createElement("xmax")

mathData = int(((float(oneline[1])) * Pwidth + 1) + (float(oneline[3])) * 0.5 * Pwidth)

xmaxContent = xmlBuilder.createTextNode(str(mathData))

xmax.appendChild(xmaxContent)

bndbox.appendChild(xmax)

ymax = xmlBuilder.createElement("ymax")

mathData = int(((float(oneline[2])) * Pheight + 1) + (float(oneline[4])) * 0.5 * Pheight)

ymaxContent = xmlBuilder.createTextNode(str(mathData))

ymax.appendChild(ymaxContent)

bndbox.appendChild(ymax)

object.appendChild(bndbox)

annotation.appendChild(object)

# 使用字符串方式写入 XML 文件,避免生成 XML 声明

# 使用临时文件写入 XML

temp_xml_path = os.path.join(xmlPath, name[0:-4] + "_temp.xml")

with open(temp_xml_path, 'w', encoding='utf-8') as temp_file:code>

xmlBuilder.writexml(temp_file, indent='\t', newl='\n', addindent='\t', encoding='utf-8')code>

# 读取临时 XML 文件并处理

with open(temp_xml_path, 'r', encoding='utf-8') as temp_file:code>

lines = temp_file.readlines()

# 删除第一行并缩进后面的行

with open(os.path.join(xmlPath, name[0:-4] + ".xml"), 'w', encoding='utf-8') as final_file:code>

for line in lines[1:]: # 从第二行开始写入

final_file.write(line[1:]) # 去掉每一行的第一个缩进

# 删除临时文件

os.remove(temp_xml_path)

if __name__ == "__main__":

picPath = "data/pic/" # 图片所在文件夹路径,后面的/一定要带上

txtPath = "data/Annotations_yolo/" # txt所在文件夹路径,后面的/一定要带上

xmlPath = "data/Annotations_voc/" # xml文件保存路径,后面的/一定要带上

makexml(picPath, txtPath, xmlPath,

folder_name="pic",code>

dirpath=".\\data\\pic\\")code>

{\color{#E16B8C}{④}}

④ 为了验证转换的正确性,使用demo.py文件(主要更改__main__的部分)可视化VOC标注的效果。

# demo.py文件的内容

import os

import xml.etree.ElementTree as ET

from PIL import Image, ImageDraw

def parse_voc_xml(xml_file):

"""解析 VOC 格式的 XML 文件,提取检测框信息"""

tree = ET.parse(xml_file)

root = tree.getroot()

boxes = []

for obj in root.findall('object'):

name = obj.find('name').text

xmlbox = obj.find('bndbox')

xmin = int(xmlbox.find('xmin').text)

ymin = int(xmlbox.find('ymin').text)

xmax = int(xmlbox.find('xmax').text)

ymax = int(xmlbox.find('ymax').text)

boxes.append((name, xmin, ymin, xmax, ymax))

return boxes

def visualize_boxes(image_path, boxes):

"""在图片上绘制检测框"""

image = Image.open(image_path)

draw = ImageDraw.Draw(image)

for box in boxes:

name, xmin, ymin, xmax, ymax = box

draw.rectangle([xmin, ymin, xmax, ymax], outline='red', width=3)code>

draw.text((xmin, ymin), name, fill='red')code>

return image

def main(image_path, xml_path):

# 解析 XML 文件,获取检测框

boxes = parse_voc_xml(xml_path)

# 可视化检测框

visualized_image = visualize_boxes(image_path, boxes)

# 显示图片

visualized_image.show()

if __name__ == "__main__":

# 输入图片和对应的 XML 文件路径

image_path = "data/pic/002861.jpg" # 替换为你的图片路径

xml_path = "data/Annotations_voc/002861.xml" # 替换为你的 XML 文件路径

main(image_path, xml_path)

可以看到运行后的可视化效果如下,说明YOLO格式转VOC格式成功。

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。