从零搭建pytorch模型教程(八)实践部分(二)目标检测数据集格式转换

news2024/12/27 14:02:01

前言

图像目标检测领域有一个非常著名的数据集叫做COCO,基本上现在在目标检测领域发论文,COCO是不可能绕过的Benchmark。因此许多的开源目标检测算法框架都会支持解析COCO数据集格式。通过将其他数据集格式转换成COCO格式可以无痛的使用这些开源框架来训练新的数据集,比如Pascal VOC数据集。

本文首先将介绍COCO和VOC目标检测数据集格式以及VOC转换到COCO格式的核心步骤,最后将自定义一种数据格式利用上述核心步骤将其转换到COCO格式下。只要理解了不同数据集的标注方法,转换数据集其实就是一个非常简单自然的过程,可以拓展到任意方式标注的数据集上。

数据集格式介绍

COCO
其实COCO数据集的标签内容不仅仅涵盖目标检测,还包含了目标关键点、实例Mask以及图片描述等信息。在这里我们着重介绍COCO的目标检测相关内容。我们以COCO2017为例先看看其标签文件结构:

图片

其中红框框出来的就是以Json格式组织的目标检测相关的标注文件,其主要由三个部分构成:

"info"字段:数据集的基本信息描述、版本号、年份等信息。

“images字段” :包含了图片路径、宽高信息、唯一标志ID等信息。

“annotations字段”:包含了图片中的Box位置、类别等信息。

其简单示例如下所示:

{
   "info": {
       "description": "COCO 2017 Dataset",
       "url": "http://cocodataset.org",
       "version": "1.0",
       "year": 2017,
       "contributor": "COCO Consortium",
       "date_created": "2017/09/01"
  },
"images": [
      {
           "license": 4,
           "file_name": "000000397133.jpg",
           "coco_url": "http://images.cocodataset.org/val2017/000000397133.jpg",
           "height": 427,
           "width": 640,
           "date_captured": "2013-11-14 17:02:52",
           "flickr_url": "",
           "id": 397133
      },
      {
           "license": 1,
           "file_name": "000000037777.jpg",
           "coco_url": "http://images.cocodataset.org/val2017/000000037777.jpg",
           "height": 230,
           "width": 352,
           "date_captured": "2013-11-14 20:55:31",
           "flickr_url": "",
           "id": 37777
      }
  ],
   "annotations": [
    {
           "area": 702.1057499999998,  //Box的尺寸
           "image_id": 289343,         //对应的图像ID
           "bbox": [
               473.07,                 //左上角点x坐标
               395.93,                 //左上角点y坐标
               38.65,                  //Box的宽
               28.67                   //Box的高
          ],
           "category_id": 18,          //对应的类别
           "id": 1768,                 //该标签独有ID
           "iscrowd": 0                //0表示非密集场景,1表示密集场景
      }
  ]
}

PASCAL VOC

PASCAL VOC数据集有两个相对重要年份的数据集:PASCAL VOC 2007与PASCAL VOC 2012,每年都会在上一年的基础上增加一些额外的数据或标签。PASCAL VOC数据集也涵盖了分类、检测、分割、动作识别等标签。

我们这里着重介绍其检测部分,以PASCAL VOC 2012数据集为例,包含了20个类别1W+数据集,2W+标注Box的目标。其标签格式是每一个图片都有一个对应的XML文件作为其标注信息载体,标注信息主要包含如下几方面内容:

图像基本信息:图像名、图像尺寸等

object字段:目标分类标签、box标签(xmin,ymin,xmax,ymax)等信息

XML主要格式如下:

