NanoDet训练

github:RangiLyu/nanodet: NanoDet-Plus⚡Super fast and lightweight anchor-free object detection model. 🔥Only 980 KB(int8) / 1.8MB (fp16) and run 97FPS on cellphone🔥

CPU:guo-pu/NanoDet-PyTorch-CPU: 此代码用于目标检测,模型小,检测速度快速,适合没GPU显卡的嵌入式设备运行,比如“树莓派”、ARM开发板、嵌入式开发板。

hpc203/nanodet-plus-opencv: 分别使用OpenCV、ONNXRuntime部署NanoDet-Plus,包含C++和Python两个版本的程序

目标检测——使用nanodet训练自己制作的数据集并测试模型,通俗易懂(详细图文教程)_nanodet训练自己的模型-CSDN博客

第一模块:深度学习轻量级模型nanodet-CSDN博客

轻量级目标检测模型NanoDet-Plus微调、部署(保姆级教学)-CSDN博客

数据集:

使用Labelimg制作VOC格式数据集或yolo格式数据集(详细图文教程)_yolov5训练集-CSDN博客

conda

安装最新版最详细Anaconda新手安装+配置+环境创建教程_anaconda配置-CSDN博客

添加环境变量

1
2
3
4
..\anaconda3
..\anaconda3\Scripts
..\anaconda3\Library\bin
..\anaconda3\Library\mingw-w64\bin

查看安装情况

1
conda --version

创建/删除 环境
命令创建python版本为X.X、名字为 env_name 的虚拟环境。env_name文件可以在Anaconda安装目录 envs文件下找到。

1
conda create -n env_name python=3.8

在conda环境下,输入以下命令查看当前存在的环境:

1
conda env list

删除环境

1
2
conda remove -n env_name --all
conda env remove -n env_name

重命名环境(将 --clone 后面的环境重命名成 -n 后面的名字)

1
conda create -n torch --clone py3  	# 将 py3 重命名为 torch

创建完成环境之后,系统会提示如何 进入和退出环境,如下

1
2
conda activate env_name  		# 进入环境
conda deactivate # 退出环境

pytorch

查看CUDA版本nvidia-smi nvcc -V

Previous PyTorch Versions | PyTorch

NanoDet项目需要使用不高于2.00版本的Pytorch,而Pytorch低版本安装需要降低CUDA版本,需要重新安装CUDA 11.7

CUDA旧版存档:CUDA Toolkit Archive | NVIDIA Developer

成功解决:AssertionError: Torch not compiled with CUDA enabled - 知乎

创建conda环境

1
conda create -n env_name python=3.8

查看环境

1
conda env list

进入环境

1
conda activate PyTorch

安装pytorch

1
conda install pytorch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 pytorch-cuda=11.7 -c pytorch -c nvidia

测试torch环境

1
2
3
4
import torch
print(torch.cuda.is_available())
print(torch.cuda.device_count())
print(torch.cuda.get_device_name(0))

NanoDet

此处使用目标检测——使用nanodet训练自己制作的数据集并测试模型,通俗易懂(详细图文教程)_nanodet训练自己的模型-CSDN博客

整合文件链接:https://pan.baidu.com/s/1H_qB7OZKJodtbEImKN_TeQ 提取码:dcj7

NanoDet项目较早需要使用不高于2.00版本的Pytorch,高版本Pytorch去除了部分NanoDet所用方法

1
conda install pytorch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 pytorch-cuda=11.7 -c pytorch -c nvidia

注意pytorch与torchvision版本对应

pytorch/vision:特定于计算机视觉的数据集、转换和模型

截至现在25/04/22,由于requirements.txt中未对依赖库做版本限制,会导致安装最新稳定版本,导致NanoDet缺少方法,建议以下库指定版本安装

pytorch-lightning = 1.1.8

现使用版本

(图中版本还是过高,在后期train中无法正常允许,需使用1.1.8版本)

修改requirements.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Cython
matplotlib
numpy
omegaconf>=2.0.1
onnx
onnx-simplifier
opencv-python
pyaml
pycocotools
pytorch-lightning==1.1.8
tabulate
tensorboard
termcolor
tqdm

安装

1
pip install -r requirements.txt

在项目根目录安装nanodet

1
python setup.py develop

下载模型,测试NanoDet

1
python demo/demo.py video --config ./config/nanodet-plus-m-1.5x_320.yml --model ./model/nanodet-plus-m-1.5x_320.pth --path ./Video/dai.avi 

到此环境完成配置

自训练模型

数据集准备

数据标注工具

labelme:

Releases · wkentaro/labelme

深度学习图像标签标注软件labelme超详细教程 - 知乎

