掘金 阅读 ( ) • 2024-03-08 11:18

theme: v-green

前言

在做计算机视觉和深度学习的时候经常会有大量的数据集,这就牵扯到数据标注这件事情,尤其在一张图片进行多分类的情况下,手工标注费时费力,对于一些就是数据框每次创建,修订,以及分类选择,大部分时间都是花在了这些事情上,下面我从各个方向写了几个代码进行自动生成标注文件的内容(作者使用的是labelImg,标注文件选择的是xml文件,代码是使用的Python)。

  1. 创建xml标注文件
  2. 生成特定分类内容
  3. 特定标注文件内容的更改
  4. 特定数据框的删除
  5. 数据框的增加

注意,本篇代码写的较为简陋,能用,如有不懂得地方,在下方评论区评论即可。

创建xml文件以及生成默认的数据框

使用lxml创建一系列的数据标注xml标签文件,生成文件的元信息。

将分类信息写在data字典里面。注意改写图像的元信息(例如图片的尺寸啥的,不过这个一般在LabelImg同时打开图片的对应的xml会自动改写这些元信息,不过文件名称一定得对应起来。)

# _*_ coding: utf-8 _*_
# @Time     : 2024/3/7 22:18
# @Author   : jiaojiao
from lxml.etree import Element, tostring, parse
from lxml.etree import SubElement as subElement

def xml_construct(save_path, folder, filename, pngfilename, width=1920, height=1080, depth=3, segmented=0):
   default_text = 'default'
   node_root = Element('annotation')  # 根节点

   node_folder = subElement(node_root, 'folder')  # 在节点下添加名为'folder'的子节点
   node_folder.text = folder  # 设定节点的文字

   node_filename = subElement(node_root, 'filename')
   node_filename.text = filename

   node_path = subElement(node_root, 'path')
   node_path.text = pngfilename

   node_source = subElement(node_root, 'source')
   node_source_width = subElement(node_source, 'database')
   node_source_width.text = '%s' % 'Unknown'

   node_size = subElement(node_root, 'size')
   node_size_height = subElement(node_size, 'width')
   node_size_height.text = '%s' % int(width)
   node_size_height = subElement(node_size, 'height')
   node_size_height.text = '%s' % int(height)
   node_size_depth = subElement(node_size, 'depth')
   node_size_depth.text = '%s' % int(depth)

   node_segmented = subElement(node_root, 'segmented')
   node_segmented.text = '%s' % int(segmented)
   #上面都是xml文件的元信息
   
   #下面是分类数据的标注信息
   # # withers
   # bust1_data = data["bust1"]
   # node_object = subElement(node_root, 'object')
   # node_name = subElement(node_object, "name")
   # node_name.text = '%s' % 'bust1'
   # node_pose = subElement(node_object, "pose")
   # node_pose.text = '%s' % 'Unspecified'
   # node_truncated = subElement(node_object, "truncated")
   # node_truncated.text = '%s' % '0'
   # node_difficult = subElement(node_object, "difficult")
   # node_difficult.text = '%s' % '0'
   # node_bndbox = subElement(node_object, "bndbox")
   # node_xmin = subElement(node_bndbox, 'xmin')
   # node_xmin.text = '%s' % bust1_data[0]
   # node_ymin = subElement(node_bndbox, 'ymin')
   # node_ymin.text = '%s' % bust1_data[1]
   # node_xmax = subElement(node_bndbox, 'xmax')
   # node_xmax.text = '%s' % bust1_data[2]
   # node_ymax = subElement(node_bndbox, 'ymax')
   # node_ymax.text = '%s' % bust1_data[3]
   xml = tostring(node_root, pretty_print=True)  # 将上面设定的一串节点信息导出
   with open(save_path, 'wb') as f:  # 将节点信息写入到文件路径中
      f.write(xml)
   return

def process_directory(begannum,endnum):
   filename0 = "0275-1."                      #生成的标注文件文件名
   files = [filename0 + f"{str(i):0>3}" + ".xml" for i in range(begannum,endnum+1)]
   pngs = [filename0 + f"{str(i):0>3}" + ".png" for i in range(begannum,endnum+1)]
   # print(files)
   # 处理每个XML文件
   for index in range(len(files)):
      folder = './imgs_xml/'      #存放特定文件夹下面
      filename = pngs[index]
      pngfilename = pngs[index]   #标注文件名
      xml_construct(folder+files[index], folder, filename, pngfilename)

if __name__ == "__main__":
   # 标注文件数字
   begannum = 1     
   endnum = 3
   # 标注文件数据框数据(有多少分类写多少个)
   data = {"withers":[1056,291,1127,357]}
   process_directory(begannum,endnum)

删除特定文件的标注框

需要批量删除数据框的时候使用这段代码,

注意更改begannum和endnum,还有数据框名字

# _*_ coding: utf-8 _*_
# @Time     : 2024/1/27 19:19
# @Author   : jiaojiao