<annotation>
 <folder>VOC2012</folder>
 <filename>2007_000063.jpg</filename>           //标签对应的图片文件
 <source>
  <database>The VOC2007 Database</database>
  <annotation>PASCAL VOC2007</annotation>
  <image>flickr</image>
 </source>
 <size>                                         //图像尺寸
  <width>500</width>
  <height>375</height>
  <depth>3</depth>
 </size>
 <segmented>1</segmented>
 <object>
  <name>dog</name>                           //类别
  <pose>Unspecified</pose>
  <truncated>0</truncated>
  <difficult>0</difficult>                   //1表示这个目标是比较难识别的
  <bndbox>                                   //box信息
   <xmin>123</xmin>                       //左上角x坐标
   <ymin>115</ymin>                       //左上角y坐标
   <xmax>379</xmax>                       //右下角x坐标
   <ymax>275</ymax>                       //右下角y坐标
  </bndbox>
 </object>
 <object>
  <name>chair</name>
  <pose>Frontal</pose>
  <truncated>1</truncated>
  <difficult>0</difficult>
  <bndbox>
   <xmin>75</xmin>
   <ymin>1</ymin>
   <xmax>428</xmax>
   <ymax>375</ymax>
  </bndbox>
 </object>
</annotation>

数据集格式转换

在知道了各个数据集格式的基础上做数据集格式的转换就已经是非常简单的任务了,也有很多优秀的开源框架已经帮我们做好了这些事情比如MMDetection中就已经提供好了现成的工具供我们白嫖(bushi),使用了。

我们抽取其一些核心部分来一起看看,详细代码请参考MMDetection

Github:https://github.com/openmmlab/mmdetection/tree/master/tools/dataset_converters

从不同的数据集转换到COCO下主要也就两个步骤:

解析待转换数据集格式。

用COCO格式重构Json文件。

上述第二步对任意待转换数据集都是一样的,可以抽象为一个函数,输入的是解析好的不同数据集的Box信息等数据。下面我们以几个不同的数据集为例介绍。

From VOC to COCO
从VOC数据集到COCO数据集格式转换主要包含如下两个步骤:

解析VOC数据集数据:遍历图片以及对应XML文件,返回一个数组A,数组中的每一个实例包含了图片路径、Box相关标注信息等。

遍历A将A中的实例信息用COCO的格式表达出来并生成Json文件

其主要由两块核心代码构成,一个是VOC的XML文件解析,一个是Json文件生成。

VOC XML标注文件解析

xml文件解析已经有下面这个非常方便的Python库供大家使用

import xml.etree.ElementTree as ET

def parse_xml(args):
   xml_path, img_path = args
   tree = ET.parse(xml_path)           # 构建XML文件解析树
   root = tree.getroot()               # 获取XML文件的根节点
   size = root.find('size')            # 获取图像的尺寸
   w = int(size.find('width').text)    # 图像宽高
   h = int(size.find('height').text)
   bboxes = []
   labels = []
   bboxes_ignore = []
   labels_ignore = []
   for obj in root.findall('object'):  # 遍历object字段下所有box信息
       name = obj.find('name').text
       label = label_ids[name]
       difficult = int(obj.find('difficult').text)  #这个difficult对应的是COCO中iscrowded
       bnd_box = obj.find('bndbox')
       bbox = [                             # 对应的Box标注信息(x1,y1,x2,y2)
           int(bnd_box.find('xmin').text),
           int(bnd_box.find('ymin').text),
           int(bnd_box.find('xmax').text),
           int(bnd_box.find('ymax').text)
      ]
       if difficult:                        # 将difficult属性的Box放入ignore列表
           bboxes_ignore.append(bbox)       # 最后计算AP时这个GT是被忽略的
           labels_ignore.append(label)
       else:
           bboxes.append(bbox)
           labels.append(label)
   if not bboxes:
       bboxes = np.zeros((0, 4))
       labels = np.zeros((0, ))
   else:
       bboxes = np.array(bboxes, ndmin=2) - 1
       labels = np.array(labels)
   if not bboxes_ignore:
       bboxes_ignore = np.zeros((0, 4))
       labels_ignore = np.zeros((0, ))
   else:
       bboxes_ignore = np.array(bboxes_ignore, ndmin=2) - 1
       labels_ignore = np.array(labels_ignore)
   annotation = {
       'filename': img_path,
       'width': w,
       'height': h,
       'ann': {
           'bboxes': bboxes.astype(np.float32),
           'labels': labels.astype(np.int64),
           'bboxes_ignore': bboxes_ignore.astype(np.float32),
           'labels_ignore': labels_ignore.astype(np.int64)
      }
  }
   return annotation

