2022年 11月 4日

python常用的脚本文件

最近打算记录一些自己常用的脚本文件,方便之后需要的时候能够查看

1  根据一定的比例划分数据集

在数据集文件中,我们需要将按照一定整个数据集划分成训练集( training dataset) 和测试集 (test dataset)。darknet的数据集目录结构是这样的:

  1. darknet-master
  2. ---- data
  3. ---- obj.names # 物体类别名称(如果有两类物体,就写上两类物体的名称)
  4. ---- obj.data # 将数据集的信息保存在这个文件中,yolov4从这个文件中读取数据集信息
  5. ---- obj # 存放图片以及每个图片的标签信息
  6. ---- train.txt # 存放训练集地址 (相对地址,比如: data/obj/image1.jpg)
  7. ---- test.txt # 存在训练集地址 (相对地址,比如: data/obj/image3.jpg)

我们将在脚本执行的文件目录下新建一个data文件夹,将训练样本和测试样本的地址分别保存data/train.txtdata/test.txt文件中。脚本的内容如下 (脚本名称是 partitioin_dataset.py):

  1. import os
  2. import random
  3. import shutil
  4. import argparse
  5. # step3: 将训练集数据和测试集的图片地址写在训练集和测试集txt文件中
  6. # step3: 将训练集数据和测试集的图片地址写在训练集和测试集txt文件中
  7. def write_path_in_txt(datasets_path,txt_path):
  8. # 如果文件不存在,就创建一个文件
  9. with open(txt_path,'a+') as f:
  10. f.truncate(0)
  11. for img_path in datasets_path:
  12. # 将xml后缀改成jpg
  13. img_path = img_path.replace("xml","jpg")
  14. info = 'data/obj/{}\n'.format(img_path)
  15. #print(info)
  16. f.write(info)
  17. # step2. 划分数据集
  18. # path_dataset: 数据集的地址
  19. # ratio: 训练集所占的比重
  20. def partition_dataset(path_dataset,ratio = 0.8):
  21. if ratio < 0.5 or ratio > 1:
  22. print("0.5 < ratio < 1")
  23. return
  24. # 得到所有的xml文件,将其放在datasets_xml文件中
  25. for _,_,imgs_path in os.walk(path_dataset):
  26. datasets_xml = [img for img in imgs_path if img.endswith('xml')]
  27. # 得到训练数据集和测试数据集
  28. train_list, test_list = [], []
  29. train_list = random.sample(datasets_xml,int(len(datasets_xml)*ratio))
  30. # 遍历整个数据集,将不在训练集中的图片写到到验证集中
  31. for names in datasets_xml:
  32. if names not in train_list:
  33. test_list.append(names)
  34. # 将训练集和测试集写到train.txt和text.txt文件夹中
  35. write_path_in_txt(train_list,'./data/train.txt')
  36. write_path_in_txt(test_list,'./data/test.txt')
  37. def get_parser():
  38. parser = argparse.ArgumentParser(description="partition dataset by ratio")
  39. parser.add_argument('--path', help="the absolute path of the dataset")
  40. parser.add_argument('--ratio', type=float, default = 0.8, help="the ratio of the training set, 0.5 < ratio < 1")
  41. return parser
  42. if __name__ == '__main__':
  43. parser = get_parser()
  44. args = parser.parse_args()
  45. path = args.path
  46. ratio = args.ratio
  47. # step1. 首先创建一个文件夹(如果存在了,就不创建了)
  48. if not os.path.exists('./data'):
  49. os.makedirs('./data')
  50. partition_dataset(path,ratio)
  51. ###################################### 使用样例 ####################################
  52. # python partitioin_dataset.py --path C:\Users\cumt\Desktop\path_dataset --ratio 0.8

 

2 改变标签的坐标

使用labelImage标注的坐标是 (x1, y2, x2, y2),但是darknet需要的坐标形式如下

   <object-class>  <x_center>  <y_center>  <width>  <height>

object-class: 表示物体的数字标签。比如0, 1, 2等整数。

<x_center>  <y_center>  <width>  <height>: 表示物体的相对中心坐标及其相对宽高(这四个值的大小在0-1之间)。

举例来说:

<x_center> = <absolute_x> / <image_width> = bounding box中心x实际坐标 / 图片实际宽度

<y_center> = <absolute_y> / <image_height> = bounding box中心y实际坐标 / 图片实际高度

<width> = <absolute_width> / <image_width> = bbox宽度 / 图片实际宽度

<height> = <absolute_width> / <image_width> = bbox高度 / 图片实际高度

如果一幅图片中包含不止一个物体,那么每幅图片的标签信息应该如何填写?举例来说,对于image1.jpg图片有三个物体,image1.txt文件就是这样:

  1. 1 0.716797 0.395833 0.216406 0.147222
  2. 0 0.687109 0.379167 0.255469 0.158333
  3. 1 0.420312 0.395833 0.140625 0.166667

具体内容参考我的这一篇博客