labelimg:

使用Labelimg制作VOC格式数据集或yolo格式数据集(详细图文教程)_yolov5训练集-CSDN博客

【教程】标注工具Labelimg的安装与使用 - 知乎

深度学习工具|LabelImg(标注工具)的安装与使用教程_labelimg软件-CSDN博客

标注得VOC格式数据集转为coco数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
### 用这个将xml转化为json格式可以正常的训练

import xml.etree.ElementTree as ET
import os
import json

coco = dict()
coco['images'] = []
coco['type'] = 'instances'
coco['annotations'] = []
coco['categories'] = []

category_set = dict()
image_set = set()

category_item_id = 0
# image_id = 'ball-'
image_id = 0
id_num = 0
annotation_id = 0


def addCatItem(name):
global category_item_id
category_item = dict()
category_item['supercategory'] = 'none'
category_item_id += 1
category_item['id'] = category_item_id
category_item['name'] = name
coco['categories'].append(category_item)
category_set[name] = category_item_id
return category_item_id


def addImgItem(file_name, size):
global image_id, id_num
if file_name is None:
raise Exception('Could not find filename tag in xml file.')
if size['width'] is None:
raise Exception('Could not find width tag in xml file.')
if size['height'] is None:
raise Exception('Could not find height tag in xml file.')

image_item = dict()
# temp = str(id_num)
temp = int(id_num)
# image_item['id'] = image_id + temp
image_item['id'] = temp
id_num += 1
image_item['file_name'] = file_name
image_item['width'] = size['width']
image_item['height'] = size['height']
coco['images'].append(image_item)
image_set.add(file_name)
return image_item['id']


def addAnnoItem(object_name, image_id, category_id, bbox):
global annotation_id
annotation_item = dict()
annotation_item['segmentation'] = []
seg = []
# bbox[] is x,y,w,h
# left_top
seg.append(bbox[0])
seg.append(bbox[1])
# left_bottom
seg.append(bbox[0])
seg.append(bbox[1] + bbox[3])
# right_bottom
seg.append(bbox[0] + bbox[2])
seg.append(bbox[1] + bbox[3])
# right_top
seg.append(bbox[0] + bbox[2])
seg.append(bbox[1])

annotation_item['segmentation'].append(seg)

annotation_item['area'] = bbox[2] * bbox[3]
annotation_item['iscrowd'] = 0
annotation_item['ignore'] = 0
annotation_item['image_id'] = image_id
annotation_item['bbox'] = bbox
annotation_item['category_id'] = category_id
annotation_id += 1
annotation_item['id'] = annotation_id
coco['annotations'].append(annotation_item)


def parseXmlFiles(xml_path):
for f in os.listdir(xml_path):
if not f.endswith('.xml'):
continue

bndbox = dict()
size = dict()
current_image_id = None
current_category_id = None
file_name = None
size['width'] = None
size['height'] = None
size['depth'] = None

xml_file = os.path.join(xml_path, f)
print(xml_file)

tree = ET.parse(xml_file)
root = tree.getroot()
if root.tag != 'annotation':
raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag))

# elem is <folder>, <filename>, <size>, <object>
for elem in root:
current_parent = elem.tag
current_sub = None
object_name = None

if elem.tag == 'folder':
continue

if elem.tag == 'filename':
file_name = elem.text
if file_name in category_set:
raise Exception('file_name duplicated')

# add img item only after parse <size> tag
elif current_image_id is None and file_name is not None and size['width'] is not None:
if file_name not in image_set:
current_image_id = addImgItem(file_name, size)
print('add image with {} and {}'.format(file_name, size))
else:
raise Exception('duplicated image: {}'.format(file_name))
# subelem is <width>, <height>, <depth>, <name>, <bndbox>
for subelem in elem:
bndbox['xmin'] = None
bndbox['xmax'] = None
bndbox['ymin'] = None
bndbox['ymax'] = None

current_sub = subelem.tag
if current_parent == 'object' and subelem.tag == 'name':
object_name = subelem.text
if object_name not in category_set:
current_category_id = addCatItem(object_name)
else:
current_category_id = category_set[object_name]

elif current_parent == 'size':
if size[subelem.tag] is not None:
raise Exception('xml structure broken at size tag.')
size[subelem.tag] = int(subelem.text)

# option is <xmin>, <ymin>, <xmax>, <ymax>, when subelem is <bndbox>
for option in subelem:
if current_sub == 'bndbox':
if bndbox[option.tag] is not None:
raise Exception('xml structure corrupted at bndbox tag.')
bndbox[option.tag] = int(option.text)