用解析好的annotation重构COCO格式的Json文件:

import numpy as np

def voc_classes():
   return [
       'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat',
       'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person',
       'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'
  ]

def cvt_to_coco_json(annotations):
   image_id = 0
   annotation_id = 0
   coco = dict()
   coco['images'] = []
   coco['type'] = 'instance'
   coco['categories'] = []
   coco['annotations'] = []
   image_set = set()

   # coco annotations字段添加标注信息
   def addAnnItem(annotation_id, image_id, category_id, bbox, difficult_flag):
       annotation_item = dict()
       annotation_item['segmentation'] = []

       # 这里省略了seg部分代码
       seg = []
       annotation_item['segmentation'].append(seg)
       
# 转换为COCO对应的x1,y1,w,h格式
       xywh = np.array(
          [bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]])
       annotation_item['area'] = int(xywh[2] * xywh[3])
       # 如果difficult标志为1,该目标对应coco中iscrowd字段为1表明为密集目标场景
       if difficult_flag == 1:
           annotation_item['ignore'] = 0
           annotation_item['iscrowd'] = 1
       else:
           annotation_item['ignore'] = 0
           annotation_item['iscrowd'] = 0
       annotation_item['image_id'] = int(image_id)
       annotation_item['bbox'] = xywh.astype(int).tolist()
       annotation_item['category_id'] = int(category_id)
       annotation_item['id'] = int(annotation_id)
       coco['annotations'].append(annotation_item)
       return annotation_id + 1

   #
   for category_id, name in enumerate(voc_classes()):
       category_item = dict()
       category_item['supercategory'] = str('none')
       category_item['id'] = int(category_id)
       category_item['name'] = str(name)
       coco['categories'].append(category_item)

   for ann_dict in annotations:
       file_name = ann_dict['filename']
       ann = ann_dict['ann']
       assert file_name not in image_set
       image_item = dict()
       image_item['id'] = int(image_id)
       image_item['file_name'] = str(file_name)
       image_item['height'] = int(ann_dict['height'])
       image_item['width'] = int(ann_dict['width'])
       coco['images'].append(image_item)        # 设置COCO的"images"字段
       image_set.add(file_name)

       # 设置COCO的"annotations"字段
       bboxes = ann['bboxes'][:, :4]            # 获取box和label类别信息
       labels = ann['labels']
       for bbox_id in range(len(bboxes)):
           bbox = bboxes[bbox_id]
           label = labels[bbox_id]
           annotation_id = addAnnItem(
               annotation_id, image_id, label, bbox, difficult_flag=0)

       # ignore的目标表示该GT被忽视
       bboxes_ignore = ann['bboxes_ignore'][:, :4]
       labels_ignore = ann['labels_ignore']
       for bbox_id in range(len(bboxes_ignore)):
           bbox = bboxes_ignore[bbox_id]
           label = labels_ignore[bbox_id]
           annotation_id = addAnnItem(
               annotation_id, image_id, label, bbox, difficult_flag=1)

       image_id += 1

   return coco

拿到返回的coco对象后只需要调用下列方法就可以将对象序列化成Json文件了。

import mmcv
mmcv.dump(coco, out_file) # out_file为输出的json文件名

值得注意的一点是上面提到的iscorwd这个字段,这个字段标注为1时,最后统计AP时,该GT与预测框完成匹配后还可以考虑与其他预测框进行匹配,允许多个预测框与其匹配(因为场景是密集的)。

自定义格式数据集 to COCO

