本篇是 2024 年 TIP 论文 Toward Robust Referring Image Segmentation 的复现过程。
特点是对不存在的目标不会进行错误分割,鲁棒性较高,其结果如图:
配置环境
根据论文给出的链接 robust-ref-seg 配置环境。
下载数据集
按照 README 指示,从 RefCOCO 下载数据集。
注意在 make
的时候会报错 Cannot assign type 'double' to 'siz'
,可以参考 issue#8 的内容,将 setup.py
中的 ext_modules=cythonize(ext_modules)
替换为 ext_modules=cythonize(ext_modules, language_level = "2")
。
然后在下载数据集,由于 README 提供的链接都挂掉了,可以参考 issue#14 里的回答进行下载。
下载模型
bert-base-uncased 模型下载以下文件。
uncased 表示是无关大小写的模型。
Swin-base 下载该模型:
然后按照 README 指示建立目录结构即可。
数据集结构
refcoco 数据集的结构为:
├── refcoco
│ ├── instances.json
│ ├── refs(google).p
│ └── refs(unc).p
其中,instances.json 是从 MS COCO 数据集中继承来的文件标注,refs(xxx).p 是 Python 序列化的 Pickle 格式二进制文件,包含 RefCOCO 数据集特有的语言描述及其目标信息。
instance.json 标注格式如下(仅作示例):
{
"info": {
"description": "COCO 2014 Dataset",
"url": "http://cocodataset.org",
"version": "1.0",
"year": 2014,
"contributor": "COCO Consortium",
"date_created": "2017/09/01"
},
"images": [
{
"license": 1,
"file_name": "COCO_train2014_000000000094.jpg",
"coco_url": "http://images.cocodataset.org/train2014/COCO_train2014_000000000094.jpg",
"height": 427,
"width": 640,
"date_captured": "2013-11-17 02:07:37",
"flickr_url": "http://farm7.staticflickr.com/6102/6339531433_a465b39852_z.jpg",
"id": 94 // 图像 id
}
],
// 许可类别,这里只保留一个作为示例
"licenses": [
{
"url": "http://creativecommons.org/licenses/by-nc-sa/2.0/",
"id": 1,
"name": "Attribution-NonCommercial-ShareAlike License"
}
],
"annotations": [
{
// 用若干坐标围绕一个轮廓而成的多边形标注,如果目标有多个轮廓则用多个列表
"segmentation": [
[
317.98780487804873,
286.73780487804873,
315.7012195121951,
288.2621951219512,
316.0060975609756,
291.92073170731703,
320.2743902439024,
296.0365853658536,
264.56876456876455,
312.8205128205128,
264.56876456876455,
314.9184149184149,
284.1491841491841,
317.7156177156177,
]
],
"area": 51754.216533564235, // 多边形面积
"iscrowd": 0, // 是否是被遮挡区域
"image_id": 94,
// [x_min, y_min, w, h]
"bbox": [
134.26923076923077,
425.2307692307692,
639,
286
],
"category_id": 1,
"id": 1 // 注释id,后面和.p文件的ann_id联动
}
],
"categories": [
{
"supercategory": "animal",
"id": 1,
"name": "dog"
}
]
}
json文件过大时 vs code 不能查看,很容易卡死。找到两个查看大 json 文件的软件。
HugeJsonViewer 免费,但查找功能有 bug。
dadroit 查看超过 50M 的文件需要付费。
另外也可以直接用 vim 打开大 json 文件,不过搜索时依然很卡很慢。
refs(xxx).p 则使用 Python 的 pickle
模块加载文件。
import pickle
# 加载 refs(unc).p 文件
with open('refs(unc).p', 'rb') as f:
data = pickle.load(f)
# 查看第一个引用表达
print(data[0])
其结构为:
{
'sent_ids': [
0,
1,
],
'file_name': 'COCO_train2014_000000581857_16.jpg',
// 通过ann_id和image_id关联COCO中的图像
'ann_id': 1719310, // COCO 数据集中对应的目标注释 ID
'ref_id': 0, // 引用表达的唯一 ID
'image_id': 581857, // 图像 ID
'split': 'train', // 数据集划分(如 train、val、testA、testB)
'sentences': [ // 与目标关联的语言描述
{
'tokens': ['the', 'lady', 'with', 'the', 'blue', 'shirt'], // 描述分词后的结果
'raw': 'THE LADY WITH THE BLUE SHIRT', // 原始描述
'sent_id': 0,
'sent': 'the lady with the blue shirt' // 句子
},
{
'tokens': ['lady', 'with', 'back', 'to', 'us'],
'raw': 'lady w back to us',
'sent_id': 1,
'sent': 'lady with back to us'
}
],
'category_id': 1 // 目标类别 ID(如 "人")
}
}
可以这样修改 refs(xxx).p 文件:
## 修改描述
# 假设修改第一个引用表达的第一个描述
ref = data[0]
ref['sentences'][0]['raw'] = "the new description"
ref['sentences'][0]['tokens'] = ["the", "new", "description"]
# 确认修改
print(ref['sentences'][0])
## 添加新字段
# 给每个引用表达添加一个自定义字段
for ref in data:
ref['custom_field'] = "custom_value"
# 检查修改
print(data[0]['custom_field'])
## 删除条目
# 删除某些引用表达
data = [ref for ref in data if ref['image_id'] != 57870]
# 检查剩余条目
print(len(data))
## 保存修改后数据
# 保存修改后的 .p 文件
with open('refs(unc)_modified.p', 'wb') as f:
pickle.dump(data, f)
print("文件保存成功!")
复现
- 在
main.py
的 97 行中添加自己的数据集名elif args.dataset in ['rrefcoco', 'rrefcoco+', 'rrefcocog', 'myref']:
- 将
main.py
的 142 行的os.mkdir(logger_path)
修改为os.makedirs(logger_path)
。
这是因为
mkdir()
在父目录也不存在时会创建失败,而makedirs()
会递归地创建不存在的目录。
- 在
dataset/refer.py
的 51 行中添加自己的数据集名if dataset in ['refcoco', 'refcoco+', 'refcocog', 'rrefcoco', 'rrefcoco+', 'rrefcocog', 'temp', 'myref']:
- 把修改后的
instances.json
和refs(unc).p
放到/data/myref
里。
然后就可以开始测试自己的数据集了。
在测试自己的数据集时,运行到 validation.py
的 215 行 scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lambda step: (1 - step / (len(train_loader) * args.epoch)) ** 0.9)
时会报除 0 错误。
查看代码后发现是命令行运行时,args.type
参数默认为 val。在 main 函数里创建数据集 train_dataset
的参数 split
是 train,而创建 eval_dataset
的参数 split
会使用默认的 val。因此在制作自己的 refs.p
文件时,应该至少包含 split='train'
和 split='val'
两种 ref 条目。
但其实,在 eval 模式时根本不需要 train_dataset,这是代码设计不合理的地方,可以把 main.py
的 209 行
optimizer = AdamW(params=params_to_optimize, lr=args.lr, weight_decay=args.weight_decay)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lambda step: (1 - step / (len(train_loader) * args.epoch)) ** 0.9)
if args.eval:
load_checkpoint(args, model_without_ddp, optimizer, scheduler, logger, args.ckpt_epoch, best=True)
validate_all(args, model)
exit(0)
if args.resume:
更改为:
if args.eval:
load_checkpoint(args, model_without_ddp, None, None, logger, args.ckpt_epoch, best=True)
validate_all(args, model)
exit(0)
optimizer = AdamW(params=params_to_optimize, lr=args.lr, weight_decay=args.weight_decay)
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lambda step: (1 - step / (len(train_loader) * args.epoch)) ** 0.9)
if args.resume:
至此就可以对自己的数据集正常进行测试了。