智算多多



COCO(Common Objects in Context)数据集由微软团队创建,2017版本包含超过12万张图像,涵盖80个常见物体类别。与其它数据集相比,COCO的最大特色在于其丰富多样的标注类型:
所有这些标注信息都存储在JSON格式的文件中,主要包含以下五个核心部分:
{
"info": {}, // 数据集元信息
"licenses": [], // 图像授权信息
"images": [], // 图像基本信息列表
"annotations": [], // 所有标注对象的详细信息
"categories": [] // 类别定义与属性
}
images字段记录了数据集所有图像的基本信息,每个图像对象包含以下关键属性:
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | int | 图像唯一标识符 |
| width | int | 图像宽度(像素) |
| height | int | 图像高度(像素) |
| file_name | str | 图像文件名 |
| coco_url | str | 在线访问URL |
annotations字段是实际标注数据的核心,其结构根据标注类型有所不同。以目标检测为例,主要包含:
{
"id": 176851, # 标注ID
"image_id": 425226, # 对应图像ID
"category_id": 1, # 类别ID
"segmentation": [[x1,y1,x2,y2]], # 分割多边形坐标
"area": 47803.27955, # 区域面积
"bbox": [x,y,width,height], # 边界框坐标
"iscrowd": 0 # 是否拥挤场景
}
注意:当处理关键点标注时,annotations会额外包含keypoints和num_keypoints字段,记录关键点的坐标和可见性信息。
理解了JSON结构后,让我们通过Python代码实际解析这些数据。推荐使用官方提供的pycocotools库,它提供了高效的Cython实现。
首先安装必要的依赖:
pip install pycocotools matplotlib numpy
然后加载标注文件:
from pycocotools.coco import COCO
import matplotlib.pyplot as plt
# 初始化COCO API
annFile = 'annotations/instances_val2017.json'
coco = COCO(annFile)
# 获取所有类别信息
cats = coco.loadCats(coco.getCatIds())
print([cat['name'] for cat in cats]) # 输出所有类别名称
# 获取包含'person'类别的图像
imgIds = coco.getImgIds(catIds=coco.getCatIds(['person']))
print(f"包含person的图像数量: {len(imgIds)}")
理解数据结构最好的方式就是可视化。以下代码展示如何绘制图像及其标注:
# 加载第一张包含人的图像
img = coco.loadImgs(imgIds[0])[0]
I = plt.imread(f'val2017/{img["file_name"]}')
# 加载并显示标注
plt.imshow(I)
annIds = coco.getAnnIds(imgIds=img['id'])
anns = coco.loadAnns(annIds)
coco.showAnns(anns)
plt.axis('off')
plt.show()
对于关键点数据,可视化需要特殊处理:
# 关键点可视化示例
def plot_keypoints(img, annotations):
plt.imshow(img)
for ann in annotations:
kps = np.array(ann['keypoints']).reshape(-1,3)
# 只绘制可见的关键点(v>0)
visible_kps = kps[kps[:,2]>0]
plt.scatter(visible_kps[:,0], visible_kps[:,1],
marker='o', color='red', s=20)
plt.axis('off')
# 加载关键点标注
coco_kps = COCO('annotations/person_keypoints_val2017.json')
annIds = coco_kps.getAnnIds(imgIds=img['id'])
anns = coco_kps.loadAnns(annIds)
plot_keypoints(I, anns)
实际项目中,我们经常需要将COCO格式转换为其他框架所需的格式。下面介绍两种常见转换场景。
YOLO系列模型使用简单的文本文件存储标注,每行格式为:class x_center y_center width height。转换代码如下:
def coco_to_yolo(coco_anns, img_width, img_height):
yolo_lines = []
for ann in coco_anns:
# 获取类别ID(YOLO从0开始)
class_id = ann['category_id'] - 1
# 转换bbox坐标:从[x,y,w,h]到[x_center,y_center,w,h]并归一化
x, y, w, h = ann['bbox']
x_center = (x + w/2) / img_width
y_center = (y + h/2) / img_height
w_norm = w / img_width
h_norm = h / img_height
yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}")
return yolo_lines
# 示例使用
img = coco.loadImgs(imgIds[0])[0]
anns = coco.loadAnns(coco.getAnnIds(imgIds=img['id']))
yolo_anns = coco_to_yolo(anns, img['width'], img['height'])
# 保存到文件
with open('yolo_annotations.txt', 'w') as f:
f.write('\n'.join(yolo_anns))
当使用PyTorch等框架时,通常需要自定义Dataset类。以下是一个基础实现:
from torch.utils.data import Dataset
from PIL import Image
class CocoDetection(Dataset):
def __init__(self, img_folder, ann_file, transform=None):
self.coco = COCO(ann_file)
self.img_folder = img_folder
self.transform = transform
self.ids = list(sorted(self.coco.imgs.keys()))
def __getitem__(self, idx):
img_id = self.ids[idx]
ann_ids = self.coco.getAnnIds(imgIds=img_id)
annotations = self.coco.loadAnns(ann_ids)
img_info = self.coco.loadImgs(img_id)[0]
img_path = f"{self.img_folder}/{img_info['file_name']}"
img = Image.open(img_path).convert('RGB')
if self.transform:
img = self.transform(img)
# 这里可以添加对annotations的进一步处理
return img, annotations
def __len__(self):
return len(self.ids)
处理大规模数据集时,性能往往成为瓶颈。以下是几个实用技巧:
对于大型数据集,直接解析整个JSON文件非常耗时。可以采用以下优化策略:
# 方法1:使用内存映射
import json
import mmap
def load_json_fast(file_path):
with open(file_path, 'r+') as f:
mm = mmap.mmap(f.fileno(), 0)
return json.loads(mm.read().decode('utf-8'))
# 方法2:并行处理
from multiprocessing import Pool
def process_annotation(ann):
# 对单个标注进行处理
return processed_ann
with Pool(processes=4) as pool:
results = pool.map(process_annotation, coco.dataset['annotations'])
COCO数据集中可能遇到的一些典型问题及解决方案:
针对这些问题,可以添加预处理步骤:
def clean_annotations(annotations, img_width, img_height):
valid_anns = []
for ann in annotations:
# 检查bbox是否有效
x, y, w, h = ann['bbox']
if x >= img_width or y >= img_height:
continue
if x + w <= 0 or y + h <= 0:
continue
# 修正超出边界的坐标
ann['bbox'][0] = max(0, min(x, img_width - 1))
ann['bbox'][1] = max(0, min(y, img_height - 1))
ann['bbox'][2] = min(w, img_width - ann['bbox'][0])
ann['bbox'][3] = min(h, img_height - ann['bbox'][1])
valid_anns.append(ann)
return valid_anns
最后,我们通过一个实际案例展示如何基于COCO格式创建自定义数据集。假设我们要构建一个手势识别系统,需要标注手部关键点。
首先定义自定义类别和关键点:
{
"categories": [
{
"id": 1,
"name": "hand",
"keypoints": [
"wrist","thumb_tip","index_tip","middle_tip",
"ring_tip","pinky_tip"
],
"skeleton": [
[0,1],[0,2],[0,3],[0,4],[0,5]
]
}
]
}
使用albumentations库进行高效的数据增强:
import albumentations as A
transform = A.Compose([
A.RandomRotate90(),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.2),
A.Resize(256, 256)
], keypoint_params=A.KeypointParams(
format='xy',
remove_invisible=False
))
# 应用变换
transformed = transform(
image=image,
keypoints=keypoints,
bboxes=bboxes
)
结合之前的COCO加载器,创建专门处理关键点的版本:
class HandKeypointDataset(Dataset):
def __init__(self, coco_ann_file, img_dir, transform=None):
self.coco = COCO(coco_ann_file)
self.img_dir = img_dir
self.transform = transform
self.img_ids = list(sorted(self.coco.imgs.keys()))
def __getitem__(self, idx):
img_id = self.img_ids[idx]
img_info = self.coco.loadImgs(img_id)[0]
ann_ids = self.coco.getAnnIds(imgIds=img_id)
annotations = self.coco.loadAnns(ann_ids)
# 加载图像
img_path = f"{self.img_dir}/{img_info['file_name']}"
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 提取关键点
keypoints = []
for ann in annotations:
kps = np.array(ann['keypoints']).reshape(-1, 3)
keypoints.extend(kps[:, :2].tolist()) # 只取xy坐标
# 应用变换
if self.transform:
transformed = self.transform(
image=img,
keypoints=keypoints
)
img = transformed['image']
keypoints = transformed['keypoints']
return img, torch.tensor(keypoints, dtype=torch.float32)
在实际项目中处理COCO数据集时,最常遇到的挑战是如何高效处理大规模标注数据。通过将标注文件加载到内存数据库如SQLite中可以显著提升查询速度。例如,我们可以将annotations按image_id建立索引,这样在获取特定图像的标注时,查询时间可以从线性搜索的O(n)降低到接近O(1)。