首先我们自定义一种数据标注格式,我们用txt文件作为标注信息的载体,将txt文件与图像文件通过相同的文件名一一对应。分别将标签文件以及对应图像文件放在Annotations以及JPEGImages文件夹下,同时我们生成JPEGImages图像文件的filelist.txt文件,这个文件每一行对应一个图像文件的全路径:

图片

txt文件格式如下:

图片

第一列表示类别,从0开始;第二到第五列表示Box信息依次为中心点x方向坐标,中心点y方向坐标,box的宽以及高(cx,cy,w,h)。

我们同样使用前面介绍过的cvt_to_coco_json将固定格式的annotations转换为COCO格式,那么我们只需要编写解析自定义格式数据集生成annotations的代码即可:

box尺寸小于min_size的作为ignore对象

file_path为图像路径的filelist.txt文件的全路径

def parse_info(file_path, min_size):
   annotations = []
   invalid_img = 0
   small_box = 0
   with open(file_path, 'r') as f:
       for l in tqdm(f):
           img_file = l.rstrip()
           img = cv2.imread(img_file)
           if img is None:
               invalid_img += 1
               continue
           h,w,_ = img.shape
           # 获取对应的标签文件
           ann_file = img_file.replace("JPEGImages", "Annotations").replace \
                      (".png", ".txt").replace(".jpg", ".txt")
           annotation = {'filename' : img_file,
                         'height' : h,
                         'width' : w,
                         'ann' : {}}
           if not osp.exists(ann_file):
               annotations.append(annotation)
               continue
           boxes, labels = [], []
           boxes_ignore, labels_ignore = [], []
           with open(ann_file, 'r') as fr:
               for anno in fr:
                   anno_list = anno.rstrip().split(' ')
                   cls = int(anno_list[0])
                   cx, cy = float(anno_list[1]), float(anno_list[2])
                   w, h = float(anno_list[3]), float(anno_list[4])
                   # 转换为COCO box表示格式
                   x1 = max(0, int(cx-w/2))
                   y1 = max(0, int(cy-h/2))
                   box = [x1, y1, w, h]
                   if w >= min_size and h >= min_size:
                       labels.append(cls)
                       boxes.append(box)
                   else:
                       labels_ignore.append(cls)
                       boxes_ignore.append(box)                    
               boxes = np.zeros((0, 4)) if len(boxes) == 0 else np.array(boxes)
               labels = np.zeros((0, )) if len(labels) == 0 else np.array(labels)      
               boxes_ignore = np.zeros((0, 4)) if len(boxes_ignore) == 0 else np.array(boxes_ignore)
               labels_ignore = np.zeros((0, )) if len(labels_ignore) == 0 else np.array(labels_ignore)  
               annotation['ann']['bboxes'] = np.array(boxes)
               annotation['ann']['labels'] = np.array(labels)
               annotation['ann']['bboxes_ignore'] = np.array(boxes_ignore)
               annotation['ann']['labels_ignore'] = np.array(labels_ignore)

           annotations.append(annotation)
   print('INFO:Invalid IMG:{}'.format(invalid_img))
   return annotations

写在后面

数据集的转换是非常有必要的,在软件设计中我们希望一套代码尽可能多的为不同情况服务。在这里我们希望训练代码中一套数据集(Dataset)class代码来完成所有目标检测任务训练,而不是针对不同的数据集设计不同的Dataset class代码。而对于目标检测来说,COCO可能就是这个最佳的模板~

最后

感谢你们的阅读和喜欢,我收藏了很多技术干货,可以共享给喜欢我文章的朋友们,如果你肯花时间沉下心去学习,它们一定能帮到你。

因为这个行业不同于其他行业,知识体系实在是过于庞大,知识更新也非常快。作为一个普通人,无法全部学完,所以我们在提升技术的时候,首先需要明确一个目标,然后制定好完整的计划,同时找到好的学习方法,这样才能更快的提升自己。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

五、面试资料

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下。
在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1951402.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机网络】DNS命令练习与抓包分析实验

