├── README.md ├── Superpoint_training_with_COCO_dataset.ipynb ├── Superpoint_training_with_video_dataset.ipynb ├── create_vid_labels.ipynb ├── datasets ├── COCO │ ├── COCO_model.py │ └── __pycache__ │ │ └── COCO_model.cpython-37.pyc ├── download_datasets.ipynb ├── synthetic_shapes │ ├── __pycache__ │ │ └── synthetic_shapes_functions.cpython-37.pyc │ └── synthetic_shapes_functions.py ├── utils │ ├── __pycache__ │ │ └── transforms.cpython-37.pyc │ └── transforms.py └── video_data │ ├── images_from_video.ipynb │ ├── vid1.mp4 │ ├── vid3.mp4 │ ├── video3_images_labels.csv │ ├── video3_names.csv │ ├── video_images_labels.csv │ └── video_names.csv ├── generate_synthetic_dataset.ipynb ├── homographoc_addaptation.ipynb ├── magicpoint_training_with_COCO_dataset.ipynb ├── magicpoint_training_with_synthetic_dataset.ipynb ├── models ├── __pycache__ │ ├── superpoint.cpython-37.pyc │ └── utils.cpython-37.pyc ├── superpoint.py └── utils.py ├── pretrained_weights └── superpoint_v1.pth ├── test_trained_model.ipynb ├── utils ├── __pycache__ │ ├── plot.cpython-37.pyc │ └── points.cpython-37.pyc ├── plot.py └── points.py ├── video evaluation.ipynb └── weights ├── magic_coco_weights.pth ├── magic_synthetic_weights.pth ├── super_coco_weights.pth ├── super_coco_weights_video.pth ├── super_coco_weights_video3.pth └── super_coco_weights_video_1000.pth /README.md: -------------------------------------------------------------------------------- 1 | # superpoint-pytorch 2 | This file is a pytorch implementation and evaluation of Superpoint model as described in https://arxiv.org/pdf/1712.07629v4.pdf. 3 | 4 | We found great help in Rémi Pautrat’s tensorflow implementation: https://github.com/rpautrat/SuperPoint. 5 | 6 | 7 | In interest point detection, our model seems to not fully converge: 8 | ![image](https://user-images.githubusercontent.com/73498160/111214173-4ca24600-85da-11eb-8fd3-2681b2f49719.png) 9 | 10 | But still, the results of homogrphic addaptation combined with our model seems good: 11 | ![image](https://user-images.githubusercontent.com/73498160/111214201-55931780-85da-11eb-8001-57b1807bdb1b.png) 12 | 13 | To see in comparison to other point detection models: 14 | 15 | ![image](https://user-images.githubusercontent.com/73498160/111214834-16b19180-85db-11eb-981e-29d950b2cf8a.png) 16 | 17 | The overall results do not reach the tracking ability as of the original model. 18 | 19 | with original model, the matching points are: 20 | 21 | ![image](https://user-images.githubusercontent.com/73498160/111215142-77d96500-85db-11eb-8fe2-c25bd7d8ee83.png) 22 | 23 | with our implementation: 24 | 25 | ![image](https://user-images.githubusercontent.com/73498160/111215197-8c1d6200-85db-11eb-9e06-f04815a94b86.png) 26 | 27 | 28 | Though overall results do not reach satisfying abilities, we hope the different blocks (data genereation, homographic adaptation and so on) can be of use to some future work. 29 | 30 | 31 | ## Within this file are all stages of implementation: 32 | ### 1)Generate synthetic dataset- 33 | creates a dataset containing 100000 images of self made synthetic shapes, together with the dataset file containing images names and labels 34 | This part takes about 12 hours on tesla v-100 35 | ### 2)Magicpoint_training_with_synthetic_dataset- 36 | Training magic point model as described in the paper, with the exception that in our implementation we use premade data, and not created data on the fly. We train for about 40000 iterations. This part takes 7 hours 37 | ### 3)Homographic adaptation- 38 | creates pseudo ground truth for coco images, so we can train magic point on coco. This part takes around 14 hours for an 87000 images dataset. 39 | ### 4)Magic point training on coco- 40 | similar to 2, only with different transformations to the data. 41 | 42 | 3 and 4 can be used iteratively, just change to the right path in paths.around 20000 iterations where ran, which took around 12 hours. 43 | 44 | ### 5)Super point training on coco- 45 | using the full super point architecture. Creating the twin image for training is within the train function. In this part we used a small minibatch of 3 images, due to large memory consumption by the descriptors. We trained for 250000 iterations, which took 8 hours. 46 | 47 | 48 | All the described process was made in google cloud, and the resulting weights are presented at weights folder. Pre Trained original weights are presented at pretrained weights. 49 | The Superpoint architecture can be viewed in ‘models’ folder. 50 | The different outcome point detection of every stage can be viewed in ‘test pretrained model’, just change the paths in paths headline. 51 | 52 | 53 | The evaluation method we proposed, using a 10 seconds video with small movements, together with imaging of point matching ability of different models are presented at video evaluation. 54 | 55 | All the data can be saved using the 'download_dataset' file in datasets. 56 | For superpoint training We used coco train2014. 57 | 58 | -------------------------------------------------------------------------------- /Superpoint_training_with_COCO_dataset.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"accelerator":"GPU","colab":{"name":"Superpoint_training_with_COCO_dataset.ipynb","provenance":[],"collapsed_sections":["2WXfmcIKS2As","es_Y7Yj7TpI2","audZzq95V2yT","_Q4unyskW7nu","jBIPot_UiK5f"],"toc_visible":true},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.7.8"}},"cells":[{"cell_type":"markdown","metadata":{"id":"-PiXOVyuR673"},"source":["# Setup"]},{"cell_type":"code","metadata":{"id":"QROgw9F1SVGO","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1614958830410,"user_tz":-120,"elapsed":153710,"user":{"displayName":"תקוה כהן","photoUrl":"","userId":"01741536388990615411"}},"outputId":"66b6c130-2fd9-4f11-d747-d13fbfaa4648"},"source":["%pip install kornia==0.4.0\n","import matplotlib.pyplot as plt\n","import numpy as np\n","import cv2\n","import math \n","import torch\n","from torchvision import datasets, transforms\n","from torch import nn, optim\n","import torch.nn.functional as F\n","import matplotlib.pyplot as plt\n","import kornia as K\n","import time"],"execution_count":null,"outputs":[{"output_type":"stream","text":["Collecting kornia==0.4.0\n","\u001b[?25l Downloading https://files.pythonhosted.org/packages/fb/18/f767c3f8c28945f0ae5d5db34517f56897cece8175389f54cb8ffacdab99/kornia-0.4.0-py2.py3-none-any.whl (195kB)\n","\u001b[K |████████████████████████████████| 204kB 17.2MB/s \n","\u001b[?25hCollecting torch<1.7.0,>=1.6.0\n","\u001b[?25l Downloading https://files.pythonhosted.org/packages/5d/5e/35140615fc1f925023f489e71086a9ecc188053d263d3594237281284d82/torch-1.6.0-cp37-cp37m-manylinux1_x86_64.whl (748.8MB)\n","\u001b[K |████████████████████████████████| 748.8MB 23kB/s \n","\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from kornia==0.4.0) (1.19.5)\n","Requirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from torch<1.7.0,>=1.6.0->kornia==0.4.0) (0.16.0)\n","\u001b[31mERROR: torchvision 0.8.2+cu101 has requirement torch==1.7.1, but you'll have torch 1.6.0 which is incompatible.\u001b[0m\n","Installing collected packages: torch, kornia\n"," Found existing installation: torch 1.7.1+cu101\n"," Uninstalling torch-1.7.1+cu101:\n"," Successfully uninstalled torch-1.7.1+cu101\n","Successfully installed kornia-0.4.0 torch-1.6.0\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"GZv-fvGtSk8x"},"source":["## Mount drive"]},{"cell_type":"code","metadata":{"id":"iTEDRFcbSlzV","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1614958899806,"user_tz":-120,"elapsed":223096,"user":{"displayName":"תקוה כהן","photoUrl":"","userId":"01741536388990615411"}},"outputId":"06710d1f-2090-47b4-e51c-eeffb46581ad"},"source":["your_path = ''\r\n","from google.colab import drive\r\n","drive.mount('/content/gdrive')\r\n","path = '/content/gdrive/My Drive/'+your_path\r\n","import sys\r\n","sys.path.append(path)\r\n","from utils.points import cords_to_map"],"execution_count":null,"outputs":[{"output_type":"stream","text":["Mounted at /content/gdrive\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"1EfKNOPTTCX9"},"source":["## GPU"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"vLsTtFwBS_WG","executionInfo":{"status":"ok","timestamp":1614958899807,"user_tz":-120,"elapsed":223091,"user":{"displayName":"תקוה כהן","photoUrl":"","userId":"01741536388990615411"}},"outputId":"d134065b-23ae-47c3-8434-af5139c371bd"},"source":["print(torch.__version__)\n","train_on_gpu = torch.cuda.is_available()\n","\n","if not train_on_gpu:\n"," DEVICE = 'cpu'\n"," print('CUDA is not available. Training on CPU ...')\n","else:\n"," DEVICE = 'cuda'\n"," print('CUDA is available! Training on GPU ...')\n","\n"],"execution_count":null,"outputs":[{"output_type":"stream","text":["1.6.0\n","CUDA is available! Training on GPU ...\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"cV77yN94TOdn"},"source":["# DATA"]},{"cell_type":"markdown","metadata":{"id":"2WXfmcIKS2As"},"source":["## Define transformatiom"]},{"cell_type":"code","metadata":{"id":"fjnZ_rPKaRKR"},"source":["from datasets.utils.transforms import ColorJitter, ToGray, Rescale,ToTensor, get_twin"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"es_Y7Yj7TpI2"},"source":["## Dataset model"]},{"cell_type":"code","metadata":{"id":"XmocRKD4TV9v"},"source":["from datasets.COCO.COCO_model import COCO_dataset\n","\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"QLd0aweBU-MC"},"source":["## Generate dataset and dataloader"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"id":"QUb6fXeyVGdm","executionInfo":{"status":"ok","timestamp":1614958903218,"user_tz":-120,"elapsed":226488,"user":{"displayName":"תקוה כהן","photoUrl":"","userId":"01741536388990615411"}},"outputId":"df6c53ad-f220-41ec-97d9-45efb8856f35"},"source":["transform = transforms.Compose([Rescale((240,320)),\n"," ColorJitter(), \n"," ToGray(),\n"," ToTensor()]) \n","\n","dataset = COCO_dataset(path+'/datasets/COCO/labeled_coco.csv',\n"," path+'/datasets/COCO/val2017/', \n"," transform=transform, \n"," landmark_bool=True)\n","dataloader = torch.utils.data.DataLoader(dataset, batch_size=5, shuffle=True)\n","'''\n","print(len(dataset))\n","print(len(dataloader))\n","for iter, (im,label) in enumerate(dataloader):\n"," print('ok')\n"," if iter>2:\n"," break\n","'''\n","\n"," "],"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"application/vnd.google.colaboratory.intrinsic+json":{"type":"string"},"text/plain":["\"\\nprint(len(dataset))\\nprint(len(dataloader))\\nfor iter, (im,label) in enumerate(dataloader):\\n print('ok')\\n if iter>2:\\n break\\n\""]},"metadata":{"tags":[]},"execution_count":6}]},{"cell_type":"markdown","metadata":{"id":"VOlf25b1R5HQ"},"source":["## Test model"]},{"cell_type":"markdown","metadata":{"id":"audZzq95V2yT"},"source":["### plot function"]},{"cell_type":"code","metadata":{"id":"KRfG9AjiVkj5"},"source":["from utils.plot import plot_imgs\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"_Q4unyskW7nu"},"source":["### test"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":67},"id":"hxMmcv06W59h","executionInfo":{"status":"ok","timestamp":1614958903229,"user_tz":-120,"elapsed":226483,"user":{"displayName":"תקוה כהן","photoUrl":"","userId":"01741536388990615411"}},"outputId":"2775633d-c16c-46ab-aea7-17516f25e2cf"},"source":["'''for iter, (im, label) in enumerate(dataloader):\n"," label = label.type(torch.double)\n"," label = label.unsqueeze(1)\n"," imgs= K.tensor_to_image(im)\n"," size = im.size()\n"," map = cords_to_map(label, size, device=False)\n"," im_map = K.tensor_to_image(map)\n"," if iter%10==0:\n"," print('iteration {}/{} is running'.format(iter,len(dataloader)))\n"," plot_imgs(imgs, label=label)\n"," plot_imgs(im_map)\n"," if iter>0:\n"," break\n","'''"],"execution_count":null,"outputs":[{"output_type":"execute_result","data":{"application/vnd.google.colaboratory.intrinsic+json":{"type":"string"},"text/plain":["\"for iter, (im, label) in enumerate(dataloader):\\n label = label.type(torch.double)\\n label = label.unsqueeze(1)\\n imgs= K.tensor_to_image(im)\\n size = im.size()\\n map = cords_to_map(label, size, device=False)\\n im_map = K.tensor_to_image(map)\\n if iter%10==0:\\n print('iteration {}/{} is running'.format(iter,len(dataloader)))\\n plot_imgs(imgs, label=label)\\n plot_imgs(im_map)\\n if iter>0:\\n break\\n\""]},"metadata":{"tags":[]},"execution_count":8}]},{"cell_type":"markdown","metadata":{"id":"jBIPot_UiK5f"},"source":["# Net architecture"]},{"cell_type":"markdown","metadata":{"id":"xQwW0N-mi0Du"},"source":["## Superpoint model"]},{"cell_type":"code","metadata":{"id":"rBCJC3jbif_q"},"source":["from models.superpoint import SuperPointNet"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"IP8lKGmjja9X"},"source":["## functions for Loss calculations"]},{"cell_type":"code","metadata":{"id":"WCsM6CjfjZYi"},"source":["from models.utils import detector_loss, descriptor_loss\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"OZ0eeaexl-0X"},"source":["# Training Superpoint on COCO"]},{"cell_type":"markdown","metadata":{"id":"nAGM9utB1pyc"},"source":["## Train function"]},{"cell_type":"code","metadata":{"id":"PModR5u1l-Lf"},"source":["def train_coco_magic(dataloader, net, save_path, filename, lr=0.001):\n"," t_0 = time.time()\n"," optimizer = torch.optim.Adam(model.parameters(), lr=lr)\n"," DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n"," net.to(DEVICE)\n"," model.train()\n"," N_epoch = 4\n"," for e in range(N_epoch):\n"," for iter, (im, label) in enumerate(dataloader):\n"," optimizer.zero_grad()\n"," im = im.to(DEVICE).type(torch.float)\n"," label = label.to(DEVICE)\n"," \n"," #get twin im and homography\n"," twin_im, H = get_twin(im)\n"," if len(H.size()) < 3:\n"," H = H.unsqueeze(0)\n"," H_invert = torch.inverse(H)\n"," \n"," #go through model\n"," chi_points, desc = net(im)\n"," twin_chi_points, twin_desc = net(twin_im)\n"," #get map label\n"," label = label.type(torch.double)\n"," size = im.size()\n"," map = cords_to_map(label, size)\n"," map[map<0.01] = 0\n"," map[:,:,0:5,0] = 0\n"," map[:,:,:,0:7] = 0\n"," map[:,:,-5:,:] = 0\n"," map[:,:,:,-7:] = 0 \n","\n"," #get twin map and valid mask\n"," twin_map = map.type(torch.float) \n"," twin_map = K.warp_perspective(twin_map, H, dsize=(im.size(2), im.size(3)))\n"," valid_mask = torch.ones_like(im, dtype=torch.float32).to(DEVICE)\n"," valid_mask = K.warp_perspective(valid_mask, H, dsize=(im.size(2), im.size(3)))\n","\n"," \n"," #loss\n"," detector_loss_1 = detector_loss(map, chi_points)\n"," detector_loss_2 = detector_loss(twin_map, twin_chi_points)\n"," desc_loss = descriptor_loss(desc, twin_desc, H, H_invert, valid_mask)\n"," loss = detector_loss_1 + detector_loss_2 + 0.0001*desc_loss\n"," \n"," #optimize\n"," loss.backward()\n"," optimizer.step()\n"," \n"," \n"," if iter%10==0:\n"," print('iteration {}/{} is running'.format(e*len(dataloader)+iter,N_epoch*len(dataloader)))\n"," print('loss is:',loss.item())\n"," if iter%50==0:\n"," t_c = time.time()\n"," minute = (t_c-t_0)/60\n"," print('saving weights from iteration {} with loss {}, {} minutes pased'.format(e*len(dataloader)+iter,loss.item(),int(minute)))\n"," print('detector loss 1: {}, detector loss 2: {}, descriptor loss: {}'.format(detector_loss_1,detector_loss_2,desc_loss))\n"," torch.save(model.state_dict(), save_path+filename)\n"," # Save weights\n"," torch.save(model.state_dict(), save_path+filename)\n"," t_f = time.time()\n"," hours = (t_f-t_0)/3600\n"," print('finished in {} hours'.format(hours))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"0ziQBW1vScba"},"source":["## Run"]},{"cell_type":"code","metadata":{"id":"j1USCOAGSd6q"},"source":["#tensorflow\n","from torch.utils.tensorboard import SummaryWriter\n","from datetime import datetime\n","import os \n","\n","%load_ext tensorboard\n","\n","logs_base_dir = 'logs'\n","os.makedirs(logs_base_dir, exist_ok=True)\n","\n","\n","\n","dataloader = torch.utils.data.DataLoader(dataset, batch_size=3, shuffle=True)\n","model = SuperPointNet(superpoint_bool=True)\n","weights_path = path+'/weights/magic_coco_weights.pth'\n","model.load_state_dict(torch.load(weights_path,\n"," map_location=lambda storage, loc: storage))\n","\n","\n","\n","train_coco_magic(dataloader, model, path, '/weights/super_coco_weights_test.pth')\n"],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"9XSBl7vMWvRP"},"source":["#writer_magic.flush()\n","%tensorboard --logdir 'logs'"],"execution_count":null,"outputs":[]}]} -------------------------------------------------------------------------------- /datasets/COCO/COCO_model.py: -------------------------------------------------------------------------------- 1 | import skimage 2 | import torch 3 | import pandas as pd 4 | import PIL 5 | import os 6 | import numpy as np 7 | from torch.utils.data import Dataset, DataLoader 8 | 9 | class COCO_dataset(Dataset): 10 | 11 | def __init__(self, csv_file, root_dir, transform=None, landmark_bool=False): 12 | """ 13 | Args: 14 | csv_file (string): Path to the csv file with annotations. 15 | root_dir (string): Directory with all the images. 16 | transform (callable, optional): Optional transform to be applied 17 | on a sample. 18 | """ 19 | self.landmarks_frame = pd.read_csv(csv_file) 20 | self.root_dir = root_dir 21 | self.transform = transform 22 | self.landmark_bool = landmark_bool 23 | 24 | def __len__(self): 25 | return len(self.landmarks_frame) 26 | 27 | def __getitem__(self, idx): 28 | if torch.is_tensor(idx): 29 | idx = idx.tolist() 30 | 31 | img_name = os.path.join(self.root_dir, 32 | self.landmarks_frame.iloc[idx, 0]) 33 | image = PIL.Image.open(img_name) 34 | 35 | if self.landmark_bool: 36 | landmarks = self.landmarks_frame.iloc[idx, 1:] 37 | landmarks = np.array([landmarks]) 38 | landmarks = landmarks.astype('float').reshape(-1, 3) 39 | else: 40 | landmarks = 0 41 | sample = (image, landmarks) 42 | 43 | if self.transform: 44 | sample = self.transform(sample) 45 | 46 | return sample 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /datasets/COCO/__pycache__/COCO_model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/datasets/COCO/__pycache__/COCO_model.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/download_datasets.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":5,"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.7.8"},"colab":{"name":"download_datasets.ipynb","provenance":[],"collapsed_sections":[]}},"cells":[{"cell_type":"markdown","metadata":{"id":"jLZfxY5uUGNP"},"source":["Mount Drive"],"id":"jLZfxY5uUGNP"},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"_Lrz2cTFUJsL","executionInfo":{"status":"ok","timestamp":1615064124815,"user_tz":-120,"elapsed":22012,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"ed2e024d-1d08-4294-edf3-c9e166d9fa51"},"source":["your_path = ''\r\n","from google.colab import drive\r\n","drive.mount('/content/gdrive')\r\n","path = '/content/gdrive/My Drive/'+your_path\r\n","import sys\r\n","sys.path.append(path)"],"id":"_Lrz2cTFUJsL","execution_count":null,"outputs":[{"output_type":"stream","text":["Mounted at /content/gdrive\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"magnetic-trinidad"},"source":["# download coco"],"id":"magnetic-trinidad"},{"cell_type":"code","metadata":{"id":"russian-retail"},"source":["import requests, zipfile, io\n","zip_url = \"http://images.cocodataset.org/zips/train2014.zip\"\n","print(type(zip_url))\n","r = requests.get(zip_url)\n","print(type(r))\n","z = zipfile.ZipFile(io.BytesIO(r.content))\n","\n","z.extractall(path+'/datasets/COCO')"],"id":"russian-retail","execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"mounted-omaha"},"source":["### download coco annotations :"],"id":"mounted-omaha"},{"cell_type":"code","metadata":{"id":"silent-estonia","outputId":"ad1b9142-d967-4041-b606-815a94676f44"},"source":["import requests, zipfile, io\n","zip_url = \"http://images.cocodataset.org/annotations/annotations_trainval2014.zip\"\n","print(type(zip_url))\n","r = requests.get(zip_url)\n","print(type(r))\n","z = zipfile.ZipFile(io.BytesIO(r.content))\n","\n","z.extractall(path+'/datasets/COCO')"],"id":"silent-estonia","execution_count":null,"outputs":[{"output_type":"stream","text":["\n","\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"million-solution"},"source":["### check dataset :"],"id":"million-solution"},{"cell_type":"code","metadata":{"id":"identical-witness","outputId":"a761c727-0da1-4622-e097-67d222d95689"},"source":["import torch\n","from torchvision import datasets, transforms\n","from torch import nn, optim\n","import torch.nn.functional as F\n","import matplotlib.pyplot as plt\n","import numpy as np\n","import copy\n","import cv2\n","import pycocotools\n","\n","cap = datasets.CocoCaptions(root = 'COCO/train2014',\n"," annFile = 'COCO/annotations/captions_train2014.json',\n"," transform=transforms.ToTensor())\n","img, target = cap[4] # load 4th sample\n","print('image type:',type(img))\n","print(\"Image Size: \", img.size())\n","print(target)\n","%matplotlib inline\n","plt.imshow(img.permute(1, 2, 0))\n","plt.show()"],"id":"identical-witness","execution_count":null,"outputs":[{"output_type":"stream","text":["loading annotations into memory...\n","Done (t=0.92s)\n","creating index...\n","index created!\n","image type: \n","Image Size: torch.Size([3, 640, 481])\n","['Woman in swim suit holding parasol on sunny day.', 'A woman posing for the camera, holding a pink, open umbrella and wearing a bright, floral, ruched bathing suit, by a life guard stand with lake, green trees, and a blue sky with a few clouds behind.', 'A woman in a floral swimsuit holds a pink umbrella.', 'A woman with an umbrella near the sea', 'A girl in a bathing suit with a pink umbrella.']\n"],"name":"stdout"},{"output_type":"display_data","data":{"image/png":"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\n","text/plain":["
"]},"metadata":{"tags":[],"needs_background":"light"}}]},{"cell_type":"markdown","metadata":{"id":"increased-shooting"},"source":["# download hpatches dataset"],"id":"increased-shooting"},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"extended-burden","executionInfo":{"status":"ok","timestamp":1615064279009,"user_tz":-120,"elapsed":65497,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"ea472041-7d1e-4b1a-c92b-85655ebcc665"},"source":["import urllib.request, tarfile\n","tar_url = \"http://icvl.ee.ic.ac.uk/vbalnt/hpatches/hpatches-sequences-release.tar.gz\"\n","print(type(tar_url))\n","r = urllib.request.urlopen(tar_url)\n","print(type(r))\n","tar_file = tarfile.open(fileobj=r, mode=\"r|gz\")\n","\n","tar_file.extractall(path+'/datasets')"],"id":"extended-burden","execution_count":null,"outputs":[{"output_type":"stream","text":["\n","\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"distinct-friendship"},"source":["### change images to jpg"],"id":"distinct-friendship"},{"cell_type":"code","metadata":{"id":"thorough-recovery"},"source":["import glob\n","import os\n","from PIL import Image\n","path_list = glob.glob(path+'/datasets'+'/hpatches-sequences-release/*/*.ppm')\n","\n","for path in path_list:\n"," im = Image.open(path)\n"," new_path = path.replace('.ppm', '.jpg')\n"," im.save(new_path)\n"," os.remove(path)\n"],"id":"thorough-recovery","execution_count":null,"outputs":[]}]} -------------------------------------------------------------------------------- /datasets/synthetic_shapes/__pycache__/synthetic_shapes_functions.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/datasets/synthetic_shapes/__pycache__/synthetic_shapes_functions.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/synthetic_shapes/synthetic_shapes_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 as cv 3 | import math 4 | import kornia as K 5 | 6 | #class synthetic_func: 7 | def ccw(A, B, C, dim): 8 | """ Check if the points are listed in counter-clockwise order """ 9 | if dim == 2: # only 2 dimensions 10 | return((C[:, 1] - A[:, 1]) * (B[:, 0] - A[:, 0]) 11 | > (B[:, 1] - A[:, 1]) * (C[:, 0] - A[:, 0])) 12 | else: # dim should be equal to 3 13 | return((C[:, 1, :] - A[:, 1, :]) 14 | * (B[:, 0, :] - A[:, 0, :]) 15 | > (B[:, 1, :] - A[:, 1, :]) 16 | * (C[:, 0, :] - A[:, 0, :])) 17 | 18 | def intersect(A, B, C, D, dim): 19 | """ Return true if line segments AB and CD intersect """ 20 | return np.any((ccw(A, C, D, dim) != ccw(B, C, D, dim)) & 21 | (ccw(A, B, C, dim) != ccw(A, B, D, dim))) 22 | 23 | def overlap(center, rad, centers, rads): 24 | """ Check that the circle with (center, rad) 25 | doesn't overlap with the other circles """ 26 | flag = False 27 | for i in range(len(rads)): 28 | if np.linalg.norm(center - centers[i]) + min(rad, rads[i]) < max(rad, rads[i]): 29 | flag = True 30 | break 31 | return flag 32 | 33 | def get_different_color(previous_colors, min_dist=50, max_count=20): 34 | """ Output a color that contrasts with the previous colors 35 | Parameters: 36 | previous_colors: np.array of the previous colors 37 | min_dist: the difference between the new color and 38 | the previous colors must be at least min_dist 39 | max_count: maximal number of iterations 40 | """ 41 | color = random_state.randint(256) 42 | count = 0 43 | while np.any(np.abs(previous_colors - color) < min_dist) and count < max_count: 44 | count += 1 45 | color = random_state.randint(256) 46 | return color 47 | 48 | def generate_custom_background(size, background_color, nb_blobs=3000, 49 | kernel_boundaries=(50, 100)): 50 | """ Generate a customized background to fill the shapes 51 | Parameters: 52 | background_color: average color of the background image 53 | nb_blobs: number of circles to draw 54 | kernel_boundaries: interval of the possible sizes of the kernel 55 | """ 56 | img = np.zeros(size, dtype=np.uint8) 57 | img = img + get_random_color(background_color) 58 | blobs = np.concatenate([np.random.randint(0, size[1], size=(nb_blobs, 1)), 59 | np.random.randint(0, size[0], size=(nb_blobs, 1))], 60 | axis=1) 61 | for i in range(nb_blobs): 62 | col = get_random_color(background_color) 63 | cv.circle(img, (blobs[i][0], blobs[i][1]), 64 | np.random.randint(20), col, -1) 65 | kernel_size = np.random.randint(kernel_boundaries[0], kernel_boundaries[1]) 66 | cv.blur(img, (kernel_size, kernel_size), img) 67 | return img 68 | 69 | random_state = np.random.RandomState(None) 70 | 71 | def get_random_color(background_color): 72 | """ Output a random scalar in grayscale with a least a small 73 | contrast with the background color """ 74 | color = random_state.randint(256) 75 | if abs(color - background_color) < 30: # not enough contrast 76 | color = (color + 128) % 256 77 | return color 78 | 79 | def keep_points_inside(points, size): 80 | """ Keep only the points whose coordinates are inside the dimensions of 81 | the image of size 'size' """ 82 | mask = (points[:, 0] >= 0) & (points[:, 0] < size[1]) &\ 83 | (points[:, 1] >= 0) & (points[:, 1] < size[0]) 84 | return points[mask, :] 85 | 86 | def draw_stripes(img, max_nb_cols=13, min_width_ratio=0.04, 87 | transform_params=(0.05, 0.15)): 88 | """ Draw stripes in a distorted rectangle and output the interest points 89 | Parameters: 90 | max_nb_cols: maximal number of stripes to be drawn 91 | min_width_ratio: the minimal width of a stripe is 92 | min_width_ratio * smallest dimension of the image 93 | transform_params: set the range of the parameters of the transformations 94 | """ 95 | background_color = int(np.mean(img)) 96 | # Create the grid 97 | board_size = (int(img.shape[0] * (1 + random_state.rand())), 98 | int(img.shape[1] * (1 + random_state.rand()))) 99 | col = random_state.randint(5, max_nb_cols) # number of cols 100 | cols = np.concatenate([board_size[1] * random_state.rand(col - 1), 101 | np.array([0, board_size[1] - 1])], axis=0) 102 | cols = np.unique(cols.astype(int)) 103 | # Remove the indices that are too close 104 | min_dim = min(img.shape) 105 | min_width = min_dim * min_width_ratio 106 | cols = cols[(np.concatenate([cols[1:], 107 | np.array([board_size[1] + min_width])], 108 | axis=0) - cols) >= min_width] 109 | col = cols.shape[0] - 1 # update the number of cols 110 | cols = np.reshape(cols, (col + 1, 1)) 111 | cols1 = np.concatenate([cols, np.zeros((col + 1, 1), np.int32)], axis=1) 112 | cols2 = np.concatenate([cols, 113 | (board_size[0] - 1) * np.ones((col + 1, 1), np.int32)], 114 | axis=1) 115 | points = np.concatenate([cols1, cols2], axis=0) 116 | 117 | # Warp the grid using an affine transformation and an homography 118 | # The parameters of the transformations are constrained 119 | # to get transformations not too far-fetched 120 | # Prepare the matrices 121 | alpha_affine = np.max(img.shape) * (transform_params[0] 122 | + random_state.rand() * transform_params[1]) 123 | center_square = np.float32(img.shape) // 2 124 | square_size = min(img.shape) // 3 125 | pts1 = np.float32([center_square + square_size, 126 | [center_square[0]+square_size, center_square[1]-square_size], 127 | center_square - square_size, 128 | [center_square[0]-square_size, center_square[1]+square_size]]) 129 | pts2 = pts1 + random_state.uniform(-alpha_affine, 130 | alpha_affine, 131 | size=pts1.shape).astype(np.float32) 132 | affine_transform = cv.getAffineTransform(pts1[:3], pts2[:3]) 133 | pts2 = pts1 + random_state.uniform(-alpha_affine / 2, 134 | alpha_affine / 2, 135 | size=pts1.shape).astype(np.float32) 136 | perspective_transform = cv.getPerspectiveTransform(pts1, pts2) 137 | 138 | # Apply the affine transformation 139 | points = np.transpose(np.concatenate((points, 140 | np.ones((2 * (col + 1), 1))), 141 | axis=1)) 142 | warped_points = np.transpose(np.dot(affine_transform, points)) 143 | 144 | # Apply the homography 145 | warped_col0 = np.add(np.sum(np.multiply(warped_points, 146 | perspective_transform[0, :2]), axis=1), 147 | perspective_transform[0, 2]) 148 | warped_col1 = np.add(np.sum(np.multiply(warped_points, 149 | perspective_transform[1, :2]), axis=1), 150 | perspective_transform[1, 2]) 151 | warped_col2 = np.add(np.sum(np.multiply(warped_points, 152 | perspective_transform[2, :2]), axis=1), 153 | perspective_transform[2, 2]) 154 | warped_col0 = np.divide(warped_col0, warped_col2) 155 | warped_col1 = np.divide(warped_col1, warped_col2) 156 | warped_points = np.concatenate([warped_col0[:, None], warped_col1[:, None]], axis=1) 157 | warped_points = warped_points.astype(int) 158 | 159 | # Fill the rectangles 160 | color = get_random_color(background_color) 161 | for i in range(col): 162 | color = (color + 128 + random_state.randint(-30, 30)) % 256 163 | cv.fillConvexPoly(img, np.array([(warped_points[i, 0], 164 | warped_points[i, 1]), 165 | (warped_points[i+1, 0], 166 | warped_points[i+1, 1]), 167 | (warped_points[i+col+2, 0], 168 | warped_points[i+col+2, 1]), 169 | (warped_points[i+col+1, 0], 170 | warped_points[i+col+1, 1])]), 171 | color) 172 | 173 | # Draw lines on the boundaries of the stripes at random 174 | nb_rows = random_state.randint(2, 5) 175 | nb_cols = random_state.randint(2, col + 2) 176 | thickness = random_state.randint(min_dim * 0.01, min_dim * 0.015) 177 | for _ in range(nb_rows): 178 | row_idx = random_state.choice([0, col + 1]) 179 | col_idx1 = random_state.randint(col + 1) 180 | col_idx2 = random_state.randint(col + 1) 181 | color = get_random_color(background_color) 182 | cv.line(img, (warped_points[row_idx + col_idx1, 0], 183 | warped_points[row_idx + col_idx1, 1]), 184 | (warped_points[row_idx + col_idx2, 0], 185 | warped_points[row_idx + col_idx2, 1]), 186 | color, thickness) 187 | for _ in range(nb_cols): 188 | col_idx = random_state.randint(col + 1) 189 | color = get_random_color(background_color) 190 | cv.line(img, (warped_points[col_idx, 0], 191 | warped_points[col_idx, 1]), 192 | (warped_points[col_idx + col + 1, 0], 193 | warped_points[col_idx + col + 1, 1]), 194 | color, thickness) 195 | 196 | # Keep only the points inside the image 197 | points = keep_points_inside(warped_points, img.shape[:2]) 198 | return points 199 | 200 | 201 | def draw_lines(img, nb_lines=10): 202 | """ Draw random lines and output the positions of the endpoints 203 | Parameters: 204 | nb_lines: maximal number of lines 205 | """ 206 | num_lines = random_state.randint(1, nb_lines) 207 | segments = np.empty((0, 4), dtype=np.int) 208 | points = np.empty((0, 2), dtype=np.int) 209 | background_color = int(np.mean(img)) 210 | min_dim = min(img.shape) 211 | for i in range(num_lines): 212 | x1 = random_state.randint(img.shape[1]) 213 | y1 = random_state.randint(img.shape[0]) 214 | p1 = np.array([[x1, y1]]) 215 | x2 = random_state.randint(img.shape[1]) 216 | y2 = random_state.randint(img.shape[0]) 217 | p2 = np.array([[x2, y2]]) 218 | # Check that there is no overlap 219 | if intersect(segments[:, 0:2], segments[:, 2:4], p1, p2, 2): 220 | continue 221 | segments = np.concatenate([segments, np.array([[x1, y1, x2, y2]])], axis=0) 222 | col = get_random_color(background_color) 223 | thickness = random_state.randint(min_dim * 0.01, min_dim * 0.02) 224 | cv.line(img, (x1, y1), (x2, y2), col, thickness) 225 | points = np.concatenate([points, np.array([[x1, y1], [x2, y2]])], axis=0) 226 | return points 227 | 228 | def generate_background(size=(960, 1280), nb_blobs=100, min_rad_ratio=0.01, 229 | max_rad_ratio=0.05, min_kernel_size=50, max_kernel_size=300): 230 | """ Generate a customized background image 231 | Parameters: 232 | size: size of the image 233 | nb_blobs: number of circles to draw 234 | min_rad_ratio: the radius of blobs is at least min_rad_size * max(size) 235 | max_rad_ratio: the radius of blobs is at most max_rad_size * max(size) 236 | min_kernel_size: minimal size of the kernel 237 | max_kernel_size: maximal size of the kernel 238 | """ 239 | img = np.zeros(size, dtype=np.uint8) 240 | dim = max(size) 241 | cv.randu(img, 0, 255) 242 | cv.threshold(img, random_state.randint(256), 255, cv.THRESH_BINARY, img) 243 | background_color = int(np.mean(img)) 244 | blobs = np.concatenate([random_state.randint(0, size[1], size=(nb_blobs, 1)), 245 | random_state.randint(0, size[0], size=(nb_blobs, 1))], 246 | axis=1) 247 | for i in range(nb_blobs): 248 | col = get_random_color(background_color) 249 | cv.circle(img, (blobs[i][0], blobs[i][1]), 250 | np.random.randint(int(dim * min_rad_ratio), 251 | int(dim * max_rad_ratio)), 252 | col, -1) 253 | kernel_size = random_state.randint(min_kernel_size, max_kernel_size) 254 | cv.blur(img, (kernel_size, kernel_size), img) 255 | return img 256 | 257 | def angle_between_vectors(v1, v2): 258 | """ Compute the angle (in rad) between the two vectors v1 and v2. """ 259 | v1_u = v1 / np.linalg.norm(v1) 260 | v2_u = v2 / np.linalg.norm(v2) 261 | return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)) 262 | 263 | def draw_multiple_polygons(img, max_sides=8, nb_polygons=30, **extra): 264 | """ Draw multiple polygons with a random number of corners 265 | and return the corner points 266 | Parameters: 267 | max_sides: maximal number of sides + 1 268 | nb_polygons: maximal number of polygons 269 | """ 270 | segments = np.empty((0, 4), dtype=np.int) 271 | centers = [] 272 | rads = [] 273 | points = np.empty((0, 2), dtype=np.int) 274 | background_color = int(np.mean(img)) 275 | for i in range(nb_polygons): 276 | num_corners = random_state.randint(3, max_sides) 277 | min_dim = min(img.shape[0], img.shape[1]) 278 | rad = max(random_state.rand() * min_dim / 2, min_dim / 10) 279 | x = random_state.randint(rad, img.shape[1] - rad) # Center of a circle 280 | y = random_state.randint(rad, img.shape[0] - rad) 281 | 282 | # Sample num_corners points inside the circle 283 | slices = np.linspace(0, 2 * math.pi, num_corners + 1) 284 | angles = [slices[i] + random_state.rand() * (slices[i+1] - slices[i]) 285 | for i in range(num_corners)] 286 | new_points = [[int(x + max(random_state.rand(), 0.4) * rad * math.cos(a)), 287 | int(y + max(random_state.rand(), 0.4) * rad * math.sin(a))] 288 | for a in angles] 289 | new_points = np.array(new_points) 290 | 291 | # Filter the points that are too close or that have an angle too flat 292 | norms = [np.linalg.norm(new_points[(i-1) % num_corners, :] 293 | - new_points[i, :]) for i in range(num_corners)] 294 | mask = np.array(norms) > 0.01 295 | new_points = new_points[mask, :] 296 | num_corners = new_points.shape[0] 297 | corner_angles = [angle_between_vectors(new_points[(i-1) % num_corners, :] - 298 | new_points[i, :], 299 | new_points[(i+1) % num_corners, :] - 300 | new_points[i, :]) 301 | for i in range(num_corners)] 302 | mask = np.array(corner_angles) < (2 * math.pi / 3) 303 | new_points = new_points[mask, :] 304 | num_corners = new_points.shape[0] 305 | if num_corners < 3: # not enough corners 306 | continue 307 | 308 | new_segments = np.zeros((1, 4, num_corners)) 309 | new_segments[:, 0, :] = [new_points[i][0] for i in range(num_corners)] 310 | new_segments[:, 1, :] = [new_points[i][1] for i in range(num_corners)] 311 | new_segments[:, 2, :] = [new_points[(i+1) % num_corners][0] 312 | for i in range(num_corners)] 313 | new_segments[:, 3, :] = [new_points[(i+1) % num_corners][1] 314 | for i in range(num_corners)] 315 | 316 | # Check that the polygon will not overlap with pre-existing shapes 317 | if intersect(segments[:, 0:2, None], 318 | segments[:, 2:4, None], 319 | new_segments[:, 0:2, :], 320 | new_segments[:, 2:4, :], 321 | 3) or overlap(np.array([x, y]), rad, centers, rads): 322 | continue 323 | centers.append(np.array([x, y])) 324 | rads.append(rad) 325 | new_segments = np.reshape(np.swapaxes(new_segments, 0, 2), (-1, 4)) 326 | segments = np.concatenate([segments, new_segments], axis=0) 327 | 328 | # Color the polygon with a custom background 329 | corners = new_points.reshape((-1, 1, 2)) 330 | mask = np.zeros(img.shape, np.uint8) 331 | custom_background = generate_custom_background(img.shape, background_color, 332 | **extra) 333 | cv.fillPoly(mask, [corners], 255) 334 | locs = np.where(mask != 0) 335 | img[locs[0], locs[1]] = custom_background[locs[0], locs[1]] 336 | points = np.concatenate([points, new_points], axis=0) 337 | return points 338 | 339 | def draw_polygon(img, max_sides=8): 340 | """ Draw a polygon with a random number of corners 341 | and return the corner points 342 | Parameters: 343 | max_sides: maximal number of sides + 1 344 | """ 345 | num_corners = random_state.randint(3, max_sides) 346 | min_dim = min(img.shape[0], img.shape[1]) 347 | rad = max(random_state.rand() * min_dim / 2, min_dim / 10) 348 | x = random_state.randint(rad, img.shape[1] - rad) # Center of a circle 349 | y = random_state.randint(rad, img.shape[0] - rad) 350 | 351 | # Sample num_corners points inside the circle 352 | slices = np.linspace(0, 2 * math.pi, num_corners + 1) 353 | angles = [slices[i] + random_state.rand() * (slices[i+1] - slices[i]) 354 | for i in range(num_corners)] 355 | points = np.array([[int(x + max(random_state.rand(), 0.4) * rad * math.cos(a)), 356 | int(y + max(random_state.rand(), 0.4) * rad * math.sin(a))] 357 | for a in angles]) 358 | 359 | # Filter the points that are too close or that have an angle too flat 360 | norms = [np.linalg.norm(points[(i-1) % num_corners, :] 361 | - points[i, :]) for i in range(num_corners)] 362 | mask = np.array(norms) > 0.01 363 | points = points[mask, :] 364 | num_corners = points.shape[0] 365 | corner_angles = [angle_between_vectors(points[(i-1) % num_corners, :] - 366 | points[i, :], 367 | points[(i+1) % num_corners, :] - 368 | points[i, :]) 369 | for i in range(num_corners)] 370 | mask = np.array(corner_angles) < (2 * math.pi / 3) 371 | points = points[mask, :] 372 | num_corners = points.shape[0] 373 | if num_corners < 3: # not enough corners 374 | return draw_polygon(img, max_sides) 375 | 376 | corners = points.reshape((-1, 1, 2)) 377 | col = get_random_color(int(np.mean(img))) 378 | cv.fillPoly(img, [corners], col) 379 | return points 380 | 381 | def draw_multiple_polygons(img, max_sides=8, nb_polygons=30, **extra): 382 | """ Draw multiple polygons with a random number of corners 383 | and return the corner points 384 | Parameters: 385 | max_sides: maximal number of sides + 1 386 | nb_polygons: maximal number of polygons 387 | """ 388 | segments = np.empty((0, 4), dtype=np.int) 389 | centers = [] 390 | rads = [] 391 | points = np.empty((0, 2), dtype=np.int) 392 | background_color = int(np.mean(img)) 393 | for i in range(nb_polygons): 394 | num_corners = random_state.randint(3, max_sides) 395 | min_dim = min(img.shape[0], img.shape[1]) 396 | rad = max(random_state.rand() * min_dim / 2, min_dim / 10) 397 | x = random_state.randint(rad, img.shape[1] - rad) # Center of a circle 398 | y = random_state.randint(rad, img.shape[0] - rad) 399 | 400 | # Sample num_corners points inside the circle 401 | slices = np.linspace(0, 2 * math.pi, num_corners + 1) 402 | angles = [slices[i] + random_state.rand() * (slices[i+1] - slices[i]) 403 | for i in range(num_corners)] 404 | new_points = [[int(x + max(random_state.rand(), 0.4) * rad * math.cos(a)), 405 | int(y + max(random_state.rand(), 0.4) * rad * math.sin(a))] 406 | for a in angles] 407 | new_points = np.array(new_points) 408 | 409 | # Filter the points that are too close or that have an angle too flat 410 | norms = [np.linalg.norm(new_points[(i-1) % num_corners, :] 411 | - new_points[i, :]) for i in range(num_corners)] 412 | mask = np.array(norms) > 0.01 413 | new_points = new_points[mask, :] 414 | num_corners = new_points.shape[0] 415 | corner_angles = [angle_between_vectors(new_points[(i-1) % num_corners, :] - 416 | new_points[i, :], 417 | new_points[(i+1) % num_corners, :] - 418 | new_points[i, :]) 419 | for i in range(num_corners)] 420 | mask = np.array(corner_angles) < (2 * math.pi / 3) 421 | new_points = new_points[mask, :] 422 | num_corners = new_points.shape[0] 423 | if num_corners < 3: # not enough corners 424 | continue 425 | 426 | new_segments = np.zeros((1, 4, num_corners)) 427 | new_segments[:, 0, :] = [new_points[i][0] for i in range(num_corners)] 428 | new_segments[:, 1, :] = [new_points[i][1] for i in range(num_corners)] 429 | new_segments[:, 2, :] = [new_points[(i+1) % num_corners][0] 430 | for i in range(num_corners)] 431 | new_segments[:, 3, :] = [new_points[(i+1) % num_corners][1] 432 | for i in range(num_corners)] 433 | 434 | # Check that the polygon will not overlap with pre-existing shapes 435 | if intersect(segments[:, 0:2, None], 436 | segments[:, 2:4, None], 437 | new_segments[:, 0:2, :], 438 | new_segments[:, 2:4, :], 439 | 3) or overlap(np.array([x, y]), rad, centers, rads): 440 | continue 441 | centers.append(np.array([x, y])) 442 | rads.append(rad) 443 | new_segments = np.reshape(np.swapaxes(new_segments, 0, 2), (-1, 4)) 444 | segments = np.concatenate([segments, new_segments], axis=0) 445 | 446 | # Color the polygon with a custom background 447 | corners = new_points.reshape((-1, 1, 2)) 448 | mask = np.zeros(img.shape, np.uint8) 449 | custom_background = generate_custom_background(img.shape, background_color, 450 | **extra) 451 | cv.fillPoly(mask, [corners], 255) 452 | locs = np.where(mask != 0) 453 | img[locs[0], locs[1]] = custom_background[locs[0], locs[1]] 454 | points = np.concatenate([points, new_points], axis=0) 455 | return points 456 | 457 | def draw_ellipses(img, nb_ellipses=20): 458 | """ Draw several ellipses 459 | Parameters: 460 | nb_ellipses: maximal number of ellipses 461 | """ 462 | centers = np.empty((0, 2), dtype=np.int) 463 | rads = np.empty((0, 1), dtype=np.int) 464 | min_dim = min(img.shape[0], img.shape[1]) / 4 465 | background_color = int(np.mean(img)) 466 | for i in range(nb_ellipses): 467 | ax = int(max(random_state.rand() * min_dim, min_dim / 5)) 468 | ay = int(max(random_state.rand() * min_dim, min_dim / 5)) 469 | max_rad = max(ax, ay) 470 | x = random_state.randint(max_rad, img.shape[1] - max_rad) # center 471 | y = random_state.randint(max_rad, img.shape[0] - max_rad) 472 | new_center = np.array([[x, y]]) 473 | 474 | # Check that the ellipsis will not overlap with pre-existing shapes 475 | diff = centers - new_center 476 | if np.any(max_rad > (np.sqrt(np.sum(diff * diff, axis=1)) - rads)): 477 | continue 478 | centers = np.concatenate([centers, new_center], axis=0) 479 | rads = np.concatenate([rads, np.array([[max_rad]])], axis=0) 480 | 481 | col = get_random_color(background_color) 482 | angle = random_state.rand() * 90 483 | img = cv.ellipse(img, (x, y), (ax, ay), angle, 0, 360, col, -1) 484 | return np.empty((0, 2), dtype=np.int) 485 | 486 | 487 | def draw_star(img, nb_branches=6): 488 | """ Draw a star and output the interest points 489 | Parameters: 490 | nb_branches: number of branches of the star 491 | """ 492 | num_branches = random_state.randint(3, nb_branches) 493 | min_dim = min(img.shape[0], img.shape[1]) 494 | thickness = random_state.randint(min_dim * 0.01, min_dim * 0.02) 495 | rad = max(random_state.rand() * min_dim / 2, min_dim / 5) 496 | x = random_state.randint(rad, img.shape[1] - rad) # select the center of a circle 497 | y = random_state.randint(rad, img.shape[0] - rad) 498 | # Sample num_branches points inside the circle 499 | slices = np.linspace(0, 2 * math.pi, num_branches + 1) 500 | angles = [slices[i] + random_state.rand() * (slices[i+1] - slices[i]) 501 | for i in range(num_branches)] 502 | points = np.array([[int(x + max(random_state.rand(), 0.3) * rad * math.cos(a)), 503 | int(y + max(random_state.rand(), 0.3) * rad * math.sin(a))] 504 | for a in angles]) 505 | points = np.concatenate(([[x, y]], points), axis=0) 506 | background_color = int(np.mean(img)) 507 | for i in range(1, num_branches + 1): 508 | col = get_random_color(background_color) 509 | cv.line(img, (points[0][0], points[0][1]), 510 | (points[i][0], points[i][1]), 511 | col, thickness) 512 | return points 513 | 514 | 515 | def draw_checkerboard(img, max_rows=7, max_cols=7, transform_params=(0.05, 0.15)): 516 | """ Draw a checkerboard and output the interest points 517 | Parameters: 518 | max_rows: maximal number of rows + 1 519 | max_cols: maximal number of cols + 1 520 | transform_params: set the range of the parameters of the transformations""" 521 | background_color = int(np.mean(img)) 522 | # Create the grid 523 | rows = random_state.randint(3, max_rows) # number of rows 524 | cols = random_state.randint(3, max_cols) # number of cols 525 | s = min((img.shape[1] - 1) // cols, (img.shape[0] - 1) // rows) # size of a cell 526 | x_coord = np.tile(range(cols + 1), 527 | rows + 1).reshape(((rows + 1) * (cols + 1), 1)) 528 | y_coord = np.repeat(range(rows + 1), 529 | cols + 1).reshape(((rows + 1) * (cols + 1), 1)) 530 | points = s * np.concatenate([x_coord, y_coord], axis=1) 531 | 532 | # Warp the grid using an affine transformation and an homography 533 | # The parameters of the transformations are constrained 534 | # to get transformations not too far-fetched 535 | alpha_affine = np.max(img.shape) * (transform_params[0] 536 | + random_state.rand() * transform_params[1]) 537 | center_square = np.float32(img.shape) // 2 538 | min_dim = min(img.shape) 539 | square_size = min_dim // 3 540 | pts1 = np.float32([center_square + square_size, 541 | [center_square[0]+square_size, center_square[1]-square_size], 542 | center_square - square_size, 543 | [center_square[0]-square_size, center_square[1]+square_size]]) 544 | pts2 = pts1 + random_state.uniform(-alpha_affine, 545 | alpha_affine, 546 | size=pts1.shape).astype(np.float32) 547 | affine_transform = cv.getAffineTransform(pts1[:3], pts2[:3]) 548 | pts2 = pts1 + random_state.uniform(-alpha_affine / 2, 549 | alpha_affine / 2, 550 | size=pts1.shape).astype(np.float32) 551 | perspective_transform = cv.getPerspectiveTransform(pts1, pts2) 552 | 553 | # Apply the affine transformation 554 | points = np.transpose(np.concatenate((points, 555 | np.ones(((rows + 1) * (cols + 1), 1))), 556 | axis=1)) 557 | warped_points = np.transpose(np.dot(affine_transform, points)) 558 | 559 | # Apply the homography 560 | warped_col0 = np.add(np.sum(np.multiply(warped_points, 561 | perspective_transform[0, :2]), axis=1), 562 | perspective_transform[0, 2]) 563 | warped_col1 = np.add(np.sum(np.multiply(warped_points, 564 | perspective_transform[1, :2]), axis=1), 565 | perspective_transform[1, 2]) 566 | warped_col2 = np.add(np.sum(np.multiply(warped_points, 567 | perspective_transform[2, :2]), axis=1), 568 | perspective_transform[2, 2]) 569 | warped_col0 = np.divide(warped_col0, warped_col2) 570 | warped_col1 = np.divide(warped_col1, warped_col2) 571 | warped_points = np.concatenate([warped_col0[:, None], warped_col1[:, None]], axis=1) 572 | warped_points = warped_points.astype(int) 573 | 574 | # Fill the rectangles 575 | colors = np.zeros((rows * cols,), np.int32) 576 | for i in range(rows): 577 | for j in range(cols): 578 | # Get a color that contrast with the neighboring cells 579 | if i == 0 and j == 0: 580 | col = get_random_color(background_color) 581 | else: 582 | neighboring_colors = [] 583 | if i != 0: 584 | neighboring_colors.append(colors[(i-1) * cols + j]) 585 | if j != 0: 586 | neighboring_colors.append(colors[i * cols + j - 1]) 587 | col = get_different_color(np.array(neighboring_colors)) 588 | colors[i * cols + j] = col 589 | # Fill the cell 590 | cv.fillConvexPoly(img, np.array([(warped_points[i * (cols + 1) + j, 0], 591 | warped_points[i * (cols + 1) + j, 1]), 592 | (warped_points[i * (cols + 1) + j + 1, 0], 593 | warped_points[i * (cols + 1) + j + 1, 1]), 594 | (warped_points[(i + 1) 595 | * (cols + 1) + j + 1, 0], 596 | warped_points[(i + 1) 597 | * (cols + 1) + j + 1, 1]), 598 | (warped_points[(i + 1) 599 | * (cols + 1) + j, 0], 600 | warped_points[(i + 1) 601 | * (cols + 1) + j, 1])]), 602 | col) 603 | 604 | # Draw lines on the boundaries of the board at random 605 | nb_rows = random_state.randint(2, rows + 2) 606 | nb_cols = random_state.randint(2, cols + 2) 607 | thickness = random_state.randint(min_dim * 0.01, min_dim * 0.015) 608 | for _ in range(nb_rows): 609 | row_idx = random_state.randint(rows + 1) 610 | col_idx1 = random_state.randint(cols + 1) 611 | col_idx2 = random_state.randint(cols + 1) 612 | col = get_random_color(background_color) 613 | cv.line(img, (warped_points[row_idx * (cols + 1) + col_idx1, 0], 614 | warped_points[row_idx * (cols + 1) + col_idx1, 1]), 615 | (warped_points[row_idx * (cols + 1) + col_idx2, 0], 616 | warped_points[row_idx * (cols + 1) + col_idx2, 1]), 617 | col, thickness) 618 | for _ in range(nb_cols): 619 | col_idx = random_state.randint(cols + 1) 620 | row_idx1 = random_state.randint(rows + 1) 621 | row_idx2 = random_state.randint(rows + 1) 622 | col = get_random_color(background_color) 623 | cv.line(img, (warped_points[row_idx1 * (cols + 1) + col_idx, 0], 624 | warped_points[row_idx1 * (cols + 1) + col_idx, 1]), 625 | (warped_points[row_idx2 * (cols + 1) + col_idx, 0], 626 | warped_points[row_idx2 * (cols + 1) + col_idx, 1]), 627 | col, thickness) 628 | 629 | # Keep only the points inside the image 630 | points = keep_points_inside(warped_points, img.shape[:2]) 631 | return points 632 | 633 | def draw_cube(img, min_size_ratio=0.2, min_angle_rot=math.pi / 10, 634 | scale_interval=(0.4, 0.6), trans_interval=(0.5, 0.2)): 635 | """ Draw a 2D projection of a cube and output the corners that are visible 636 | Parameters: 637 | min_size_ratio: min(img.shape) * min_size_ratio is the smallest achievable 638 | cube side size 639 | min_angle_rot: minimal angle of rotation 640 | scale_interval: the scale is between scale_interval[0] and 641 | scale_interval[0]+scale_interval[1] 642 | trans_interval: the translation is between img.shape*trans_interval[0] and 643 | img.shape*(trans_interval[0] + trans_interval[1]) 644 | """ 645 | # Generate a cube and apply to it an affine transformation 646 | # The order matters! 647 | # The indices of two adjacent vertices differ only of one bit (as in Gray codes) 648 | background_color = int(np.mean(img)) 649 | min_dim = min(img.shape[:2]) 650 | min_side = min_dim * min_size_ratio 651 | lx = min_side + random_state.rand() * 2 * min_dim / 3 # dimensions of the cube 652 | ly = min_side + random_state.rand() * 2 * min_dim / 3 653 | lz = min_side + random_state.rand() * 2 * min_dim / 3 654 | cube = np.array([[0, 0, 0], 655 | [lx, 0, 0], 656 | [0, ly, 0], 657 | [lx, ly, 0], 658 | [0, 0, lz], 659 | [lx, 0, lz], 660 | [0, ly, lz], 661 | [lx, ly, lz]]) 662 | rot_angles = random_state.rand(3) * 3 * math.pi / 10. + math.pi / 10. 663 | rotation_1 = np.array([[math.cos(rot_angles[0]), -math.sin(rot_angles[0]), 0], 664 | [math.sin(rot_angles[0]), math.cos(rot_angles[0]), 0], 665 | [0, 0, 1]]) 666 | rotation_2 = np.array([[1, 0, 0], 667 | [0, math.cos(rot_angles[1]), -math.sin(rot_angles[1])], 668 | [0, math.sin(rot_angles[1]), math.cos(rot_angles[1])]]) 669 | rotation_3 = np.array([[math.cos(rot_angles[2]), 0, -math.sin(rot_angles[2])], 670 | [0, 1, 0], 671 | [math.sin(rot_angles[2]), 0, math.cos(rot_angles[2])]]) 672 | scaling = np.array([[scale_interval[0] + 673 | random_state.rand() * scale_interval[1], 0, 0], 674 | [0, scale_interval[0] + 675 | random_state.rand() * scale_interval[1], 0], 676 | [0, 0, scale_interval[0] + 677 | random_state.rand() * scale_interval[1]]]) 678 | trans = np.array([img.shape[1] * trans_interval[0] + 679 | random_state.randint(-img.shape[1] * trans_interval[1], 680 | img.shape[1] * trans_interval[1]), 681 | img.shape[0] * trans_interval[0] + 682 | random_state.randint(-img.shape[0] * trans_interval[1], 683 | img.shape[0] * trans_interval[1]), 684 | 0]) 685 | cube = trans + np.transpose(np.dot(scaling, 686 | np.dot(rotation_1, 687 | np.dot(rotation_2, 688 | np.dot(rotation_3, 689 | np.transpose(cube)))))) 690 | 691 | # The hidden corner is 0 by construction 692 | # The front one is 7 693 | cube = cube[:, :2] # project on the plane z=0 694 | cube = cube.astype(int) 695 | points = cube[1:, :] # get rid of the hidden corner 696 | 697 | # Get the three visible faces 698 | faces = np.array([[7, 3, 1, 5], [7, 5, 4, 6], [7, 6, 2, 3]]) 699 | 700 | # Fill the faces and draw the contours 701 | col_face = get_random_color(background_color) 702 | for i in [0, 1, 2]: 703 | cv.fillPoly(img, [cube[faces[i]].reshape((-1, 1, 2))], 704 | col_face) 705 | thickness = random_state.randint(min_dim * 0.003, min_dim * 0.015) 706 | for i in [0, 1, 2]: 707 | for j in [0, 1, 2, 3]: 708 | col_edge = (col_face + 128 709 | + random_state.randint(-64, 64))\ 710 | % 256 # color that constrats with the face color 711 | cv.line(img, (cube[faces[i][j], 0], cube[faces[i][j], 1]), 712 | (cube[faces[i][(j + 1) % 4], 0], cube[faces[i][(j + 1) % 4], 1]), 713 | col_edge, thickness) 714 | 715 | # Keep only the points inside the image 716 | points = keep_points_inside(points, img.shape[:2]) 717 | return points 718 | 719 | def add_gausian_noise(img): 720 | """ Apply random noise to the image """ 721 | add_gausian_noise = np.random.choice([True, False], p=[0.95, 0.05]) 722 | if add_gausian_noise: 723 | # convert to torch tensor 724 | data: torch.tensor = K.image_to_tensor(img, keepdim=False) # BxCxHxW 725 | # create the operator 726 | gauss = K.filters.GaussianBlur2d((11, 11), (100,100)) 727 | # blur the image 728 | x_blur: torch.tensor = gauss(data.float()) 729 | # convert back to numpy 730 | img_blur: np.ndarray = K.tensor_to_image(x_blur.byte()) 731 | 732 | return img_blur 733 | 734 | return img 735 | 736 | def pad_points(points, desired_num_points): 737 | pts_num=points.shape[0] 738 | one_padding = np.ones((pts_num,1), dtype=points.dtype) 739 | points = np.append(points, one_padding, axis=1) 740 | points = np.append(points, np.zeros((desired_num_points-pts_num,3)), axis=0) 741 | return points 742 | 743 | def parse_primitives(names, all_primitives): 744 | p = all_primitives if (names == 'all') \ 745 | else (names if isinstance(names, list) else [names]) 746 | assert set(p) <= set(all_primitives) 747 | return p 748 | -------------------------------------------------------------------------------- /datasets/utils/__pycache__/transforms.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/datasets/utils/__pycache__/transforms.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/utils/transforms.py: -------------------------------------------------------------------------------- 1 | from torchvision import datasets, transforms 2 | import PIL 3 | import torch 4 | from torch.utils.data import Dataset, DataLoader 5 | import os 6 | from skimage import io, transform 7 | import skimage 8 | import numpy as np 9 | import pandas as pd 10 | import kornia as K 11 | import random 12 | 13 | 14 | class ToTensor(object): 15 | """Convert ndarrays in sample to Tensors.""" 16 | 17 | def __call__(self, sample): 18 | image, landmarks = sample 19 | 20 | # swap color axis because 21 | # numpy image: H x W x C 22 | # torch image: C X H X W 23 | T = transforms.ToTensor() 24 | image = T(image) 25 | if not landmarks is 0: 26 | landmarks = torch.tensor(landmarks,dtype=torch.float32) 27 | return (image,landmarks) 28 | 29 | 30 | class Rescale(object): 31 | """Rescale the image in a sample to a given size. 32 | 33 | Args: 34 | output_size (tuple or int): Desired output size. If tuple, output is 35 | matched to output_size. If int, smaller of image edges is matched 36 | to output_size keeping aspect ratio the same. 37 | """ 38 | 39 | def __init__(self, output_size): 40 | assert isinstance(output_size, (int, tuple)) 41 | self.output_size = output_size 42 | 43 | def __call__(self, sample): 44 | image, landmarks = sample 45 | 46 | h, w = image.size[:2] 47 | new_h, new_w = self.output_size 48 | 49 | new_h, new_w = int(new_h), int(new_w) 50 | m = transforms.Resize((new_h, new_w), PIL.Image.BICUBIC) 51 | img = m(image) 52 | 53 | return (img,landmarks) 54 | 55 | 56 | 57 | class ToGray(object): 58 | """Convert ndarrays in sample to Tensors.""" 59 | 60 | def __call__(self, sample): 61 | image, landmarks = sample 62 | G = transforms.Grayscale(num_output_channels=1) 63 | if image.size[0]>1: 64 | image = G(image) 65 | return (image,landmarks) 66 | 67 | 68 | 69 | class ColorJitter(object): 70 | """Randomly change the brightness, contrast and saturation of an image.""" 71 | 72 | def __call__(self, sample): 73 | image, landmarks = sample 74 | col = transforms.ColorJitter(brightness=(0.7,1.3)) 75 | image = col(image) 76 | return (image,landmarks) 77 | 78 | 79 | 80 | class GaussianBlur(object): 81 | """Blurs image with randomly chosen Gaussian blur""" 82 | 83 | def __call__(self, sample): 84 | image, landmarks = sample 85 | kernel = random.randint(1,5) 86 | image = image.filter(PIL.ImageFilter.GaussianBlur(radius=kernel)) 87 | return (image,landmarks) 88 | 89 | 90 | 91 | params = {'deg':5, 'transx':0.2, 'transy':0.2, 'minscale':1.2, 'maxscale':1.5, 'shear':(-15,15)} 92 | def get_twin(im,params=params): 93 | image_tens = im.type(torch.float32) 94 | h=im.size()[2] 95 | w=im.size()[3] 96 | #warp parameters 97 | degrees = (-params['deg'],params['deg']) 98 | translate = (params['transx'],params['transy']) 99 | scale = (params['minscale'],params['maxscale']) 100 | shear = params['shear'] 101 | 102 | #get warped images and homographies 103 | aug = K.augmentation.RandomAffine(degrees=degrees, translate=translate, scale=scale, shear=shear , return_transform=True) 104 | image_tens_warp, H = aug(image_tens) 105 | return image_tens_warp, H 106 | -------------------------------------------------------------------------------- /datasets/video_data/images_from_video.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"images_from_video.ipynb","provenance":[],"collapsed_sections":[],"authorship_tag":"ABX9TyPL+zuZI07iSVnHULjGF2oh"},"kernelspec":{"name":"python3","display_name":"Python 3"}},"cells":[{"cell_type":"code","metadata":{"id":"2ixTAOcN_Fax","executionInfo":{"status":"ok","timestamp":1615322877253,"user_tz":-120,"elapsed":1532,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}}},"source":["import cv2"],"execution_count":1,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"PHtLDiSO_G6C"},"source":["# Mount drive"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"B1BImebe_HOT","executionInfo":{"status":"ok","timestamp":1615322907407,"user_tz":-120,"elapsed":29715,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"61568df2-d53c-4bf1-a061-e7bf875a9e25"},"source":["your_path = ''\r\n","from google.colab import drive\r\n","drive.mount('/content/gdrive')\r\n","path = '/content/gdrive/My Drive/project_307927749_200760544'\r\n","import sys\r\n","sys.path.append(path)"],"execution_count":2,"outputs":[{"output_type":"stream","text":["Mounted at /content/gdrive\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"esgk1yzO7obZ"},"source":["# Change vid to grayscale and sve images"]},{"cell_type":"code","metadata":{"id":"77Tvc6Tw7oyV","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1615322919320,"user_tz":-120,"elapsed":13538,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"69109430-571d-48b1-e3c7-773de2fd3a56"},"source":["import cv2 \r\n"," \r\n","# reading the vedio \r\n","cap = cv2.VideoCapture(path+'/datasets/video_data/vid3.mp4') \r\n","n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\r\n","w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\r\n","h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\r\n","fps = cap.get(cv2.CAP_PROP_FPS)\r\n","fourcc = cv2.VideoWriter_fourcc(*'MJPG')\r\n","out = cv2.VideoWriter(path+'/datasets/video_data/gray_vid1.mp4', fourcc, fps, (w, h), isColor=False)\r\n","im_path = path+'/datasets/video_data/video3_images/'\r\n","\r\n","\r\n"," \r\n","# running the loop \r\n","ret, img = cap.read()\r\n","print(ret)\r\n","count=0\r\n","while ret: \r\n"," \r\n"," gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) \r\n"," out.write(gray)\r\n"," cv2.imwrite(im_path+\"frame{}.jpg\".format(count), gray) # save frame as JPEG file \r\n"," ret,img = cap.read()\r\n"," print('Read a new frame (fram {}): '.format(count), ret) \r\n"," count+=1\r\n","# closing the window \r\n","cv2.destroyAllWindows() \r\n","cap.release()"],"execution_count":3,"outputs":[{"output_type":"stream","text":["True\n","Read a new frame (fram 0): True\n","Read a new frame (fram 1): True\n","Read a new frame (fram 2): True\n","Read a new frame (fram 3): True\n","Read a new frame (fram 4): True\n","Read a new frame (fram 5): True\n","Read a new frame (fram 6): True\n","Read a new frame (fram 7): True\n","Read a new frame (fram 8): True\n","Read a new frame (fram 9): True\n","Read a new frame (fram 10): True\n","Read a new frame (fram 11): True\n","Read a new frame (fram 12): True\n","Read a new frame (fram 13): True\n","Read a new frame (fram 14): True\n","Read a new frame (fram 15): True\n","Read a new frame (fram 16): True\n","Read a new frame (fram 17): True\n","Read a new frame (fram 18): True\n","Read a new frame (fram 19): True\n","Read a new frame (fram 20): True\n","Read a new frame (fram 21): True\n","Read a new frame (fram 22): True\n","Read a new frame (fram 23): True\n","Read a new frame (fram 24): True\n","Read a new frame (fram 25): True\n","Read a new frame (fram 26): True\n","Read a new frame (fram 27): True\n","Read a new frame (fram 28): True\n","Read a new frame (fram 29): True\n","Read a new frame (fram 30): True\n","Read a new frame (fram 31): True\n","Read a new frame (fram 32): True\n","Read a new frame (fram 33): True\n","Read a new frame (fram 34): True\n","Read a new frame (fram 35): True\n","Read a new frame (fram 36): True\n","Read a new frame (fram 37): True\n","Read a new frame (fram 38): True\n","Read a new frame (fram 39): True\n","Read a new frame (fram 40): True\n","Read a new frame (fram 41): True\n","Read a new frame (fram 42): True\n","Read a new frame (fram 43): True\n","Read a new frame (fram 44): True\n","Read a new frame (fram 45): True\n","Read a new frame (fram 46): True\n","Read a new frame (fram 47): True\n","Read a new frame (fram 48): True\n","Read a new frame (fram 49): True\n","Read a new frame (fram 50): True\n","Read a new frame (fram 51): True\n","Read a new frame (fram 52): True\n","Read a new frame (fram 53): True\n","Read a new frame (fram 54): True\n","Read a new frame (fram 55): True\n","Read a new frame (fram 56): True\n","Read a new frame (fram 57): True\n","Read a new frame (fram 58): True\n","Read a new frame (fram 59): True\n","Read a new frame (fram 60): True\n","Read a new frame (fram 61): True\n","Read a new frame (fram 62): True\n","Read a new frame (fram 63): True\n","Read a new frame (fram 64): True\n","Read a new frame (fram 65): True\n","Read a new frame (fram 66): True\n","Read a new frame (fram 67): True\n","Read a new frame (fram 68): True\n","Read a new frame (fram 69): True\n","Read a new frame (fram 70): True\n","Read a new frame (fram 71): True\n","Read a new frame (fram 72): True\n","Read a new frame (fram 73): True\n","Read a new frame (fram 74): True\n","Read a new frame (fram 75): True\n","Read a new frame (fram 76): True\n","Read a new frame (fram 77): True\n","Read a new frame (fram 78): True\n","Read a new frame (fram 79): True\n","Read a new frame (fram 80): True\n","Read a new frame (fram 81): True\n","Read a new frame (fram 82): True\n","Read a new frame (fram 83): True\n","Read a new frame (fram 84): True\n","Read a new frame (fram 85): True\n","Read a new frame (fram 86): True\n","Read a new frame (fram 87): True\n","Read a new frame (fram 88): True\n","Read a new frame (fram 89): True\n","Read a new frame (fram 90): True\n","Read a new frame (fram 91): True\n","Read a new frame (fram 92): True\n","Read a new frame (fram 93): True\n","Read a new frame (fram 94): True\n","Read a new frame (fram 95): True\n","Read a new frame (fram 96): True\n","Read a new frame (fram 97): True\n","Read a new frame (fram 98): True\n","Read a new frame (fram 99): True\n","Read a new frame (fram 100): True\n","Read a new frame (fram 101): True\n","Read a new frame (fram 102): True\n","Read a new frame (fram 103): True\n","Read a new frame (fram 104): True\n","Read a new frame (fram 105): True\n","Read a new frame (fram 106): True\n","Read a new frame (fram 107): True\n","Read a new frame (fram 108): True\n","Read a new frame (fram 109): True\n","Read a new frame (fram 110): True\n","Read a new frame (fram 111): True\n","Read a new frame (fram 112): True\n","Read a new frame (fram 113): True\n","Read a new frame (fram 114): True\n","Read a new frame (fram 115): True\n","Read a new frame (fram 116): True\n","Read a new frame (fram 117): True\n","Read a new frame (fram 118): True\n","Read a new frame (fram 119): True\n","Read a new frame (fram 120): True\n","Read a new frame (fram 121): True\n","Read a new frame (fram 122): True\n","Read a new frame (fram 123): True\n","Read a new frame (fram 124): True\n","Read a new frame (fram 125): True\n","Read a new frame (fram 126): True\n","Read a new frame (fram 127): True\n","Read a new frame (fram 128): True\n","Read a new frame (fram 129): True\n","Read a new frame (fram 130): True\n","Read a new frame (fram 131): True\n","Read a new frame (fram 132): True\n","Read a new frame (fram 133): True\n","Read a new frame (fram 134): True\n","Read a new frame (fram 135): True\n","Read a new frame (fram 136): True\n","Read a new frame (fram 137): True\n","Read a new frame (fram 138): True\n","Read a new frame (fram 139): True\n","Read a new frame (fram 140): True\n","Read a new frame (fram 141): True\n","Read a new frame (fram 142): True\n","Read a new frame (fram 143): True\n","Read a new frame (fram 144): True\n","Read a new frame (fram 145): True\n","Read a new frame (fram 146): True\n","Read a new frame (fram 147): True\n","Read a new frame (fram 148): True\n","Read a new frame (fram 149): True\n","Read a new frame (fram 150): True\n","Read a new frame (fram 151): True\n","Read a new frame (fram 152): True\n","Read a new frame (fram 153): True\n","Read a new frame (fram 154): True\n","Read a new frame (fram 155): True\n","Read a new frame (fram 156): True\n","Read a new frame (fram 157): True\n","Read a new frame (fram 158): True\n","Read a new frame (fram 159): True\n","Read a new frame (fram 160): True\n","Read a new frame (fram 161): True\n","Read a new frame (fram 162): True\n","Read a new frame (fram 163): True\n","Read a new frame (fram 164): True\n","Read a new frame (fram 165): True\n","Read a new frame (fram 166): True\n","Read a new frame (fram 167): True\n","Read a new frame (fram 168): True\n","Read a new frame (fram 169): True\n","Read a new frame (fram 170): True\n","Read a new frame (fram 171): True\n","Read a new frame (fram 172): True\n","Read a new frame (fram 173): True\n","Read a new frame (fram 174): True\n","Read a new frame (fram 175): True\n","Read a new frame (fram 176): True\n","Read a new frame (fram 177): True\n","Read a new frame (fram 178): True\n","Read a new frame (fram 179): True\n","Read a new frame (fram 180): True\n","Read a new frame (fram 181): True\n","Read a new frame (fram 182): True\n","Read a new frame (fram 183): True\n","Read a new frame (fram 184): True\n","Read a new frame (fram 185): True\n","Read a new frame (fram 186): True\n","Read a new frame (fram 187): True\n","Read a new frame (fram 188): True\n","Read a new frame (fram 189): True\n","Read a new frame (fram 190): True\n","Read a new frame (fram 191): True\n","Read a new frame (fram 192): True\n","Read a new frame (fram 193): True\n","Read a new frame (fram 194): True\n","Read a new frame (fram 195): True\n","Read a new frame (fram 196): True\n","Read a new frame (fram 197): True\n","Read a new frame (fram 198): True\n","Read a new frame (fram 199): True\n","Read a new frame (fram 200): True\n","Read a new frame (fram 201): True\n","Read a new frame (fram 202): True\n","Read a new frame (fram 203): True\n","Read a new frame (fram 204): True\n","Read a new frame (fram 205): True\n","Read a new frame (fram 206): True\n","Read a new frame (fram 207): True\n","Read a new frame (fram 208): True\n","Read a new frame (fram 209): True\n","Read a new frame (fram 210): True\n","Read a new frame (fram 211): True\n","Read a new frame (fram 212): True\n","Read a new frame (fram 213): True\n","Read a new frame (fram 214): True\n","Read a new frame (fram 215): True\n","Read a new frame (fram 216): True\n","Read a new frame (fram 217): True\n","Read a new frame (fram 218): True\n","Read a new frame (fram 219): True\n","Read a new frame (fram 220): True\n","Read a new frame (fram 221): True\n","Read a new frame (fram 222): True\n","Read a new frame (fram 223): True\n","Read a new frame (fram 224): True\n","Read a new frame (fram 225): True\n","Read a new frame (fram 226): True\n","Read a new frame (fram 227): True\n","Read a new frame (fram 228): True\n","Read a new frame (fram 229): True\n","Read a new frame (fram 230): True\n","Read a new frame (fram 231): True\n","Read a new frame (fram 232): True\n","Read a new frame (fram 233): True\n","Read a new frame (fram 234): True\n","Read a new frame (fram 235): True\n","Read a new frame (fram 236): True\n","Read a new frame (fram 237): True\n","Read a new frame (fram 238): True\n","Read a new frame (fram 239): True\n","Read a new frame (fram 240): True\n","Read a new frame (fram 241): True\n","Read a new frame (fram 242): True\n","Read a new frame (fram 243): True\n","Read a new frame (fram 244): True\n","Read a new frame (fram 245): True\n","Read a new frame (fram 246): True\n","Read a new frame (fram 247): True\n","Read a new frame (fram 248): True\n","Read a new frame (fram 249): True\n","Read a new frame (fram 250): True\n","Read a new frame (fram 251): True\n","Read a new frame (fram 252): True\n","Read a new frame (fram 253): True\n","Read a new frame (fram 254): True\n","Read a new frame (fram 255): True\n","Read a new frame (fram 256): True\n","Read a new frame (fram 257): True\n","Read a new frame (fram 258): True\n","Read a new frame (fram 259): True\n","Read a new frame (fram 260): True\n","Read a new frame (fram 261): True\n","Read a new frame (fram 262): True\n","Read a new frame (fram 263): True\n","Read a new frame (fram 264): True\n","Read a new frame (fram 265): True\n","Read a new frame (fram 266): True\n","Read a new frame (fram 267): True\n","Read a new frame (fram 268): True\n","Read a new frame (fram 269): True\n","Read a new frame (fram 270): True\n","Read a new frame (fram 271): True\n","Read a new frame (fram 272): True\n","Read a new frame (fram 273): True\n","Read a new frame (fram 274): True\n","Read a new frame (fram 275): True\n","Read a new frame (fram 276): True\n","Read a new frame (fram 277): True\n","Read a new frame (fram 278): True\n","Read a new frame (fram 279): True\n","Read a new frame (fram 280): True\n","Read a new frame (fram 281): True\n","Read a new frame (fram 282): False\n"],"name":"stdout"}]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"2neqHQX6BR9P","executionInfo":{"status":"ok","timestamp":1615128469289,"user_tz":-120,"elapsed":3016,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"c6bb6f0b-fa8f-4fb7-c436-bd3e928136d8"},"source":["import PIL\r\n","import numpy as np\r\n","import glob\r\n","lst = glob.glob(path+'/datasets/video_data/video_images/*')\r\n","print(len(lst))\r\n","im_lst = []\r\n","for name in lst:\r\n"," image = PIL.Image.open(name)\r\n"," im_array = np.asarray(image)\r\n"," im_lst.append(im_array)\r\n","im_lst = np.array(im_lst)\r\n","new_im = np.mean(im_lst,axis=0)\r\n","print(new_im.shape)\r\n","#cv2.imwrite(im_path+\"mean_im.jpg\", new_im)\r\n","reduce_back = np.absolute(im_lst-new_im)\r\n","print(reduce_back.shape)\r\n","print(np.sum(im_lst))\r\n","print(np.sum(reduce_back))"],"execution_count":null,"outputs":[{"output_type":"stream","text":["301\n","(392, 696)\n","(301, 392, 696)\n","9433891292\n","2321494688.2724166\n"],"name":"stdout"}]}]} -------------------------------------------------------------------------------- /datasets/video_data/vid1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/datasets/video_data/vid1.mp4 -------------------------------------------------------------------------------- /datasets/video_data/vid3.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/datasets/video_data/vid3.mp4 -------------------------------------------------------------------------------- /datasets/video_data/video3_names.csv: -------------------------------------------------------------------------------- 1 | im_name 2 | frame0.jpg 3 | frame1.jpg 4 | frame2.jpg 5 | frame3.jpg 6 | frame4.jpg 7 | frame5.jpg 8 | frame6.jpg 9 | frame7.jpg 10 | frame8.jpg 11 | frame9.jpg 12 | frame10.jpg 13 | frame11.jpg 14 | frame12.jpg 15 | frame13.jpg 16 | frame14.jpg 17 | frame15.jpg 18 | frame16.jpg 19 | frame17.jpg 20 | frame18.jpg 21 | frame19.jpg 22 | frame20.jpg 23 | frame21.jpg 24 | frame22.jpg 25 | frame23.jpg 26 | frame24.jpg 27 | frame25.jpg 28 | frame26.jpg 29 | frame27.jpg 30 | frame28.jpg 31 | frame29.jpg 32 | frame30.jpg 33 | frame31.jpg 34 | frame32.jpg 35 | frame33.jpg 36 | frame34.jpg 37 | frame35.jpg 38 | frame36.jpg 39 | frame37.jpg 40 | frame38.jpg 41 | frame39.jpg 42 | frame40.jpg 43 | frame41.jpg 44 | frame42.jpg 45 | frame43.jpg 46 | frame44.jpg 47 | frame45.jpg 48 | frame46.jpg 49 | frame47.jpg 50 | frame48.jpg 51 | frame49.jpg 52 | frame50.jpg 53 | frame51.jpg 54 | frame52.jpg 55 | frame53.jpg 56 | frame54.jpg 57 | frame55.jpg 58 | frame56.jpg 59 | frame57.jpg 60 | frame58.jpg 61 | frame59.jpg 62 | frame60.jpg 63 | frame61.jpg 64 | frame62.jpg 65 | frame63.jpg 66 | frame64.jpg 67 | frame65.jpg 68 | frame66.jpg 69 | frame67.jpg 70 | frame68.jpg 71 | frame69.jpg 72 | frame70.jpg 73 | frame71.jpg 74 | frame72.jpg 75 | frame73.jpg 76 | frame74.jpg 77 | frame75.jpg 78 | frame76.jpg 79 | frame77.jpg 80 | frame78.jpg 81 | frame79.jpg 82 | frame80.jpg 83 | frame81.jpg 84 | frame82.jpg 85 | frame83.jpg 86 | frame84.jpg 87 | frame85.jpg 88 | frame86.jpg 89 | frame87.jpg 90 | frame88.jpg 91 | frame89.jpg 92 | frame90.jpg 93 | frame91.jpg 94 | frame92.jpg 95 | frame93.jpg 96 | frame94.jpg 97 | frame95.jpg 98 | frame96.jpg 99 | frame97.jpg 100 | frame98.jpg 101 | frame99.jpg 102 | frame100.jpg 103 | frame101.jpg 104 | frame102.jpg 105 | frame103.jpg 106 | frame104.jpg 107 | frame105.jpg 108 | frame106.jpg 109 | frame107.jpg 110 | frame108.jpg 111 | frame109.jpg 112 | frame110.jpg 113 | frame111.jpg 114 | frame112.jpg 115 | frame113.jpg 116 | frame114.jpg 117 | frame115.jpg 118 | frame116.jpg 119 | frame117.jpg 120 | frame118.jpg 121 | frame119.jpg 122 | frame120.jpg 123 | frame121.jpg 124 | frame122.jpg 125 | frame123.jpg 126 | frame124.jpg 127 | frame125.jpg 128 | frame126.jpg 129 | frame127.jpg 130 | frame128.jpg 131 | frame129.jpg 132 | frame130.jpg 133 | frame131.jpg 134 | frame132.jpg 135 | frame133.jpg 136 | frame134.jpg 137 | frame135.jpg 138 | frame136.jpg 139 | frame137.jpg 140 | frame138.jpg 141 | frame139.jpg 142 | frame140.jpg 143 | frame141.jpg 144 | frame142.jpg 145 | frame143.jpg 146 | frame144.jpg 147 | frame145.jpg 148 | frame146.jpg 149 | frame147.jpg 150 | frame148.jpg 151 | frame149.jpg 152 | frame150.jpg 153 | frame151.jpg 154 | frame152.jpg 155 | frame153.jpg 156 | frame154.jpg 157 | frame155.jpg 158 | frame156.jpg 159 | frame157.jpg 160 | frame158.jpg 161 | frame159.jpg 162 | frame160.jpg 163 | frame161.jpg 164 | frame162.jpg 165 | frame163.jpg 166 | frame164.jpg 167 | frame165.jpg 168 | frame166.jpg 169 | frame167.jpg 170 | frame168.jpg 171 | frame169.jpg 172 | frame170.jpg 173 | frame171.jpg 174 | frame172.jpg 175 | frame173.jpg 176 | frame174.jpg 177 | frame175.jpg 178 | frame176.jpg 179 | frame177.jpg 180 | frame178.jpg 181 | frame179.jpg 182 | frame180.jpg 183 | frame181.jpg 184 | frame182.jpg 185 | frame183.jpg 186 | frame184.jpg 187 | frame185.jpg 188 | frame186.jpg 189 | frame187.jpg 190 | frame188.jpg 191 | frame189.jpg 192 | frame190.jpg 193 | frame191.jpg 194 | frame192.jpg 195 | frame193.jpg 196 | frame194.jpg 197 | frame195.jpg 198 | frame196.jpg 199 | frame197.jpg 200 | frame198.jpg 201 | frame199.jpg 202 | frame200.jpg 203 | frame201.jpg 204 | frame202.jpg 205 | frame203.jpg 206 | frame204.jpg 207 | frame205.jpg 208 | frame206.jpg 209 | frame207.jpg 210 | frame208.jpg 211 | frame209.jpg 212 | frame210.jpg 213 | frame211.jpg 214 | frame212.jpg 215 | frame213.jpg 216 | frame214.jpg 217 | frame215.jpg 218 | frame216.jpg 219 | frame217.jpg 220 | frame218.jpg 221 | frame219.jpg 222 | frame220.jpg 223 | frame221.jpg 224 | frame222.jpg 225 | frame223.jpg 226 | frame224.jpg 227 | frame225.jpg 228 | frame226.jpg 229 | frame227.jpg 230 | frame228.jpg 231 | frame229.jpg 232 | frame230.jpg 233 | frame231.jpg 234 | frame232.jpg 235 | frame233.jpg 236 | frame234.jpg 237 | frame235.jpg 238 | frame236.jpg 239 | frame237.jpg 240 | frame238.jpg 241 | frame239.jpg 242 | frame240.jpg 243 | frame241.jpg 244 | frame242.jpg 245 | frame243.jpg 246 | frame244.jpg 247 | frame245.jpg 248 | frame246.jpg 249 | frame247.jpg 250 | frame248.jpg 251 | frame249.jpg 252 | frame250.jpg 253 | frame251.jpg 254 | frame252.jpg 255 | frame253.jpg 256 | frame254.jpg 257 | frame255.jpg 258 | frame256.jpg 259 | frame257.jpg 260 | frame258.jpg 261 | frame259.jpg 262 | frame260.jpg 263 | frame261.jpg 264 | frame262.jpg 265 | frame263.jpg 266 | frame264.jpg 267 | frame265.jpg 268 | frame266.jpg 269 | frame267.jpg 270 | frame268.jpg 271 | frame269.jpg 272 | frame270.jpg 273 | frame271.jpg 274 | frame272.jpg 275 | frame273.jpg 276 | frame274.jpg 277 | frame275.jpg 278 | frame276.jpg 279 | frame277.jpg 280 | frame278.jpg 281 | frame279.jpg 282 | frame280.jpg 283 | frame281.jpg 284 | frame282.jpg 285 | -------------------------------------------------------------------------------- /datasets/video_data/video_names.csv: -------------------------------------------------------------------------------- 1 | im_name 2 | frame0.jpg 3 | frame1.jpg 4 | frame2.jpg 5 | frame3.jpg 6 | frame4.jpg 7 | frame5.jpg 8 | frame6.jpg 9 | frame7.jpg 10 | frame8.jpg 11 | frame9.jpg 12 | frame10.jpg 13 | frame11.jpg 14 | frame12.jpg 15 | frame13.jpg 16 | frame14.jpg 17 | frame15.jpg 18 | frame16.jpg 19 | frame17.jpg 20 | frame18.jpg 21 | frame19.jpg 22 | frame20.jpg 23 | frame21.jpg 24 | frame22.jpg 25 | frame23.jpg 26 | frame24.jpg 27 | frame25.jpg 28 | frame26.jpg 29 | frame27.jpg 30 | frame28.jpg 31 | frame29.jpg 32 | frame30.jpg 33 | frame31.jpg 34 | frame32.jpg 35 | frame33.jpg 36 | frame34.jpg 37 | frame35.jpg 38 | frame36.jpg 39 | frame37.jpg 40 | frame38.jpg 41 | frame39.jpg 42 | frame40.jpg 43 | frame41.jpg 44 | frame42.jpg 45 | frame43.jpg 46 | frame44.jpg 47 | frame45.jpg 48 | frame46.jpg 49 | frame47.jpg 50 | frame48.jpg 51 | frame49.jpg 52 | frame50.jpg 53 | frame51.jpg 54 | frame52.jpg 55 | frame53.jpg 56 | frame54.jpg 57 | frame55.jpg 58 | frame56.jpg 59 | frame57.jpg 60 | frame58.jpg 61 | frame59.jpg 62 | frame60.jpg 63 | frame61.jpg 64 | frame62.jpg 65 | frame63.jpg 66 | frame64.jpg 67 | frame65.jpg 68 | frame66.jpg 69 | frame67.jpg 70 | frame68.jpg 71 | frame69.jpg 72 | frame70.jpg 73 | frame71.jpg 74 | frame72.jpg 75 | frame73.jpg 76 | frame74.jpg 77 | frame75.jpg 78 | frame76.jpg 79 | frame77.jpg 80 | frame78.jpg 81 | frame79.jpg 82 | frame80.jpg 83 | frame81.jpg 84 | frame82.jpg 85 | frame83.jpg 86 | frame84.jpg 87 | frame85.jpg 88 | frame86.jpg 89 | frame87.jpg 90 | frame88.jpg 91 | frame89.jpg 92 | frame90.jpg 93 | frame91.jpg 94 | frame92.jpg 95 | frame93.jpg 96 | frame94.jpg 97 | frame95.jpg 98 | frame96.jpg 99 | frame97.jpg 100 | frame98.jpg 101 | frame99.jpg 102 | frame100.jpg 103 | frame101.jpg 104 | frame102.jpg 105 | frame103.jpg 106 | frame104.jpg 107 | frame105.jpg 108 | frame106.jpg 109 | frame107.jpg 110 | frame108.jpg 111 | frame109.jpg 112 | frame110.jpg 113 | frame111.jpg 114 | frame112.jpg 115 | frame113.jpg 116 | frame114.jpg 117 | frame115.jpg 118 | frame116.jpg 119 | frame117.jpg 120 | frame118.jpg 121 | frame119.jpg 122 | frame120.jpg 123 | frame121.jpg 124 | frame122.jpg 125 | frame123.jpg 126 | frame124.jpg 127 | frame125.jpg 128 | frame126.jpg 129 | frame127.jpg 130 | frame128.jpg 131 | frame129.jpg 132 | frame130.jpg 133 | frame131.jpg 134 | frame132.jpg 135 | frame133.jpg 136 | frame134.jpg 137 | frame135.jpg 138 | frame136.jpg 139 | frame137.jpg 140 | frame138.jpg 141 | frame139.jpg 142 | frame140.jpg 143 | frame141.jpg 144 | frame142.jpg 145 | frame143.jpg 146 | frame144.jpg 147 | frame145.jpg 148 | frame146.jpg 149 | frame147.jpg 150 | frame148.jpg 151 | frame149.jpg 152 | frame150.jpg 153 | frame151.jpg 154 | frame152.jpg 155 | frame153.jpg 156 | frame154.jpg 157 | frame155.jpg 158 | frame156.jpg 159 | frame157.jpg 160 | frame158.jpg 161 | frame159.jpg 162 | frame160.jpg 163 | frame161.jpg 164 | frame162.jpg 165 | frame163.jpg 166 | frame164.jpg 167 | frame165.jpg 168 | frame166.jpg 169 | frame167.jpg 170 | frame168.jpg 171 | frame169.jpg 172 | frame170.jpg 173 | frame171.jpg 174 | frame172.jpg 175 | frame173.jpg 176 | frame174.jpg 177 | frame175.jpg 178 | frame176.jpg 179 | frame177.jpg 180 | frame178.jpg 181 | frame179.jpg 182 | frame180.jpg 183 | frame181.jpg 184 | frame182.jpg 185 | frame183.jpg 186 | frame184.jpg 187 | frame185.jpg 188 | frame186.jpg 189 | frame187.jpg 190 | frame188.jpg 191 | frame189.jpg 192 | frame190.jpg 193 | frame191.jpg 194 | frame192.jpg 195 | frame193.jpg 196 | frame194.jpg 197 | frame195.jpg 198 | frame196.jpg 199 | frame197.jpg 200 | frame198.jpg 201 | frame199.jpg 202 | frame200.jpg 203 | frame201.jpg 204 | frame202.jpg 205 | frame203.jpg 206 | frame204.jpg 207 | frame205.jpg 208 | frame206.jpg 209 | frame207.jpg 210 | frame208.jpg 211 | frame209.jpg 212 | frame210.jpg 213 | frame211.jpg 214 | frame212.jpg 215 | frame213.jpg 216 | frame214.jpg 217 | frame215.jpg 218 | frame216.jpg 219 | frame217.jpg 220 | frame218.jpg 221 | frame219.jpg 222 | frame220.jpg 223 | frame221.jpg 224 | frame222.jpg 225 | frame223.jpg 226 | frame224.jpg 227 | frame225.jpg 228 | frame226.jpg 229 | frame227.jpg 230 | frame228.jpg 231 | frame229.jpg 232 | frame230.jpg 233 | frame231.jpg 234 | frame232.jpg 235 | frame233.jpg 236 | frame234.jpg 237 | frame235.jpg 238 | frame236.jpg 239 | frame237.jpg 240 | frame238.jpg 241 | frame239.jpg 242 | frame240.jpg 243 | frame241.jpg 244 | frame242.jpg 245 | frame243.jpg 246 | frame244.jpg 247 | frame245.jpg 248 | frame246.jpg 249 | frame247.jpg 250 | frame248.jpg 251 | frame249.jpg 252 | frame250.jpg 253 | frame251.jpg 254 | frame252.jpg 255 | frame253.jpg 256 | frame254.jpg 257 | frame255.jpg 258 | frame256.jpg 259 | frame257.jpg 260 | frame258.jpg 261 | frame259.jpg 262 | frame260.jpg 263 | frame261.jpg 264 | frame262.jpg 265 | frame263.jpg 266 | frame264.jpg 267 | frame265.jpg 268 | frame266.jpg 269 | frame267.jpg 270 | frame268.jpg 271 | frame269.jpg 272 | frame270.jpg 273 | frame271.jpg 274 | frame272.jpg 275 | frame273.jpg 276 | frame274.jpg 277 | frame275.jpg 278 | frame276.jpg 279 | frame277.jpg 280 | frame278.jpg 281 | frame279.jpg 282 | frame280.jpg 283 | frame281.jpg 284 | frame282.jpg 285 | frame283.jpg 286 | frame284.jpg 287 | frame285.jpg 288 | frame286.jpg 289 | frame287.jpg 290 | frame288.jpg 291 | frame289.jpg 292 | frame290.jpg 293 | frame291.jpg 294 | frame292.jpg 295 | frame293.jpg 296 | frame294.jpg 297 | frame295.jpg 298 | frame296.jpg 299 | frame297.jpg 300 | frame298.jpg 301 | frame299.jpg 302 | frame300.jpg 303 | -------------------------------------------------------------------------------- /generate_synthetic_dataset.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"accelerator":"GPU","colab":{"name":"generate_synthetic_dataset.ipynb","provenance":[],"collapsed_sections":["TTdDWG8l6TKV","em8r2FA_-taB","dDoSQiNC_LUR"]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.7.8"}},"cells":[{"cell_type":"markdown","metadata":{"id":"c180ufYMKvDg"},"source":["# Setup"]},{"cell_type":"code","metadata":{"id":"6NJOlYnZHWgZ"},"source":["%pip install kornia==0.4.0\n","import torch\n","from torchvision import datasets, transforms\n","import torchvision\n","from torch import nn, optim\n","import torch.nn.functional as F\n","import matplotlib.pyplot as plt\n","import numpy as np\n","import copy\n","import pycocotools"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"lSdb5G3AHpIW"},"source":["## Mount drive"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"hiIy5uDPHrsD","executionInfo":{"elapsed":632,"status":"ok","timestamp":1614535013547,"user":{"displayName":"עומר כהן","photoUrl":"","userId":"13277286837273100580"},"user_tz":-120},"outputId":"46479654-1995-4cc3-ec59-d192cd37daeb"},"source":["your_path = ''\n","from google.colab import drive\n","drive.mount('/content/gdrive')\n","path = '/content/gdrive/My Drive/'+your_path\n","import sys\n","sys.path.append(path)"],"execution_count":null,"outputs":[{"output_type":"stream","text":["Requirement already satisfied: kornia in /opt/conda/lib/python3.7/site-packages (0.3.1)\n","Requirement already satisfied: torch==1.5.0 in /opt/conda/lib/python3.7/site-packages (from kornia) (1.5.0+cu101)\n","Requirement already satisfied: numpy in /opt/conda/lib/python3.7/site-packages (from kornia) (1.18.5)\n","Requirement already satisfied: future in /opt/conda/lib/python3.7/site-packages (from torch==1.5.0->kornia) (0.18.2)\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"BlV9yXL_IaNQ"},"source":["## GPU"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"wWPdzdLiIZ0Y","executionInfo":{"elapsed":704,"status":"ok","timestamp":1614535024404,"user":{"displayName":"עומר כהן","photoUrl":"","userId":"13277286837273100580"},"user_tz":-120},"outputId":"2457e56d-7509-48cf-ebf5-4f0fb8c673e3"},"source":["import torch\n","train_on_gpu = torch.cuda.is_available()\n","print(torch.__version__)\n","if not train_on_gpu:\n"," DEVICE = 'cpu'\n"," print('CUDA is not available. Training on CPU ...')\n","else:\n"," DEVICE = 'cuda'\n"," print('CUDA is available! Training on GPU ...')"],"execution_count":null,"outputs":[{"output_type":"stream","text":["1.7.1\n","CUDA is not available. Training on CPU ...\n"],"name":"stdout"},{"output_type":"stream","text":["/opt/conda/lib/python3.7/site-packages/torch/cuda/__init__.py:52: UserWarning: CUDA initialization: The NVIDIA driver on your system is too old (found version 10010). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternatively, go to: https://pytorch.org to install a PyTorch version that has been compiled with your version of the CUDA driver. (Triggered internally at /pytorch/c10/cuda/CUDAFunctions.cpp:100.)\n"," return torch._C._cuda_getDeviceCount() > 0\n"],"name":"stderr"}]},{"cell_type":"markdown","metadata":{"id":"WXEgw5xAKiuc"},"source":["# Data analysis"]},{"cell_type":"markdown","metadata":{"id":"EKCLD5rO6AU7"},"source":["###synthetic"]},{"cell_type":"markdown","metadata":{"id":"TTdDWG8l6TKV"},"source":["####def synthetic functions"]},{"cell_type":"code","metadata":{"id":"oFBQnl-g6Cvn"},"source":["from datasets.synthetic_shapes.synthetic_shapes_functions import *"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"em8r2FA_-taB"},"source":["####def transforms"]},{"cell_type":"code","metadata":{"id":"LHMwKY0m-3wl"},"source":["from torchvision import datasets, transforms\n","import PIL\n","import torch\n","from torch.utils.data import Dataset, DataLoader\n","import os\n","from skimage import io, transform\n","import numpy as np\n","import pandas as pd\n","import kornia as K\n","\n","class ToTensor(object):\n"," \"\"\"Convert ndarrays in sample to Tensors.\"\"\"\n","\n"," def __call__(self, sample):\n"," image, landmarks = sample\n","\n"," # swap color axis because\n"," # numpy image: H x W x C\n"," # torch image: C X H X W\n"," T = transforms.ToTensor()\n"," image = T(image)\n"," if not landmarks is 0:\n"," landmarks = torch.tensor(landmarks,dtype=torch.float32)\n"," return (image,landmarks)\n","\n","\n","class Rescale(object):\n"," \"\"\"Rescale the image in a sample to a given size.\n","\n"," Args:\n"," output_size (tuple or int): Desired output size. If tuple, output is\n"," matched to output_size. If int, smaller of image edges is matched\n"," to output_size keeping aspect ratio the same.\n"," \"\"\"\n","\n"," def __init__(self, output_size):\n"," assert isinstance(output_size, (int, tuple))\n"," self.output_size = output_size\n","\n"," def __call__(self, sample):\n"," image, landmarks = sample\n","\n"," h, w = image.shape[:2]\n"," if isinstance(self.output_size, int):\n"," if h > w:\n"," new_h, new_w = self.output_size * h / w, self.output_size\n"," else:\n"," new_h, new_w = self.output_size, self.output_size * w / h\n"," else:\n"," new_h, new_w = self.output_size\n","\n"," new_h, new_w = int(new_h), int(new_w)\n"," \n"," img = skimage.transform.resize(image, (new_h, new_w))\n"," \n"," landmarks[:,:2] = landmarks[:,:2] * [new_w / w, new_h / h]\n"," landmarks = landmarks.astype(int)\n"," return (img,landmarks)\n"," \n","\n","class ToGray(object):\n"," \"\"\"Convert ndarrays in sample to Tensors.\"\"\"\n","\n"," def __call__(self, sample):\n"," image, landmarks = sample\n"," G = transforms.Grayscale(num_output_channels=1)\n"," if image.size()[0]>1:\n"," image = G(image)\n"," return (image,landmarks) \n","\n","\n","\n","class ColorJitter(object):\n"," \"\"\"Randomly change the brightness, contrast and saturation of an image.\"\"\"\n","\n"," def __call__(self, sample):\n"," image, landmarks = sample\n"," col = transforms.ColorJitter(brightness=(0.5,1.4))\n"," image = col(image)\n"," return (image,landmarks) \n","\n","\n","\n","class GaussianBlur(object):\n"," \"\"\"Blurs image with randomly chosen Gaussian blur\"\"\"\n","\n"," def __call__(self, sample):\n"," image, landmarks = sample\n"," gaus = transforms.GaussianBlur(9, sigma=(1, 1.8))\n"," image = gaus(image)\n"," return (image,landmarks) \n","\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"dDoSQiNC_LUR"},"source":["#### def dataset"]},{"cell_type":"code","metadata":{"id":"14Do13kK_OaJ"},"source":["import skimage\n","\n","drawing_primitives = [\n"," 'draw_lines',\n"," 'draw_polygon',\n"," 'draw_multiple_polygons',\n"," 'draw_ellipses',\n"," 'draw_star',\n"," 'draw_checkerboard',\n"," 'draw_stripes',\n"," 'draw_cube']\n","\n","\n","\n","class mySyntheticShapes(Dataset):\n"," def __init__(self, config, num_imgs_returned, transform=None):\n"," self.config = config \n"," self.num_imgs_returned = num_imgs_returned\n"," self.transform = transform\n","\n"," def __len__(self):\n"," '''Denotes the total number of samples'''\n"," return self.num_imgs_returned\n","\n"," def __getitem__(self, index):\n"," i=0\n"," while True:\n"," try:\n"," primitives = parse_primitives(self.config['primitives'], drawing_primitives)\n"," primitive = np.random.choice(primitives)\n"," #print('primitive = ', primitive)\n","\n"," image = generate_background(self.config['generation']['image_size'],\n"," **self.config['generation']['params']['generate_background'])\n"," image = add_gausian_noise(image)\n","\n"," primitive_func_str = primitive+'(image, **self.config[\"generation\"][\"params\"].get(primitive, {}))'\n"," points = np.array(eval(primitive_func_str))\n"," points = pad_points(points, desired_num_points=60)\n","\n"," sample = (image,points)\n"," #print('image shape = ', image.shape, 'points shape =', points.shape, 'pts_num=',pts_num)\n","\n"," if self.transform:\n"," sample = self.transform(sample)\n","\n"," except:\n"," print(\"An exception occurred\")\n"," i+=1\n"," if i<10:\n"," continue\n"," break\n"," return sample\n","\n","transform = transforms.Compose([Rescale((240,320)),\n"," ToTensor(),\n"," ToGray()])\n","\n","\n","\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"abJOFXWG_n7Z"},"source":["####load dataset"]},{"cell_type":"code","metadata":{"id":"Rlm_Ycxm_rIr"},"source":["import csv\n","config = {\n"," 'primitives': 'all',\n"," 'truncate': {},\n"," 'validation_size': -1,\n"," 'test_size': -1,\n"," 'on-the-fly': False,\n"," 'cache_in_memory': False,\n"," 'suffix': None,\n"," 'add_augmentation_to_test_set': False,\n"," 'num_parallel_calls': 10,\n"," 'generation': {\n"," 'split_sizes': {'training': 10000, 'validation': 200, 'test': 500},\n"," 'image_size': [960,1280], \n"," 'random_seed': 0,\n"," 'params': {\n"," 'generate_background': {\n"," 'min_kernel_size': 150, 'max_kernel_size': 500,\n"," 'min_rad_ratio': 0.02, 'max_rad_ratio': 0.031}, \n"," 'draw_stripes': {'transform_params': (0.1, 0.1)},\n"," 'draw_multiple_polygons': {'kernel_boundaries': (50, 100)}\n"," },\n"," },\n"," 'preprocessing': {\n"," 'resize': [240, 320],\n"," 'blur_size': 11,\n"," },\n"," 'augmentation': {\n"," 'photometric': {\n"," 'enable': False,\n"," 'primitives': 'all',\n"," 'params': {},\n"," 'random_order': True,\n"," },\n"," 'homographic': {\n"," 'enable': False,\n"," 'params': {},\n"," 'valid_border_margin': 0,\n"," },\n"," }\n"," }\n","\n","\n","num_imgs_returned=100000\n","dataset = mySyntheticShapes(config, num_imgs_returned, transform)\n","dataloader = DataLoader(dataset, batch_size=1, \n"," shuffle=True, num_workers=0)\n","\n","\n","#start generating data\n","filename = path+'/datasets/synthetic_shapes/syn_shape_labels.csv'\n","im_path = path+'/datasets/synthetic_shapes/synthetic_dataset/'\n","with open(filename, 'w') as csvfile: \n"," #creating a csv writer object \n"," csvwriter = csv.writer(csvfile)\n"," #write headline\n"," head = np.array(['x{},y{},conf{}'.format(i,i,i).split(',') for i in range(60)]).reshape((1,-1))\n"," head = np.concatenate((np.array([['im_name']]),head), axis=1)\n"," csvwriter.writerows(head)\n"," \n"," for iter, (im,label) in enumerate(dataloader):\n"," print(iter)\n"," full_cord = torch.flatten(label)\n"," full_cord = np.asarray(full_cord)\n"," \n"," name = 'syn_shape_'+str(iter)+'.jpg'\n"," full_cord = full_cord.reshape((1,-1))\n"," new_cords = np.concatenate(([[name]],full_cord),axis=1)\n"," csvwriter.writerows(new_cords)\n"," torchvision.utils.save_image(im,im_path+name)\n"],"execution_count":null,"outputs":[]}]} -------------------------------------------------------------------------------- /magicpoint_training_with_COCO_dataset.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"accelerator":"GPU","colab":{"name":"magicpoint_training_with_COCO_dataset.ipynb","provenance":[],"collapsed_sections":["2WXfmcIKS2As","es_Y7Yj7TpI2","audZzq95V2yT","_Q4unyskW7nu","jBIPot_UiK5f"],"toc_visible":true},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.7.8"}},"cells":[{"cell_type":"markdown","metadata":{"id":"-PiXOVyuR673"},"source":["# Setup"]},{"cell_type":"code","metadata":{"id":"QROgw9F1SVGO","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1614948240362,"user_tz":-120,"elapsed":3060,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"75698a79-69ce-4bd7-f993-b6e4b8964df6"},"source":["%pip install kornia==0.4.0\n","import matplotlib.pyplot as plt\n","import numpy as np\n","import cv2\n","import math \n","import torch\n","from torchvision import datasets, transforms\n","from torch import nn, optim\n","import torch.nn.functional as F\n","import matplotlib.pyplot as plt\n","import kornia as K\n","import time"],"execution_count":null,"outputs":[{"output_type":"stream","text":["Requirement already satisfied: kornia==0.4.0 in /usr/local/lib/python3.7/dist-packages (0.4.0)\n","Requirement already satisfied: torch<1.7.0,>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from kornia==0.4.0) (1.6.0)\n","Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from kornia==0.4.0) (1.19.5)\n","Requirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from torch<1.7.0,>=1.6.0->kornia==0.4.0) (0.16.0)\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"GZv-fvGtSk8x"},"source":["## Mount drive"]},{"cell_type":"code","metadata":{"id":"iTEDRFcbSlzV","colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"status":"ok","timestamp":1614948240363,"user_tz":-120,"elapsed":3050,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"ee8b4027-97a0-4d27-f04d-d9cefb3141b3"},"source":["your_path = ''\r\n","from google.colab import drive\r\n","drive.mount('/content/gdrive')\r\n","path = '/content/gdrive/My Drive/'+your_path\r\n","import sys\r\n","sys.path.append(path)"],"execution_count":null,"outputs":[{"output_type":"stream","text":["Drive already mounted at /content/gdrive; to attempt to forcibly remount, call drive.mount(\"/content/gdrive\", force_remount=True).\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"1EfKNOPTTCX9"},"source":["## GPU"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"vLsTtFwBS_WG","executionInfo":{"status":"ok","timestamp":1614948240861,"user_tz":-120,"elapsed":3541,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"9414d78f-7a56-4091-cc4d-25bb665cfaaa"},"source":["print(torch.__version__)\n","train_on_gpu = torch.cuda.is_available()\n","\n","if not train_on_gpu:\n"," DEVICE = 'cpu'\n"," print('CUDA is not available. Training on CPU ...')\n","else:\n"," DEVICE = 'cuda'\n"," print('CUDA is available! Training on GPU ...')\n","\n"],"execution_count":null,"outputs":[{"output_type":"stream","text":["1.6.0\n","CUDA is available! Training on GPU ...\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"cV77yN94TOdn"},"source":["# DATA"]},{"cell_type":"markdown","metadata":{"id":"2WXfmcIKS2As"},"source":["## Define transformatiom"]},{"cell_type":"code","metadata":{"id":"fjnZ_rPKaRKR"},"source":["from datasets.utils.transforms import ColorJitter, ToGray, Rescale,ToTensor"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"es_Y7Yj7TpI2"},"source":["## Dataset model"]},{"cell_type":"code","metadata":{"id":"XmocRKD4TV9v"},"source":["from datasets.COCO.COCO_model import COCO_dataset\n","\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"QLd0aweBU-MC"},"source":["## Generate dataset and dataloader"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"QUb6fXeyVGdm","executionInfo":{"status":"ok","timestamp":1614948255544,"user_tz":-120,"elapsed":18207,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"34695c5d-8069-49c8-dd81-7820cfcf1fe3"},"source":["transform = transforms.Compose([Rescale((240,320)),\n"," ColorJitter(), \n"," ToGray(),\n"," ToTensor()]) \n","\n","dataset = COCO_dataset(path+'/datasets/COCO/labeled_coco.csv',\n"," path+'/datasets/COCO/val2017/', \n"," transform=transform, \n"," landmark_bool=True)\n","dataloader = torch.utils.data.DataLoader(dataset, batch_size=5, shuffle=True)\n","\n","print(len(dataset))\n","print(len(dataloader))\n","for iter, (im,label) in enumerate(dataloader):\n"," print('ok')\n"," if iter>5:\n"," break\n","\n","\n"," "],"execution_count":null,"outputs":[{"output_type":"stream","text":["5000\n","1000\n","ok\n","ok\n","ok\n","ok\n","ok\n","ok\n","ok\n"],"name":"stdout"}]},{"cell_type":"markdown","metadata":{"id":"VOlf25b1R5HQ"},"source":["## Test model"]},{"cell_type":"markdown","metadata":{"id":"audZzq95V2yT"},"source":["### plot function"]},{"cell_type":"code","metadata":{"id":"KRfG9AjiVkj5"},"source":["sys.path.append(path)\n","from utils.plot import plot_imgs\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"_Q4unyskW7nu"},"source":["### test"]},{"cell_type":"code","metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000,"output_embedded_package_id":"1k3eb1S9ot4fp_Kw98kLp8urckDMs07Z7"},"id":"hxMmcv06W59h","executionInfo":{"status":"ok","timestamp":1614948272842,"user_tz":-120,"elapsed":35492,"user":{"displayName":"Omer Cohen","photoUrl":"","userId":"16255370433172908858"}},"outputId":"5823020d-4cf4-47b5-8aea-7b80bed667b1"},"source":["from utils.points import cords_to_map\n","\n","for iter, (im, label) in enumerate(dataloader):\n"," label = label.type(torch.double)\n"," label = label.unsqueeze(1)\n"," imgs= K.tensor_to_image(im)\n"," size = im.size()\n"," map = cords_to_map(label, size, device=False)\n"," im_map = K.tensor_to_image(map)\n"," if iter%10==0:\n"," print('iteration {}/{} is running'.format(iter,len(dataloader)))\n"," plot_imgs(imgs, label=label)\n"," plot_imgs(im_map)\n"," if iter>2:\n"," break\n"],"execution_count":null,"outputs":[{"output_type":"display_data","data":{"text/plain":"Output hidden; open in https://colab.research.google.com to view."},"metadata":{}}]},{"cell_type":"markdown","metadata":{"id":"jBIPot_UiK5f"},"source":["# Net architecture"]},{"cell_type":"markdown","metadata":{"id":"xQwW0N-mi0Du"},"source":["## Superpoint model"]},{"cell_type":"code","metadata":{"id":"rBCJC3jbif_q"},"source":["from models.superpoint import SuperPointNet"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"IP8lKGmjja9X"},"source":["## functions for Loss calculations"]},{"cell_type":"code","metadata":{"id":"WCsM6CjfjZYi"},"source":["from models.utils import detector_loss\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"OZ0eeaexl-0X"},"source":["# Training magic point on COCO"]},{"cell_type":"code","metadata":{"id":"EKIoF3HSbByQ"},"source":["import torch\r\n","import kornia as K\r\n","import torch.nn.functional as F\r\n","\r\n","\r\n","def detector_loss(true_map, chi, v_mask=None):\r\n"," DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\r\n"," n, c, h, w = true_map.size()\r\n"," block_size = 8\r\n"," true_map = true_map.type(torch.float)\r\n"," unfolded_map = torch.nn.functional.unfold(true_map, block_size, stride=block_size)\r\n"," unfolded_map = unfolded_map.view(n, c * block_size ** 2, h // block_size, w // block_size)\r\n"," unfolded_map = unfolded_map.permute(0,2,3,1)\r\n"," shape = torch.cat([torch.tensor(unfolded_map.size())[:3], torch.tensor([1])], dim=0)\r\n"," unfolded_map = torch.cat([2*unfolded_map, torch.ones(tuple(shape)).to(DEVICE)], dim=3)\r\n"," noise = torch.rand(unfolded_map.size())*0.1\r\n"," noise = noise.to(DEVICE)\r\n"," label = torch.argmax(unfolded_map+noise,dim=3)\r\n"," #define valid mask\r\n"," if v_mask:\r\n"," valid_mask = v_mask.type(torch.float32).to(DEVICE)\r\n"," else:\r\n"," valid_mask = torch.ones_like(true_map, dtype=torch.float32).to(DEVICE) \r\n"," # adjust valid_mask\r\n"," valid_mask = F.unfold(valid_mask, block_size, stride=block_size)\r\n"," valid_mask = valid_mask.view(n, c * block_size ** 2, h // block_size, w // block_size)\r\n"," valid_mask = valid_mask.permute(0,2,3,1)\r\n"," valid_mask = torch.prod(valid_mask, dim=3)\r\n"," label[valid_mask==0] = 65\r\n"," #get loss\r\n"," m = torch.nn.LogSoftmax(dim=1) \r\n"," loss = torch.nn.NLLLoss(ignore_index=65)\r\n"," output = loss(m(chi), label)\r\n"," return output\r\n","\r\n","\r\n"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"nAGM9utB1pyc"},"source":["## Train function"]},{"cell_type":"code","metadata":{"id":"PModR5u1l-Lf"},"source":["def train_coco_magic(dataloader,writer, net, save_path, filename, lr=0.001):\n"," t_0 = time.time()\n"," optimizer = torch.optim.Adam(model.parameters(), lr=lr)\n"," DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n"," net.to(DEVICE)\n"," model.train()\n"," for e in range(12):\n"," for iter, (im, label) in enumerate(dataloader):\n"," optimizer.zero_grad()\n"," im = im.to(DEVICE).type(torch.float)\n"," label = label.to(DEVICE)\n"," #go through model\n"," chi_points, desc = net(im)\n"," #get map label\n"," label = label.type(torch.double)\n"," size = im.size()\n"," map = cords_to_map(label, size)\n"," map[map<0.01] = 0\n"," #loss\n"," loss = detector_loss(map, chi_points)\n"," loss.backward()\n"," optimizer.step()\n"," writer.add_scalar(\"Loss/train\", loss, e*len(dataloader)+iter)\n"," if iter%10==0:\n"," print('iteration {}/{} is running'.format(e*len(dataloader)+iter,12*len(dataloader)))\n"," print('loss is:',loss.item())\n"," if iter%50==0:\n"," t_c = time.time()\n"," minute = (t_c-t_0)/60\n"," print('saving weights from iteration {} with loss {}, {} minutes pased'.format(e*len(dataloader)+iter,loss.item(),int(minute)))\n"," torch.save(model.state_dict(), save_path+filename)\n"," # Save weights\n"," torch.save(model.state_dict(), save_path+filename)\n"," t_f = time.time()\n"," hours = (t_f-t_0)/3600\n"," print('finished in {} hours'.format(hours))"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"0ziQBW1vScba"},"source":["## Run"]},{"cell_type":"code","metadata":{"id":"j1USCOAGSd6q"},"source":["#tensorflow\n","from torch.utils.tensorboard import SummaryWriter\n","from datetime import datetime\n","import os \n","\n","%load_ext tensorboard\n","\n","logs_base_dir = 'logs'\n","os.makedirs(logs_base_dir, exist_ok=True)\n","\n","log_dir_magic_synthetic_loss = \"%s/magicpoint/coco_loss/%s\" % (logs_base_dir, datetime.now().strftime(\"%m%d-%H%M\"))\n","writer_magic = SummaryWriter(log_dir_magic_synthetic_loss)\n","\n","\n","dataloader = torch.utils.data.DataLoader(dataset, batch_size=2, shuffle=True)\n","model = SuperPointNet(superpoint_bool=False)\n","weights_path = path+'/weights/magic_coco_weights_iter2.pth'\n","model.load_state_dict(torch.load(weights_path,\n"," map_location=lambda storage, loc: storage))\n","\n","\n","\n","train_coco_magic(dataloader, writer_magic, model,path, '/weights/magic_coco_weights_test.pth')\n"],"execution_count":null,"outputs":[]},{"cell_type":"code","metadata":{"id":"9XSBl7vMWvRP"},"source":["#writer_magic.flush()\n","%tensorboard --logdir 'logs'"],"execution_count":null,"outputs":[]}]} -------------------------------------------------------------------------------- /models/__pycache__/superpoint.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/models/__pycache__/superpoint.cpython-37.pyc -------------------------------------------------------------------------------- /models/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/models/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /models/superpoint.py: -------------------------------------------------------------------------------- 1 | import PIL 2 | import torch 3 | from torch import nn 4 | import numpy as np 5 | from utils.points import nms, get_prob_map 6 | import kornia as K 7 | 8 | class SuperPointNet(torch.nn.Module): 9 | def __init__(self, superpoint_bool): 10 | super(SuperPointNet, self).__init__() 11 | self.relu = nn.ReLU(inplace=True) 12 | self.pool = nn.MaxPool2d(kernel_size=2, stride=2) 13 | c1, c2, c3, c4, c5, d1 = 64, 64, 128, 128, 256, 256 14 | # Shared Encoder. 15 | self.conv1a = nn.Conv2d(1, c1, kernel_size=3, stride=1, padding=1) 16 | self.conv1b = nn.Conv2d(c1, c1, kernel_size=3, stride=1, padding=1) 17 | self.conv2a = nn.Conv2d(c1, c2, kernel_size=3, stride=1, padding=1) 18 | self.conv2b = nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1) 19 | self.conv3a = nn.Conv2d(c2, c3, kernel_size=3, stride=1, padding=1) 20 | self.conv3b = nn.Conv2d(c3, c3, kernel_size=3, stride=1, padding=1) 21 | self.conv4a = nn.Conv2d(c3, c4, kernel_size=3, stride=1, padding=1) 22 | self.conv4b = nn.Conv2d(c4, c4, kernel_size=3, stride=1, padding=1) 23 | # Detector Head. 24 | self.convPa = nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1) 25 | self.convPb = nn.Conv2d(c5, 65, kernel_size=1, stride=1, padding=0) 26 | # Descriptor Head. 27 | self.convDa = nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1) 28 | self.convDb = nn.Conv2d(c5, d1, kernel_size=1, stride=1, padding=0) 29 | self.superpoint_bool = superpoint_bool 30 | 31 | 32 | def forward(self, x): 33 | """ Forward pass that jointly computes unprocessed point and descriptor 34 | tensors. 35 | Input 36 | x: Image pytorch tensor shaped N x 1 x H x W. 37 | Output 38 | semi: Output point pytorch tensor shaped N x 65 x H/8 x W/8. 39 | desc: Output descriptor pytorch tensor shaped N x 256 x H/8 x W/8(super) 40 | or None(magicpoint). 41 | """ 42 | # Shared Encoder. 43 | Encoder = nn.Sequential(self.conv1a, self.relu, 44 | self.conv1b, self.relu, 45 | self.pool, 46 | self.conv2a, self.relu, 47 | self.conv2b, self.relu, 48 | self.pool, 49 | self.conv3a, self.relu, 50 | self.conv3b, self.relu, 51 | self.pool, 52 | self.conv4a, self.relu, 53 | self.conv4b, self.relu) 54 | x = Encoder(x) 55 | 56 | 57 | # Detector Head. 58 | cPa = self.relu(self.convPa(x)) 59 | semi = self.convPb(cPa) 60 | 61 | 62 | # Descriptor Head. 63 | #if we want superpoint model: 64 | if self.superpoint_bool: 65 | cDa = self.relu(self.convDa(x)) 66 | desc = self.convDb(cDa) 67 | dn = torch.norm(desc, p=2, dim=1) # Compute the norm. 68 | desc = desc.div(torch.unsqueeze(dn, 1)) # Divide by norm to normalize. 69 | 70 | #if we want magicpoint model: 71 | else: 72 | desc = None 73 | 74 | return semi, desc 75 | 76 | 77 | def get_descriptors(desc): 78 | desc_full_size = torch.nn.functional.interpolate(desc,(desc.size()[2]*8,desc.size()[3]*8), mode='bilinear') 79 | dn = torch.norm(desc_full_size, p=2, dim=1) # Compute the norm. 80 | descriptor_output = desc_full_size.div(torch.unsqueeze(dn, 1)) # Divide by norm to normalize. 81 | return descriptor_output 82 | 83 | 84 | 85 | import torchvision 86 | def superpoint_frontend(im, model,N_nms=4,num_points=200): 87 | DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' 88 | im_tens = np.asarray(im) 89 | transform = transforms.Compose([torchvision.transforms.ToTensor()]) 90 | im = transform(im_tens).to(DEVICE) 91 | 92 | if len(im.shape)<4: 93 | im = im.unsqueeze(0) 94 | if len(im.shape)<4: 95 | im = im.unsqueeze(0).to(DEVICE) 96 | with torch.no_grad(): 97 | semi, desc = model.forward(im) 98 | map = get_prob_map(semi) 99 | points = nms(map, N_nms,num_points) 100 | descriptors = get_descriptors(desc) 101 | map_im = K.tensor_to_image(points) 102 | cords = np.where(map_im>0) 103 | y = np.reshape(cords[0],(1,1,-1,1)) 104 | x = np.reshape(cords[1],(1,1,-1,1)) 105 | 106 | descriptors = descriptors[0,:,y.flatten(),x.flatten()] 107 | descriptors = np.array(descriptors.permute(1,0).cpu()) 108 | 109 | cords = np.concatenate((x,y),axis=3) 110 | cords = cords.squeeze(0).squeeze(0) 111 | return cords, descriptors -------------------------------------------------------------------------------- /models/utils.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import kornia as K 3 | import torch.nn.functional as F 4 | 5 | 6 | 7 | def detector_loss(true_map, chi, v_mask=None): 8 | DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' 9 | n, c, h, w = true_map.size() 10 | block_size = 8 11 | true_map = true_map.type(torch.float) 12 | unfolded_map = torch.nn.functional.unfold(true_map, block_size, stride=block_size) 13 | unfolded_map = unfolded_map.view(n, c * block_size ** 2, h // block_size, w // block_size) 14 | unfolded_map = unfolded_map.permute(0,2,3,1) 15 | shape = torch.cat([torch.tensor(unfolded_map.size())[:3], torch.tensor([1])], dim=0) 16 | unfolded_map = torch.cat([2*unfolded_map, torch.ones(tuple(shape)).to(DEVICE)], dim=3) 17 | noise = torch.rand(unfolded_map.size())*0.1 18 | noise = noise.to(DEVICE) 19 | label = torch.argmax(unfolded_map+noise,dim=3) 20 | #define valid mask 21 | if not v_mask is None: 22 | valid_mask = v_mask.type(torch.float32).to(DEVICE) 23 | else: 24 | valid_mask = torch.ones_like(true_map, dtype=torch.float32).to(DEVICE) 25 | # adjust valid_mask 26 | valid_mask = F.unfold(valid_mask, block_size, stride=block_size) 27 | valid_mask = valid_mask.view(n, c * block_size ** 2, h // block_size, w // block_size) 28 | valid_mask = valid_mask.permute(0,2,3,1) 29 | valid_mask = torch.prod(valid_mask, dim=3) 30 | label[valid_mask==0] = 65 31 | #get loss 32 | m = torch.nn.LogSoftmax(dim=1) 33 | loss = torch.nn.NLLLoss(ignore_index=65) 34 | output = loss(m(chi), label) 35 | return output 36 | 37 | 38 | 39 | 40 | 41 | def descriptor_loss(DESC, warp_DESC, H, H_invert, v_mask): 42 | DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' 43 | H_invert = H_invert.unsqueeze(1) 44 | desc = DESC.permute(0,2,3,1) 45 | B,Hc,Wc = tuple(desc.size())[:3] 46 | 47 | #create grid of center of HcxWc region 48 | cords = torch.stack(torch.meshgrid(torch.range(0,Hc-1),torch.range(0,Wc-1)), dim=-1).type(torch.int32).to(DEVICE) 49 | cords = cords.unsqueeze(0) 50 | cords = cords*8 + 4 51 | 52 | #change from ij to xy cords to warp grid 53 | xy_cords = torch.cat((cords[:,:,:,1].unsqueeze(3),cords[:,:,:,0].unsqueeze(3)),dim=-1) 54 | xy_warp_cords = K.geometry.warp.warp_grid(xy_cords,H_invert) 55 | 56 | #change back to ij 57 | warp_cords = torch.cat((xy_warp_cords[:,:,:,1].unsqueeze(3),xy_warp_cords[:,:,:,0].unsqueeze(3)),dim=-1) 58 | 59 | # calc S 60 | ''' 61 | S[id_batch, h, w, h', w'] == 1 if the point of coordinates (h, w) warped by 62 | the homography is at a distance from (h', w') less than 8 and 0 otherwise 63 | ''' 64 | cords = cords.view((1,1,1,Hc,Wc,2)).type(torch.float) 65 | warp_cords = warp_cords.view((B, Hc, Wc, 1, 1, 2)) 66 | distance_map = torch.norm(cords-warp_cords, dim=-1) 67 | S = distance_map <= 7.5 68 | S = S.type(torch.float) 69 | 70 | #descriptors 71 | desc = DESC.view((B, Hc, Wc, 1, 1, -1)) 72 | desc = F.normalize(desc, dim=-1) 73 | warp_desc = warp_DESC.view((B, 1, 1, Hc, Wc, -1)) 74 | warp_desc = F.normalize(warp_desc, dim=-1) 75 | 76 | #dot product calc 77 | ''' 78 | dot_product_desc[id_batch, h, w, h', w'] is the dot product between the 79 | descriptor at position (h, w) in the original descriptors map and the 80 | descriptor at position (h', w') in the warped image 81 | ''' 82 | dot_product = torch.sum(desc*warp_desc,dim=-1) 83 | relu = torch.nn.ReLU() 84 | dot_product = relu(dot_product) 85 | 86 | dot_product = F.normalize(dot_product.view((B, Hc, Wc, Hc * Wc)),dim=3) 87 | dot_product = dot_product.view((B, Hc, Wc, Hc, Wc)) 88 | 89 | dot_product = F.normalize(dot_product.view((B, Hc * Wc, Hc, Wc)),dim=1) 90 | dot_product = dot_product.view((B, Hc, Wc, Hc, Wc)) 91 | 92 | # Compute the loss 93 | pos_margin = 1 94 | neg_margin = 0.2 95 | lambda_d = 250 96 | positive_dist = torch.max(torch.zeros_like(dot_product), pos_margin - dot_product) 97 | negative_dist = torch.max(torch.zeros_like(dot_product), dot_product - neg_margin) 98 | loss = lambda_d * S * positive_dist + (1 - S) * negative_dist 99 | 100 | # adjust valid_mask 101 | block_size = 8 102 | valid_mask = F.unfold(v_mask, block_size, stride=block_size) 103 | valid_mask = valid_mask.view(B, block_size ** 2, Hc, Wc) 104 | valid_mask = valid_mask.permute(0,2,3,1) 105 | valid_mask = torch.prod(valid_mask, dim=3) 106 | valid_mask = valid_mask.view((B,1,1,Hc,Wc)) 107 | 108 | normalization = torch.sum(valid_mask, dim=(1,2,3,4)) * Hc * Wc 109 | loss = torch.sum(loss*valid_mask, dim=(1,2,3,4))/normalization 110 | loss = torch.sum(loss) 111 | 112 | return loss 113 | -------------------------------------------------------------------------------- /pretrained_weights/superpoint_v1.pth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/pretrained_weights/superpoint_v1.pth -------------------------------------------------------------------------------- /utils/__pycache__/plot.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/utils/__pycache__/plot.cpython-37.pyc -------------------------------------------------------------------------------- /utils/__pycache__/points.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omercohen93/superpoint-pytorch/32e615d85cdbace8e7e2b376cfd39325b3b04ed3/utils/__pycache__/points.cpython-37.pyc -------------------------------------------------------------------------------- /utils/plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import cv2 4 | 5 | 6 | def plot_imgs(imgs, label=None, titles=None, cmap='gray', ylabel='', normalize=False, ax=None, dpi=100): 7 | if not label is None: 8 | landmarks = np.asarray(label[:,0,:,:]) 9 | landmarks = landmarks.astype('float') 10 | n = len(imgs) 11 | if not isinstance(cmap, list): 12 | cmap = [cmap]*n 13 | if ax is None: 14 | _, ax = plt.subplots(1, n, figsize=(6*n, 6), dpi=dpi) 15 | if n == 1: 16 | ax = [ax] 17 | else: 18 | if not isinstance(ax, list): 19 | ax = [ax] 20 | assert len(ax) == len(imgs) 21 | for i in range(n): 22 | if imgs[i].shape[-1] == 3: 23 | imgs[i] = imgs[i][..., ::-1] # BGR to RGB 24 | #imgs[i] = cv2.GaussianBlur(imgs[i], (21, 21), 0) #add this blur in train 25 | ax[i].imshow(imgs[i], cmap=plt.get_cmap(cmap[i]), 26 | vmin=None if normalize else 0, 27 | vmax=None if normalize else 1) 28 | if titles: 29 | ax[i].set_title(titles[i]) 30 | ax[i].get_yaxis().set_ticks([]) 31 | ax[i].get_xaxis().set_ticks([]) 32 | for spine in ax[i].spines.values(): # remove frame 33 | spine.set_visible(False) 34 | if not label is None: 35 | ax[i].scatter(landmarks[i,:, 0][landmarks[i,:, 2]>0.01], landmarks[i,:, 1][landmarks[i,:, 2]>0.01], s=40, marker='.', c='r') 36 | 37 | ax[0].set_ylabel(ylabel) 38 | plt.tight_layout() 39 | 40 | def show_batch(sample_batched): 41 | #imgs, pts = sample_batched 42 | #imgs = imgs.detach().numpy() 43 | img, pts = sample_batched['image'].detach().numpy(), sample_batched['points'].detach().numpy() 44 | print('show_batch func.img=', img,'pts',pts) 45 | batch_size = len(img) 46 | 47 | for i in range(batch_size): 48 | if pts != np.empty((0, 2)): 49 | keypoints = get_keypoints(img, pts, (0,255,0)) 50 | plot_imgs(imgs=[keypoints/255],dpi=200) 51 | else: 52 | print('no points to disply') 53 | plot_imgs([img/255]) 54 | -------------------------------------------------------------------------------- /utils/points.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | from torchvision import transforms 4 | import PIL 5 | 6 | # get point map from coordinates set [x,y,V] where V is the value 7 | def cords_to_map(label,im_size, thres_point=0.015, device=True): 8 | if device: 9 | DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' 10 | else: 11 | DEVICE = 'cpu' 12 | map = torch.zeros(im_size).type(torch.double).to(DEVICE) 13 | if len(label.size())<4: 14 | label = label.unsqueeze(1) 15 | label = label.type(torch.double) 16 | vals = label[:,:,:,2].flatten() 17 | x = label[:,:,:,0].type(torch.long).flatten() 18 | y = label[:,:,:,1].type(torch.long).flatten() 19 | B = torch.arange(im_size[0], dtype=torch.long)[:, None].expand(im_size[0], label.size(2)).flatten().to(DEVICE) 20 | map[B[vals>0],0,y[vals>0],x[vals>0]]=vals[vals>0] 21 | one = torch.tensor(1).to(DEVICE) 22 | zero = torch.tensor(0).to(DEVICE) 23 | map = torch.where(map>thres_point, one, zero) 24 | return map 25 | 26 | 27 | # get coordinates set [x,y,V] where V is the value from point map 28 | def map_to_cords(BATCH_SIZE, iter, points,names): 29 | cord = torch.where(points>0) 30 | y = cord[2].cpu() 31 | y = np.array(y.unsqueeze(1)) 32 | x = cord[3].cpu() 33 | x = np.array(x.unsqueeze(1)) 34 | prob = points[cord].cpu() 35 | prob = np.array(prob.unsqueeze(1)) 36 | full_cord = np.concatenate((x,y,prob),axis=1) 37 | full_cord = full_cord.reshape((BATCH_SIZE,-1)) 38 | new_cords = np.concatenate((names[BATCH_SIZE*iter:BATCH_SIZE*(iter+1),:],full_cord), axis=1) 39 | return new_cords 40 | 41 | 42 | #perform non maximum supresion, posible to get tok k point, and/or all points above thres 43 | def nms(prob, size, topk=None, thres=None): 44 | orig_shape = prob.size() 45 | pool = torch.nn.MaxPool2d(size,size, int(size/2), return_indices=True) 46 | unpool = torch.nn.MaxUnpool2d(size, size, int(size/2)) 47 | folded_points, ind = pool(prob) 48 | points = unpool(folded_points, ind, orig_shape) 49 | 50 | if not topk is None: 51 | # Reshape and calculate positions of top 10% 52 | points = points.view(points.size(0), points.size(1), -1) 53 | nb_pixels = points.size(2) 54 | ret = torch.topk(points, k=topk, dim=2) 55 | ret.indices.shape 56 | 57 | # Scatter to zero'd tensor 58 | res = torch.zeros_like(points) 59 | res.scatter_(2, ret.indices, ret.values) 60 | points = res.view(orig_shape) 61 | if not thres is None: 62 | points[points