我们会遍历数据集下所有的xml文件,然后读取信息并将转换之后的坐标信息,以txt的形式保存在数据集的同目录下。最终的代码就是这样的:

  1. '''
  2. 这个脚本文件用来将(x1,y1,x2,y2)的坐标转成darkent-yolov4的形式
  3. 读取文件中所有的xml文件,然后生成对应txt文件,txt文件在数据集的同目录下
  4. '''
  5. import xml.etree.ElementTree as ET
  6. from tqdm import tqdm
  7. import argparse
  8. import os
  9. def transformation(path_dataset,obj_names_lst):
  10. # 使用labelImage标注图片的时候,有的xml文件中关于图片的width和height可能为0
  11. empty_list = []
  12. for path_root, _ ,path_files_list in os.walk(path_dataset):
  13. for path_file in tqdm(path_files_list,total=len(path_files_list),unit='fils'):
  14. if path_file.endswith('xml'):
  15. name = path_file
  16. path_file = os.path.join(path_root,path_file)
  17. # 读取xml文件,然后将其写入txt文件中
  18. # 解析xml文件
  19. tree = ET.parse(path_file)
  20. # 得到根结点
  21. root = tree.getroot()
  22. # 获取图片的width和height
  23. for sizeNode in root.iter('size'):
  24. width_img = float(sizeNode.find('width').text)
  25. height_img = float(sizeNode.find('height').text)
  26. # 打开txt文件,将内容写进去
  27. path_txt = os.path.splitext(path_file)[0]+'.txt'
  28. # path_list.append(path_txt)
  29. with open(path_txt,'a+') as f:
  30. # 首先先清空txt文件
  31. f.truncate(0)
  32. # 打开xml文件,name, xmin,ymin,xmax,ymax信息
  33. for objectNode in root.iter('object'):
  34. cls_name = objectNode.find('name').text
  35. for index, name in enumerate(obj_names_lst):
  36. if cls_name == obj_names_lst[index]:
  37. obj_cls = str(index)
  38. break
  39. # # 获取obj类别标签
  40. # if cls_name == 'person':
  41. # obj_cls = str(0)
  42. # elif cls_name == 'hat':
  43. # obj_cls = str(1)
  44. # 得到xmin,ymin,xmax,ymax
  45. xmin = float(objectNode.getchildren()[4].find('xmin').text)
  46. ymin = float(objectNode.getchildren()[4].find('ymin').text)
  47. xmax = float(objectNode.getchildren()[4].find('xmax').text)
  48. ymax = float(objectNode.getchildren()[4].find('ymax').text)
  49. # 得到x_center,y_center,width,height
  50. # x_center = (xmin+xmax)/2; y_center = (ymin_ymax) / 2; width = (xmax-xmin); height = (ymax-ymin)
  51. x_center, y_center, width, height = (xmin+xmax) / 2, (ymin+ymax) / 2, xmax-xmin, ymax-ymin
  52. # 得到相对的值
  53. # 有的值为零
  54. if (0 ==width_img or 0 == width_img):
  55. if not name in empty_list:
  56. empty_list.append(name)
  57. else:
  58. abs_x = float(x_center / width_img)
  59. abs_y = float(y_center / height_img)
  60. abs_width = float(width / width_img)
  61. abs_height = float(height / height_img)
  62. info = obj_cls +" " + str(abs_x) + " " + str(abs_y) + " " + str(abs_width) + " " + str(abs_height) + "\n"
  63. # 创建相关的文件,将内容写进去
  64. f.write(info)
  65. return empty_list
  66. def get_parser():
  67. parser = argparse.ArgumentParser("coordination transformation: (x1,y1,x2,y2) -> (abs_x, abs_y, abs_w, abs_h)")
  68. parser.add_argument("--path",help="the absolute path of the dataset", default=str)
  69. parser.add_argument("--obj_names",help="obj names",nargs="+")
  70. return parser
  71. if __name__ == '__main__':
  72. parser = get_parser()
  73. args = parser.parse_args()
  74. path = args.path
  75. obj_names_lst = args.obj_names
  76. empty_list = transformation(path,obj_names_lst)
  77. if (len(empty_list)):
  78. print("##################### the width and height of these xml are zero #####################")
  79. print(empty_list)
  80. ###################################### 使用样例 ####################################
  81. # --obj_names输入的是从0开始的标签值,使用的时候就是一个列表
  82. # python coord_transformation.py --path C:\Users\cumt\Desktop\path_dataset --obj_names person hat

3 计算数据集中每类bounding box的个数

最近在整理一个数据集,需要知道该数据集中不同类别的bbox的个数,所以自己就写了一个脚本文件 (脚本名称是get_num_bboxes.py),这个脚本文件会读取数据集下所有的xml文件,读取其中的name,最终以词典的形式输出打印。

  1. import os
  2. import argparse
  3. from tqdm import tqdm
  4. import xml.etree.ElementTree as ET
  5. def calc_bbox_num(input_path):
  6. # 判断是是否是一个路径
  7. if (not os.path.isdir(input_path)):
  8. print("input path is not a folder path")
  9. names_bbox = {}
  10. for path_root, _, name_file_list in os.walk(input_path):
  11. for path_file in tqdm(name_file_list,total=len(name_file_list),unit='xml file'):
  12. if path_file.endswith('xml'):
  13. # 读取xml文件 (要得到绝对路径)
  14. path_file = os.path.join(path_root,path_file)
  15. tree = ET.parse(path_file)
  16. root = tree.getroot()
  17. # 得到所有的object结点
  18. for objectNode in root.iter('object'):
  19. cls_name = objectNode.find('name').text
  20. if cls_name not in names_bbox.keys():
  21. names_bbox[cls_name] = 1
  22. else:
  23. names_bbox[cls_name] += 1
  24. print(names_bbox)
  25. def get_parser():
  26. parser = argparse.ArgumentParser(description="calculate bounding box's number of one dataset ")
  27. # 输入的是一个绝对路径
  28. parser.add_argument('--path',help='the absolute path of the dataset')
  29. return parser
  30. if __name__ == '__main__':
  31. parser = get_parser()
  32. args = parser.parse_args()
  33. input_path = args.path
  34. calc_bbox_num(input_path)
  35. ###################################### 使用样例 ####################################
  36. # python get_num_bboxes.py --path C:\Users\cumt\Desktop\path_dataset

最终的运行结果

读取的过程会有进度条进行动态显示