MMDetection目标检测工具使用指南及问题总结
MMDetection 是一个基于 PyTorch 的开源对象检测工具箱。它是OpenMMLab项目的一部分。master 分支与PyTorch 1.3+ 一起使用。旧版本v1.x分支适用于PyTorch 1.1 到1.4,但强烈建议使用v2.0,以获得更快的速度、更高的性能、更好的设计和更友好的使用。
一、环境配置 1.创建虚拟环境、激活环境 在Linux系统安装Anaconda,进入终端,进行创建和激活环境
//创建虚拟环境opne-mmlab并激活(python=3.7)
conda create -n open-mmlab python=3.7
conda activate open-mmlab
2.下载pytroch、torchvision、cudatoolkit
//pytroch==1.5.0 torchvision==0.6.0 cudatoolkit==10.2.89
conda install pytorch==1.5.0 torchvision==0.6.0 cudatoolkit=10.2 -c pytorch
如果下载较慢,可以到网站上下载对应的离线包,然后在虚拟环境中进入到下载文件夹,使用pip安装离线包到虚拟环境当中 Pytorch_stable下载地址. 清华镜像下载
3.下载mmcv-full
pip install mmcv-full
4.下载mmdetection 文件夹放到桌面上命名为mmdetection,如果下载过慢,就到mmdetection网站上下载
git clone https://github.com/open-mmlab/mmdetection.git
5.下载mmdetection需求包 从桌面终端进入根目录(在open-mmlab虚拟环境中),我下载的mmdetection版本是V2.11
cd mmdetection
pip install -r requirements.txt
// 如果上面的不行使用下面的命令
pip install -r build.txt
pip install -v -e . 或者 python setup.py develop
6.下载mmdet
pip install mmdet
后续差什么依赖包就按照提示pip安装对应包 参考:球场书生的博文
验证环境是否安装完所有依赖包
1.下载预训练模型,创建文件夹checkpoints,并把下载的预训练模型放在其中
2.运行demo命令验证
python demo/image_demo.py demo/demo.jpg configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth
环境搭建或安装也可见GitHub官网:https://github.com/open-mmlab/mmdetection
二、如何训练自定义数据集
mmdetection兼容coco数据集以及voc数据集,但是coco数据集可能训练的结果比较好。如果数据符合COCO或VOC数据集格式,可以直接进入模型选择、训练和参数配置环节,否则需要将数据集转换至COCO格式或VOC格式。
数据准备 VOC格式数据目录结构如下
mmdetection
├── mmdet
├── tools
├── configs
├── data #手动创建data、VOCdevkit、VOC2007、Annotations、JPEGImages、ImageSets、Main这些文件夹
│ ├── VOCdevkit(数据集名称)
│ │ ├── VOC2007
│ │ │ ├── Annotations #把valid.txt、train.txt对应的xml文件放在这
│ │ │ ├── JPEGImages #把valid.txt、train.txt对应的图片放在这
│ │ │ ├── ImageSets
│ │ │ │ ├── Main
│ │ │ │ │ ├── valid.txt
│ │ │ │ │ ├── train.txt
Annotations:为xml格式文件,用来记录目标的坐标以及类别信息。(可以使用LabelImg工具标注自己的数据集,可以生成与voc相同的xml格式标签。)
**JPEGImages:**基本为jpg格式文件,将自己整理的图片存入此文件夹
**ImageSets:**train.txt:训练集 ,val.txt:验证集
COCO格式数据目录结构如下:
mmdetection
├── mmdet
├── tools
├── configs
├── data
│ ├── coco
│ │ ├── annotations
│ │ │ │──instances_train.json #由训练集对应的xml文件转化而来
│ │ │ │──instances_val.json
│ │ │ │──instances_test.json #可有可无
│ │ ├── train #存放训练集图像.jpg
│ │ ├── val #存放验证集图像.jpg
│ │ ├── test #存放测试集图像.jpg
VOC格式数据转为COCO格式数据:
终端运行voc2coco.py文件
import sys
import os
import json
import xml.etree.ElementTree as ET
import glob
START_BOUNDING_BOX_ID = 1
PRE_DEFINE_CATEGORIES = None
# If necessary, pre-define category and its id
# PRE_DEFINE_CATEGORIES = {"aeroplane": 1, "bicycle": 2, "bird": 3, "boat": 4,
# "bottle":5, "bus": 6, "car": 7, "cat": 8, "chair": 9,
# "cow": 10, "diningtable": 11, "dog": 12, "horse": 13,
# "motorbike": 14, "person": 15, "pottedplant": 16,
# "sheep": 17, "sofa": 18, "train": 19, "tvmonitor": 20}
def get(root, name):
vars = root.findall(name)
return vars
def get_and_check(root, name, length):
vars = root.findall(name)
if len(vars) == 0:
raise ValueError("Can not find %s in %s." % (name, root.tag))
if length > 0 and len(vars) != length:
raise ValueError(
"The size of %s is supposed to be %d, but is %d."
% (name, length, len(vars))
)
if length == 1:
vars = vars[0]
return vars
def get_filename_as_int(filename):
try:
filename = filename.replace("\\", "/")
filename = os.path.splitext(os.path.basename(filename))[0]
return int(filename)
except:
raise ValueError("Filename %s is supposed to be an integer." % (filename))
def get_categories(xml_files):
"""Generate category name to id mapping from a list of xml files.
Arguments:
xml_files {list} -- A list of xml file paths.
Returns:
dict -- category name to id mapping.
"""
classes_names = []
for xml_file in xml_files:
tree = ET.parse(xml_file)
root = tree.getroot()
for member in root.findall("object"):
classes_names.append(member[0].text)
classes_names = list(set(classes_names))
classes_names.sort()
return {name: i for i, name in enumerate(classes_names)}
def convert(xml_files, json_file):
json_dict = {"images": [], "type": "instances", "annotations": [], "categories": []}
if PRE_DEFINE_CATEGORIES is not None:
categories = PRE_DEFINE_CATEGORIES
else:
categories = get_categories(xml_files)
bnd_id = START_BOUNDING_BOX_ID
for xml_file in xml_files:
tree = ET.parse(xml_file)
root = tree.getroot()
path = get(root, "path")
if len(path) == 1:
filename = os.path.basename(path[0].text)
elif len(path) == 0:
filename = get_and_check(root, "filename", 1).text
else:
raise ValueError("%d paths found in %s" % (len(path), xml_file))
## The filename must be a number
image_id = get_filename_as_int(filename)
size = get_and_check(root, "size", 1)
width = int(get_and_check(size, "width", 1).text)
height = int(get_and_check(size, "height", 1).text)
image = {
"file_name": filename,
"height": height,
"width": width,
"id": image_id,
}
json_dict["images"].append(image)
## Currently we do not support segmentation.
# segmented = get_and_check(root, 'segmented', 1).text
# assert segmented == '0'
for obj in get(root, "object"):
category = get_and_check(obj, "name", 1).text
if category not in categories:
new_id = len(categories)
categories[category] = new_id
category_id = categories[category]
bndbox = get_and_check(obj, "bndbox", 1)
xmin = int(get_and_check(bndbox, "xmin", 1).text) - 1
ymin = int(get_and_check(bndbox, "ymin", 1).text) - 1
xmax = int(get_and_check(bndbox, "xmax", 1).text)
ymax = int(get_and_check(bndbox, "ymax", 1).text)
assert xmax > xmin
assert ymax > ymin
o_width = abs(xmax - xmin)
o_height = abs(ymax - ymin)
ann = {
"area": o_width * o_height,
"iscrowd": 0,
"image_id": image_id,
"bbox": [xmin, ymin, o_width, o_height],
"category_id": category_id,
"id": bnd_id,
"ignore": 0,
"segmentation": [],
}
json_dict["annotations"].append(ann)
bnd_id = bnd_id + 1
for cate, cid in categories.items():
cat = {"supercategory": "none", "id": cid, "name": cate}
json_dict["categories"].append(cat)
os.makedirs(os.path.dirname(json_file), exist_ok=True)
json_fp = open(json_file, "w")
json_str = json.dumps(json_dict)
json_fp.write(json_str)
json_fp.close()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Convert Pascal VOC annotation to COCO format."
)
parser.add_argument("xml_dir", help="Directory path to xml files.", type=str)
parser.add_argument("json_file", help="Output COCO format json file.", type=str)
args = parser.parse_args()
xml_files = glob.glob(os.path.join(args.xml_dir, "*.xml"))
# If you want to do train/test split, you can pass a subset of xml files to convert function.
print("Number of xml files: {}".format(len(xml_files)))
convert(xml_files, args.json_file)
print("Success: {}".format(args.json_file))
运行命令如下:
python voc2coco.py ./data/VOC/Annotations ./data/coco/output.json
注意:VOC格式数据中,图像和xml文件名称必须是数字,而且一样,比如"10008.jpg 10008.xml"
三、修改相关文件
coco格式数据相关文件类似
- 修改class_names.py文件
修改路径:
mmdetection/mmdet/core/evaluation/class_names.py
修改内容:将voc_classes的返回值改为要训练数据集的类别名称。
- 修改voc.py文件 修改路径:
mmdetection/mmdet/datasets/voc.py
修改内容:将VOCDataset中的CLASSES改为对应训练数据集的类别集合。
- 修改配置文件 配置文件路径:
mmdetection/configs
默认使用的是COCO格式,我们找到我们需要的模型,修改成我们所使用的VOC格式。假设我们使用的是cascade_rcnn_r50_fpn_1x_coco.py,我们可复制同一文件内容到cascade_rcnn_r50_fpn_1x_voc_(对应数据集名称).py。
里面内容为:
_base_ = [
'../_base_/models/cascade_rcnn_r50_fpn_xxx.py',
'../_base_/datasets/voc_xxx_detection.py',
'../_base_/schedules/schedule_1x_xxx_voc.py', '../_base_/xxx_voc_runtime.py'
]
# xxx为对应数据集名称
# models文件夹为模型结构设置
# datasets文件夹为数据集设置
# schedules文件夹为优化器、学习策略设置
# runtime文件为整体训练超参设置
-
models中对应文件修改num_classes变量 (2.x版本中num_classes不需要+1)
-
datasets中对应文件修改dataset_type、data_root、img_scale、ann_file、img_prefix变量。
- schedules中新建schedule_xxx_voc.py设置对应优化器
- 新建xxx_voc_runtime.py文件设置整体超参
训练之前需要编译 修改完后,需要重新编译(python setup.py install),不然会出现“AssertionError: The num_classes (20) in Shared2FCBBoxHead of MMDataParallel does not matches the length of CLASSES 80) in RepeatDataset"的报错。
开始训练
python tools/train.py configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_voc_xxx.py
对应log日志以及断点保存在work_dirs目录下。
模型测试 对于检测,需要自己写一个demo,调用训练好的模型,来生成目标检测的结果并保存。
终端运行上述代码:python image_demo.py 报如下错误:
cannot import name ‘show_result‘ from ‘mmdet.apis‘
经过查阅文献,得知可能MMDetection在版本更新时删除了这个函数show_result()。于是在Github上寻找解决办法,发现新的代码对于’show_result’的使用发生了改变。
解决办法: 重新打开image_demo.py 文件 ,删掉show_result函数
再次运行下面代码(是下面的),即可
import mmcv
import os
import numpy as np
from mmcv.runner import load_checkpoint
from mmdet.models import build_detector
from mmdet.apis import init_detector, inference_detector
config_file = 'configs/cascade_rcnn/cascade_rcnn_r50_fpn_1x_coco.py'
checkpoint_file = 'work_dirs/cascade_rcnn_r50_fpn_1x_coco/epoch_9.pth'
model = init_detector(config_file,checkpoint_file)
img = 'data/1111.jpg'
out_dir = 'result/'
if not os.path.exists(out_dir):
os.mkdir(out_dir)
result = inference_detector(model,img)
#model.show_result(img, result, model.CLASSES, out_file='testOut.jpg')
model.show_result(img, result, score_thr=0.3, show=False, out_file='result/result1111.jpg')
print(result)
mmdetection(重要全) 用mmdetection跑通自己的数据集 【小伟哥AI之路】mmdetection之configs中的各项参数具体解释 MMDetection,训练VOC格式数据集 【mmdetection】使用cascade-rcnn、faster-rcnn训练自定义的coco数据集
mmdetection训练,测试,学习 环境搭建和安装github 如何将VOC XML文件转化成COCO数据格式 使用mmdetection测试以及生成检测结果 关于cannot import name ‘show_result‘ from ‘mmdet.apis‘问题的解决 cannot import name ‘show_result‘ from ‘mmdet.apis‘问题的解
MMDETECTION的安装并训练自己的VOC数据集(内存不足,调整batch_size大小 或者调整输入图片尺寸) 【mmdetection】使用自定义的coco格式数据集进行训练及测试