Dell AI/ADS 比赛
笔试
1 | 选择题 55道 每题一分 |
面试
- 自我介绍
- 专业相关
- 有没有AI比赛经历
- 有没有做过自动驾驶的项目
- 如何理解深度学习
- 深度学习和机器学习的联系与区别
- CNN 是什么
- CNN 有哪些层
- RNN 是什么
- 有监督学习和无监督学习
- 现有网络可以改进的方向
- 模型深度越深越好吗(容限)
- 输入数据(标签)改进方向(数据清洗)
- yolo全程(you only look once)
- you only look once 怎么实现的
- 简述 yolov3 以及和 v1 v2 区别
- 与yolo对应的一个算法
- RCNN 池化改进方向
- FastRCNN 改进在哪
- 写过什么模型网络以及调参经历
复试
题目:交通标志识别
根据单独提供的原始图像数据集设计一个完整的交通标志(限速、取消限速)检测工作流。你需要标记数据,选择一个合适的模型,调整参数进行训练,并验证你训练模型的结果
环境
- 平台硬件:工作站
- 操作系统:windows10、Ubuntu18.04
- GPU型号:NVIDIA GeForce RTX 3090 24GB
- 开发语言:python3.7
- 深度学习框架:PyTorch 1.7.0
- 训练集大小:828
- 测试集大小:100
- 验证集大小:227
- 训练出模型的时长:0.561h
- 推理100张图片时长:1.434s
- 是否有模型对比:模型仅13.7Mb,轻量,速度快于yolov3、EFFicientDet,精度由于场景单一都很高
实验步骤
- 数据集筛选:将赛题所给的数据集进行筛选,人工删除交通标识模糊、扭曲、以及溢出图片边界的低价值图像,最后从1774张图片中筛选出1055张
- 图像标注:使用labelimg将筛选出的图像进行标注,标注分 limit 和 nonlimit 两类,得到对应的xml标记文件
- 标注格式转换:使用tools/xmltotxt.py脚本,将xml标记文件转换成yolo所需要的txt标注格式文件
- 数据集整理:dataa/images/train 下存放828张训练集图像 dataa/images/val 下存放227张验证集图像 dataa/labels/train 下存放训练集对应的标注文件 dataa/labels/val 下存放验证集对应的标注文件 注:训练集和验证集图像由人工从多个场景中按比例筛选分类; 测试集图像由最后在整个数据集中随机挑选100张
- 编写yaml数据配置文件:见于dataa/custom.yaml
- 模型选择与构建:本项目基于yolov5s修改,是因为yolov5s网络小、速度快,能满足交通标志检测的实时性需求。
Yolov5s 网络架构 - 输入图像缩放与填充:将输入图像缩放为640640,能最大程度上利用原始图像(640480)的有效信息
- 网络Backbone:Focus结构中切片操作;主干网络中设计CSP结构
- 网络Neck: FPN+PAN
- 网络输出层:采用GIOU_Loss作为Bounding box 的损失函数;加权NMS非极大值抑制
- 环境配置:安装requirements.txt中库包,注:3090 pytorch对应版本为1.7.0
- 预训练模型:使用 yolov5s.pt
- 超参数设置:batch size
设置为32,此时对应的GPU训练内存约为7G;以及根据实际情况修改其他超参数
- 训练:运行 train.py, 得到结果和训练后的权重文件 best.pt
实验结果
- 训练结果:记录于runs/train/exp,下面展示部分训练结果
- 测试生成的模型:使用得到的best.py权重文件,运行detect.py,测试100张图片,总共用时1.434s,单张图片推断仅需8ms
- 测试结果:测试结果位于runs/detect/exp3,部分测试结果如下
- 模型可视化:将best.pt转换成onnx格式,再利用netron可视化,高清图见 best.onnx.png/svg
interface.py 编写
按照比赛要求接口的测试脚本 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
187import argparse
import time
from pathlib import Path
import cv2
import torch
import torch.backends.cudnn as cudnn
from models.experimental import attempt_load
from utils.datasets import LoadStreams, LoadImages
from utils.general import check_img_size, check_requirements, check_imshow, non_max_suppression, apply_classifier, \
scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path, save_one_box
from utils.plots import colors, plot_one_box
from utils.torch_utils import select_device, load_classifier, time_synchronized
sign = {
nonlimit: 'cancel'
}
def inference(opt):
source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size
save_img = not opt.nosave and not source.endswith('.txt') # save inference images
webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
('rtsp://', 'rtmp://', 'http://', 'https://'))
# Directories
save_dir = increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok) # increment run
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Initialize
set_logging()
device = select_device(opt.device)
half = device.type != 'cpu' # half precision only supported on CUDA
# Load model
model = attempt_load(weights, map_location=device) # load FP32 model
stride = int(model.stride.max()) # model stride
imgsz = check_img_size(imgsz, s=stride) # check img_size
names = model.module.names if hasattr(model, 'module') else model.names # get class names
if half:
model.half() # to FP16
# Second-stage classifier
classify = False
if classify:
modelc = load_classifier(name='resnet101', n=2) # initialize
modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()
# Set Dataloader
vid_path, vid_writer = None, None
if webcam:
view_img = check_imshow()
cudnn.benchmark = True # set True to speed up constant image size inference
dataset = LoadStreams(source, img_size=imgsz, stride=stride)
else:
dataset = LoadImages(source, img_size=imgsz, stride=stride)
# Run inference
if device.type != 'cpu':
model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # run once
t0 = time.time()
for path, img, im0s, vid_cap in dataset:
img = torch.from_numpy(img).to(device)
img = img.half() if half else img.float() # uint8 to fp16/32
img /= 255.0 # 0 - 255 to 0.0 - 1.0
if img.ndimension() == 3:
img = img.unsqueeze(0)
# Inference
t1 = time_synchronized()
pred = model(img, augment=opt.augment)[0]
# Apply NMS
pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes, agnostic=opt.agnostic_nms)
t2 = time_synchronized()
# Apply Classifier
if classify:
pred = apply_classifier(pred, modelc, img, im0s)
# Process detections
for i, det in enumerate(pred): # detections per image
if webcam: # batch_size >= 1
p, s, im0, frame = path[i], f'{i}: ', im0s[i].copy(), dataset.count
else:
p, s, im0, frame = path, '', im0s.copy(), getattr(dataset, 'frame', 0)
p = Path(p) # to Path
save_path = str(save_dir / p.name) # img.jpg
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # img.txt
s += '%gx%g ' % img.shape[2:] # print string
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
if len(det):
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()
# Print results
for c in det[:, -1].unique():
n = (det[:, -1] == c).sum() # detections per class
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
# Write results
for *xyxy, conf, cls in reversed(det):
if save_txt: # Write to file
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh) # label format
with open(txt_path + '.txt', 'a') as f:
f.write(('%g ' * len(line)).rstrip() % line + '\n')
if save_img or opt.save_crop or view_img: # Add bbox to image
c = int(cls) # integer class
label = None if opt.hide_labels else (names[c] if opt.hide_conf else f'{names[c]} {conf:.2f}')
plot_one_box(xyxy, im0, label=label, color=colors(c, True), line_thickness=opt.line_thickness)
if opt.save_crop:
save_one_box(xyxy, im0s, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
# Print time (inference + NMS)
print(f'{s}Done. ({t2 - t1:.3f}s)')
# Stream results
if view_img:
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 1 millisecond
# Save results (image with detections)
if save_img:
if dataset.mode == 'image':
cv2.imwrite(save_path, im0)
else: # 'video' or 'stream'
if vid_path != save_path: # new video
vid_path = save_path
if isinstance(vid_writer, cv2.VideoWriter):
vid_writer.release() # release previous video writer
if vid_cap: # video
fps = vid_cap.get(cv2.CAP_PROP_FPS)
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
else: # stream
fps, w, h = 30, im0.shape[1], im0.shape[0]
save_path += '.mp4'
vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
vid_writer.write(im0)
if save_txt or save_img:
s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
print(f"Results saved to {save_dir}{s}")
print(f'Done. ({time.time() - t0:.3f}s)')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default='best.pt', help='model.pt path(s)')
parser.add_argument('--source', type=str, default='data/images', help='source') # file/folder, 0 for webcam
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='display results')
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--update', action='store_true', help='update all models')
parser.add_argument('--project', default='runs/detect', help='save results to project/name')
parser.add_argument('--name', default='exp', help='save results to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
opt = parser.parse_args()
print(opt)
check_requirements(exclude=('tensorboard', 'pycocotools', 'thop'))
with torch.no_grad():
if opt.update: # update all models (to fix SourceChangeWarning)
for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt']:
inference(opt=opt)
strip_optimizer(opt.weights)
else:
inference(opt=opt)
##需要补充按照要求格式的txt文件部分##
决赛
决赛流程
- 复试通过后六强,进入决赛
- 5月26日- 28日,线下集训(上海市杨浦区)。主要内容为小车组装,代码修改、测试、训练等
- 5月29日上午模拟路考,下午方案陈述考试(占总成绩50%)
- 5月30日,真实道路考试
硬件选择
- 硬件选择计分规则:方案上采用NX主控会减分、采用多摄会加分、使用景深和激光雷达模组有加分、使用较大麦轮会扣分、使用增强版动力电机模组加分、使用增强版车架加分
- 多目方案的选取:单目、双目、三目视觉原始图像如下所示。经过测试,对于赛道这种场景单一的情况,其实单目效果最好,但为了方案分数,采用裁剪拼接的三目方案。
单目双目三目最终三目方案
- 未采用景深和激光雷达模组,主要是因为其主要探测距离和障碍物,在纯视觉已经有很好效果的基础上不是很必要,且调试时间不够。
- 采用较小车架,是权衡加减分以及机动性(转弯)的结果
- 最终硬件结果
算法与软件
- 在给出的基础架构上修改,交通标志识别部分算法,无法融合进框架,索性放弃,在训练上做文章(过拟合)
- 具体框架文件丢失!找不到了!
- 部分,提取码:6666
- 训练:将自主训练的模型与预训练模型以及之前效果较好的模型混合训练,可以取得更好的效果,训练时间上5分钟左右是过拟合的最好状态
- 关于限速的训练,修改手柄油门等参数:
结果
- 路试部分效果
- 最后全国第二