You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
718 KiB

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Demo LoFTR-DS on a single pair of images\n",
"\n",
"This notebook shows how to use the loftr matcher with default config(dual-softmax) and the pretrained weights."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"os.chdir(\"..\")\n",
"import torch\n",
"import cv2\n",
"import numpy as np\n",
"import matplotlib.cm as cm\n",
"from src.utils.plotting import make_matching_figure"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Indoor Example"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from src.loftr import LoFTR, default_cfg\n",
"\n",
"# The default config uses dual-softmax.\n",
"# The outdoor and indoor models share the same config.\n",
"# You can change the default values like thr and coarse_match_type.\n",
"matcher = LoFTR(config=default_cfg)\n",
"matcher.load_state_dict(torch.load(\"weights/indoor_ds.ckpt\")['state_dict'])\n",
"matcher = matcher.eval().cuda()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# Load example images\n",
"img0_pth = \"assets/scannet_sample_images/scene0711_00_frame-001680.jpg\"\n",
"img1_pth = \"assets/scannet_sample_images/scene0711_00_frame-001995.jpg\"\n",
"img0_raw = cv2.imread(img0_pth, cv2.IMREAD_GRAYSCALE)\n",
"img1_raw = cv2.imread(img1_pth, cv2.IMREAD_GRAYSCALE)\n",
"img0_raw = cv2.resize(img0_raw, (640, 480))\n",
"img1_raw = cv2.resize(img1_raw, (640, 480))\n",
"\n",
"img0 = torch.from_numpy(img0_raw)[None][None].cuda() / 255.\n",
"img1 = torch.from_numpy(img1_raw)[None][None].cuda() / 255.\n",
"batch = {'image0': img0, 'image1': img1}\n",
"\n",
"# Inference with LoFTR and get prediction\n",
"with torch.no_grad():\n",
" matcher(batch)\n",
" mkpts0 = batch['mkpts0_f'].cpu().numpy()\n",
" mkpts1 = batch['mkpts1_f'].cpu().numpy()\n",
" mconf = batch['mconf'].cpu().numpy()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 750x450 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Draw\n",
"color = cm.jet(mconf)\n",
"text = [\n",
" 'LoFTR',\n",
" 'Matches: {}'.format(len(mkpts0)),\n",
"]\n",
"fig = make_matching_figure(img0_raw, img1_raw, mkpts0, mkpts1, color, text=text)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Outdoor Example"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"from src.loftr import LoFTR, default_cfg\n",
"\n",
"# The default config uses dual-softmax.\n",
"# The outdoor and indoor models share the same config.\n",
"# You can change the default values like thr and coarse_match_type.\n",
"matcher = LoFTR(config=default_cfg)\n",
"matcher.load_state_dict(torch.load(\"weights/outdoor_ds.ckpt\")['state_dict'])\n",
"matcher = matcher.eval().cuda()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Load example images\n",
"img0_pth = \"assets/phototourism_sample_images/united_states_capitol_26757027_6717084061.jpg\"\n",
"img1_pth = \"assets/phototourism_sample_images/united_states_capitol_98169888_3347710852.jpg\"\n",
"img0_raw = cv2.imread(img0_pth, cv2.IMREAD_GRAYSCALE)\n",
"img1_raw = cv2.imread(img1_pth, cv2.IMREAD_GRAYSCALE)\n",
"img0_raw = cv2.resize(img0_raw, (img0_raw.shape[1]//8*8, img0_raw.shape[0]//8*8)) # input size shuold be divisible by 8\n",
"img1_raw = cv2.resize(img1_raw, (img1_raw.shape[1]//8*8, img1_raw.shape[0]//8*8))\n",
"\n",
"img0 = torch.from_numpy(img0_raw)[None][None].cuda() / 255.\n",
"img1 = torch.from_numpy(img1_raw)[None][None].cuda() / 255.\n",
"batch = {'image0': img0, 'image1': img1}\n",
"\n",
"# Inference with LoFTR and get prediction\n",
"with torch.no_grad():\n",
" matcher(batch)\n",
" mkpts0 = batch['mkpts0_f'].cpu().numpy()\n",
" mkpts1 = batch['mkpts1_f'].cpu().numpy()\n",
" mconf = batch['mconf'].cpu().numpy()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 750x450 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Draw\n",
"color = cm.jet(mconf)\n",
"text = [\n",
" 'LoFTR',\n",
" 'Matches: {}'.format(len(mkpts0)),\n",
"]\n",
"fig = make_matching_figure(img0_raw, img1_raw, mkpts0, mkpts1, color, text=text)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.10 64-bit ('loftr': conda)",
"name": "python3810jvsc74a57bd065d19ceb1c5e26d1d1e4e93c9824aa3d4633a56cbb655ff57f645571fa154c9a"
},
"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.8.10"
},
"orig_nbformat": 2
},
"nbformat": 4,
"nbformat_minor": 2
}