From dc009edecd9a823528f975bf0e2f5b6a6fc823c2 Mon Sep 17 00:00:00 2001 From: Xinyu Zhou Date: Thu, 10 Sep 2020 17:41:49 +0800 Subject: [PATCH] clean up the mess; use cpu by default --- .gitignore | 7 + .pre-commit-config.yaml | 5 + README.md | 15 +- ShuffleNetV2.py | 92 +++--- TestAccuracy.py | 143 ++++++--- TrainModel.py | 273 +++++++++++++----- augmentations.py | 76 ++--- autocrop.py | 129 +++++++++ croppingDataset.py | 218 +++++++++----- croppingModel.py | 215 +++++++------- demo_eval.py | 132 ++++++--- mobilenetv2.py | 34 ++- requirements.txt | 5 + rod_align/__init__.pyc | Bin 174 -> 0 bytes rod_align/_ext/__init__.py | 0 rod_align/_ext/__init__.pyc | Bin 174 -> 0 bytes rod_align/_ext/rod_align/__init__.py | 4 +- rod_align/_ext/rod_align/__init__.pyc | Bin 836 -> 0 bytes rod_align/_ext/rod_align/_rod_align.so | Bin 86392 -> 19928 bytes rod_align/build.py | 26 +- rod_align/functions/__init__.pyc | Bin 184 -> 0 bytes rod_align/functions/rod_align.py | 49 ++-- rod_align/functions/rod_align.pyc | Bin 2110 -> 0 bytes rod_align/modules/__init__.pyc | Bin 182 -> 0 bytes rod_align/modules/rod_align.py | 17 +- rod_align/modules/rod_align.pyc | Bin 3105 -> 0 bytes roi_align/__init__.pyc | Bin 169 -> 0 bytes roi_align/__pycache__/__init__.cpython-35.pyc | Bin 155 -> 0 bytes roi_align/_ext/__init__.py | 0 roi_align/_ext/__init__.pyc | Bin 174 -> 0 bytes .../_ext/__pycache__/__init__.cpython-35.pyc | Bin 160 -> 0 bytes roi_align/_ext/roi_align/__init__.py | 4 +- roi_align/_ext/roi_align/__init__.pyc | Bin 786 -> 0 bytes .../__pycache__/__init__.cpython-35.pyc | Bin 621 -> 0 bytes roi_align/_ext/roi_align/_roi_align.so | Bin 86504 -> 19928 bytes roi_align/build.py | 26 +- roi_align/functions/__init__.pyc | Bin 179 -> 0 bytes .../__pycache__/__init__.cpython-35.pyc | Bin 165 -> 0 bytes .../__pycache__/roi_align.cpython-35.pyc | Bin 1668 -> 0 bytes roi_align/functions/roi_align.py | 49 ++-- roi_align/functions/roi_align.pyc | Bin 2085 -> 0 bytes roi_align/modules/__init__.pyc | Bin 177 -> 0 bytes .../__pycache__/__init__.cpython-35.pyc | Bin 163 -> 0 bytes .../__pycache__/roi_align.cpython-35.pyc | Bin 2276 -> 0 bytes roi_align/modules/roi_align.py | 17 +- roi_align/modules/roi_align.pyc | Bin 2995 -> 0 bytes thop/__init__.py | 2 +- thop/__init__.pyc | Bin 218 -> 0 bytes thop/count_hooks.py | 10 +- thop/count_hooks.pyc | Bin 5174 -> 0 bytes thop/profile.py | 9 +- thop/profile.pyc | Bin 2888 -> 0 bytes thop/utils.py | 3 +- 53 files changed, 1052 insertions(+), 508 deletions(-) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 autocrop.py create mode 100644 requirements.txt delete mode 100644 rod_align/__init__.pyc create mode 100644 rod_align/_ext/__init__.py delete mode 100644 rod_align/_ext/__init__.pyc delete mode 100644 rod_align/_ext/rod_align/__init__.pyc mode change 100644 => 100755 rod_align/_ext/rod_align/_rod_align.so delete mode 100644 rod_align/functions/__init__.pyc delete mode 100644 rod_align/functions/rod_align.pyc delete mode 100644 rod_align/modules/__init__.pyc delete mode 100644 rod_align/modules/rod_align.pyc delete mode 100644 roi_align/__init__.pyc delete mode 100644 roi_align/__pycache__/__init__.cpython-35.pyc create mode 100644 roi_align/_ext/__init__.py delete mode 100644 roi_align/_ext/__init__.pyc delete mode 100644 roi_align/_ext/__pycache__/__init__.cpython-35.pyc delete mode 100644 roi_align/_ext/roi_align/__init__.pyc delete mode 100644 roi_align/_ext/roi_align/__pycache__/__init__.cpython-35.pyc mode change 100644 => 100755 roi_align/_ext/roi_align/_roi_align.so delete mode 100644 roi_align/functions/__init__.pyc delete mode 100644 roi_align/functions/__pycache__/__init__.cpython-35.pyc delete mode 100644 roi_align/functions/__pycache__/roi_align.cpython-35.pyc delete mode 100644 roi_align/functions/roi_align.pyc delete mode 100644 roi_align/modules/__init__.pyc delete mode 100644 roi_align/modules/__pycache__/__init__.cpython-35.pyc delete mode 100644 roi_align/modules/__pycache__/roi_align.cpython-35.pyc delete mode 100644 roi_align/modules/roi_align.pyc delete mode 100644 thop/__init__.pyc delete mode 100644 thop/count_hooks.pyc delete mode 100644 thop/profile.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e52d57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.pyc +venv/ +dataset.zip +dataset/ +pretrained_model/ +pretrained_model.zip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4b4e685 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: +- repo: https://github.com/python/black + rev: stable + hooks: + - id: black diff --git a/README.md b/README.md index f4dfbfc..56bc70a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,27 @@ # Grid-Anchor-based-Image-Cropping-Pytorch This code includes several extensions we have made to our conference version. Please read the [paper](https://drive.google.com/open?id=1Bd1VaqYVycB7Npv5OdXKl-znKs_APl4n) for details. +## Change Log +### 2020-09-10 +- Add an `autocrop.py` driver for batch processing on image folder. +- Overrides `_roi_align.so` and `_roi_align.so` with corresponding CPU version. (I'm using a MAC without nvidia gpu :( ). +- Removed a lot of `*.pyc` files +- Add a `requirements.txt` with correct (old) dependencies. +- Fix formatting issues with black in pre-commit hook -### Requirements +## Requirements python 2.7, pytorch 0.4.1, numpy, cv2, scipy. -### Usage +## Usage 1. Download the source code, the [dataset](https://drive.google.com/open?id=1X9xK5O9cx4_MvDkWAs5wVuM-mPWINaqa) and the [pretrained model](https://drive.google.com/open?id=1kaNWvfIdtbh2GIPNSWXdxqyS-d2DR1F3). 2. Run ``TrainModel.py`` to train a new model on our dataset or Run ``demo_eval.py`` to test the pretrained model on any images. 3. To change the aspect ratio of generated crops, please change the ``generate_bboxes`` function in ``croppingDataset.py`` (line 115). -### Annotation software +## Annotation software The executable annotation software can be found [here](https://github.com/lld533/Grid-Anchor-based-Image-Cropping-Pytorch). -### Other implementation +## Other implementation 1. [PyTorch 1.0 or later](https://github.com/lld533/Grid-Anchor-based-Image-Cropping-Pytorch) 2. [Matlab (conference version)](https://github.com/HuiZeng/Grid-Anchor-based-Image-Cropping) diff --git a/ShuffleNetV2.py b/ShuffleNetV2.py index 6040088..77c0ef8 100644 --- a/ShuffleNetV2.py +++ b/ShuffleNetV2.py @@ -6,11 +6,12 @@ from torch.nn import init import math + def conv_bn(inp, oup, stride): return nn.Sequential( nn.Conv2d(inp, oup, 3, stride, 1, bias=False), nn.BatchNorm2d(oup), - nn.ReLU(inplace=True) + nn.ReLU(inplace=True), ) @@ -18,17 +19,17 @@ def conv_1x1_bn(inp, oup): return nn.Sequential( nn.Conv2d(inp, oup, 1, 1, 0, bias=False), nn.BatchNorm2d(oup), - nn.ReLU(inplace=True) + nn.ReLU(inplace=True), ) + def channel_shuffle(x, groups): batchsize, num_channels, height, width = x.data.size() channels_per_group = num_channels // groups - + # reshape - x = x.view(batchsize, groups, - channels_per_group, height, width) + x = x.view(batchsize, groups, channels_per_group, height, width) x = torch.transpose(x, 1, 2).contiguous() @@ -36,7 +37,8 @@ def channel_shuffle(x, groups): x = x.view(batchsize, -1, height, width) return x - + + class InvertedResidual(nn.Module): def __init__(self, inp, oup, stride, benchmodel): super(InvertedResidual, self).__init__() @@ -44,11 +46,11 @@ def __init__(self, inp, oup, stride, benchmodel): self.stride = stride assert stride in [1, 2] - oup_inc = oup//2 - + oup_inc = oup // 2 + if self.benchmodel == 1: - #assert inp == oup_inc - self.banch2 = nn.Sequential( + # assert inp == oup_inc + self.banch2 = nn.Sequential( # pw nn.Conv2d(oup_inc, oup_inc, 1, 1, 0, bias=False), nn.BatchNorm2d(oup_inc), @@ -60,8 +62,8 @@ def __init__(self, inp, oup, stride, benchmodel): nn.Conv2d(oup_inc, oup_inc, 1, 1, 0, bias=False), nn.BatchNorm2d(oup_inc), nn.ReLU(inplace=True), - ) - else: + ) + else: self.banch1 = nn.Sequential( # dw nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False), @@ -70,8 +72,8 @@ def __init__(self, inp, oup, stride, benchmodel): nn.Conv2d(inp, oup_inc, 1, 1, 0, bias=False), nn.BatchNorm2d(oup_inc), nn.ReLU(inplace=True), - ) - + ) + self.banch2 = nn.Sequential( # pw nn.Conv2d(inp, oup_inc, 1, 1, 0, bias=False), @@ -85,34 +87,34 @@ def __init__(self, inp, oup, stride, benchmodel): nn.BatchNorm2d(oup_inc), nn.ReLU(inplace=True), ) - + @staticmethod def _concat(x, out): # concatenate along channel axis - return torch.cat((x, out), 1) + return torch.cat((x, out), 1) def forward(self, x): - if 1==self.benchmodel: - x1 = x[:, :(x.shape[1]//2), :, :] - x2 = x[:, (x.shape[1]//2):, :, :] + if 1 == self.benchmodel: + x1 = x[:, : (x.shape[1] // 2), :, :] + x2 = x[:, (x.shape[1] // 2) :, :, :] out = self._concat(x1, self.banch2(x2)) - elif 2==self.benchmodel: + elif 2 == self.benchmodel: out = self._concat(self.banch1(x), self.banch2(x)) return channel_shuffle(out, 2) class ShuffleNetV2(nn.Module): - def __init__(self, n_class=1000, input_size=224, width_mult=1.): + def __init__(self, n_class=1000, input_size=224, width_mult=1.0): super(ShuffleNetV2, self).__init__() - + assert input_size % 32 == 0 - + self.stage_repeats = [4, 8, 4] # index 0 is invalid and should never be called. # only used for indexing convenience. if width_mult == 0.5: - self.stage_out_channels = [-1, 24, 48, 96, 192, 1024] + self.stage_out_channels = [-1, 24, 48, 96, 192, 1024] elif width_mult == 1.0: self.stage_out_channels = [-1, 24, 116, 232, 464, 1024] elif width_mult == 1.5: @@ -122,36 +124,42 @@ def __init__(self, n_class=1000, input_size=224, width_mult=1.): else: raise ValueError( """{} groups is not supported for - 1x1 Grouped Convolutions""".format(num_groups)) + 1x1 Grouped Convolutions""".format( + num_groups + ) + ) # building first layer input_channel = self.stage_out_channels[1] - self.conv1 = conv_bn(3, input_channel, 2) - self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) - + self.conv1 = conv_bn(3, input_channel, 2) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.features = [] # building inverted residual blocks for idxstage in range(len(self.stage_repeats)): numrepeat = self.stage_repeats[idxstage] - output_channel = self.stage_out_channels[idxstage+2] + output_channel = self.stage_out_channels[idxstage + 2] for i in range(numrepeat): if i == 0: - #inp, oup, stride, benchmodel): - self.features.append(InvertedResidual(input_channel, output_channel, 2, 2)) + # inp, oup, stride, benchmodel): + self.features.append( + InvertedResidual(input_channel, output_channel, 2, 2) + ) else: - self.features.append(InvertedResidual(input_channel, output_channel, 1, 1)) + self.features.append( + InvertedResidual(input_channel, output_channel, 1, 1) + ) input_channel = output_channel - - + # make it nn.Sequential self.features = nn.Sequential(*self.features) # building last several layers - self.conv_last = conv_1x1_bn(input_channel, self.stage_out_channels[-1]) - self.globalpool = nn.Sequential(nn.AvgPool2d(int(input_size/32))) - - # building classifier - self.classifier = nn.Sequential(nn.Linear(self.stage_out_channels[-1], n_class)) + self.conv_last = conv_1x1_bn(input_channel, self.stage_out_channels[-1]) + self.globalpool = nn.Sequential(nn.AvgPool2d(int(input_size / 32))) + + # building classifier + self.classifier = nn.Sequential(nn.Linear(self.stage_out_channels[-1], n_class)) def forward(self, x): x = self.conv1(x) @@ -163,10 +171,12 @@ def forward(self, x): x = self.classifier(x) return x -def shufflenetv2(width_mult=1.): + +def shufflenetv2(width_mult=1.0): model = ShuffleNetV2(width_mult=width_mult) return model - + + if __name__ == "__main__": """Testing """ diff --git a/TestAccuracy.py b/TestAccuracy.py index 0395e51..10dd09f 100644 --- a/TestAccuracy.py +++ b/TestAccuracy.py @@ -11,37 +11,62 @@ from scipy.stats import spearmanr, pearsonr parser = argparse.ArgumentParser( - description='Single Shot MultiBox Detector Training With Pytorch') -parser.add_argument('--dataset_root', default='dataset/GAIC/', help='Dataset root directory path') -parser.add_argument('--image_size', default=256, type=int, help='Batch size for training') -parser.add_argument('--batch_size', default=1, type=int, help='Batch size for training') -parser.add_argument('--num_workers', default=0, type=int, help='Number of workers used in dataloading') -parser.add_argument('--cuda', default=True, help='Use CUDA to train model') -parser.add_argument('--net_path', default='weights/ablation/cropping/mobilenetv2/downsample4_multi_Aug1_Align9_Cdim8/23_0.625_0.583_0.553_0.525_0.785_0.762_0.748_0.723_0.783_0.806.pth_____', - help='Directory for saving checkpoint models') + description="Single Shot MultiBox Detector Training With Pytorch" +) +parser.add_argument( + "--dataset_root", default="dataset/GAIC/", help="Dataset root directory path" +) +parser.add_argument( + "--image_size", default=256, type=int, help="Batch size for training" +) +parser.add_argument("--batch_size", default=1, type=int, help="Batch size for training") +parser.add_argument( + "--num_workers", default=0, type=int, help="Number of workers used in dataloading" +) +parser.add_argument("--cuda", default=True, help="Use CUDA to train model") +parser.add_argument( + "--net_path", + default="weights/ablation/cropping/mobilenetv2/downsample4_multi_Aug1_Align9_Cdim8/23_0.625_0.583_0.553_0.525_0.785_0.762_0.748_0.723_0.783_0.806.pth_____", + help="Directory for saving checkpoint models", +) args = parser.parse_args() if torch.cuda.is_available(): if args.cuda: - torch.set_default_tensor_type('torch.cuda.FloatTensor') + torch.set_default_tensor_type("torch.cuda.FloatTensor") if not args.cuda: - print("WARNING: It looks like you have a CUDA device, but aren't " + - "using CUDA.\nRun with --cuda for optimal training speed.") - torch.set_default_tensor_type('torch.FloatTensor') + print( + "WARNING: It looks like you have a CUDA device, but aren't " + + "using CUDA.\nRun with --cuda for optimal training speed." + ) + torch.set_default_tensor_type("torch.FloatTensor") else: - torch.set_default_tensor_type('torch.FloatTensor') + torch.set_default_tensor_type("torch.FloatTensor") -data_loader = data.DataLoader(GAICD(image_size=args.image_size, dataset_dir=args.dataset_root, set='test'), args.batch_size, num_workers=args.num_workers, shuffle=False) +data_loader = data.DataLoader( + GAICD(image_size=args.image_size, dataset_dir=args.dataset_root, set="test"), + args.batch_size, + num_workers=args.num_workers, + shuffle=False, +) + def test(): - net = build_crop_model(scale='multi', alignsize=9, reddim=8, loadweight=True, model='mobilenetv2', downsample=4) + net = build_crop_model( + scale="multi", + alignsize=9, + reddim=8, + loadweight=True, + model="mobilenetv2", + downsample=4, + ) net.load_state_dict(torch.load(args.net_path)) if args.cuda: - net = torch.nn.DataParallel(net,device_ids=[0]) + net = torch.nn.DataParallel(net, device_ids=[0]) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False net = net.cuda() @@ -61,14 +86,22 @@ def test(): wacc4_10.append(0) for id, sample in enumerate(data_loader): - image = sample['image'] - bboxs = sample['bbox'] - MOS = sample['MOS'] + image = sample["image"] + bboxs = sample["bbox"] + MOS = sample["MOS"] roi = [] - for idx in range(0,len(bboxs['xmin'])): - roi.append((0, bboxs['xmin'][idx],bboxs['ymin'][idx],bboxs['xmax'][idx],bboxs['ymax'][idx])) + for idx in range(0, len(bboxs["xmin"])): + roi.append( + ( + 0, + bboxs["xmin"][idx], + bboxs["ymin"][idx], + bboxs["xmax"][idx], + bboxs["ymax"][idx], + ) + ) if args.cuda: image = Variable(image.cuda()) @@ -78,9 +111,9 @@ def test(): roi = Variable(torch.Tensor(roi)) t0 = time.time() - out = net(image,roi) + out = net(image, roi) t1 = time.time() - print('timer: %.4f sec.' % (t1 - t0)) + print("timer: %.4f sec." % (t1 - t0)) id_MOS = sorted(range(len(MOS)), key=lambda k: MOS[k], reverse=True) id_out = sorted(range(len(out)), key=lambda k: out[k], reverse=True) @@ -92,35 +125,37 @@ def test(): for k in range(4): temp_acc_4_5 = 0.0 temp_acc_4_10 = 0.0 - for j in range(k+1): + for j in range(k + 1): if MOS[id_out[j]] >= MOS[id_MOS[4]]: temp_acc_4_5 += 1.0 if MOS[id_out[j]] >= MOS[id_MOS[9]]: temp_acc_4_10 += 1.0 - acc4_5[k] += temp_acc_4_5 / (k+1.0) - acc4_10[k] += temp_acc_4_10 / (k+1.0) + acc4_5[k] += temp_acc_4_5 / (k + 1.0) + acc4_10[k] += temp_acc_4_10 / (k + 1.0) for k in range(4): temp_wacc_4_5 = 0.0 temp_wacc_4_10 = 0.0 - temp_rank_of_returned_crop = rank_of_returned_crop[:(k+1)] + temp_rank_of_returned_crop = rank_of_returned_crop[: (k + 1)] temp_rank_of_returned_crop.sort() - for j in range(k+1): + for j in range(k + 1): if temp_rank_of_returned_crop[j] <= 4: - temp_wacc_4_5 += 1.0 * math.exp(-0.2*(temp_rank_of_returned_crop[j]-j)) + temp_wacc_4_5 += 1.0 * math.exp( + -0.2 * (temp_rank_of_returned_crop[j] - j) + ) if temp_rank_of_returned_crop[j] <= 9: - temp_wacc_4_10 += 1.0 * math.exp(-0.1*(temp_rank_of_returned_crop[j]-j)) - wacc4_5[k] += temp_wacc_4_5 / (k+1.0) - wacc4_10[k] += temp_wacc_4_10 / (k+1.0) - + temp_wacc_4_10 += 1.0 * math.exp( + -0.1 * (temp_rank_of_returned_crop[j] - j) + ) + wacc4_5[k] += temp_wacc_4_5 / (k + 1.0) + wacc4_10[k] += temp_wacc_4_10 / (k + 1.0) MOS_arr = [] out = torch.squeeze(out).cpu().detach().numpy() for k in range(len(MOS)): MOS_arr.append(MOS[k].numpy()[0]) - srcc.append(spearmanr(MOS_arr,out)[0]) - pcc.append(pearsonr(MOS_arr,out)[0]) - + srcc.append(spearmanr(MOS_arr, out)[0]) + pcc.append(pearsonr(MOS_arr, out)[0]) for k in range(4): acc4_5[k] = acc4_5[k] / 200.0 @@ -131,10 +166,34 @@ def test(): avg_srcc = sum(srcc) / 200.0 avg_pcc = sum(pcc) / 200.0 - sys.stdout.write('[%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f]\n' % (acc4_5[0],acc4_5[1],acc4_5[2],acc4_5[3],acc4_10[0],acc4_10[1],acc4_10[2],acc4_10[3])) - sys.stdout.write('[%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f]\n' % (wacc4_5[0],wacc4_5[1],wacc4_5[2],wacc4_5[3],wacc4_10[0],wacc4_10[1],wacc4_10[2],wacc4_10[3])) - sys.stdout.write('[Avg SRCC: %.3f] [Avg PCC: %.3f]\n' % (avg_srcc,avg_pcc)) - - -if __name__ == '__main__': + sys.stdout.write( + "[%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f]\n" + % ( + acc4_5[0], + acc4_5[1], + acc4_5[2], + acc4_5[3], + acc4_10[0], + acc4_10[1], + acc4_10[2], + acc4_10[3], + ) + ) + sys.stdout.write( + "[%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f]\n" + % ( + wacc4_5[0], + wacc4_5[1], + wacc4_5[2], + wacc4_5[3], + wacc4_10[0], + wacc4_10[1], + wacc4_10[2], + wacc4_10[3], + ) + ) + sys.stdout.write("[Avg SRCC: %.3f] [Avg PCC: %.3f]\n" % (avg_srcc, avg_pcc)) + + +if __name__ == "__main__": test() diff --git a/TrainModel.py b/TrainModel.py index 13c9750..8b04a2c 100644 --- a/TrainModel.py +++ b/TrainModel.py @@ -18,24 +18,65 @@ np.random.seed(SEED) random.seed(SEED) -parser = argparse.ArgumentParser(description='Grid anchor based image cropping') -parser.add_argument('--dataset_root', default='dataset/GAIC/', help='Dataset root directory path') -parser.add_argument('--base_model', default='mobilenetv2', help='Pretrained base model') -parser.add_argument('--scale', default='multi', type=str, help='choose single or multi scale') -parser.add_argument('--downsample', default=4, type=int, help='downsample time') -parser.add_argument('--augmentation', default=1, type=int, help='choose single or multi scale') -parser.add_argument('--image_size', default=256, type=int, help='Batch size for training') -parser.add_argument('--align_size', default=9, type=int, help='Spatial size of RoIAlign and RoDAlign') -parser.add_argument('--reduced_dim', default=8, type=int, help='Spatial size of RoIAlign and RoDAlign') -parser.add_argument('--batch_size', default=1, type=int, help='Batch size for training') -parser.add_argument('--resume', default=None, type=str, help='Checkpoint state_dict file to resume training from') -parser.add_argument('--start_iter', default=0, type=int, help='Resume training at this iter') -parser.add_argument('--num_workers', default=0, type=int, help='Number of workers used in dataloading') -parser.add_argument('--lr', '--learning-rate', default=1e-4, type=float, help='initial learning rate') -parser.add_argument('--save_folder', default='weights/ablation/cropping/', help='Directory for saving checkpoint models') +parser = argparse.ArgumentParser(description="Grid anchor based image cropping") +parser.add_argument( + "--dataset_root", default="dataset/GAIC/", help="Dataset root directory path" +) +parser.add_argument("--base_model", default="mobilenetv2", help="Pretrained base model") +parser.add_argument( + "--scale", default="multi", type=str, help="choose single or multi scale" +) +parser.add_argument("--downsample", default=4, type=int, help="downsample time") +parser.add_argument( + "--augmentation", default=1, type=int, help="choose single or multi scale" +) +parser.add_argument( + "--image_size", default=256, type=int, help="Batch size for training" +) +parser.add_argument( + "--align_size", default=9, type=int, help="Spatial size of RoIAlign and RoDAlign" +) +parser.add_argument( + "--reduced_dim", default=8, type=int, help="Spatial size of RoIAlign and RoDAlign" +) +parser.add_argument("--batch_size", default=1, type=int, help="Batch size for training") +parser.add_argument( + "--resume", + default=None, + type=str, + help="Checkpoint state_dict file to resume training from", +) +parser.add_argument( + "--start_iter", default=0, type=int, help="Resume training at this iter" +) +parser.add_argument( + "--num_workers", default=0, type=int, help="Number of workers used in dataloading" +) +parser.add_argument( + "--lr", "--learning-rate", default=1e-4, type=float, help="initial learning rate" +) +parser.add_argument( + "--save_folder", + default="weights/ablation/cropping/", + help="Directory for saving checkpoint models", +) args = parser.parse_args() -args.save_folder = args.save_folder + args.base_model + '/' + 'downsample' + str(args.downsample) + '_' + args.scale + '_Aug' + str(args.augmentation) + '_Align' +str(args.align_size) + '_Cdim'+str(args.reduced_dim) +args.save_folder = ( + args.save_folder + + args.base_model + + "/" + + "downsample" + + str(args.downsample) + + "_" + + args.scale + + "_Aug" + + str(args.augmentation) + + "_Align" + + str(args.align_size) + + "_Cdim" + + str(args.reduced_dim) +) if not os.path.exists(args.save_folder): os.makedirs(args.save_folder) @@ -43,31 +84,53 @@ cuda = True if torch.cuda.is_available() else False if cuda: - torch.set_default_tensor_type('torch.cuda.FloatTensor') + torch.set_default_tensor_type("torch.cuda.FloatTensor") else: - torch.set_default_tensor_type('torch.FloatTensor') - - -data_loader_train = data.DataLoader(GAICD(image_size=args.image_size, dataset_dir=args.dataset_root, set='train', augmentation=args.augmentation), - batch_size=args.batch_size, num_workers=args.num_workers, shuffle=True, worker_init_fn=random.seed(SEED)) - -data_loader_test = data.DataLoader(GAICD(image_size=args.image_size, dataset_dir=args.dataset_root, set='test'), - batch_size=args.batch_size, num_workers=args.num_workers, shuffle=False) - -net = build_crop_model(scale=args.scale, alignsize=args.align_size, reddim=args.reduced_dim, loadweight=True, model=args.base_model, downsample=args.downsample) + torch.set_default_tensor_type("torch.FloatTensor") + + +data_loader_train = data.DataLoader( + GAICD( + image_size=args.image_size, + dataset_dir=args.dataset_root, + set="train", + augmentation=args.augmentation, + ), + batch_size=args.batch_size, + num_workers=args.num_workers, + shuffle=True, + worker_init_fn=random.seed(SEED), +) + +data_loader_test = data.DataLoader( + GAICD(image_size=args.image_size, dataset_dir=args.dataset_root, set="test"), + batch_size=args.batch_size, + num_workers=args.num_workers, + shuffle=False, +) + +net = build_crop_model( + scale=args.scale, + alignsize=args.align_size, + reddim=args.reduced_dim, + loadweight=True, + model=args.base_model, + downsample=args.downsample, +) # fix the batch normalization in mobilenet and shufflenet because batchsize = 1 net.eval() if cuda: - net = torch.nn.DataParallel(net,device_ids=[0]) + net = torch.nn.DataParallel(net, device_ids=[0]) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False - #cudnn.benchmark = True + # cudnn.benchmark = True net = net.cuda() optimizer = optim.Adam(net.parameters(), lr=args.lr) + def test(): acc4_5 = [] acc4_10 = [] @@ -84,14 +147,22 @@ def test(): wacc4_10.append(0) for id, sample in enumerate(data_loader_test): - image = sample['image'] - bboxs = sample['bbox'] - MOS = sample['MOS'] + image = sample["image"] + bboxs = sample["bbox"] + MOS = sample["MOS"] roi = [] - for idx in range(0,len(bboxs['xmin'])): - roi.append((0, bboxs['xmin'][idx],bboxs['ymin'][idx],bboxs['xmax'][idx],bboxs['ymax'][idx])) + for idx in range(0, len(bboxs["xmin"])): + roi.append( + ( + 0, + bboxs["xmin"][idx], + bboxs["ymin"][idx], + bboxs["xmax"][idx], + bboxs["ymax"][idx], + ) + ) if cuda: image = Variable(image.cuda()) @@ -100,24 +171,26 @@ def test(): image = Variable(image) roi = Variable(roi) - #t0 = time.time() - out = net(image,roi) - loss = torch.nn.SmoothL1Loss(reduction='elementwise_mean')(out.squeeze(), torch.Tensor(MOS)) + # t0 = time.time() + out = net(image, roi) + loss = torch.nn.SmoothL1Loss(reduction="elementwise_mean")( + out.squeeze(), torch.Tensor(MOS) + ) total_loss += loss.item() - avg_loss = total_loss / (id+1) + avg_loss = total_loss / (id + 1) - id_MOS = sorted(range(len(MOS)), key=lambda k: MOS[k], reverse = True) - id_out = sorted(range(len(out)), key=lambda k: out[k], reverse = True) + id_MOS = sorted(range(len(MOS)), key=lambda k: MOS[k], reverse=True) + id_out = sorted(range(len(out)), key=lambda k: out[k], reverse=True) for k in range(4): temp_acc_4_5 = 0.0 temp_acc_4_10 = 0.0 - for j in range(k+1): + for j in range(k + 1): if MOS[id_out[j]] >= MOS[id_MOS[4]]: temp_acc_4_5 += 1.0 if MOS[id_out[j]] >= MOS[id_MOS[9]]: temp_acc_4_10 += 1.0 - acc4_5[k] += temp_acc_4_5 / (k+1.0) - acc4_10[k] += temp_acc_4_10 / (k+1.0) + acc4_5[k] += temp_acc_4_5 / (k + 1.0) + acc4_10[k] += temp_acc_4_10 / (k + 1.0) rank_of_returned_crop = [] for k in range(4): @@ -126,26 +199,30 @@ def test(): for k in range(4): temp_wacc_4_5 = 0.0 temp_wacc_4_10 = 0.0 - temp_rank_of_returned_crop = rank_of_returned_crop[:(k+1)] + temp_rank_of_returned_crop = rank_of_returned_crop[: (k + 1)] temp_rank_of_returned_crop.sort() - for j in range(k+1): + for j in range(k + 1): if temp_rank_of_returned_crop[j] <= 4: - temp_wacc_4_5 += 1.0 * math.exp(-0.2*(temp_rank_of_returned_crop[j]-j)) + temp_wacc_4_5 += 1.0 * math.exp( + -0.2 * (temp_rank_of_returned_crop[j] - j) + ) if temp_rank_of_returned_crop[j] <= 9: - temp_wacc_4_10 += 1.0 * math.exp(-0.1*(temp_rank_of_returned_crop[j]-j)) - wacc4_5[k] += temp_wacc_4_5 / (k+1.0) - wacc4_10[k] += temp_wacc_4_10 / (k+1.0) + temp_wacc_4_10 += 1.0 * math.exp( + -0.1 * (temp_rank_of_returned_crop[j] - j) + ) + wacc4_5[k] += temp_wacc_4_5 / (k + 1.0) + wacc4_10[k] += temp_wacc_4_10 / (k + 1.0) MOS_arr = [] out = torch.squeeze(out).cpu().detach().numpy() for k in range(len(MOS)): MOS_arr.append(MOS[k].numpy()[0]) - srcc.append(spearmanr(MOS_arr,out)[0]) - pcc.append(pearsonr(MOS_arr,out)[0]) + srcc.append(spearmanr(MOS_arr, out)[0]) + pcc.append(pearsonr(MOS_arr, out)[0]) - #t1 = time.time() + # t1 = time.time() - #print('timer: %.4f sec.' % (t1 - t0)) + # print('timer: %.4f sec.' % (t1 - t0)) for k in range(4): acc4_5[k] = acc4_5[k] / 200.0 acc4_10[k] = acc4_10[k] / 200.0 @@ -155,7 +232,6 @@ def test(): avg_srcc = sum(srcc) / 200.0 avg_pcc = sum(pcc) / 200.0 - return acc4_5, acc4_10, avg_srcc, avg_pcc, avg_loss, wacc4_5, wacc4_10 @@ -165,18 +241,26 @@ def train(): total_loss = 0 for id, sample in enumerate(data_loader_train): - image = sample['image'] - bboxs = sample['bbox'] + image = sample["image"] + bboxs = sample["bbox"] roi = [] MOS = [] - random_ID = range(0,len(bboxs['xmin'])) + random_ID = range(0, len(bboxs["xmin"])) random.shuffle(random_ID) for idx in random_ID[:64]: - roi.append((0, bboxs['xmin'][idx],bboxs['ymin'][idx],bboxs['xmax'][idx],bboxs['ymax'][idx])) - MOS.append(sample['MOS'][idx]) + roi.append( + ( + 0, + bboxs["xmin"][idx], + bboxs["ymin"][idx], + bboxs["xmax"][idx], + bboxs["ymax"][idx], + ) + ) + MOS.append(sample["MOS"][idx]) if cuda: image = Variable(image.cuda()) @@ -188,23 +272,74 @@ def train(): # forward - out = net(image,roi) - loss = torch.nn.SmoothL1Loss(reduction='elementwise_mean')(out.squeeze(), MOS) + out = net(image, roi) + loss = torch.nn.SmoothL1Loss(reduction="elementwise_mean")( + out.squeeze(), MOS + ) total_loss += loss.item() - avg_loss = total_loss / (id+1) + avg_loss = total_loss / (id + 1) # backprop optimizer.zero_grad() loss.backward() optimizer.step() - sys.stdout.write('\r[Epoch %d/%d] [Batch %d/%d] [Train Loss: %.4f]' % (epoch, 79, id, len(data_loader_train), avg_loss)) + sys.stdout.write( + "\r[Epoch %d/%d] [Batch %d/%d] [Train Loss: %.4f]" + % (epoch, 79, id, len(data_loader_train), avg_loss) + ) acc4_5, acc4_10, avg_srcc, avg_pcc, test_avg_loss, wacc4_5, wacc4_10 = test() - sys.stdout.write('[Test Loss: %.4f] [%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f] [SRCC: %.3f] [PCC: %.3f]\n' % (test_avg_loss,acc4_5[0],acc4_5[1],acc4_5[2],acc4_5[3],acc4_10[0],acc4_10[1],acc4_10[2],acc4_10[3],avg_srcc,avg_pcc)) - sys.stdout.write('[%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f]\n' % (wacc4_5[0],wacc4_5[1],wacc4_5[2],wacc4_5[3],wacc4_10[0],wacc4_10[1],wacc4_10[2],wacc4_10[3])) - torch.save(net.module.state_dict(), args.save_folder + '/' + repr(epoch) + '_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f' % (acc4_5[0],acc4_5[1],acc4_5[2],acc4_5[3],acc4_10[0],acc4_10[1],acc4_10[2],acc4_10[3],avg_srcc,avg_pcc) + '.pth') - - -if __name__ == '__main__': + sys.stdout.write( + "[Test Loss: %.4f] [%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f] [SRCC: %.3f] [PCC: %.3f]\n" + % ( + test_avg_loss, + acc4_5[0], + acc4_5[1], + acc4_5[2], + acc4_5[3], + acc4_10[0], + acc4_10[1], + acc4_10[2], + acc4_10[3], + avg_srcc, + avg_pcc, + ) + ) + sys.stdout.write( + "[%.3f, %.3f, %.3f, %.3f] [%.3f, %.3f, %.3f, %.3f]\n" + % ( + wacc4_5[0], + wacc4_5[1], + wacc4_5[2], + wacc4_5[3], + wacc4_10[0], + wacc4_10[1], + wacc4_10[2], + wacc4_10[3], + ) + ) + torch.save( + net.module.state_dict(), + args.save_folder + + "/" + + repr(epoch) + + "_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f_%.3f" + % ( + acc4_5[0], + acc4_5[1], + acc4_5[2], + acc4_5[3], + acc4_10[0], + acc4_10[1], + acc4_10[2], + acc4_10[3], + avg_srcc, + avg_pcc, + ) + + ".pth", + ) + + +if __name__ == "__main__": train() diff --git a/augmentations.py b/augmentations.py index a330903..f3f0da3 100644 --- a/augmentations.py +++ b/augmentations.py @@ -16,10 +16,8 @@ def intersect(box_a, box_b): def jaccard_numpy(box_a, box_b): inter = intersect(box_a, box_b) - area_a = ((box_a[:, 2]-box_a[:, 0]) * - (box_a[:, 3]-box_a[:, 1])) # [A,B] - area_b = ((box_b[2]-box_b[0]) * - (box_b[3]-box_b[1])) # [A,B] + area_a = (box_a[:, 2] - box_a[:, 0]) * (box_a[:, 3] - box_a[:, 1]) # [A,B] + area_b = (box_b[2] - box_b[0]) * (box_b[3] - box_b[1]) # [A,B] union = area_a + area_b - inter return inter / union # [A,B] @@ -94,8 +92,7 @@ def __init__(self, size=300): self.size = size def __call__(self, image, boxes=None, labels=None): - image = cv2.resize(image, (self.size, - self.size)) + image = cv2.resize(image, (self.size, self.size)) return image, boxes, labels @@ -128,9 +125,7 @@ def __call__(self, image, boxes=None, labels=None): class RandomLightingNoise(object): def __init__(self): - self.perms = ((0, 1, 2), (0, 2, 1), - (1, 0, 2), (1, 2, 0), - (2, 0, 1), (2, 1, 0)) + self.perms = ((0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)) def __call__(self, image, boxes=None, labels=None): if random.randint(2): @@ -141,14 +136,14 @@ def __call__(self, image, boxes=None, labels=None): class ConvertColor(object): - def __init__(self, current='BGR', transform='HSV'): + def __init__(self, current="BGR", transform="HSV"): self.transform = transform self.current = current def __call__(self, image, boxes=None, labels=None): - if self.current == 'BGR' and self.transform == 'HSV': + if self.current == "BGR" and self.transform == "HSV": image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) - elif self.current == 'HSV' and self.transform == 'BGR': + elif self.current == "HSV" and self.transform == "BGR": image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR) else: raise NotImplementedError @@ -185,12 +180,20 @@ def __call__(self, image, boxes=None, labels=None): class ToCV2Image(object): def __call__(self, tensor, boxes=None, labels=None): - return tensor.cpu().numpy().astype(np.float32).transpose((1, 2, 0)), boxes, labels + return ( + tensor.cpu().numpy().astype(np.float32).transpose((1, 2, 0)), + boxes, + labels, + ) class ToTensor(object): def __call__(self, cvimage, boxes=None, labels=None): - return torch.from_numpy(cvimage.astype(np.float32)).permute(2, 0, 1), boxes, labels + return ( + torch.from_numpy(cvimage.astype(np.float32)).permute(2, 0, 1), + boxes, + labels, + ) class RandomSampleCrop(object): @@ -206,6 +209,7 @@ class RandomSampleCrop(object): boxes (Tensor): the adjusted bounding boxes in pt form labels (Tensor): the class labels for each bbox """ + def __init__(self): self.sample_options = ( # using entire original input image @@ -229,9 +233,9 @@ def __call__(self, image, boxes=None, labels=None): min_iou, max_iou = mode if min_iou is None: - min_iou = float('-inf') + min_iou = float("-inf") if max_iou is None: - max_iou = float('inf') + max_iou = float("inf") # max trails (50) for _ in range(50): @@ -248,7 +252,7 @@ def __call__(self, image, boxes=None, labels=None): top = random.uniform(height - h) # convert to integer rect x1,y1,x2,y2 - rect = np.array([int(left), int(top), int(left+w), int(top+h)]) + rect = np.array([int(left), int(top), int(left + w), int(top + h)]) # calculate IoU (jaccard overlap) b/t the cropped and gt boxes overlap = jaccard_numpy(boxes, rect) @@ -258,8 +262,7 @@ def __call__(self, image, boxes=None, labels=None): continue # cut the crop from the image - current_image = current_image[rect[1]:rect[3], rect[0]:rect[2], - :] + current_image = current_image[rect[1] : rect[3], rect[0] : rect[2], :] # keep overlap with gt box IF center in sampled patch centers = (boxes[:, :2] + boxes[:, 2:]) / 2.0 @@ -284,13 +287,11 @@ def __call__(self, image, boxes=None, labels=None): current_labels = labels[mask] # should we use the box left and top corner or the crop's - current_boxes[:, :2] = np.maximum(current_boxes[:, :2], - rect[:2]) + current_boxes[:, :2] = np.maximum(current_boxes[:, :2], rect[:2]) # adjust to crop (by substracting crop's left,top) current_boxes[:, :2] -= rect[:2] - current_boxes[:, 2:] = np.minimum(current_boxes[:, 2:], - rect[2:]) + current_boxes[:, 2:] = np.minimum(current_boxes[:, 2:], rect[2:]) # adjust to crop (by substracting crop's left,top) current_boxes[:, 2:] -= rect[:2] @@ -307,15 +308,16 @@ def __call__(self, image, boxes, labels): height, width, depth = image.shape ratio = random.uniform(1, 4) - left = random.uniform(0, width*ratio - width) - top = random.uniform(0, height*ratio - height) + left = random.uniform(0, width * ratio - width) + top = random.uniform(0, height * ratio - height) expand_image = np.zeros( - (int(height*ratio), int(width*ratio), depth), - dtype=image.dtype) + (int(height * ratio), int(width * ratio), depth), dtype=image.dtype + ) expand_image[:, :, :] = self.mean - expand_image[int(top):int(top + height), - int(left):int(left + width)] = image + expand_image[ + int(top) : int(top + height), int(left) : int(left + width) + ] = image image = expand_image boxes = boxes.copy() @@ -330,7 +332,7 @@ def __call__(self, image, annotations, classes): _, width, _ = image.shape if random.randint(2): image = image[:, ::-1] - for i in range (len(annotations)): + for i in range(len(annotations)): annotations[i][1] = width - annotations[i][1] annotations[i][3] = width - annotations[i][3] return image, annotations, classes @@ -366,11 +368,11 @@ class PhotometricDistort(object): def __init__(self): self.pd = [ RandomContrast(), - ConvertColor(transform='HSV'), + ConvertColor(transform="HSV"), RandomSaturation(), RandomHue(), - ConvertColor(current='HSV', transform='BGR'), - RandomContrast() + ConvertColor(current="HSV", transform="BGR"), + RandomContrast(), ] self.rand_brightness = RandomBrightness() self.rand_light_noise = RandomLightingNoise() @@ -387,11 +389,9 @@ def __call__(self, image, boxes, labels): class CropAugmentation(object): def __init__(self): - self.augment = Compose([ - ConvertFromInts(), - PhotometricDistort(), - RandomMirror() - ]) + self.augment = Compose( + [ConvertFromInts(), PhotometricDistort(), RandomMirror()] + ) def __call__(self, img, annotations): image, annotations, label = self.augment(img, annotations) diff --git a/autocrop.py b/autocrop.py new file mode 100644 index 0000000..af1bb97 --- /dev/null +++ b/autocrop.py @@ -0,0 +1,129 @@ +from croppingModel import build_crop_model +from croppingDataset import setup_test_dataset + +import os +import torch +from torch.autograd import Variable +from torch.utils.data import DataLoader +import cv2 +import argparse + +from tqdm import tqdm + +networks = { + "mobilenetv2_0.5": { + "model": "mobilenetv2", + "path": "mobilenetv2_0.5-eaa6f9ad.pth", + }, + "mobilenetv2_0.75": { + "model": "mobilenetv2", + "path": "mobilenetv2_0.75-dace9791.pth", + }, + "mobilenetv2_1.0": { + "model": "mobilenetv2", + "path": "mobilenetv2_1.0-0c6065bc.pth", + }, + "shufflenetv2_x0.5": { + "model": "shufflenetv2", + "path": "shufflenetv2_x0.5_60.646_81.696.pth.tar", + }, + "shufflenetv2_x1": { + "model": "shufflenetv2", + "path": "shufflenetv2_x1_69.402_88.374.pth.tar", + }, +} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument(dest="input_image_dir") + parser.add_argument(dest="output_image_dir") + parser.add_argument( + "--network", choices=sorted(networks), default="mobilenetv2_1.0" + ) + args = parser.parse_args() + + os.makedirs(args.output_image_dir, exist_ok=True) + + net_conf = networks[args.network] + net = build_crop_model( + scale="multi", + alignsize=9, + reddim=8, + loadweight=True, + model=net_conf["model"], + downsample=4, + ) + net.load_state_dict( + torch.load( + os.path.join("pretrained_model", net_conf["path"]), map_location="cpu" + ), + strict=False, + ) + net.eval() + + dataset = setup_test_dataset(dataset_dir=args.input_image_dir) + data_loader = DataLoader( + dataset, batch_size=1, num_workers=0, shuffle=False, pin_memory=True + ) + + for sample in tqdm(data_loader): + imgpath = sample["imgpath"] + image = sample["image"] + bboxes = sample["sourceboxes"] + resized_image = sample["resized_image"] + tbboxes = sample["tbboxes"] + + if len(tbboxes["xmin"]) == 0: + continue + + roi = [] + + for idx in range(0, len(tbboxes["xmin"])): + roi.append( + ( + 0, + tbboxes["xmin"][idx], + tbboxes["ymin"][idx], + tbboxes["xmax"][idx], + tbboxes["ymax"][idx], + ) + ) + + resized_image = Variable(resized_image) + roi = Variable(torch.Tensor(roi)) + + """ + t0 = time.time() + for r in range(0,100): + out = net(resized_image,roi) + t1 = time.time() + print('timer: %.4f sec.' % (t1 - t0)) + """ + + out = net(resized_image, roi) + + id_out = sorted(range(len(out)), key=lambda k: out[k], reverse=True) + image = image.cpu().numpy().squeeze(0) + + for i in range(4): + top1_box = bboxes[id_out[i]] + top1_box = [ + top1_box[0].numpy()[0], + top1_box[1].numpy()[0], + top1_box[2].numpy()[0], + top1_box[3].numpy()[0], + ] + top1_crop = image[ + int(top1_box[0]) : int(top1_box[2]), int(top1_box[1]) : int(top1_box[3]) + ] + imgname = os.path.basename(imgpath[0]) + filename, file_extension = os.path.splitext(imgname) + cv2.imwrite( + args.output_image_dir + "/" + filename + "_" + str(i) + file_extension, + top1_crop[:, :, (2, 1, 0)], + ) + + +if __name__ == "__main__": + main() diff --git a/croppingDataset.py b/croppingDataset.py index 8424c3b..1f94e9c 100644 --- a/croppingDataset.py +++ b/croppingDataset.py @@ -12,14 +12,13 @@ class TransformFunction(object): - - def __call__(self, sample,image_size): - image, annotations = sample['image'], sample['annotations'] + def __call__(self, sample, image_size): + image, annotations = sample["image"], sample["annotations"] scale = image_size / min(image.shape[:2]) h = round(image.shape[0] * scale / 32.0) * 32 w = round(image.shape[1] * scale / 32.0) * 32 - resized_image = cv2.resize(image,(int(w),int(h))) / 256.0 + resized_image = cv2.resize(image, (int(w), int(h))) / 256.0 rgb_mean = np.array(RGB_MEAN, dtype=np.float32) rgb_std = np.array(RGB_STD, dtype=np.float32) resized_image = resized_image.astype(np.float32) @@ -30,53 +29,76 @@ def __call__(self, sample,image_size): scale_width = float(resized_image.shape[1]) / image.shape[1] transformed_bbox = {} - transformed_bbox['xmin'] = [] - transformed_bbox['ymin'] = [] - transformed_bbox['xmax'] = [] - transformed_bbox['ymax'] = [] + transformed_bbox["xmin"] = [] + transformed_bbox["ymin"] = [] + transformed_bbox["xmax"] = [] + transformed_bbox["ymax"] = [] MOS = [] for annotation in annotations: - transformed_bbox['xmin'].append(math.floor(float(annotation[1]) * scale_width)) - transformed_bbox['ymin'].append(math.floor(float(annotation[0]) * scale_height)) - transformed_bbox['xmax'].append(math.ceil(float(annotation[3]) * scale_width)) - transformed_bbox['ymax'].append(math.ceil(float(annotation[2]) * scale_height)) + transformed_bbox["xmin"].append( + math.floor(float(annotation[1]) * scale_width) + ) + transformed_bbox["ymin"].append( + math.floor(float(annotation[0]) * scale_height) + ) + transformed_bbox["xmax"].append( + math.ceil(float(annotation[3]) * scale_width) + ) + transformed_bbox["ymax"].append( + math.ceil(float(annotation[2]) * scale_height) + ) MOS.append((float(annotation[-1]) - MOS_MEAN) / MOS_STD) resized_image = resized_image.transpose((2, 0, 1)) - return {'image': resized_image, 'bbox': transformed_bbox, 'MOS': MOS} + return {"image": resized_image, "bbox": transformed_bbox, "MOS": MOS} -class GAICD(data.Dataset): - def __init__(self, image_size=256, dataset_dir='dataset/GAIC/', set = 'train', - transform=TransformFunction(), augmentation=False): +class GAICD(data.Dataset): + def __init__( + self, + image_size=256, + dataset_dir="dataset/GAIC/", + set="train", + transform=TransformFunction(), + augmentation=False, + ): self.image_size = float(image_size) self.dataset_dir = dataset_dir self.set = set - image_lists = os.listdir(self.dataset_dir + 'images/' + set) + image_lists = os.listdir(self.dataset_dir + "images/" + set) self._imgpath = list() self._annopath = list() for image in image_lists: - self._imgpath.append(os.path.join(self.dataset_dir, 'images', set, image)) - self._annopath.append(os.path.join(self.dataset_dir, 'annotations', set, image[:-3]+"txt")) + self._imgpath.append(os.path.join(self.dataset_dir, "images", set, image)) + self._annopath.append( + os.path.join(self.dataset_dir, "annotations", set, image[:-3] + "txt") + ) self.transform = transform if augmentation: self.augmentation = CropAugmentation() else: self.augmentation = None - def __getitem__(self, idx): image = cv2.imread(self._imgpath[idx]) - with open(self._annopath[idx],'r') as fid: + with open(self._annopath[idx], "r") as fid: annotations_txt = fid.readlines() annotations = list() for annotation in annotations_txt: annotation_split = annotation.split() if float(annotation_split[4]) != -2: - annotations.append([float(annotation_split[0]),float(annotation_split[1]),float(annotation_split[2]),float(annotation_split[3]),float(annotation_split[4])]) + annotations.append( + [ + float(annotation_split[0]), + float(annotation_split[1]), + float(annotation_split[2]), + float(annotation_split[3]), + float(annotation_split[4]), + ] + ) if self.augmentation: image, annotations = self.augmentation(image, annotations) @@ -84,10 +106,10 @@ def __getitem__(self, idx): # to rgb image = image[:, :, (2, 1, 0)] - sample = {'image': image, 'annotations': annotations} + sample = {"image": image, "annotations": annotations} if self.transform: - sample = self.transform(sample,self.image_size) + sample = self.transform(sample, self.image_size) return sample @@ -96,13 +118,12 @@ def __len__(self): class TransformFunctionTest(object): - def __call__(self, image, image_size): scale = image_size / min(image.shape[:2]) h = round(image.shape[0] * scale / 32.0) * 32 w = round(image.shape[1] * scale / 32.0) * 32 - resized_image = cv2.resize(image,(int(w),int(h))) / 256.0 + resized_image = cv2.resize(image, (int(w), int(h))) / 256.0 rgb_mean = np.array(RGB_MEAN, dtype=np.float32) rgb_std = np.array(RGB_STD, dtype=np.float32) resized_image = resized_image.astype(np.float32) @@ -115,21 +136,28 @@ def __call__(self, image, image_size): bboxes = generate_bboxes(resized_image) transformed_bbox = {} - transformed_bbox['xmin'] = [] - transformed_bbox['ymin'] = [] - transformed_bbox['xmax'] = [] - transformed_bbox['ymax'] = [] + transformed_bbox["xmin"] = [] + transformed_bbox["ymin"] = [] + transformed_bbox["xmax"] = [] + transformed_bbox["ymax"] = [] source_bboxes = list() for bbox in bboxes: - source_bboxes.append([round(bbox[0] * scale_height),round(bbox[1] * scale_width),round(bbox[2] * scale_height),round(bbox[3] * scale_width)]) - transformed_bbox['xmin'].append(bbox[1]) - transformed_bbox['ymin'].append(bbox[0]) - transformed_bbox['xmax'].append(bbox[3]) - transformed_bbox['ymax'].append(bbox[2]) + source_bboxes.append( + [ + round(bbox[0] * scale_height), + round(bbox[1] * scale_width), + round(bbox[2] * scale_height), + round(bbox[3] * scale_width), + ] + ) + transformed_bbox["xmin"].append(bbox[1]) + transformed_bbox["ymin"].append(bbox[0]) + transformed_bbox["xmax"].append(bbox[3]) + transformed_bbox["ymax"].append(bbox[2]) resized_image = resized_image.transpose((2, 0, 1)) - return resized_image,transformed_bbox,source_bboxes + return resized_image, transformed_bbox, source_bboxes def generate_bboxes(image): @@ -140,15 +168,27 @@ def generate_bboxes(image): step_h = h / bins step_w = w / bins annotations = list() - for x1 in range(0,4): - for y1 in range(0,4): - for x2 in range(8,12): - for y2 in range(8,12): - if (x2-x1)*(y2-y1)>0.4999*bins*bins and (y2-y1)*step_w/(x2-x1)/step_h>0.5 and (y2-y1)*step_w/(x2-x1)/step_h<2.0: - annotations.append([float(step_h*(0.5+x1)),float(step_w*(0.5+y1)),float(step_h*(0.5+x2)),float(step_w*(0.5+y2))]) + for x1 in range(0, 4): + for y1 in range(0, 4): + for x2 in range(8, 12): + for y2 in range(8, 12): + if ( + (x2 - x1) * (y2 - y1) > 0.4999 * bins * bins + and (y2 - y1) * step_w / (x2 - x1) / step_h > 0.5 + and (y2 - y1) * step_w / (x2 - x1) / step_h < 2.0 + ): + annotations.append( + [ + float(step_h * (0.5 + x1)), + float(step_w * (0.5 + y1)), + float(step_h * (0.5 + x2)), + float(step_w * (0.5 + y2)), + ] + ) return annotations + def generate_bboxes_16_9(image): h = image.shape[0] @@ -156,15 +196,23 @@ def generate_bboxes_16_9(image): h_step = 9 w_step = 16 annotations = list() - for i in range(14,30): - out_h = h_step*i - out_w = w_step*i - if out_h < h and out_w < w and out_h*out_w>0.4*h*w: - for w_start in range(0,w-out_w,w_step): - for h_start in range(0,h-out_h,h_step): - annotations.append([float(h_start),float(w_start),float(h_start+out_h-1),float(w_start+out_w-1)]) + for i in range(14, 30): + out_h = h_step * i + out_w = w_step * i + if out_h < h and out_w < w and out_h * out_w > 0.4 * h * w: + for w_start in range(0, w - out_w, w_step): + for h_start in range(0, h - out_h, h_step): + annotations.append( + [ + float(h_start), + float(w_start), + float(h_start + out_h - 1), + float(w_start + out_w - 1), + ] + ) return annotations + def generate_bboxes_4_3(image): h = image.shape[0] @@ -172,15 +220,23 @@ def generate_bboxes_4_3(image): h_step = 12 w_step = 16 annotations = list() - for i in range(14,30): - out_h = h_step*i - out_w = w_step*i - if out_h < h and out_w < w and out_h*out_w>0.4*h*w: - for w_start in range(0,w-out_w,w_step): - for h_start in range(0,h-out_h,h_step): - annotations.append([float(h_start),float(w_start),float(h_start+out_h-1),float(w_start+out_w-1)]) + for i in range(14, 30): + out_h = h_step * i + out_w = w_step * i + if out_h < h and out_w < w and out_h * out_w > 0.4 * h * w: + for w_start in range(0, w - out_w, w_step): + for h_start in range(0, h - out_h, h_step): + annotations.append( + [ + float(h_start), + float(w_start), + float(h_start + out_h - 1), + float(w_start + out_w - 1), + ] + ) return annotations + def generate_bboxes_1_1(image): h = image.shape[0] @@ -188,28 +244,39 @@ def generate_bboxes_1_1(image): h_step = 12 w_step = 12 annotations = list() - for i in range(14,30): - out_h = h_step*i - out_w = w_step*i - if out_h < h and out_w < w and out_h*out_w>0.4*h*w: - for w_start in range(0,w-out_w,w_step): - for h_start in range(0,h-out_h,h_step): - annotations.append([float(h_start),float(w_start),float(h_start+out_h-1),float(w_start+out_w-1)]) + for i in range(14, 30): + out_h = h_step * i + out_w = w_step * i + if out_h < h and out_w < w and out_h * out_w > 0.4 * h * w: + for w_start in range(0, w - out_w, w_step): + for h_start in range(0, h - out_h, h_step): + annotations.append( + [ + float(h_start), + float(w_start), + float(h_start + out_h - 1), + float(w_start + out_w - 1), + ] + ) return annotations -class setup_test_dataset(data.Dataset): - def __init__(self, image_size=256.0,dataset_dir='testsetDir', transform=TransformFunctionTest()): +class setup_test_dataset(data.Dataset): + def __init__( + self, + image_size=256.0, + dataset_dir="testsetDir", + transform=TransformFunctionTest(), + ): self.image_size = float(image_size) self.dataset_dir = dataset_dir - image_lists = os.listdir(self.dataset_dir) + image_lists = sorted(os.listdir(self.dataset_dir)) self._imgpath = list() self._annopath = list() for image in image_lists: - self._imgpath.append(os.path.join(self.dataset_dir, image)) + self._imgpath.append(os.path.join(self.dataset_dir, image)) self.transform = transform - def __getitem__(self, idx): image = cv2.imread(self._imgpath[idx]) @@ -217,12 +284,19 @@ def __getitem__(self, idx): image = image[:, :, (2, 1, 0)] if self.transform: - resized_image,transformed_bbox,source_bboxes = self.transform(image,self.image_size) - - sample = {'imgpath': self._imgpath[idx], 'image': image, 'resized_image': resized_image, 'tbboxes':transformed_bbox , 'sourceboxes': source_bboxes} + resized_image, transformed_bbox, source_bboxes = self.transform( + image, self.image_size + ) + + sample = { + "imgpath": self._imgpath[idx], + "image": image, + "resized_image": resized_image, + "tbboxes": transformed_bbox, + "sourceboxes": source_bboxes, + } return sample def __len__(self): return len(self._imgpath) - diff --git a/croppingModel.py b/croppingModel.py index 5352232..9d9b1f4 100644 --- a/croppingModel.py +++ b/croppingModel.py @@ -10,7 +10,6 @@ class vgg_base(nn.Module): - def __init__(self, loadweights=True, downsample=4): super(vgg_base, self).__init__() @@ -25,30 +24,37 @@ def __init__(self, loadweights=True, downsample=4): self.feature4 = nn.Sequential(vgg.features[23:30]) self.feature5 = nn.Sequential(vgg.features[30:]) - #flops, params = profile(self.feature, input_size=(1, 3, 256,256)) + # flops, params = profile(self.feature, input_size=(1, 3, 256,256)) def forward(self, x): - #return self.feature(x) + # return self.feature(x) f3 = self.feature3(x) f4 = self.feature4(f3) f5 = self.feature5(f4) return f3, f4, f5 -class resnet50_base(nn.Module): +class resnet50_base(nn.Module): def __init__(self, loadweights=True, downsample=4): super(resnet50_base, self).__init__() resnet50 = models.resnet50(pretrained=True) - self.feature3 = nn.Sequential(resnet50.conv1,resnet50.bn1,resnet50.relu,resnet50.maxpool,resnet50.layer1,resnet50.layer2) + self.feature3 = nn.Sequential( + resnet50.conv1, + resnet50.bn1, + resnet50.relu, + resnet50.maxpool, + resnet50.layer1, + resnet50.layer2, + ) self.feature4 = nn.Sequential(resnet50.layer3) self.feature5 = nn.Sequential(resnet50.layer4) - #flops, params = profile(self.feature, input_size=(1, 3, 256,256)) + # flops, params = profile(self.feature, input_size=(1, 3, 256,256)) def forward(self, x): - #return self.feature(x) + # return self.feature(x) f3 = self.feature3(x) f4 = self.feature4(f3) f5 = self.feature5(f4) @@ -56,28 +62,32 @@ def forward(self, x): class mobilenetv2_base(nn.Module): - - def __init__(self, loadweights=True, downsample=4, model_path='pretrained_model/mobilenetv2_1.0-0c6065bc.pth'): + def __init__( + self, + loadweights=True, + downsample=4, + model_path="pretrained_model/mobilenetv2_1.0-0c6065bc.pth", + ): super(mobilenetv2_base, self).__init__() model = MobileNetV2(width_mult=1.0) if loadweights: - model.load_state_dict(torch.load(model_path)) + model.load_state_dict(torch.load(model_path, map_location="cpu")) - #if downsample == 4: + # if downsample == 4: # self.feature = nn.Sequential(model.features[:14]) - #elif downsample == 5: + # elif downsample == 5: # self.feature = nn.Sequential(model.features) self.feature3 = nn.Sequential(model.features[:7]) self.feature4 = nn.Sequential(model.features[7:14]) self.feature5 = nn.Sequential(model.features[14:]) - #flops, params = profile(self.feature, input_size=(1, 3, 256,256)) + # flops, params = profile(self.feature, input_size=(1, 3, 256,256)) def forward(self, x): - #return self.feature(x) + # return self.feature(x) f3 = self.feature3(x) f4 = self.feature4(f3) f5 = self.feature5(f4) @@ -85,8 +95,12 @@ def forward(self, x): class shufflenetv2_base(nn.Module): - - def __init__(self, loadweights=True, downsample=4, model_path='pretrained_model/shufflenetv2_x1_69.402_88.374.pth.tar'): + def __init__( + self, + loadweights=True, + downsample=4, + model_path="pretrained_model/shufflenetv2_x1_69.402_88.374.pth.tar", + ): super(shufflenetv2_base, self).__init__() model = shufflenetv2(width_mult=1.0) @@ -98,71 +112,74 @@ def __init__(self, loadweights=True, downsample=4, model_path='pretrained_model/ self.feature4 = nn.Sequential(model.features[4:12]) self.feature5 = nn.Sequential(model.features[12:]) - #if downsample == 4: + # if downsample == 4: # self.feature = nn.Sequential(model.conv1, model.maxpool, model.features[:12]) - #elif downsample == 5: + # elif downsample == 5: # self.feature = nn.Sequential(model.conv1, model.maxpool, model.features) - #flops, params = profile(self.feature, input_size=(1, 3, 256,256)) + # flops, params = profile(self.feature, input_size=(1, 3, 256,256)) def forward(self, x): - #return self.feature(x) + # return self.feature(x) f3 = self.feature3(x) f4 = self.feature4(f3) f5 = self.feature5(f4) return f3, f4, f5 -def fc_layers(reddim = 32, alignsize = 8): - conv1 = nn.Sequential(nn.Conv2d(reddim, 768, kernel_size=alignsize, padding=0),nn.ReLU(inplace=True)) - #conv1 = nn.Sequential(nn.Conv2d(reddim, 768, kernel_size=3, padding=1, stride=2),nn.ReLU(inplace=True), +def fc_layers(reddim=32, alignsize=8): + conv1 = nn.Sequential( + nn.Conv2d(reddim, 768, kernel_size=alignsize, padding=0), nn.ReLU(inplace=True) + ) + # conv1 = nn.Sequential(nn.Conv2d(reddim, 768, kernel_size=3, padding=1, stride=2),nn.ReLU(inplace=True), # nn.Conv2d(768, reddim, kernel_size=1, padding=0),nn.ReLU(inplace=True), # nn.Conv2d(reddim, 768, kernel_size=3, padding=1,stride=2),nn.ReLU(inplace=True), # nn.Conv2d(768, reddim, kernel_size=1, padding=0),nn.ReLU(inplace=True), # nn.Conv2d(reddim, 768, kernel_size=3, padding=0,stride=1),nn.ReLU(inplace=True)) - #conv1 = nn.Sequential(nn.Conv2d(reddim, 768, kernel_size=5, padding=2, stride=2),nn.ReLU(inplace=True), + # conv1 = nn.Sequential(nn.Conv2d(reddim, 768, kernel_size=5, padding=2, stride=2),nn.ReLU(inplace=True), # nn.Conv2d(768, reddim, kernel_size=1, padding=0),nn.ReLU(inplace=True), # nn.Conv2d(reddim, 768, kernel_size=5, padding=0,stride=1),nn.ReLU(inplace=True)) - conv2 = nn.Sequential(nn.Conv2d(768, 128, kernel_size=1),nn.ReLU(inplace=True)) - #dropout = nn.Dropout(p=0.5) + conv2 = nn.Sequential(nn.Conv2d(768, 128, kernel_size=1), nn.ReLU(inplace=True)) + dropout = nn.Dropout(p=0.5) conv3 = nn.Conv2d(128, 1, kernel_size=1) layers = nn.Sequential(conv1, conv2, dropout, conv3) return layers class crop_model_single_scale(nn.Module): - - def __init__(self, alignsize = 8, reddim = 8, loadweight = True, model = None, downsample=4): + def __init__( + self, alignsize=8, reddim=8, loadweight=True, model=None, downsample=4 + ): super(crop_model_single_scale, self).__init__() - if model == 'shufflenetv2': - self.Feat_ext = shufflenetv2_base(loadweight,downsample) + if model == "shufflenetv2": + self.Feat_ext = shufflenetv2_base(loadweight, downsample) if downsample == 4: self.DimRed = nn.Conv2d(232, reddim, kernel_size=1, padding=0) else: self.DimRed = nn.Conv2d(464, reddim, kernel_size=1, padding=0) - elif model == 'mobilenetv2': - self.Feat_ext = mobilenetv2_base(loadweight,downsample) + elif model == "mobilenetv2": + self.Feat_ext = mobilenetv2_base(loadweight, downsample) if downsample == 4: self.DimRed = nn.Conv2d(96, reddim, kernel_size=1, padding=0) else: self.DimRed = nn.Conv2d(320, reddim, kernel_size=1, padding=0) - elif model == 'vgg16': - self.Feat_ext = vgg_base(loadweight,downsample) + elif model == "vgg16": + self.Feat_ext = vgg_base(loadweight, downsample) self.DimRed = nn.Conv2d(512, reddim, kernel_size=1, padding=0) - elif model == 'resnet50': - self.Feat_ext = resnet50_base(loadweight,downsample) + elif model == "resnet50": + self.Feat_ext = resnet50_base(loadweight, downsample) self.DimRed = nn.Conv2d(1024, reddim, kernel_size=1, padding=0) - self.RoIAlign = RoIAlignAvg(alignsize, alignsize, 1.0/2**downsample) - self.RoDAlign = RoDAlignAvg(alignsize, alignsize, 1.0/2**downsample) - self.FC_layers = fc_layers(reddim*2, alignsize) + self.RoIAlign = RoIAlignAvg(alignsize, alignsize, 1.0 / 2 ** downsample) + self.RoDAlign = RoDAlignAvg(alignsize, alignsize, 1.0 / 2 ** downsample) + self.FC_layers = fc_layers(reddim * 2, alignsize) - #flops, params = profile(self.FC_layers, input_size=(1,reddim*2,9,9)) + # flops, params = profile(self.FC_layers, input_size=(1,reddim*2,9,9)) def forward(self, im_data, boxes): - f3,base_feat,f5 = self.Feat_ext(im_data) + f3, base_feat, f5 = self.Feat_ext(im_data) red_feat = self.DimRed(base_feat) RoI_feat = self.RoIAlign(red_feat, boxes) RoD_feat = self.RoDAlign(red_feat, boxes) @@ -171,37 +188,38 @@ def forward(self, im_data, boxes): return prediction def _init_weights(self): - print('Initializing weights...') + print("Initializing weights...") self.DimRed.apply(weights_init) self.FC_layers.apply(weights_init) class crop_model_multi_scale_individual(nn.Module): - - def __init__(self, alignsize = 8, reddim = 32, loadweight = True, model = None, downsample = 4): + def __init__( + self, alignsize=8, reddim=32, loadweight=True, model=None, downsample=4 + ): super(crop_model_multi_scale_individual, self).__init__() - if model == 'shufflenetv2': - self.Feat_ext1 = shufflenetv2_base(loadweight,downsample) - self.Feat_ext2 = shufflenetv2_base(loadweight,downsample) - self.Feat_ext3 = shufflenetv2_base(loadweight,downsample) + if model == "shufflenetv2": + self.Feat_ext1 = shufflenetv2_base(loadweight, downsample) + self.Feat_ext2 = shufflenetv2_base(loadweight, downsample) + self.Feat_ext3 = shufflenetv2_base(loadweight, downsample) self.DimRed = nn.Conv2d(232, reddim, kernel_size=1, padding=0) - elif model == 'mobilenetv2': - self.Feat_ext1 = mobilenetv2_base(loadweight,downsample) - self.Feat_ext2 = mobilenetv2_base(loadweight,downsample) - self.Feat_ext3 = mobilenetv2_base(loadweight,downsample) + elif model == "mobilenetv2": + self.Feat_ext1 = mobilenetv2_base(loadweight, downsample) + self.Feat_ext2 = mobilenetv2_base(loadweight, downsample) + self.Feat_ext3 = mobilenetv2_base(loadweight, downsample) self.DimRed = nn.Conv2d(96, reddim, kernel_size=1, padding=0) - elif model == 'vgg16': - self.Feat_ext1 = vgg_base(loadweight,downsample) - self.Feat_ext2 = vgg_base(loadweight,downsample) - self.Feat_ext3 = vgg_base(loadweight,downsample) + elif model == "vgg16": + self.Feat_ext1 = vgg_base(loadweight, downsample) + self.Feat_ext2 = vgg_base(loadweight, downsample) + self.Feat_ext3 = vgg_base(loadweight, downsample) self.DimRed = nn.Conv2d(512, reddim, kernel_size=1, padding=0) - self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0/2.0) + self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0 / 2.0) self.upsample2 = nn.UpsamplingBilinear2d(scale_factor=2.0) - self.RoIAlign = RoIAlignAvg(alignsize, alignsize, 1.0/2**downsample) - self.RoDAlign = RoDAlignAvg(alignsize, alignsize, 1.0/2**downsample) - self.FC_layers = fc_layers(reddim*2, alignsize) + self.RoIAlign = RoIAlignAvg(alignsize, alignsize, 1.0 / 2 ** downsample) + self.RoDAlign = RoDAlignAvg(alignsize, alignsize, 1.0 / 2 ** downsample) + self.FC_layers = fc_layers(reddim * 2, alignsize) def forward(self, im_data, boxes): @@ -215,8 +233,8 @@ def forward(self, im_data, boxes): down_feat = self.Feat_ext3(down_im) down_feat = self.upsample2(down_feat) - #cat_feat = torch.cat((base_feat,up_feat,down_feat),1) - cat_feat = 0.5*base_feat + 0.35*up_feat + 0.15*down_feat + # cat_feat = torch.cat((base_feat,up_feat,down_feat),1) + cat_feat = 0.5 * base_feat + 0.35 * up_feat + 0.15 * down_feat red_feat = self.DimRed(cat_feat) RoI_feat = self.RoIAlign(red_feat, boxes) RoD_feat = self.RoDAlign(red_feat, boxes) @@ -225,52 +243,53 @@ def forward(self, im_data, boxes): return prediction def _init_weights(self): - print('Initializing weights...') + print("Initializing weights...") self.DimRed.apply(weights_init) self.FC_layers.apply(weights_init) -class crop_model_multi_scale_shared(nn.Module): - def __init__(self, alignsize = 8, reddim = 32, loadweight = True, model = None, downsample = 4): +class crop_model_multi_scale_shared(nn.Module): + def __init__( + self, alignsize=8, reddim=32, loadweight=True, model=None, downsample=4 + ): super(crop_model_multi_scale_shared, self).__init__() - if model == 'shufflenetv2': - self.Feat_ext = shufflenetv2_base(loadweight,downsample) + if model == "shufflenetv2": + self.Feat_ext = shufflenetv2_base(loadweight, downsample) self.DimRed = nn.Conv2d(812, reddim, kernel_size=1, padding=0) - elif model == 'mobilenetv2': - self.Feat_ext = mobilenetv2_base(loadweight,downsample) + elif model == "mobilenetv2": + self.Feat_ext = mobilenetv2_base(loadweight, downsample) self.DimRed = nn.Conv2d(448, reddim, kernel_size=1, padding=0) - elif model == 'vgg16': - self.Feat_ext = vgg_base(loadweight,downsample) + elif model == "vgg16": + self.Feat_ext = vgg_base(loadweight, downsample) self.DimRed = nn.Conv2d(1536, reddim, kernel_size=1, padding=0) - elif model == 'resnet50': - self.Feat_ext = resnet50_base(loadweight,downsample) + elif model == "resnet50": + self.Feat_ext = resnet50_base(loadweight, downsample) self.DimRed = nn.Conv2d(3584, reddim, kernel_size=1, padding=0) - self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0/2.0) + self.downsample2 = nn.UpsamplingBilinear2d(scale_factor=1.0 / 2.0) self.upsample2 = nn.UpsamplingBilinear2d(scale_factor=2.0) - self.RoIAlign = RoIAlignAvg(alignsize, alignsize, 1.0/2**downsample) - self.RoDAlign = RoDAlignAvg(alignsize, alignsize, 1.0/2**downsample) - self.FC_layers = fc_layers(reddim*2, alignsize) - + self.RoIAlign = RoIAlignAvg(alignsize, alignsize, 1.0 / 2 ** downsample) + self.RoDAlign = RoDAlignAvg(alignsize, alignsize, 1.0 / 2 ** downsample) + self.FC_layers = fc_layers(reddim * 2, alignsize) def forward(self, im_data, boxes): - #base_feat = self.Feat_ext(im_data) + # base_feat = self.Feat_ext(im_data) - #up_im = self.upsample2(im_data) - #up_feat = self.Feat_ext(up_im) - #up_feat = self.downsample2(up_feat) + # up_im = self.upsample2(im_data) + # up_feat = self.Feat_ext(up_im) + # up_feat = self.downsample2(up_feat) - #down_im = self.downsample2(im_data) - #down_feat = self.Feat_ext(down_im) - #down_feat = self.upsample2(down_feat) + # down_im = self.downsample2(im_data) + # down_feat = self.Feat_ext(down_im) + # down_feat = self.upsample2(down_feat) - f3,f4,f5 = self.Feat_ext(im_data) - cat_feat = torch.cat((self.downsample2(f3),f4,0.5*self.upsample2(f5)),1) + f3, f4, f5 = self.Feat_ext(im_data) + cat_feat = torch.cat((self.downsample2(f3), f4, 0.5 * self.upsample2(f5)), 1) - #cat_feat = torch.cat((base_feat,up_feat,down_feat),1) - #cat_feat = base_feat + 0.35*up_feat + 0.15*down_feat + # cat_feat = torch.cat((base_feat,up_feat,down_feat),1) + # cat_feat = base_feat + 0.35*up_feat + 0.15*down_feat red_feat = self.DimRed(cat_feat) RoI_feat = self.RoIAlign(red_feat, boxes) RoD_feat = self.RoDAlign(red_feat, boxes) @@ -279,10 +298,11 @@ def forward(self, im_data, boxes): return prediction def _init_weights(self): - print('Initializing weights...') + print("Initializing weights...") self.DimRed.apply(weights_init) self.FC_layers.apply(weights_init) + def xavier(param): init.xavier_uniform_(param) @@ -293,12 +313,13 @@ def weights_init(m): m.bias.data.zero_() -def build_crop_model(scale='single', alignsize=8, reddim=32, loadweight=True, model=None, downsample=4): +def build_crop_model( + scale="single", alignsize=8, reddim=32, loadweight=True, model=None, downsample=4 +): - if scale=='single': + if scale == "single": return crop_model_single_scale(alignsize, reddim, loadweight, model, downsample) - elif scale=='multi': - return crop_model_multi_scale_shared(alignsize, reddim, loadweight, model, downsample) - - - + elif scale == "multi": + return crop_model_multi_scale_shared( + alignsize, reddim, loadweight, model, downsample + ) diff --git a/demo_eval.py b/demo_eval.py index 2c19ac2..045ed0e 100644 --- a/demo_eval.py +++ b/demo_eval.py @@ -15,19 +15,30 @@ def str2bool(v): parser = argparse.ArgumentParser( - description='Grid anchor based image cropping With Pytorch') -parser.add_argument('--input_dir', default='dataset/GAIC/images/test', - help='root directory path of testing images') -parser.add_argument('--output_dir', default='dataset/test_result', - help='root directory path of testing images') -parser.add_argument('--batch_size', default=1, type=int, - help='Batch size for training') -parser.add_argument('--num_workers', default=0, type=int, - help='Number of workers used in dataloading') -parser.add_argument('--cuda', default=True, type=str2bool, - help='Use CUDA to train model') -parser.add_argument('--net_path', default='pretrained_model/mobilenet_0.625_0.583_0.553_0.525_0.785_0.762_0.748_0.723_0.783_0.806.pth', - help='Directory for saving checkpoint models') + description="Grid anchor based image cropping With Pytorch" +) +parser.add_argument( + "--input_dir", + default="dataset/GAIC/images/test", + help="root directory path of testing images", +) +parser.add_argument( + "--output_dir", + default="dataset/test_result", + help="root directory path of testing images", +) +parser.add_argument("--batch_size", default=1, type=int, help="Batch size for training") +parser.add_argument( + "--num_workers", default=0, type=int, help="Number of workers used in dataloading" +) +parser.add_argument( + "--cuda", default=False, type=str2bool, help="Use CUDA to train model" +) +parser.add_argument( + "--net_path", + default="pretrained_model/mobilenet_0.625_0.583_0.553_0.525_0.785_0.762_0.748_0.723_0.783_0.806.pth", + help="Directory for saving checkpoint models", +) args = parser.parse_args() if not os.path.exists(args.output_dir): @@ -35,46 +46,68 @@ def str2bool(v): if torch.cuda.is_available(): if args.cuda: - torch.set_default_tensor_type('torch.cuda.FloatTensor') + torch.set_default_tensor_type("torch.cuda.FloatTensor") if not args.cuda: - print("WARNING: It looks like you have a CUDA device, but aren't " + - "using CUDA.\nRun with --cuda for optimal training speed.") - torch.set_default_tensor_type('torch.FloatTensor') + print( + "WARNING: It looks like you have a CUDA device, but aren't " + + "using CUDA.\nRun with --cuda for optimal training speed." + ) + torch.set_default_tensor_type("torch.FloatTensor") else: - torch.set_default_tensor_type('torch.FloatTensor') + torch.set_default_tensor_type("torch.FloatTensor") -dataset = setup_test_dataset(dataset_dir = args.input_dir) +dataset = setup_test_dataset(dataset_dir=args.input_dir) def test(): - net = build_crop_model(scale='multi', alignsize=9, reddim=8, loadweight=True, model='mobilenetv2',downsample=4) - net.load_state_dict(torch.load(args.net_path)) + net = build_crop_model( + scale="multi", + alignsize=9, + reddim=8, + loadweight=True, + model="mobilenetv2", + downsample=4, + ) + net.load_state_dict(torch.load(args.net_path, map_location="cpu"), strict=False) net.eval() if args.cuda: - net = torch.nn.DataParallel(net,device_ids=[0]) + net = torch.nn.DataParallel(net, device_ids=[0]) cudnn.benchmark = True net = net.cuda() - - data_loader = data.DataLoader(dataset, args.batch_size, num_workers=args.num_workers,shuffle=False,pin_memory=True) + data_loader = data.DataLoader( + dataset, + args.batch_size, + num_workers=args.num_workers, + shuffle=False, + pin_memory=True, + ) for id, sample in enumerate(data_loader): - imgpath = sample['imgpath'] - image = sample['image'] - bboxes = sample['sourceboxes'] - resized_image = sample['resized_image'] - tbboxes = sample['tbboxes'] + imgpath = sample["imgpath"] + image = sample["image"] + bboxes = sample["sourceboxes"] + resized_image = sample["resized_image"] + tbboxes = sample["tbboxes"] - if len(tbboxes['xmin'])==0: + if len(tbboxes["xmin"]) == 0: continue roi = [] - for idx in range(0,len(tbboxes['xmin'])): - roi.append((0, tbboxes['xmin'][idx],tbboxes['ymin'][idx],tbboxes['xmax'][idx],tbboxes['ymax'][idx])) + for idx in range(0, len(tbboxes["xmin"])): + roi.append( + ( + 0, + tbboxes["xmin"][idx], + tbboxes["ymin"][idx], + tbboxes["xmax"][idx], + tbboxes["ymax"][idx], + ) + ) if args.cuda: resized_image = Variable(resized_image.cuda()) @@ -83,25 +116,34 @@ def test(): resized_image = Variable(resized_image) roi = Variable(torch.Tensor(roi)) - t0 = time.time() - for r in range(0,100): - out = net(resized_image,roi) + for r in range(0, 100): + out = net(resized_image, roi) t1 = time.time() - print('timer: %.4f sec.' % (t1 - t0)) + print("timer: %.4f sec." % (t1 - t0)) - out = net(resized_image,roi) + out = net(resized_image, roi) - id_out = sorted(range(len(out)), key=lambda k: out[k], reverse = True) + id_out = sorted(range(len(out)), key=lambda k: out[k], reverse=True) image = image.cpu().numpy().squeeze(0) for i in range(4): top1_box = bboxes[id_out[i]] - top1_box = [top1_box[0].numpy()[0],top1_box[1].numpy()[0],top1_box[2].numpy()[0],top1_box[3].numpy()[0]] - top1_crop = image[int(top1_box[0]):int(top1_box[2]),int(top1_box[1]):int(top1_box[3])] - imgname = imgpath[0].split('/')[-1] - cv2.imwrite(args.output_dir + '/' + imgname[:-4] + '_' +str(i) + imgname[-4:],top1_crop[:,:,(2, 1, 0)]) - - -if __name__ == '__main__': + top1_box = [ + top1_box[0].numpy()[0], + top1_box[1].numpy()[0], + top1_box[2].numpy()[0], + top1_box[3].numpy()[0], + ] + top1_crop = image[ + int(top1_box[0]) : int(top1_box[2]), int(top1_box[1]) : int(top1_box[3]) + ] + imgname = imgpath[0].split("/")[-1] + cv2.imwrite( + args.output_dir + "/" + imgname[:-4] + "_" + str(i) + imgname[-4:], + top1_crop[:, :, (2, 1, 0)], + ) + + +if __name__ == "__main__": test() diff --git a/mobilenetv2.py b/mobilenetv2.py index 5fee6b1..d6990f8 100644 --- a/mobilenetv2.py +++ b/mobilenetv2.py @@ -9,7 +9,7 @@ import torch.nn as nn import math -__all__ = ['mobilenetv2'] +__all__ = ["mobilenetv2"] def _make_divisible(v, divisor, min_value=None): @@ -36,7 +36,7 @@ def conv_3x3_bn(inp, oup, stride): return nn.Sequential( nn.Conv2d(inp, oup, 3, stride, 1, bias=False), nn.BatchNorm2d(oup), - nn.ReLU6(inplace=True) + nn.ReLU6(inplace=True), ) @@ -44,7 +44,7 @@ def conv_1x1_bn(inp, oup): return nn.Sequential( nn.Conv2d(inp, oup, 1, 1, 0, bias=False), nn.BatchNorm2d(oup), - nn.ReLU6(inplace=True) + nn.ReLU6(inplace=True), ) @@ -59,7 +59,9 @@ def __init__(self, inp, oup, stride, expand_ratio): if expand_ratio == 1: self.conv = nn.Sequential( # dw - nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), + nn.Conv2d( + hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False + ), nn.BatchNorm2d(hidden_dim), nn.ReLU6(inplace=True), # pw-linear @@ -73,7 +75,9 @@ def __init__(self, inp, oup, stride, expand_ratio): nn.BatchNorm2d(hidden_dim), nn.ReLU6(inplace=True), # dw - nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), + nn.Conv2d( + hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False + ), nn.BatchNorm2d(hidden_dim), nn.ReLU6(inplace=True), # pw-linear @@ -89,16 +93,16 @@ def forward(self, x): class MobileNetV2(nn.Module): - def __init__(self, num_classes=1000, input_size=224, width_mult=1.): + def __init__(self, num_classes=1000, input_size=224, width_mult=1.0): super(MobileNetV2, self).__init__() # setting of inverted residual blocks self.cfgs = [ # t, c, n, s - [1, 16, 1, 1], - [6, 24, 2, 2], - [6, 32, 3, 2], - [6, 64, 4, 2], - [6, 96, 3, 1], + [1, 16, 1, 1], + [6, 24, 2, 2], + [6, 32, 3, 2], + [6, 64, 4, 2], + [6, 96, 3, 1], [6, 160, 3, 2], [6, 320, 1, 1], ] @@ -118,7 +122,9 @@ def __init__(self, num_classes=1000, input_size=224, width_mult=1.): input_channel = output_channel self.features = nn.Sequential(*layers) # building last several layers - output_channel = _make_divisible(1280 * width_mult, 8) if width_mult > 1.0 else 1280 + output_channel = ( + _make_divisible(1280 * width_mult, 8) if width_mult > 1.0 else 1280 + ) self.conv = conv_1x1_bn(input_channel, output_channel) self.avgpool = nn.AvgPool2d(input_size // 32, stride=1) self.classifier = nn.Linear(output_channel, num_classes) @@ -137,7 +143,7 @@ def _initialize_weights(self): for m in self.modules(): if isinstance(m, nn.Conv2d): n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels - m.weight.data.normal_(0, math.sqrt(2. / n)) + m.weight.data.normal_(0, math.sqrt(2.0 / n)) if m.bias is not None: m.bias.data.zero_() elif isinstance(m, nn.BatchNorm2d): @@ -148,9 +154,9 @@ def _initialize_weights(self): m.weight.data.normal_(0, 0.01) m.bias.data.zero_() + def mobilenetv2(**kwargs): """ Constructs a MobileNet V2 model """ return MobileNetV2(**kwargs) - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4912a75 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +opencv_python==4.2.0.34 +torch==0.4.1 +torchvision==0.2.0 +numpy==1.18.4 +scipy==1.4.1 diff --git a/rod_align/__init__.pyc b/rod_align/__init__.pyc deleted file mode 100644 index fd63362c4155d9427e1f71cef00243537d893c26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmZSn%*$oHaCdYv0~9a0`aM3i8-Z-C7Jno`bGIE n@rgN^>3RC`@tJvAjy qb28KO^y5=2O7!F7GxIV_;^Xyz$~k~$+vMh_l;)(`fm~1w#0&uMb}Xp? diff --git a/rod_align/_ext/rod_align/__init__.py b/rod_align/_ext/rod_align/__init__.py index f73c1cc..0f7f4f3 100644 --- a/rod_align/_ext/rod_align/__init__.py +++ b/rod_align/_ext/rod_align/__init__.py @@ -1,8 +1,9 @@ - from torch.utils.ffi import _wrap_function from ._rod_align import lib as _lib, ffi as _ffi __all__ = [] + + def _import_symbols(locals): for symbol in dir(_lib): fn = getattr(_lib, symbol) @@ -12,4 +13,5 @@ def _import_symbols(locals): locals[symbol] = fn __all__.append(symbol) + _import_symbols(locals()) diff --git a/rod_align/_ext/rod_align/__init__.pyc b/rod_align/_ext/rod_align/__init__.pyc deleted file mode 100644 index 7b0c3f3f0929a7f2b0a184d9253d76658ee06e06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmd5(U2oGc6unN>?F5NBp?X3lp>9IEl)5SH zW+Ymo-JI%_o?%>&!dlQ1(G%a8(rq@wQ~DRaOu!1S0P@>V_Ocmm6|}RO;~BRO6M)hC zcs7l$IK#0N`v%)rfa4tn)Cz8~`Lv}+K_OxPgiu0{uqSj%I6Z7Aq-=*Nr%uE**LKOF zvlC|Yc^R#Mrly>NdMTx5%Z|1Kt5~CO3!Cs!)b71g@w`W6Hfz1#E7IGH{P& zQX(fMdJvU;uWX(1IMFu_7kxDSeYbZ8Ll8AK15yP404qPZPUR2Ewue#Y8&{2;vcc!N zD_eClxW3o6&HMY{hUy@zb3m%Er5Q_>SNg-)G)8Szkc~d7x==nJTn%cvwYgNs@UJVi z)j`TNUc5qksk`0{LB3G*Jcz#pka#1m#HDzfEX6ikA`r_Xi}e_^@oR|iA_-^ZpN<9p q)PiLyc;JHNELg{4#lXdXCHOCNU~<=;t4E{WF=fwz*BE^!7K`7%xyVKU diff --git a/rod_align/_ext/rod_align/_rod_align.so b/rod_align/_ext/rod_align/_rod_align.so old mode 100644 new mode 100755 index 32eecad0dc16acc65bbb6f8c3c88ff94b38d4e43..d4598a27a805738ab0db097ab4a35231ced472f1 GIT binary patch literal 19928 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 QtY5QBg-}&;S4c diff --git a/rod_align/modules/__init__.pyc b/rod_align/modules/__init__.pyc deleted file mode 100644 index 61c608d74fe6f3f7df2a3ebc98f8893b8cf2280f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182 zcmZSn%*$oHaCdYv0~9a0`aM3i8-Z-C7Jno`bGIE r@rgN^>3RCO`6;D2sm1#7@tJvtnHew* diff --git a/rod_align/modules/rod_align.py b/rod_align/modules/rod_align.py index afd7488..73fb9d2 100644 --- a/rod_align/modules/rod_align.py +++ b/rod_align/modules/rod_align.py @@ -12,8 +12,10 @@ def __init__(self, aligned_height, aligned_width, spatial_scale): self.spatial_scale = float(spatial_scale) def forward(self, features, rois): - return RoDAlignFunction(self.aligned_height, self.aligned_width, - self.spatial_scale)(features, rois) + return RoDAlignFunction( + self.aligned_height, self.aligned_width, self.spatial_scale + )(features, rois) + class RoDAlignAvg(Module): def __init__(self, aligned_height, aligned_width, spatial_scale): @@ -24,10 +26,12 @@ def __init__(self, aligned_height, aligned_width, spatial_scale): self.spatial_scale = float(spatial_scale) def forward(self, features, rois): - x = RoDAlignFunction(self.aligned_height+1, self.aligned_width+1, - self.spatial_scale)(features, rois) + x = RoDAlignFunction( + self.aligned_height + 1, self.aligned_width + 1, self.spatial_scale + )(features, rois) return avg_pool2d(x, kernel_size=2, stride=1) + class RoDAlignMax(Module): def __init__(self, aligned_height, aligned_width, spatial_scale): super(RoDAlignMax, self).__init__() @@ -37,6 +41,7 @@ def __init__(self, aligned_height, aligned_width, spatial_scale): self.spatial_scale = float(spatial_scale) def forward(self, features, rois): - x = RoDAlignFunction(self.aligned_height+1, self.aligned_width+1, - self.spatial_scale)(features, rois) + x = RoDAlignFunction( + self.aligned_height + 1, self.aligned_width + 1, self.spatial_scale + )(features, rois) return max_pool2d(x, kernel_size=2, stride=1) diff --git a/rod_align/modules/rod_align.pyc b/rod_align/modules/rod_align.pyc deleted file mode 100644 index 33f1c6bff497843de8990775f412b944bb1dcc71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3105 zcmdT`OK;Oa5T3Qu^Z}*i)k<7IT*!q+;*1cb6*qd=o`{52R=iFY{D{08TB>p?zl6WY z4}kf`i9<^{(iSVTo}KL4GvCZN-uTD*+V`X9ABHr$V!pp-@uxhb{8N+@6^aIm6y_1- z5fw2FVk$Z`=&&`Wv_tt46weQKl&E(l??rqW6PkN|)!>mDajK>FV6N)>>C7 zy+wz;+w6e*$yZOY7P>sVtI}z1dlBYqJS3k-W>Qu8i&UmXay*+lENS*_vZky3-Q11J zx6^VMTvdjZ-;8bOc+DFY|A+@mh?oQB2nG>gfD)5Fl-CbCyaoci_E`Kr4}pJ*7`d~E zK1PUKg_urR6RYVErfNQ2LR6>me99OR#|<|QdhjqT;lb01tuwHfToK4MiYZDa9Y?h_`WNWk=?7S$;6eT{^U!j>fzY9Vv<`d;I~z!@#ATh z<@UgaeqE(TeC`*LQNOV4OulHOdYQ$;4^sD|4Yi?$2p9ra_gMT>9vE?k`=h|ih(l*Q z1!TY;wRmEe#`cR5iVd|U@Mbm%)7tug^>yX^wIC1>eOA>+Nu45_9^Mdx&?F167*i&N zgu91X^)Ti>zxYqa%yS`(L3e8z!!2zae39lR+Ii-Djfq*n?TK4{ z)8cu@nTig3!05n9R|XcJY+YLJCho*a-<4MFL+w&4OE06H#%`%*Eg+(cX_PwmPom>R z@m^)!f;Q;=9;qj_tsXXn)A9|u%eEUqFH|Swa(cZ>j&C}>wpt|rBYHgn;^@5RK1lzT zUK*ShPU#1%{hdv6+*!1wmQDI0wy|7&O9GjmvD|Pnp%x#*P01vGs}6c-@?jeTjxjrB zxidSZ%VE^?U|=(hmtJ$&cK63ibT7@rYEPKmra1L S(0e_?-&9*_Gv2zrwf+m^vaF5( diff --git a/roi_align/__init__.pyc b/roi_align/__init__.pyc deleted file mode 100644 index 0c256f30357494dd1c0309a0c592d2c1301131aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmZSn%*$oHaCdYv0~9aj(oxI3Bw2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUmp4y`MIh3RjGOD z8Ks%}F8Rr&xv6<2#rm1KiRr1%Mfn83RC` e@tJvj(oxI3Bw2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CU%vVo`MIh3RjGOD z8Ks%}F8Rr&xv6<2#rm1KiRr1%Mfn83RC` jsTC#q@$s2?nI-Y@dIgoYIBatBQ%ZAE?LgKP12F>t@5L$4 diff --git a/roi_align/_ext/roi_align/__init__.py b/roi_align/_ext/roi_align/__init__.py index c5b6e5d..e84fbbf 100644 --- a/roi_align/_ext/roi_align/__init__.py +++ b/roi_align/_ext/roi_align/__init__.py @@ -1,8 +1,9 @@ - from torch.utils.ffi import _wrap_function from ._roi_align import lib as _lib, ffi as _ffi __all__ = [] + + def _import_symbols(locals): for symbol in dir(_lib): fn = getattr(_lib, symbol) @@ -12,4 +13,5 @@ def _import_symbols(locals): locals[symbol] = fn __all__.append(symbol) + _import_symbols(locals()) diff --git a/roi_align/_ext/roi_align/__init__.pyc b/roi_align/_ext/roi_align/__init__.pyc deleted file mode 100644 index ba603288d4cec49b5e725d60141d5ee09dba2971..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 786 zcmcgpTW-`a6unO7(HV7UB_!66ugL}~gwzqA^O1fa4InF%crr$QgxG-=O~pr97Q5;K z;9R!?R)A$6U*AXWIXC&^YWe-k&-V>IuMGDaobVRVVhb`vR?tq6XftYaG8x&Nb`!Ew z+RaFGM7ue)6Z!$;j1=~Q9*7>eVoEo~44=}!unGYyz5vLtL*2{6=$fd#)9f#}b<6=q z@6&xa=!PTgo8VmIga&Z?fg*ARx7d8#(!HRV@%sc(S#5q@P z$*$)|oYALwDh1)sD|!dnH;L-DI77F@rPTwz1L90>4x>}Cj&X>b(bUG&d!s7Y+zx5r zp3T=_o(JB{1 zscqe<>%sTEc3s)uML#r0scml*C)7OIa_|}qbmvN`ZsWgCshy5eu2Fb_E?nq`D4&=F z<{QodBwmUOaV}nEOR+7MaHRCqY&}M8!Wvqilyg-6=~(f#BHa6_2Z0F>vx< ZrTQS^i{E&&v#$UE diff --git a/roi_align/_ext/roi_align/__pycache__/__init__.cpython-35.pyc b/roi_align/_ext/roi_align/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 38ff4f707632d6ea9de12f6771a4865807b4668c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621 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 literal 86504 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