一&#xff1a;实验目的 1&#xff1a;掌握DNS缓存的清除方法&#xff0c;了解DNS缓存的作用和影响。 2&#xff1a;熟悉nslookup和dig等DNS查询工具的使用&#xff0c;理解DNS查询的基本原理和过程。 3&#xff1a;通过抓包和分析&#xff0c;深入了解DNS查询和响应消息的格…

探索 LLamaWorker 本地大模型API服务的新功能:函数调用

LLamaWorker 是一个基于 LLamaSharp 项目开发的可以在本地运行大模型服务&#xff0c;并提供与 OpenAI / Azure OpenAI 兼容的 API。同时&#xff0c;通过工具提示词的配置&#xff0c;提供函数调用 Function Call 能力&#xff0c;为开发者提供更多的可能。 1. 背景 在人工智…

Robot Operating System——AsyncParametersClient监控Parameters的增删改行为

大纲 同步创建SyncParametersClient设置监控回调回调函数主体测试完整代码 异步创建AsyncParametersClient设置监控回调测试完整代码 在《Robot Operating System——Parameter设置的预处理、校验和成功回调》一文中&#xff0c;我们使用Node::add_post_set_parameters_callbac…

Django项目中报错:django.template.exceptions.TemplateDoesNotExist: index.html

访问127.0.0.1&#xff1a;8000访问出错 查看报错原因 到Django项目当中找到settings.py&#xff0c;找到TEMPLATES中的DIRS: 添加如下代码&#xff0c;并导入OS模块&#xff1a; "DIRS": [os.path.join(BASE_DIR,templates)] 再次访问IP地址&#xff1a;

【JVM基础07】——类加载器-什么是类加载器?类加载器有哪些?双亲委派了解吗?

目录 1- 引言&#xff1a;类加载器1-1 类加载器是什么&#xff1f;(What)1-2 为什么要用类加载器&#xff1f; 作用&#xff1a;类加载的过程&#xff1f;(Why) 2- ⭐核心&#xff1a;类加载器详解(How)2-1 类加载器分类2-2 什么是双亲委派模型&#xff1f;2-3 为什么采用双亲委…

开始尝试从0写一个项目--前端(三)

器材管理板块 添加器材管理导航 src\views\home\Home.vue src\router\index.js src\views\equipment\Equipment.vue <template><div>hello!</div></template> 测试 搜索导航分页查询 src\views\equipment\Equipment.vue <template><div&…

C#、Net6、WebApi报表方案

目录 1 Pdf表单方案 1.1出现如下错误提示: 1.2 字体路径使用 2 Docx报表模板方案 2.1 pdf方案缺陷 2.2 解决方案 3 Spire.Doc报表方案 3.1 Docx方案缺陷 3.2 解决方案 4 插入复选框 5 WebApi文件流下载接口 6 软件获取方式 1 Pdf表单方案 使用【Adobe Acrobat P…

0726,没什么用的SELECT和没用的我

目录 select 可恶&#xff01;&#xff01;&#xff01; 一对多聊天室 select&#xff1a;&#xff08;抄抄抄 最怕人类开始思考 补一对一的 select 喵&#xff1a;&#xff08;抄抄抄 &#xff1f;&#xff1f;今天就这么结束了&#xff1f;&#xff1f;&#xff1f; …

全能Ai助手:写作到设计,宝藏神器帮你事半功倍

今天&#xff0c;就让我们一起踏上这场寻找“隐藏”宝藏的旅程&#xff0c;探索这些AI工具如何改变我们的生活&#xff01; 一、高效生产力的提升之道 1. 文案创作助手 案例&#xff1a;某位自媒体博主使用了一款智能写作工具&#xff0c;不仅大大节省了写作时间&#xff0c;…

JMeter接口测试:测试中奖概率!

