%matplotlib inline %reload_ext autoreload %autoreload 2 from fastai.conv_learner import * from fastai.dataset import * import json, pdb from PIL import ImageDraw, ImageFont from matplotlib import patches, patheffects torch.cuda.set_device(0) torch.backends.cudnn.benchmark=True PATH = Path('data/pascal') trn_j = json.load((PATH / 'pascal_train2007.json').open()) IMAGES,ANNOTATIONS,CATEGORIES = ['images', 'annotations', 'categories'] FILE_NAME,ID,IMG_ID,CAT_ID,BBOX = 'file_name','id','image_id','category_id','bbox' cats = dict((o[ID], o['name']) for o in trn_j[CATEGORIES]) trn_fns = dict((o[ID], o[FILE_NAME]) for o in trn_j[IMAGES]) trn_ids = [o[ID] for o in trn_j[IMAGES]] JPEGS = 'VOCdevkit/VOC2007/JPEGImages' IMG_PATH = PATH/JPEGS def get_trn_anno(): trn_anno = collections.defaultdict(lambda:[]) for o in trn_j[ANNOTATIONS]: if not o['ignore']: bb = o[BBOX] bb = np.array([bb[1], bb[0], bb[3]+bb[1]-1, bb[2]+bb[0]-1]) trn_anno[o[IMG_ID]].append((bb,o[CAT_ID])) return trn_anno trn_anno = get_trn_anno() def show_img(im, figsize=None, ax=None): if not ax: fig,ax = plt.subplots(figsize=figsize) ax.imshow(im) ax.set_xticks(np.linspace(0, 224, 8)) ax.set_yticks(np.linspace(0, 224, 8)) ax.grid() ax.set_yticklabels([]) ax.set_xticklabels([]) return ax def draw_outline(o, lw): o.set_path_effects([patheffects.Stroke( linewidth=lw, foreground='black'), patheffects.Normal()]) def draw_rect(ax, b, color='white'): patch = ax.add_patch(patches.Rectangle(b[:2], *b[-2:], fill=False, edgecolor=color, lw=2)) draw_outline(patch, 4) def draw_text(ax, xy, txt, sz=14, color='white'): text = ax.text(*xy, txt, verticalalignment='top', color=color, fontsize=sz, weight='bold') draw_outline(text, 1) def bb_hw(a): return np.array([a[1],a[0],a[3]-a[1]+1,a[2]-a[0]+1]) def draw_im(im, ann): ax = show_img(im, figsize=(16,8)) for b,c in ann: b = bb_hw(b) draw_rect(ax, b) draw_text(ax, b[:2], cats[c], sz=16) def draw_idx(i): im_a = trn_anno[i] im = open_image(IMG_PATH/trn_fns[i]) draw_im(im, im_a) MC_CSV = PATH/'tmp/mc.csv' trn_anno[12] mc = [set([cats[p[1]] for p in trn_anno[o]]) for o in trn_ids] mcs = [' '.join(str(p) for p in o) for o in mc] df = pd.DataFrame({'fn': [trn_fns[o] for o in trn_ids], 'clas': mcs}, columns=['fn','clas']) df.to_csv(MC_CSV, index=False) f_model=resnet34 sz=224 bs=64 tfms = tfms_from_model(f_model, sz, crop_type=CropType.NO) md = ImageClassifierData.from_csv(PATH, JPEGS, MC_CSV, tfms=tfms, bs=bs) learn = ConvLearner.pretrained(f_model, md) learn.opt_fn = optim.Adam lrf=learn.lr_find(1e-5,100) learn.sched.plot(0) lr = 2e-2 learn.fit(lr, 1, cycle_len=3, use_clr=(32,5)) lrs = np.array([lr/100, lr/10, lr]) learn.freeze_to(-2) learn.lr_find(lrs/1000) learn.sched.plot(0) learn.fit(lrs/10, 1, cycle_len=5, use_clr=(32,5)) learn.save('mclas') learn.load('mclas') y = learn.predict() x,_ = next(iter(md.val_dl)) x = to_np(x) fig, axes = plt.subplots(3, 4, figsize=(12, 8)) for i,ax in enumerate(axes.flat): ima=md.val_ds.denorm(x)[i] ya = np.nonzero(y[i]>0.4)[0] b = '\n'.join(md.classes[o] for o in ya) ax = show_img(ima, ax=ax) draw_text(ax, (0,0), b) plt.tight_layout() CLAS_CSV = PATH/'tmp/clas.csv' MBB_CSV = PATH/'tmp/mbb.csv' f_model=resnet34 sz=224 bs=64 mc = [[cats[p[1]] for p in trn_anno[o]] for o in trn_ids] id2cat = list(cats.values()) cat2id = {v:k for k,v in enumerate(id2cat)} mcs = np.array([np.array([cat2id[p] for p in o]) for o in mc]); mcs val_idxs = get_cv_idxs(len(trn_fns)) ((val_mcs,trn_mcs),) = split_by_idx(val_idxs, mcs) mbb = [np.concatenate([p[0] for p in trn_anno[o]]) for o in trn_ids] mbbs = [' '.join(str(p) for p in o) for o in mbb] df = pd.DataFrame({'fn': [trn_fns[o] for o in trn_ids], 'bbox': mbbs}, columns=['fn','bbox']) df.to_csv(MBB_CSV, index=False) df.head() aug_tfms = [RandomRotate(3, p=0.5, tfm_y=TfmType.COORD), RandomLighting(0.05, 0.05, tfm_y=TfmType.COORD), RandomFlip(tfm_y=TfmType.COORD)] tfms = tfms_from_model(f_model, sz, crop_type=CropType.NO, tfm_y=TfmType.COORD, aug_tfms=aug_tfms) md = ImageClassifierData.from_csv(PATH, JPEGS, MBB_CSV, tfms=tfms, bs=bs, continuous=True, num_workers=4) import matplotlib.cm as cmx import matplotlib.colors as mcolors from cycler import cycler def get_cmap(N): color_norm = mcolors.Normalize(vmin=0, vmax=N-1) return cmx.ScalarMappable(norm=color_norm, cmap='Set3').to_rgba num_colr = 12 cmap = get_cmap(num_colr) colr_list = [cmap(float(x)) for x in range(num_colr)] def show_ground_truth(ax, im, bbox, clas=None, prs=None, thresh=0.3): bb = [bb_hw(o) for o in bbox.reshape(-1,4)] if prs is None: prs = [None]*len(bb) if clas is None: clas = [None]*len(bb) ax = show_img(im, ax=ax) for i,(b,c,pr) in enumerate(zip(bb, clas, prs)): if((b[2]>0) and (pr is None or pr > thresh)): draw_rect(ax, b, color=colr_list[i%num_colr]) txt = f'{i}: ' if c is not None: txt += ('bg' if c==len(id2cat) else id2cat[c]) if pr is not None: txt += f' {pr:.2f}' draw_text(ax, b[:2], txt, color=colr_list[i%num_colr]) class ConcatLblDataset(Dataset): def __init__(self, ds, y2): self.ds,self.y2 = ds,y2 self.sz = ds.sz def __len__(self): return len(self.ds) def __getitem__(self, i): x,y = self.ds[i] return (x, (y,self.y2[i])) trn_ds2 = ConcatLblDataset(md.trn_ds, trn_mcs) val_ds2 = ConcatLblDataset(md.val_ds, val_mcs) md.trn_dl.dataset = trn_ds2 md.val_dl.dataset = val_ds2 x,y=to_np(next(iter(md.val_dl))) x=md.val_ds.ds.denorm(x) x,y=to_np(next(iter(md.trn_dl))) x=md.trn_ds.ds.denorm(x) fig, axes = plt.subplots(3, 4, figsize=(16, 12)) for i,ax in enumerate(axes.flat): show_ground_truth(ax, x[i], y[0][i], y[1][i]) plt.tight_layout() anc_grid = 4 k = 1 anc_offset = 1/(anc_grid*2) anc_x = np.repeat(np.linspace(anc_offset, 1-anc_offset, anc_grid), anc_grid) anc_y = np.tile(np.linspace(anc_offset, 1-anc_offset, anc_grid), anc_grid) anc_ctrs = np.tile(np.stack([anc_x,anc_y], axis=1), (k,1)) anc_sizes = np.array([[1/anc_grid,1/anc_grid] for i in range(anc_grid*anc_grid)]) anchors = V(np.concatenate([anc_ctrs, anc_sizes], axis=1), requires_grad=False).float() grid_sizes = V(np.array([1/anc_grid]), requires_grad=False).unsqueeze(1) plt.scatter(anc_x, anc_y) plt.xlim(0, 1) plt.ylim(0, 1); anchors def hw2corners(ctr, hw): return torch.cat([ctr-hw/2, ctr+hw/2], dim=1) anchor_cnr = hw2corners(anchors[:,:2], anchors[:,2:]) anchor_cnr n_clas = len(id2cat)+1 n_act = k*(4+n_clas) class StdConv(nn.Module): def __init__(self, nin, nout, stride=2, drop=0.1): super().__init__() self.conv = nn.Conv2d(nin, nout, 3, stride=stride, padding=1) self.bn = nn.BatchNorm2d(nout) self.drop = nn.Dropout(drop) def forward(self, x): return self.drop(self.bn(F.relu(self.conv(x)))) def flatten_conv(x,k): bs,nf,gx,gy = x.size() x = x.permute(0,2,3,1).contiguous() return x.view(bs,-1,nf//k) class OutConv(nn.Module): def __init__(self, k, nin, bias): super().__init__() self.k = k self.oconv1 = nn.Conv2d(nin, (len(id2cat)+1)*k, 3, padding=1) self.oconv2 = nn.Conv2d(nin, 4*k, 3, padding=1) self.oconv1.bias.data.zero_().add_(bias) def forward(self, x): return [flatten_conv(self.oconv1(x), self.k), flatten_conv(self.oconv2(x), self.k)] class SSD_Head(nn.Module): def __init__(self, k, bias): super().__init__() self.drop = nn.Dropout(0.25) self.sconv0 = StdConv(512,256, stride=1) # self.sconv1 = StdConv(256,256) self.sconv2 = StdConv(256,256) self.out = OutConv(k, 256, bias) def forward(self, x): x = self.drop(F.relu(x)) x = self.sconv0(x) # x = self.sconv1(x) x = self.sconv2(x) return self.out(x) head_reg4 = SSD_Head(k, -3.) models = ConvnetBuilder(f_model, 0, 0, 0, custom_head=head_reg4) learn = ConvLearner(md, models) learn.opt_fn = optim.Adam k def one_hot_embedding(labels, num_classes): return torch.eye(num_classes)[labels.data.cpu()] class BCE_Loss(nn.Module): def __init__(self, num_classes): super().__init__() self.num_classes = num_classes def forward(self, pred, targ): t = one_hot_embedding(targ, self.num_classes+1) t = V(t[:,:-1].contiguous())#.cpu() x = pred[:,:-1] w = self.get_weight(x,t) return F.binary_cross_entropy_with_logits(x, t, w, size_average=False)/self.num_classes def get_weight(self,x,t): return None loss_f = BCE_Loss(len(id2cat)) def intersect(box_a, box_b): max_xy = torch.min(box_a[:, None, 2:], box_b[None, :, 2:]) min_xy = torch.max(box_a[:, None, :2], box_b[None, :, :2]) inter = torch.clamp((max_xy - min_xy), min=0) return inter[:, :, 0] * inter[:, :, 1] def box_sz(b): return ((b[:, 2]-b[:, 0]) * (b[:, 3]-b[:, 1])) def jaccard(box_a, box_b): inter = intersect(box_a, box_b) union = box_sz(box_a).unsqueeze(1) + box_sz(box_b).unsqueeze(0) - inter return inter / union def get_y(bbox,clas): bbox = bbox.view(-1,4)/sz bb_keep = ((bbox[:,2]-bbox[:,0])>0).nonzero()[:,0] return bbox[bb_keep],clas[bb_keep] def actn_to_bb(actn, anchors): actn_bbs = torch.tanh(actn) actn_centers = (actn_bbs[:,:2]/2 * grid_sizes) + anchors[:,:2] actn_hw = (actn_bbs[:,2:]/2+1) * anchors[:,2:] return hw2corners(actn_centers, actn_hw) def map_to_ground_truth(overlaps, print_it=False): prior_overlap, prior_idx = overlaps.max(1) if print_it: print(prior_overlap) # pdb.set_trace() gt_overlap, gt_idx = overlaps.max(0) gt_overlap[prior_idx] = 1.99 for i,o in enumerate(prior_idx): gt_idx[o] = i return gt_overlap,gt_idx def ssd_1_loss(b_c,b_bb,bbox,clas,print_it=False): bbox,clas = get_y(bbox,clas) a_ic = actn_to_bb(b_bb, anchors) overlaps = jaccard(bbox.data, anchor_cnr.data) gt_overlap,gt_idx = map_to_ground_truth(overlaps,print_it) gt_clas = clas[gt_idx] pos = gt_overlap > 0.4 pos_idx = torch.nonzero(pos)[:,0] gt_clas[1-pos] = len(id2cat) gt_bbox = bbox[gt_idx] loc_loss = ((a_ic[pos_idx] - gt_bbox[pos_idx]).abs()).mean() clas_loss = loss_f(b_c, gt_clas) return loc_loss, clas_loss def ssd_loss(pred,targ,print_it=False): lcs,lls = 0.,0. for b_c,b_bb,bbox,clas in zip(*pred,*targ): loc_loss,clas_loss = ssd_1_loss(b_c,b_bb,bbox,clas,print_it) lls += loc_loss lcs += clas_loss if print_it: print(f'loc: {lls.data[0]}, clas: {lcs.data[0]}') return lls+lcs x,y = next(iter(md.val_dl)) # x,y = V(x).cpu(),V(y) x,y = V(x),V(y) for i,o in enumerate(y): y[i] = o.cuda() learn.model.cuda() batch = learn.model(x) # uncomment to debug on cpu #anchors = anchors.cpu(); grid_sizes = grid_sizes.cpu(); anchor_cnr = anchor_cnr.cpu() ssd_loss(batch, y, True) learn.crit = ssd_loss lr = 3e-3 lrs = np.array([lr/100,lr/10,lr]) learn.lr_find(lrs/1000,1.) learn.sched.plot(1) learn.fit(lr, 1, cycle_len=5, use_clr=(20,10)) learn.save('0') learn.load('0') x,y = next(iter(md.val_dl)) x,y = V(x),V(y) learn.model.eval() batch = learn.model(x) b_clas,b_bb = batch b_clas.size(),b_bb.size() idx=7 b_clasi = b_clas[idx] b_bboxi = b_bb[idx] ima=md.val_ds.ds.denorm(to_np(x))[idx] bbox,clas = get_y(y[0][idx], y[1][idx]) bbox,clas def torch_gt(ax, ima, bbox, clas, prs=None, thresh=0.4): return show_ground_truth(ax, ima, to_np((bbox*224).long()), to_np(clas), to_np(prs) if prs is not None else None, thresh) fig, ax = plt.subplots(figsize=(7,7)) torch_gt(ax, ima, bbox, clas) fig, ax = plt.subplots(figsize=(7,7)) torch_gt(ax, ima, anchor_cnr, b_clasi.max(1)[1]) grid_sizes anchors a_ic = actn_to_bb(b_bboxi, anchors) fig, ax = plt.subplots(figsize=(7,7)) torch_gt(ax, ima, a_ic, b_clasi.max(1)[1], b_clasi.max(1)[0].sigmoid(), thresh=0.0) overlaps = jaccard(bbox.data, anchor_cnr.data) overlaps overlaps.max(1) overlaps.max(0) gt_overlap,gt_idx = map_to_ground_truth(overlaps) gt_overlap,gt_idx gt_clas = clas[gt_idx]; gt_clas thresh = 0.5 pos = gt_overlap > thresh pos_idx = torch.nonzero(pos)[:,0] neg_idx = torch.nonzero(1-pos)[:,0] pos_idx gt_clas[1-pos] = len(id2cat) [id2cat[o] if o 0: i = idx[-1] # index of current largest val keep[count] = i count += 1 if idx.size(0) == 1: break idx = idx[:-1] # remove kept element from view # load bboxes of next highest vals torch.index_select(x1, 0, idx, out=xx1) torch.index_select(y1, 0, idx, out=yy1) torch.index_select(x2, 0, idx, out=xx2) torch.index_select(y2, 0, idx, out=yy2) # store element-wise max with next highest score xx1 = torch.clamp(xx1, min=x1[i]) yy1 = torch.clamp(yy1, min=y1[i]) xx2 = torch.clamp(xx2, max=x2[i]) yy2 = torch.clamp(yy2, max=y2[i]) w.resize_as_(xx2) h.resize_as_(yy2) w = xx2 - xx1 h = yy2 - yy1 # check sizes of xx1 and xx2.. after each iteration w = torch.clamp(w, min=0.0) h = torch.clamp(h, min=0.0) inter = w*h # IoU = i / (area(a) + area(b) - i) rem_areas = torch.index_select(area, 0, idx) # load remaining areas) union = (rem_areas - inter) + area[i] IoU = inter/union # store result in iou # keep only elements with an IoU <= overlap idx = idx[IoU.le(overlap)] return keep, count x,y = next(iter(md.val_dl)) y = V(y) batch = learn.model(V(x)) b_clas,b_bb = batch x = to_np(x) def show_nmf(idx): ima=md.val_ds.ds.denorm(x)[idx] bbox,clas = get_y(y[0][idx], y[1][idx]) a_ic = actn_to_bb(b_bb[idx], anchors) clas_pr, clas_ids = b_clas[idx].max(1) clas_pr = clas_pr.sigmoid() conf_scores = b_clas[idx].sigmoid().t().data out1,out2,cc = [],[],[] for cl in range(0, len(conf_scores)-1): c_mask = conf_scores[cl] > 0.25 if c_mask.sum() == 0: continue scores = conf_scores[cl][c_mask] l_mask = c_mask.unsqueeze(1).expand_as(a_ic) boxes = a_ic[l_mask].view(-1, 4) ids, count = nms(boxes.data, scores, 0.4, 50) ids = ids[:count] out1.append(scores[ids]) out2.append(boxes.data[ids]) cc.append([cl]*count) if not cc: print(f"{i}: empty array") return cc = T(np.concatenate(cc)) out1 = torch.cat(out1) out2 = torch.cat(out2) fig, ax = plt.subplots(figsize=(8,8)) torch_gt(ax, ima, out2, cc, out1, 0.1) for i in range(12): show_nmf(i)