YOLOv3-YOLOv7 COCO 数据集解析
之前我一直使用 VOC 格式的数据来训练 YOLO, 这次整理下 COCO 格式的数据。
当我们在COCO 官网下载数据后,是以下格式:
. ├── annotations | ├── captions_train2017.json | ├── captions_val2017.json | ├── instances_train2017.json | ├── instances_val2017.json | ├── person_keypoints_train2017.json | └── person_keypoints_val2017.json ├── images │ ├── test2017(40670 `.jpg`) │ ├── train2017(118287 `.jpg`) │ └── val2017(5000 `.jpg`)
有篇对该数据集进行介绍的文章:https://zhuanlan.zhihu.com/p/29393415
以下两个文件,是用来检测和分割的标注文件,YOLO将该数据转为了 txt
, 使得每张图片对应一个 txt
文件。
| ├── instances_train2017.json | ├── instances_val2017.json
我们可以从 coco2017labels-segments.zip 获得转换好的文件。
解压后,放在对应文件夹即可。我们得到最后的数据文件结构:
. ├── annotations | ├── captions_train2017.json | ├── captions_val2017.json | ├── instances_train2017.json | ├── instances_val2017.json | ├── person_keypoints_train2017.json | └── person_keypoints_val2017.json ├── images │ ├── test2017(40670 `.jpg`) │ ├── train2017(118287 `.jpg`) │ └── val2017(5000 `.jpg`) ├── labels │ ├── train2017(117266 `.txt`) │ └── val2017(4952 `.txt`) ├── train2017.txt ├── test-dev2017.txt ├── val2017.txt
train2017.txt
test-dev2017.txt
val2017.txt
文件的内容是 每行一个图片路径。如下:
./images/train2017/000000109622.jpg ./images/train2017/000000160694.jpg ./images/train2017/000000308590.jpg ...
那么,我们就不需要 instances_train2017.json
和 instances_val2017.json
文件了。
每一个标注文件,即 xx.txt
内的分类都是一个 数字,后面跟着许多坐标,用于实例分割,可以根据这些分割坐标获取检测框的坐标,也就是如下代码(YOLOv7中的):
def segments2boxes(segments): # Convert segment labels to box labels, i.e. (xy1, xy2, ...) to (xywh) boxes = [] for s in segments: x, y = s.T # segment xy boxes.append([x.min(), y.min(), x.max(), y.max()]) # xyxy 如 (8,4)表示 该图片有 8个检测框,4表示左上和右下的坐标 return xyxy2xywh(np.array(boxes)) # xywh
最后把坐标转为 中心点坐标 和 w,h 这种格式。如下:
def xyxy2xywh(x): # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center y[:, 2] = x[:, 2] - x[:, 0] # width y[:, 3] = x[:, 3] - x[:, 1] # height return y
需要注意的是,COCO格式的坐标数据为 归一化的小数,也就是都是在 [0-1]之间。
下面代码是将 x,y,w,h(左上,宽高) 标注框转为 cx,cy,w,h(中心,宽高),并进行归一化的代码,可以放到 utils/general.py:xywh2cxcywh(x, shape)
def xywh2cxcywh(x, shape): # Convert nx4 boxes from [x1, y1, w, h] to [cx, cy, w, h] where xy1=top-left, cxcy=centre, shape为图像的(w,h) y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) y[:, 0] = (x[:, 0] + x[:, 2] / 2) # x center y[:, 1] = (x[:, 1] + x[:, 3] / 2) # y center y[:, 2] = x[:, 2] # width y[:, 3] = x[:, 3] # height # 归一化 dw = 1. / shape[0] dh = 1. / shape[1] y[:, 0] = y[:, 0] * dw y[:, 1] = y[:, 1] * dh y[:, 2] = y[:, 2] * dw y[:, 3] = y[:, 3] * dh return y
yolo 格式数据读取
def cache_labels(self, path=Path('./labels.cache'), prefix=''): # 重写 # Cache dataset labels, check images and read shapes x = {} # dict nm, nf, ne, nc = 0, 0, 0, 0 # number missing(所有图片没有标注的数目和), found(找到的标注和), empty(虽然有标注文件,但是文件内啥都没写), duplicate(读取时候出现问题的样本数目) pbar = tqdm(zip(self.img_files, self.label_files), desc='Scanning images', total=len(self.img_files)) # 产生这么个进度条,Scanning images: 0%| | 0/118287 [00:00<?, ?it/s] for i, (im_file, lb_file) in enumerate(pbar): # 循环每个样本,图像jpg-标注txt对 try: # verify images im = Image.open(im_file) # 验证图像是否可以打开 im.verify() # PIL verify # 检查文件完整性 shape = exif_size(im) # 获得 image size segments = [] # instance segments assert (shape[0] > 9) & (shape[1] > 9), f'image size {shape} <10 pixels' assert im.format.lower() in img_formats, f'invalid image format {im.format}' # verify labels if os.path.isfile(lb_file): nf += 1 # label found with open(lb_file, 'r') as f: l = [x.split() for x in f.read().strip().splitlines()] # 把标注txt 文件的每行(一个标注)都读取出来组成list if any([len(x) > 8 for x in l]): # is segment 如果长度大于8那么该标注是分割 classes = np.array([x[0] for x in l], dtype=np.float32) # 标注的第一列代表类别,是一个字符串类型的数字, 如 '45', 这里组成当前文件的类别list:如 [45.0, 45.0, 50.0, 45.0, 49.0, 49.0, 49.0, 49.0] segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2) for x in l] # 除了第一列,后面每两个数是一个标注的坐标,把每个实例分割框的每个点坐标 reshape 下 l = np.concatenate((classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh) 如(8,5) 这里 xy 是目标中心点坐标,并且xywh 都是经过归一化的 l = np.array(l, dtype=np.float32) if len(l): assert l.shape[1] == 5, 'labels require 5 columns each' # 即 cls,xywh assert (l >= 0).all(), 'negative labels' # 所有值都 >= 0 assert (l[:, 1:] <= 1).all(), 'non-normalized or out of bounds coordinate labels' # bbox 坐标不能在 图像外 assert np.unique(l, axis=0).shape[0] == l.shape[0], 'duplicate labels' # 标注里面有重复的框 else: ne += 1 # label empty l = np.zeros((0, 5), dtype=np.float32) else: nm += 1 # label missing l = np.zeros((0, 5), dtype=np.float32) x[im_file] = [l, shape, segments] # x是一个dict,key 为 图像path,value:该图像的标注(如 8,5), 图像的宽高,分割的坐标 except Exception as e: nc += 1 print(f'{prefix}WARNING: Ignoring corrupted image and/or label {im_file}: {e}') pbar.desc = f"{prefix}Scanning '{path.parent / path.stem}' images and labels... " \ f"{nf} found, {nm} missing, {ne} empty, {nc} corrupted" # 更新进度条 pbar.close() if nf == 0: print(f'{prefix}WARNING: No labels found in {path}. See {help_url}') x['hash'] = get_hash(self.label_files + self.img_files) x['results'] = nf, nm, ne, nc, i + 1 # 统计的数目 x['version'] = 0.1 # cache version torch.save(x, path) # save for next time logging.info(f'{prefix}New cache created: {path}') return x
如果想把自己的数据修改成 yolo格式的,可以参考下面的文章:https://blog.csdn.net/weixin_37707770/article/details/110441476
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!