介绍 Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具&#xff0c;用于对软件做压力测试。JMeter 最初被设计用于 Web 应用测试&#xff0c;但后来扩展到了其他测试领域&#xff0c;可用于测试静态和动态资源&#xff0c;如静态文件、Java 小服务程序、CGI 脚本、J…

c语言第四天笔记

关于 混合操作&#xff0c;不同计算结果推理 第一种编译结果&#xff1a; int i 5; int sum (i) (i) 6 7 13 第二种编译结果&#xff1a; int i 5; int sum (i) (i) 6 7 7 7 前面的7是因为后面i的变化被影响后&#xff0c;重新赋值 14 第一种编译结果&#xff…

Llama + Dify,在你的电脑搭建一套AI工作流

theme: smartblue 点赞 关注 收藏 学会了 本文简介 最近字节在推Coze&#xff0c;你可以在这个平台制作知识库、制作工作流&#xff0c;生成一个具有特定领域知识的智能体。 那么&#xff0c;有没有可能在本地也部署一套这个东西呢&#xff1f;这样敏感数据就不会泄露了&…

Redis的两种持久化方式---RDB、AOF

rdb其实就是一种快照持久化的方式&#xff0c;它会将Redis在某个时间点的所有的数据状态以二进制的方式保存到硬盘上的文件当中&#xff0c;它相对于aof文件会小很多&#xff0c;因为知识某个时间点的数据&#xff0c;当然&#xff0c;这就会导致它的实时性不够高&#xff0c;如…

Scrapy 爬取旅游景点相关数据(三)

这一节我们将之前爬取到的景点数据进行解析&#xff0c;并且保存为excel&#xff0c;便于后续使用&#xff0c;本节包含 &#xff08;1&#xff09; 景点数据解析 &#xff08;2&#xff09;数据保存到excel 1 编写爬虫 这次继续改进第二节的爬虫&#xff0c;新建一个爬虫文…

C#如何引用dll动态链接库文件的注释

1、dll动态库文件项目生成属性中要勾选“XML文档文件” 注意&#xff1a;XML文件的名字切勿修改。 2、添加引用时XML文件要与DLL文件在同一个目录下。 3、如果要是添加引用的时候XML不在相同目录下&#xff0c;之后又将XML文件复制到相同的目录下&#xff0c;需要删除引用&am…

蓝桥强化宝典(3)BFS

一、定义 广度优先搜索&#xff08;Breadth-First Search, BFS&#xff09;是另一种用于遍历或搜索树或图的算法。与深度优先搜索&#xff08;DFS&#xff09;沿着树的深度遍历不同&#xff0c;广度优先搜索会逐层遍历图的顶点。它从一个指定的源顶点开始&#xff0c;首先访问这…

失业潮下,有人靠天工AI做副业年入10万?

前言 你好&#xff0c;我是咪咪酱 这篇文章总结2个AI副业项目&#xff0c;不用写代码&#xff0c;就能做的2个副业项目。 第一&#xff1a;AI生成微信表情包&#xff0c;上传到微信表情包平台等&#xff0c;坚持下去&#xff0c;会有可观的收入。 第二&#xff1a;AI生成连载…

Java 8 中 20 个高频面试题及答案

文章目录 前言20 道高频题问题 1&#xff1a;给定一个整数列表&#xff0c;使用 Stream 函数找出列表中所有的偶数&#xff1f;问题 2&#xff1a;给定一个整数列表&#xff0c;使用 Stream 函数找出所有以 1 开头的数字&#xff1f;问题 3&#xff1a;如何使用 Stream 函数在给…

1.ESP32-CAM 下使用 ESP-IDF 打开摄像头

主要资料&#xff1a; 乐鑫官方编程指南 ESP-IDF 编程指南安信可官方模块页 安信可-ESP32-CAM摄像头开发板官方使用教程 安信可ESP32-CAM摄像头开发demo–局域网拍照、实时视频、人脸识别 &#xff08;开发环境是Linux&#xff09; 本文目标是在 Windows 下跑通摄像头 hello …