import os
from lxml import etree as ET

def remove_content_from_xml(xml_file, target_tag, remove_content):
   try:
      tree = ET.parse(xml_file)
      root = tree.getroot()
      # 查找目标标签
      target_elements = root.xpath(f"{target_tag}")
      print(target_elements)
      # 删除目标标签下的内容
      for element in target_elements:
         name = element.xpath("name")[0]
         if name.text==remove_content:
            root.remove(element)
            break
      # 保存修改后的XML文件
      tree.write(xml_file)
      print("成功删除 {} 下的内容".format(target_tag))
   except ET.ParseError as e:
      print("解析XML文件时发生错误: {}".format(e))
   except Exception as e:
      print("发生错误: {}".format(e))

def process_directory(directory_path, target_tag, remove_content):
   begannum = 34    #文件的开始和结束位置
   endnum = 141
   string = "0275-1.mp4L.mp4-"
   files = [string + str(i) + ".xml" for i in range(begannum,endnum+1)]
   # 处理每个XML文件
   for file in files:
      file_path = os.path.join(directory_path, file)
      # print(file_path)
      remove_content_from_xml(file_path, target_tag, remove_content)

if __name__ == "__main__":
   # 指定要处理的目录和目标标签
   directory_path = "F:\labelimg"
   target_tag = "object"
   remove_content = "joint1"    #删除得数据框名称
   # 处理目录下的所有XML文件
   process_directory(directory_path, target_tag, remove_content)

删除冗余的图像文件

有时候会出现几张没有用的标注文件,可能失误操作导致的,这段代码就可以很好的解决。

import os

folder1_path = 'F:\labelimg\labelimg\labelimg_first\1-2000\'
folder2_path = 'F:\labelimg\labelimg\labelimg_first\1-2000_Annotations\'

# 获取两个文件夹中的文件名(不包括后缀)
files_folder1 = [os.path.splitext(file)[0] for file in os.listdir(folder1_path)]
files_folder2 = [os.path.splitext(file)[0] for file in os.listdir(folder2_path)]

# 找到在folder2中存在但在folder1中不存在的文件
extra_files_folder2 = set(files_folder1) - set(files_folder2)
print(extra_files_folder2,len(set(files_folder1)))
# # 删除folder2中多余的文件
# for file in extra_files_folder2:
#     file_path = os.path.join(folder2_path, file)
#     # os.remove(file_path+".xml")
#     print(f"Deleted: {file}")

print("Cleanup completed.")

修改标注文件个别标注的信息

# _*_ coding: utf-8 _*_
# @Time     : 2024/1/28 1:05
# @Author   : jiaojiao
import os
from lxml import etree as ET

def set_content_from_xml(xml_file, set_content, data):
   try:
      tree = ET.parse(xml_file)
      root = tree.getroot()
      # 查找目标标签
      target_elements = root.xpath("object")
      # print(target_elements)
      # 修改目标标签下的内容
      for element in target_elements:
         name = element.find("name")
         # print(name.text)
         if name.text == set_content:
            xmin,ymin,xmax,ymax = data[0],data[1],data[2],data[3]
            print(xmin)
            bndbox = element.find("bndbox")
            bndbox[0].text = xmin
            bndbox[1].text = ymin
            bndbox[2].text = xmax
            bndbox[3].text = ymax
      # 保存修改后的XML文件
      tree.write(xml_file)
      print("成功修改 {} 下的内容".format(xml_file))
   except ET.ParseError as e:
      print("解析XML文件时发生错误: {}".format(e))
   except Exception as e:
      print("发生错误: {}".format(e))

def process_directory(directory_path, set_content, data):
    # 获取目录下的所有文件
    # files = [f for f in os.listdir(directory_path) if f.endswith(".xml")]
    begannum = 34
    endnum = 41
    string = "0275-1.mp4L.mp4-"
    files = [string + f"{str(i):0>3}" + ".xml" for i in range(begannum,endnum+1)]
    print(files)
    # 处理每个XML文件
    for file in files:
        file_path = os.path.join(directory_path, file)
        set_content_from_xml(file_path, set_content, data)

if __name__ == "__main__":
    # 指定要处理的目录和目标标签
    directory_path = "F:\labelimg\labelimg\labelimg_first\zszz\3-2000\Annotations\Annotations\Annotations\"
    # directory_path = 'F:\labelImg(1)\脚本\'
    set_content = "joint2"
    data = ["1426","336","1508","415"]
    # < xmin > 1426 < / xmin >
    # < ymin > 336 < / ymin >
    # < xmax > 1508 < / xmax >
    # < ymax > 415 < / ymax >
    # 处理目录下的所有XML文件
    process_directory(directory_path, set_content, data)

这些代码主要起了一个批量处理的作用,这也是python作为脚本语言的作用,使用py让我们更好的摸鱼。一天的活,几分钟解决。