yolo.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import argparse
  2. import logging
  3. import math
  4. import sys
  5. from copy import deepcopy
  6. from pathlib import Path
  7. from models.common import (
  8. Conv,
  9. Bottleneck,
  10. SPP,
  11. DWConv,
  12. Focus,
  13. BottleneckCSP,
  14. C3,
  15. ShuffleV2Block,
  16. Concat,
  17. NMS,
  18. autoShape,
  19. StemBlock,
  20. BlazeBlock,
  21. DoubleBlazeBlock,
  22. )
  23. from models.experimental import MixConv2d, CrossConv
  24. from utils.autoanchor import check_anchor_order
  25. from utils.general import make_divisible, check_file, set_logging
  26. from utils.torch_utils import (
  27. time_synchronized,
  28. fuse_conv_and_bn,
  29. model_info,
  30. scale_img,
  31. initialize_weights,
  32. select_device,
  33. copy_attr,
  34. )
  35. import torch
  36. import torch.nn as nn
  37. sys.path.append("./") # to run '$ python *.py' files in subdirectories
  38. logger = logging.getLogger(__name__)
  39. try:
  40. import thop # for FLOPS computation
  41. except ImportError:
  42. thop = None
  43. class Detect(nn.Module):
  44. stride = None # strides computed during build
  45. export_cat = False # onnx export cat output
  46. def __init__(self, nc=80, anchors=(), ch=()): # detection layer
  47. super(Detect, self).__init__()
  48. self.nc = nc # number of classes
  49. # self.no = nc + 5 # number of outputs per anchor
  50. self.no = nc + 5 + 8 # number of outputs per anchor
  51. self.nl = len(anchors) # number of detection layers
  52. self.na = len(anchors[0]) // 2 # number of anchors
  53. self.grid = [torch.zeros(1)] * self.nl # init grid
  54. a = torch.tensor(anchors).float().view(self.nl, -1, 2)
  55. self.register_buffer("anchors", a) # shape(nl,na,2)
  56. self.register_buffer(
  57. "anchor_grid", a.clone().view(self.nl, 1, -1, 1, 1, 2)
  58. ) # shape(nl,1,na,1,1,2)
  59. self.m = nn.ModuleList(
  60. nn.Conv2d(x, self.no * self.na, 1) for x in ch
  61. ) # output conv
  62. def forward(self, x):
  63. # x = x.copy() # for profiling
  64. z = [] # inference output
  65. # self.training=True
  66. if self.export_cat:
  67. for i in range(self.nl):
  68. x[i] = self.m[i](x[i]) # conv
  69. bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
  70. x[i] = (
  71. x[i]
  72. .view(bs, self.na, self.no, ny, nx)
  73. .permute(0, 1, 3, 4, 2)
  74. .contiguous()
  75. )
  76. if self.grid[i].shape[2:4] != x[i].shape[2:4]:
  77. # self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
  78. self.grid[i], self.anchor_grid[i] = self._make_grid_new(nx, ny, i)
  79. y = torch.full_like(x[i], 0)
  80. y = y + torch.cat(
  81. (
  82. x[i][:, :, :, :, 0:5].sigmoid(),
  83. torch.cat(
  84. (
  85. x[i][:, :, :, :, 5:13],
  86. x[i][:, :, :, :, 13 : 13 + self.nc].sigmoid(),
  87. ),
  88. 4,
  89. ),
  90. ),
  91. 4,
  92. )
  93. box_xy = (
  94. y[:, :, :, :, 0:2] * 2.0 - 0.5 + self.grid[i].to(x[i].device)
  95. ) * self.stride[i] # xy
  96. box_wh = (y[:, :, :, :, 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
  97. # box_conf = torch.cat((box_xy, torch.cat((box_wh, y[:, :, :, :, 4:5]), 4)), 4)
  98. landm1 = (
  99. y[:, :, :, :, 5:7] * self.anchor_grid[i]
  100. + self.grid[i].to(x[i].device) * self.stride[i]
  101. ) # landmark x1 y1
  102. landm2 = (
  103. y[:, :, :, :, 7:9] * self.anchor_grid[i]
  104. + self.grid[i].to(x[i].device) * self.stride[i]
  105. ) # landmark x2 y2
  106. landm3 = (
  107. y[:, :, :, :, 9:11] * self.anchor_grid[i]
  108. + self.grid[i].to(x[i].device) * self.stride[i]
  109. ) # landmark x3 y3
  110. landm4 = (
  111. y[:, :, :, :, 11:13] * self.anchor_grid[i]
  112. + self.grid[i].to(x[i].device) * self.stride[i]
  113. ) # landmark x4 y4
  114. prob = y[:, :, :, :, 13 : 13 + self.nc]
  115. score, index_ = torch.max(prob, dim=-1, keepdim=True)
  116. score = score.type(box_xy.dtype)
  117. index_ = index_.type(box_xy.dtype)
  118. index = torch.argmax(prob, dim=-1, keepdim=True).type(box_xy.dtype)
  119. # landm5 = y[:, :, :, :, 13:13] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i] # landmark x5 y5
  120. # landm = torch.cat((landm1, torch.cat((landm2, torch.cat((landm3, torch.cat((landm4, landm5), 4)), 4)), 4)), 4)
  121. # y = torch.cat((box_conf, torch.cat((landm, y[:, :, :, :, 13:13+self.nc]), 4)), 4)
  122. y = torch.cat(
  123. [
  124. box_xy,
  125. box_wh,
  126. y[:, :, :, :, 4:5],
  127. landm1,
  128. landm2,
  129. landm3,
  130. landm4,
  131. y[:, :, :, :, 13 : 13 + self.nc],
  132. ],
  133. -1,
  134. )
  135. z.append(y.view(bs, -1, self.no))
  136. return torch.cat(z, 1)
  137. for i in range(self.nl):
  138. x[i] = self.m[i](x[i]) # conv
  139. bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
  140. x[i] = (
  141. x[i]
  142. .view(bs, self.na, self.no, ny, nx)
  143. .permute(0, 1, 3, 4, 2)
  144. .contiguous()
  145. )
  146. if not self.training: # inference
  147. if self.grid[i].shape[2:4] != x[i].shape[2:4]:
  148. self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
  149. y = torch.full_like(x[i], 0)
  150. class_range = list(range(5)) + list(range(13, 13 + self.nc))
  151. y[..., class_range] = x[i][..., class_range].sigmoid()
  152. y[..., 5:13] = x[i][..., 5:13]
  153. # y = x[i].sigmoid()
  154. y[..., 0:2] = (
  155. y[..., 0:2] * 2.0 - 0.5 + self.grid[i].to(x[i].device)
  156. ) * self.stride[i] # xy
  157. y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
  158. # y[..., 5:13] = y[..., 5:13] * 8 - 4
  159. y[..., 5:7] = (
  160. y[..., 5:7] * self.anchor_grid[i]
  161. + self.grid[i].to(x[i].device) * self.stride[i]
  162. ) # landmark x1 y1
  163. y[..., 7:9] = (
  164. y[..., 7:9] * self.anchor_grid[i]
  165. + self.grid[i].to(x[i].device) * self.stride[i]
  166. ) # landmark x2 y2
  167. y[..., 9:11] = (
  168. y[..., 9:11] * self.anchor_grid[i]
  169. + self.grid[i].to(x[i].device) * self.stride[i]
  170. ) # landmark x3 y3
  171. y[..., 11:13] = (
  172. y[..., 11:13] * self.anchor_grid[i]
  173. + self.grid[i].to(x[i].device) * self.stride[i]
  174. ) # landmark x4 y4
  175. # y[..., 13:13] = y[..., 13:13] * self.anchor_grid[i] + self.grid[i].to(x[i].device) * self.stride[i]# landmark x5 y5
  176. # y[..., 5:7] = (y[..., 5:7] * 2 -1) * self.anchor_grid[i] # landmark x1 y1
  177. # y[..., 7:9] = (y[..., 7:9] * 2 -1) * self.anchor_grid[i] # landmark x2 y2
  178. # y[..., 9:11] = (y[..., 9:11] * 2 -1) * self.anchor_grid[i] # landmark x3 y3
  179. # y[..., 11:13] = (y[..., 11:13] * 2 -1) * self.anchor_grid[i] # landmark x4 y4
  180. # y[..., 13:13] = (y[..., 13:13] * 2 -1) * self.anchor_grid[i] # landmark x5 y5
  181. z.append(y.view(bs, -1, self.no))
  182. return x if self.training else (torch.cat(z, 1), x)
  183. @staticmethod
  184. def _make_grid(nx=20, ny=20):
  185. yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)] , indexing ='ij')
  186. return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
  187. def _make_grid_new(self, nx=20, ny=20, i=0):
  188. d = self.anchors[i].device
  189. if (
  190. "1.10.0" in torch.__version__
  191. ): # torch>=1.10.0 meshgrid workaround for torch>=0.7 compatibility
  192. yv, xv = torch.meshgrid(
  193. [torch.arange(ny).to(d), torch.arange(nx).to(d)], indexing="ij"
  194. )
  195. else:
  196. yv, xv = torch.meshgrid([torch.arange(ny).to(d), torch.arange(nx).to(d) ] , indexing='ij')
  197. grid = torch.stack((xv, yv), 2).expand((1, self.na, ny, nx, 2)).float()
  198. anchor_grid = (
  199. (self.anchors[i].clone() * self.stride[i])
  200. .view((1, self.na, 1, 1, 2))
  201. .expand((1, self.na, ny, nx, 2))
  202. .float()
  203. )
  204. return grid, anchor_grid
  205. class Model(nn.Module):
  206. def __init__(
  207. self, cfg="yolov5s.yaml", ch=3, nc=None
  208. ): # model, input channels, number of classes
  209. super(Model, self).__init__()
  210. if isinstance(cfg, dict):
  211. self.yaml = cfg # model dict
  212. else: # is *.yaml
  213. import yaml # for torch hub
  214. self.yaml_file = Path(cfg).name
  215. with open(cfg) as f:
  216. self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict
  217. # Define model
  218. ch = self.yaml["ch"] = self.yaml.get("ch", ch) # input channels
  219. if nc and nc != self.yaml["nc"]:
  220. logger.info(
  221. "Overriding model.yaml nc=%g with nc=%g" % (self.yaml["nc"], nc)
  222. )
  223. self.yaml["nc"] = nc # override yaml value
  224. self.model, self.save = parse_model(
  225. deepcopy(self.yaml), ch=[ch]
  226. ) # model, savelist
  227. self.names = [str(i) for i in range(self.yaml["nc"])] # default names
  228. # print([x.shape for x in self.forward(torch.zeros(1, ch, 64, 64))])
  229. # Build strides, anchors
  230. m = self.model[-1] # Detect()
  231. if isinstance(m, Detect):
  232. s = 128 # 2x min stride
  233. m.stride = torch.tensor(
  234. [s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))]
  235. ) # forward
  236. m.anchors /= m.stride.view(-1, 1, 1)
  237. check_anchor_order(m)
  238. self.stride = m.stride
  239. self._initialize_biases() # only run once
  240. # print('Strides: %s' % m.stride.tolist())
  241. # Init weights, biases
  242. initialize_weights(self)
  243. self.info()
  244. logger.info("")
  245. def forward(self, x, augment=False, profile=False):
  246. if augment:
  247. img_size = x.shape[-2:] # height, width
  248. s = [1, 0.83, 0.67] # scales
  249. f = [None, 3, None] # flips (2-ud, 3-lr)
  250. y = [] # outputs
  251. for si, fi in zip(s, f):
  252. xi = scale_img(x.flip(fi) if fi else x, si)
  253. yi = self.forward_once(xi)[0] # forward
  254. # cv2.imwrite('img%g.jpg' % s, 255 * xi[0].numpy().transpose((1, 2, 0))[:, :, ::-1]) # save
  255. yi[..., :4] /= si # de-scale
  256. if fi == 2:
  257. yi[..., 1] = img_size[0] - yi[..., 1] # de-flip ud
  258. elif fi == 3:
  259. yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lr
  260. y.append(yi)
  261. return torch.cat(y, 1), None # augmented inference, train
  262. else:
  263. return self.forward_once(x, profile) # single-scale inference, train
  264. def forward_once(self, x, profile=False):
  265. y, dt = [], [] # outputs
  266. for m in self.model:
  267. if m.f != -1: # if not from previous layer
  268. x = (
  269. y[m.f]
  270. if isinstance(m.f, int)
  271. else [x if j == -1 else y[j] for j in m.f]
  272. ) # from earlier layers
  273. if profile:
  274. o = (
  275. thop.profile(m, inputs=(x,), verbose=False)[0] / 1e9 * 2
  276. if thop
  277. else 0
  278. ) # FLOPS
  279. t = time_synchronized()
  280. for _ in range(10):
  281. _ = m(x)
  282. dt.append((time_synchronized() - t) * 100)
  283. print("%10.1f%10.0f%10.1fms %-40s" % (o, m.np, dt[-1], m.type))
  284. x = m(x) # run
  285. y.append(x if m.i in self.save else None) # save output
  286. if profile:
  287. print("%.1fms total" % sum(dt))
  288. return x
  289. def _initialize_biases(
  290. self, cf=None
  291. ): # initialize biases into Detect(), cf is class frequency
  292. # https://arxiv.org/abs/1708.02002 section 3.3
  293. # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1.
  294. m = self.model[-1] # Detect() module
  295. for mi, s in zip(m.m, m.stride): # from
  296. b = mi.bias.view(m.na, -1) # conv.bias(255) to (3,85)
  297. b.data[:, 4] += math.log(
  298. 8 / (640 / s) ** 2
  299. ) # obj (8 objects per 640 image)
  300. b.data[:, 5:] += (
  301. math.log(0.6 / (m.nc - 0.99))
  302. if cf is None
  303. else torch.log(cf / cf.sum())
  304. ) # cls
  305. mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)
  306. def _print_biases(self):
  307. m = self.model[-1] # Detect() module
  308. for mi in m.m: # from
  309. b = mi.bias.detach().view(m.na, -1).T # conv.bias(255) to (3,85)
  310. print(
  311. ("%6g Conv2d.bias:" + "%10.3g" * 6)
  312. % (mi.weight.shape[1], *b[:5].mean(1).tolist(), b[5:].mean())
  313. )
  314. # def _print_weights(self):
  315. # for m in self.model.modules():
  316. # if type(m) is Bottleneck:
  317. # print('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights
  318. def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers
  319. print("Fusing layers... ")
  320. for m in self.model.modules():
  321. if type(m) is Conv and hasattr(m, "bn"):
  322. m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv
  323. delattr(m, "bn") # remove batchnorm
  324. m.forward = m.fuseforward # update forward
  325. elif type(m) is nn.Upsample:
  326. m.recompute_scale_factor = None # torch 1.11.0 compatibility
  327. self.info()
  328. return self
  329. def nms(self, mode=True): # add or remove NMS module
  330. present = type(self.model[-1]) is NMS # last layer is NMS
  331. if mode and not present:
  332. print("Adding NMS... ")
  333. m = NMS() # module
  334. m.f = -1 # from
  335. m.i = self.model[-1].i + 1 # index
  336. self.model.add_module(name="%s" % m.i, module=m) # add
  337. self.eval()
  338. elif not mode and present:
  339. print("Removing NMS... ")
  340. self.model = self.model[:-1] # remove
  341. return self
  342. def autoshape(self): # add autoShape module
  343. print("Adding autoShape... ")
  344. m = autoShape(self) # wrap model
  345. copy_attr(
  346. m, self, include=("yaml", "nc", "hyp", "names", "stride"), exclude=()
  347. ) # copy attributes
  348. return m
  349. def info(self, verbose=False, img_size=640): # print model information
  350. model_info(self, verbose, img_size)
  351. def parse_model(d, ch): # model_dict, input_channels(3)
  352. logger.info(
  353. "\n%3s%18s%3s%10s %-40s%-30s"
  354. % ("", "from", "n", "params", "module", "arguments")
  355. )
  356. anchors, nc, gd, gw = (
  357. d["anchors"],
  358. d["nc"],
  359. d["depth_multiple"],
  360. d["width_multiple"],
  361. )
  362. na = (
  363. (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors
  364. ) # number of anchors
  365. no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
  366. layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
  367. for i, (f, n, m, args) in enumerate(
  368. d["backbone"] + d["head"]
  369. ): # from, number, module, args
  370. m = eval(m) if isinstance(m, str) else m # eval strings
  371. for j, a in enumerate(args):
  372. try:
  373. args[j] = eval(a) if isinstance(a, str) else a # eval strings
  374. except:
  375. pass
  376. n = max(round(n * gd), 1) if n > 1 else n # depth gain
  377. if m in [
  378. Conv,
  379. Bottleneck,
  380. SPP,
  381. DWConv,
  382. MixConv2d,
  383. Focus,
  384. CrossConv,
  385. BottleneckCSP,
  386. C3,
  387. ShuffleV2Block,
  388. StemBlock,
  389. BlazeBlock,
  390. DoubleBlazeBlock,
  391. ]:
  392. c1, c2 = ch[f], args[0]
  393. # Normal
  394. # if i > 0 and args[0] != no: # channel expansion factor
  395. # ex = 1.75 # exponential (default 2.0)
  396. # e = math.log(c2 / ch[1]) / math.log(2)
  397. # c2 = int(ch[1] * ex ** e)
  398. # if m != Focus:
  399. c2 = make_divisible(c2 * gw, 8) if c2 != no else c2
  400. # Experimental
  401. # if i > 0 and args[0] != no: # channel expansion factor
  402. # ex = 1 + gw # exponential (default 2.0)
  403. # ch1 = 32 # ch[1]
  404. # e = math.log(c2 / ch1) / math.log(2) # level 1-n
  405. # c2 = int(ch1 * ex ** e)
  406. # if m != Focus:
  407. # c2 = make_divisible(c2, 8) if c2 != no else c2
  408. args = [c1, c2, *args[1:]]
  409. if m in [BottleneckCSP, C3]:
  410. args.insert(2, n)
  411. n = 1
  412. elif m is nn.BatchNorm2d:
  413. args = [ch[f]]
  414. elif m is Concat:
  415. c2 = sum([ch[-1 if x == -1 else x + 1] for x in f])
  416. elif m is Detect:
  417. args.append([ch[x + 1] for x in f])
  418. if isinstance(args[1], int): # number of anchors
  419. args[1] = [list(range(args[1] * 2))] * len(f)
  420. else:
  421. c2 = ch[f]
  422. m_ = (
  423. nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args)
  424. ) # module
  425. t = str(m)[8:-2].replace("__main__.", "") # module type
  426. np = sum([x.numel() for x in m_.parameters()]) # number params
  427. m_.i, m_.f, m_.type, m_.np = (
  428. i,
  429. f,
  430. t,
  431. np,
  432. ) # attach index, 'from' index, type, number params
  433. logger.info("%3s%18s%3s%10.0f %-40s%-30s" % (i, f, n, np, t, args)) # print
  434. save.extend(
  435. x % i for x in ([f] if isinstance(f, int) else f) if x != -1
  436. ) # append to savelist
  437. layers.append(m_)
  438. ch.append(c2)
  439. return nn.Sequential(*layers), sorted(save)
  440. from thop import profile
  441. from thop import clever_format
  442. if __name__ == "__main__":
  443. parser = argparse.ArgumentParser()
  444. parser.add_argument("--cfg", type=str, default="yolov5s.yaml", help="model.yaml")
  445. parser.add_argument(
  446. "--device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu"
  447. )
  448. opt = parser.parse_args()
  449. opt.cfg = check_file(opt.cfg) # check file
  450. set_logging()
  451. device = select_device(opt.device)
  452. # Create model
  453. model = Model(opt.cfg).to(device)
  454. stride = model.stride.max()
  455. if stride == 32:
  456. input = torch.Tensor(1, 3, 480, 640).to(device)
  457. else:
  458. input = torch.Tensor(1, 3, 512, 640).to(device)
  459. model.train()
  460. print(model)
  461. flops, params = profile(model, inputs=(input, ))
  462. flops, params = clever_format([flops, params], "%.3f")
  463. print('Flops:', flops, ',Params:' ,params)