# only after parse the <object> tag
if bndbox['xmin'] is not None:
if object_name is None:
raise Exception('xml structure broken at bndbox tag')
if current_image_id is None:
raise Exception('xml structure broken at bndbox tag')
if current_category_id is None:
raise Exception('xml structure broken at bndbox tag')
bbox = []
# x
bbox.append(bndbox['xmin'])
# y
bbox.append(bndbox['ymin'])
# w
bbox.append(bndbox['xmax'] - bndbox['xmin'])
# h
bbox.append(bndbox['ymax'] - bndbox['ymin'])
print('add annotation with {},{},{},{}'.format(object_name, current_image_id, current_category_id,
bbox))
addAnnoItem(object_name, current_image_id, current_category_id, bbox)


if __name__ == '__main__':

xml_path = "coco/trainxml" ## 原始的xml文件路径
json_file = 'coco/annotations/instances_train2017.json' ## 转后保存.json文件的路径
#
# xml_path = "coco/valxml" ## 原始的xml文件路径
# json_file = 'coco/annotations/instances_val2017.json' ## 转后保存.json文件的路径

parseXmlFiles(xml_path)
json.dump(coco, open(json_file, 'w'))

将.xml标签文件转化为一个.json文件

分割训练集和测试集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# split_coco.py
import json
import random

def split_coco_data(coco_file, train_ratio=0.8, output_train_file="train.json", output_test_file="test.json"):
"""
将 COCO 标注文件按照指定的比例分割成训练集和测试集。

参数:
coco_file (str): COCO 标注 JSON 文件的路径。
train_ratio (float): 训练集中图像所占的比例 (默认值: 0.8)。
output_train_file (str): 保存训练集 COCO JSON 文件的路径 (默认值: "train.json")。
output_test_file (str): 保存测试集 COCO JSON 文件的路径 (默认值: "test.json")。
"""

with open(coco_file, 'r') as f:
coco_data = json.load(f)

images = coco_data['images'] # 获取所有图像信息
annotations = coco_data['annotations'] # 获取所有标注信息
categories = coco_data['categories'] if 'categories' in coco_data else [] # 获取类别信息,如果不存在则设为空列表

# 随机打乱图像列表,确保分割的随机性
random.shuffle(images)

# 计算训练集的大小
train_size = int(len(images) * train_ratio)

# 分割图像列表为训练集和测试集
train_images = images[:train_size]
test_images = images[train_size:]

# 获取训练集和测试集图像的 ID 列表
train_image_ids = [img['id'] for img in train_images]
test_image_ids = [img['id'] for img in test_images]

# 根据图像 ID 过滤标注信息,得到训练集和测试集的标注
train_annotations = [ann for ann in annotations if ann['image_id'] in train_image_ids]
test_annotations = [ann for ann in annotations if ann['image_id'] in test_image_ids]

# 创建训练集 COCO 数据集
train_coco_data = {
'images': train_images,
'annotations': train_annotations,
'categories': categories # 包含类别信息
}

# 创建测试集 COCO 数据集
test_coco_data = {
'images': test_images,
'annotations': test_annotations,
'categories': categories # 包含类别信息
}

# 将训练集 COCO 数据集保存到 JSON 文件
with open(output_train_file, 'w') as f:
json.dump(train_coco_data, f)

# 将测试集 COCO 数据集保存到 JSON 文件
with open(output_test_file, 'w') as f:
json.dump(test_coco_data, f)

print(f"COCO 数据集分割完成:")
print(f" - 训练集: {len(train_images)} 张图像, {len(train_annotations)} 个标注 (保存到 {output_train_file})")
print(f" - 测试集: {len(test_images)} 张图像, {len(test_annotations)} 个标注 (保存到 {output_test_file})")


if __name__ == "__main__":
# 将 'coco_annotations.json' 替换为你的 COCO 标注文件的实际路径
coco_annotation_file = "nanodet_train.json"

# 你可以根据需要调整 train_ratio (例如,0.7 表示 70% 用于训练,30% 用于测试)
split_coco_data(coco_annotation_file, train_ratio=0.8, output_train_file="train.json", output_test_file="test.json")

得到2:8的测试训练集

修改配置文件

原图作者目标检测——使用nanodet训练自己制作的数据集并测试模型,通俗易懂(详细图文教程)nanodet训练自己的模型-CSDN博客

开始训练

1
python tools/train.py ./config/legacy_v0.x_configs/nanodet-m.yml

待训练完成后模型保存为pth

可以用刚刚训练的模型测试一下

1
python demo/demo.py video --config ./config/nanodet-m.yml --model ./workspace/nanodet_m/model_best/nanodet_model_best.pth --path ./Video/rc.mp4