1i#EjlhZf4=EdlL# z3tl^K!|a=h%hnBME^}}18_7Y^F(HO06C)QSS0PEn;gaGM83iifp8R%}*J%gXK9C2% zR|q)moQ8oZZ+wSw+U_9ev||jn1C-OA8I&p6U?0>ghIG^BO&hq57nd~i=4F$Novm5z z3SX3lAd_L{kfJF_YB-yQl6;x@ve8V|`hazr0dwDJJ1pF3$E?y~eRvnf0JNrZcq<=d z3~Fvv7C6=0Tg+_M7`$2#xw2fSX9f9s521=CodN&1LT=AuDTF;E`{?&D8XhHd4Y2I1 zdbrQ~+B@&$RbIcZ(eOFp#&(r?uzaX`P6EcakE;CB0%-UI_9OT(T}G+A?wc(Fjk;A{ zzyHzj(m;k~IROd#oQ2>WU;g!bA`SKH56!=(qhSx^-}cGt_emOdNjky&3)$%dpmC(> z_fBre#=)cM2g{#B9Oajg36Fk1rJ)K^9=lti`r8T`>jc{i=F#u5G^C9GVOsvD1jH|| z-*>s8R+I?o8d1aT0eSsiOhe_SH$>afkld|ERo%+&}fpyZh(6t<+RWUv;RQhSLM` zy1(BxY9;gsoGRa@fP_Z`81h%sG2vMi=>I|a^Tv?>#S>PZ`}_VU zD4!iee*fQFsb6t$_dP-RipU4+@2`Jng-R_xVSp;H;ZBjS=ECdHuR^08|d8dd<35h@KO6! UyXN>5mHxg{tbA`^01Kx7U*Tmek^lez diff --git a/roi_align/build.py b/roi_align/build.py index 79f9586..654b44b 100644 --- a/roi_align/build.py +++ b/roi_align/build.py @@ -3,11 +3,11 @@ import torch from torch.utils.ffi import create_extension -sources = ['src/roi_align.c'] -headers = ['src/roi_align.h'] +sources = ["src/roi_align.c"] +headers = ["src/roi_align.h"] extra_objects = [] -#sources = [] -#headers = [] +# sources = [] +# headers = [] defines = [] with_cuda = False @@ -15,24 +15,24 @@ print(this_file) if torch.cuda.is_available(): - print('Including CUDA code.') - sources += ['src/roi_align_cuda.c'] - headers += ['src/roi_align_cuda.h'] - defines += [('WITH_CUDA', None)] + print("Including CUDA code.") + sources += ["src/roi_align_cuda.c"] + headers += ["src/roi_align_cuda.h"] + defines += [("WITH_CUDA", None)] with_cuda = True - - extra_objects = ['src/roi_align_kernel.cu.o'] + + extra_objects = ["src/roi_align_kernel.cu.o"] extra_objects = [os.path.join(this_file, fname) for fname in extra_objects] ffi = create_extension( - '_ext.roi_align', + "_ext.roi_align", headers=headers, sources=sources, define_macros=defines, relative_to=__file__, with_cuda=with_cuda, - extra_objects=extra_objects + extra_objects=extra_objects, ) -if __name__ == '__main__': +if __name__ == "__main__": ffi.build() diff --git a/roi_align/functions/__init__.pyc b/roi_align/functions/__init__.pyc deleted file mode 100644 index 7d287c0c2cd40a8e100b11b96e38f0a994aef6a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179 zcmZ9GO$q`r3`Q%u5W#!Q${auuLDz0vDwNWxZD5*|{w(x1p2K5#0aI{g;Cq2bLNfoX zSKHV7YSpi1(hrid5R6J)hHlP-K>eZI#6jTb;OWSRhHzrrqN7>>qw(&TIVB_y=}|e` rpfRS&^#N0n2{NNL6MBMsBimI0t&y;5y^)=z9N0x3+vN8BjUrKB_$n^k diff --git a/roi_align/functions/__pycache__/__init__.cpython-35.pyc b/roi_align/functions/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index b122bd413df742fcbbb9f153c5706ea06a3a734a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 zcmWgR<>j(oxI3Bw2p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CUqSjA`MIh3RjGOD z8Ks%}F8Rr&xv6<2#rm1KiRr1%Mfn83RBT orFqFEnfZCe`tk9Zd6^~g@p=W7w>WHa^HWN5Qtd!?6$3E?0N`&c{Qv*} diff --git a/roi_align/functions/__pycache__/roi_align.cpython-35.pyc b/roi_align/functions/__pycache__/roi_align.cpython-35.pyc deleted file mode 100644 index e6fb1989a622e2adab1eb79e43b0e4c8a38a368c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1668 zcmah}&5qne5U#d8w&!nm39#7+DO||Gmz@hY5Xx#HQ7(I6Zw9I5*q#|rJoeDt){@oE zg+)98FTjy!K)ePAUpeI!IPq26v$G+Q=$Zbyy8ClgyS{24Pp6|Ve*5FQzXGDa>0a|- zKSkHi02%&*N}{;49V!K_9f}h9UKN>HwgNF|y)*>6ls}M(25Q@|Y@H?pk4F6otmi0QHUFX$e*%Y&{>$F+tmC>_eoh0OJ zcIq+{ZHUe%wW<9R2J*_;f+1*yq;@&O;%@rAM_ZS+zyWQ2=7R>Ap~g0_G^8Jf$9`5F z4(QgSmCxR`q2&amekBvqYWNn79~m)$j}g+XPojEadE@`%fqoe|7fRqIqKO(~fEuJHUD^+n~@gZL&mhiN(iv>6Y_aT_q~(HaV^F2k&e~Lp5dBVP6Eg3p#q4 zm~?3?qMcODTBggSs`65|<18_W?8|CoC${9`kZHD{{JJrl#@LGK;E+QE{dstz9|K4X z#i4V8{u!)^@PSjO8%OT-$vevT4pP2H+*tHG;eWw|Q*XZ-uZE;fdc6_LIP{afXxL@# zoxS}0h-)>5T2LKgcS-}Hv`@yT6-og#s1EGJ?gX~a-k>I+F$Ubpxzk)SFRVqd(XAoJ#`WJd}FI*olDt{q+HioQ?fmg@`olV zyB&1|zv>f)PZ{|0aSyl%)BwQxX&*2VlVEae*AQV~za8y?9Zr+R)cit2U8^jh{_+@vjulOqU{*T0w_b;Tca?=0+ diff --git a/roi_align/functions/roi_align.py b/roi_align/functions/roi_align.py index bf1d2e1..17670e1 100644 --- a/roi_align/functions/roi_align.py +++ b/roi_align/functions/roi_align.py @@ -19,32 +19,47 @@ def forward(self, features, rois): batch_size, num_channels, data_height, data_width = features.size() num_rois = rois.size(0) - output = features.new(num_rois, num_channels, self.aligned_height, self.aligned_width).zero_() + output = features.new( + num_rois, num_channels, self.aligned_height, self.aligned_width + ).zero_() if features.is_cuda: - roi_align.roi_align_forward_cuda(self.aligned_height, - self.aligned_width, - self.spatial_scale, features, - rois, output) + roi_align.roi_align_forward_cuda( + self.aligned_height, + self.aligned_width, + self.spatial_scale, + features, + rois, + output, + ) else: - roi_align.roi_align_forward(self.aligned_height, - self.aligned_width, - self.spatial_scale, features, - rois, output) -# raise NotImplementedError + roi_align.roi_align_forward( + self.aligned_height, + self.aligned_width, + self.spatial_scale, + features, + rois, + output, + ) + # raise NotImplementedError return output def backward(self, grad_output): - assert(self.feature_size is not None and grad_output.is_cuda) + assert self.feature_size is not None and grad_output.is_cuda batch_size, num_channels, data_height, data_width = self.feature_size - grad_input = self.rois.new(batch_size, num_channels, data_height, - data_width).zero_() - roi_align.roi_align_backward_cuda(self.aligned_height, - self.aligned_width, - self.spatial_scale, grad_output, - self.rois, grad_input) + grad_input = self.rois.new( + batch_size, num_channels, data_height, data_width + ).zero_() + roi_align.roi_align_backward_cuda( + self.aligned_height, + self.aligned_width, + self.spatial_scale, + grad_output, + self.rois, + grad_input, + ) # print grad_input diff --git a/roi_align/functions/roi_align.pyc b/roi_align/functions/roi_align.pyc deleted file mode 100644 index 5192ab8585a602ca5acb2c08f47c2941e990134c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2085 zcmcgs-EJF282x6ww&OG*p`xYYqFg|wy{aFe2(=U{ag&S56#+Eb?vCws*SprvtWu)* zh9I7R7vMP%PsIbkIWu-#5E56!$$Wce_Uz2}bLJcTaeVmf*WbP^WcwT7`5eQ&f+_K@ zq!zh|IFVY(Dv^ssR5_3`m3knnR4!7?hhh_%DJe(tlgN$8Hy1;Gex8l7RrM!+XPE-@ z2QaVtrtnqUR0$?=c>>h6mC5UB-uP3@bo=!SPIly5FS*SZ8154o3;%Ge@K|_;BMIPM z5Ru;CJFT5XOX;!y+q`P(QtGqU@$ZPBA=vmuDfV4P&M7GU+ z-&y0TE1QuLc+S={&CN8IH1nNdMB}~(nl9RvonF~yzUZszmu=CnY~$UuTIF;5vTN7t zs+mvMKlrvQ7N%OQYfAzgYMbe8QKqwP)!csVDL!K4##Bw^jUjf2FrrSv-$WHm3ad-U zLgIf1K-QT{03}PLJf;?lyXkkSYzDG{9m-}zK4_>eYPA^$8j1sua-U_iL%B(1IpS`c zgTNU}_X{ProcxW(PYy|-8wYYT64g8jynFw}1N}UshR!M&$ygW7@M0}c8O`{E6dHTE zFHETSmF-&N!f7$M;P8Rva!mLmyt} z9_6H1f_j>MWr{`KG`4o0pOv}K%~p)$hk2AMHzYquZ-hzP`*rU#pgYqQBj2Xt1kgR^ z;cB8zl1CVy0v)Ll>~RvGhl7H3;b$B{YCi+8*r9{HnMn62$RgoxDAAAM;^QsVmh1%= zCAAy|eL!cB*Sp#p2}-zx!mx7`7Q_S$@x%?mhYF!8JkTR%$!(hma$&MHQ>@wNLT`BC zob7nuzUsQR3pLd28JeL-k$ecW7s>0qc(Yf@KdwLHkIG=3=UrZ!SU~*&-#nn9<}~;8 zbSH4C=B{hZBQmkrc0 diff --git a/roi_align/modules/__init__.pyc b/roi_align/modules/__init__.pyc deleted file mode 100644 index 9e68d5126d47266b75b6d5424c3ceead38f2203b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 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 Fe*r?C1l#}s diff --git a/thop/profile.py b/thop/profile.py index fc3b4ed..ac1ad74 100644 --- a/thop/profile.py +++ b/thop/profile.py @@ -11,26 +11,21 @@ nn.Conv2d: count_convNd, nn.Conv3d: count_convNd, nn.ConvTranspose2d: count_convtranspose2d, - nn.BatchNorm1d: count_bn, nn.BatchNorm2d: count_bn, nn.BatchNorm3d: count_bn, - nn.ReLU: count_relu, nn.ReLU6: count_relu, nn.LeakyReLU: count_relu, - nn.MaxPool1d: count_maxpool, nn.MaxPool2d: count_maxpool, nn.MaxPool3d: count_maxpool, nn.AdaptiveMaxPool1d: count_adap_maxpool, nn.AdaptiveMaxPool2d: count_adap_maxpool, nn.AdaptiveMaxPool3d: count_adap_maxpool, - nn.AvgPool1d: count_avgpool, nn.AvgPool2d: count_avgpool, nn.AvgPool3d: count_avgpool, - nn.AdaptiveAvgPool1d: count_adap_avgpool, nn.AdaptiveAvgPool2d: count_adap_avgpool, nn.AdaptiveAvgPool3d: count_adap_avgpool, @@ -46,8 +41,8 @@ def add_hooks(m): if len(list(m.children())) > 0: return - m.register_buffer('total_ops', torch.zeros(1)) - m.register_buffer('total_params', torch.zeros(1)) + m.register_buffer("total_ops", torch.zeros(1)) + m.register_buffer("total_params", torch.zeros(1)) for p in m.parameters(): m.total_params += torch.Tensor([p.numel()]) diff --git a/thop/profile.pyc b/thop/profile.pyc deleted file mode 100644 index 13582fc15ac74047f319d7d8591302d65b83dbf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2888 zcmcIlU31$+6upuir*`6eHYILpNx#4phzA(bmkv;xp$rpDQOktMz<5+^W6P1WsXs_#Zs-%)q%Z zYCsF(T?DENS}M?TfmVQ4#k*FZ^#Yv*+7R!#0=-tCO`wK&&ll)IfnEo?DBep2dZR#F zK$pdPr9f8;bPedbc;BQQ(8Q-~($2x#`^dk5xK$Bu9(V)&&M8*fJkIIz%L63f;h-i$YSg=Gl5ouhN z$ucHY_V2?xSmC{yGsjqk(pPC*Ijwe1iaOq;XBLvL&}4ze3p7~ct=6N<76(Fb&~?9A z0+H=56Frwd>r8C}7K4u^!R?uXEh+d4clmuzjOMnPQQ0-{vr z^>g7N^=B7{($5!@4cZrYuBdgRi+sqsD#Hi5<gplfiMT`U4Fk1xJ#DUVL{=)m<}!a{i#Y?(?fZe3z`Be{l85R%93CF3#IT}9C*8iF z$12e&cqEdt6w;&)2Qz%39;hhQ*_LP+szB11lUNC8>qwa;EGts6W7dVE z{pcoMhnGlfH;z^0HKiQxv=D8DD#RCdB#8~fVI;4Cz8iUgN}6nva=XLS=#cp}Yp~`1 zPFDwk>Kb21O;{=2$I$BQP_-VbsMjC*tzF&4Q6iJJ{Lt;GJBf~CKkBvO6Je!O=;3md zPTB`;fodz$GJPGl;zS?%f!fA$R@wEkBc<MFSfI-?!Cw|Y5(DB&;oJ;IrMUSUB9qxH&6N88&VrH?%<~vv~#0I*DgZ6~_OeVfe+fgLy;EUT{ zS_!u8T!!r~e#IQli5EUDC|_u~3aPvpvN^A5d!hpW=)FIMA57tgo+Oy=K$k>mtW$+W zeBXKNgBhE=5G3EjNRof%nr^?XlMo5Gm|~6q|C=Kf>z<`bR4|lAbkyFr(lZQp-b-;{VD$z6Xqa+^_iz*=A!v zORNYyEy^ABilT0hdNW1kv%IK$_F_?|0cI0T1HHuhTv5}&e8IBQ27aX6L}cF+=&+2^ zYrA+k>7jA>Rqt>IX7>glL$CD8aO2#{`uAhL$muN%i}DUzv1@k8s%8JSe9w+A+F_}e F{{z{Dg-QSb diff --git a/thop/utils.py b/thop/utils.py index c63f444..38a9e28 100644 --- a/thop/utils.py +++ b/thop/utils.py @@ -1,4 +1,3 @@ - def clever_format(num, format="%.2f"): if num > 1e12: return format % (num / 1e12) + "T" @@ -7,4 +6,4 @@ def clever_format(num, format="%.2f"): if num > 1e6: return format % (num / 1e6) + "M" if num > 1e3: - return format % (num / 1e3) + "K" \ No newline at end of file + return format % (num / 1e3) + "K"