Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added query/IMG_20251212_153550.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added query/IMG_20251212_154309.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added query/img1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added query/img2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
171 changes: 171 additions & 0 deletions src/localize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""Minimal single-image localization against an existing SfM model.

Goals:
- Do NOT re-process the mapping dataset.
- Reuse mapping outputs: SfM model + DB local features + DB global descriptors.
- Only compute query descriptors/features if they are missing (unless OVERWRITE=True).
"""

from pathlib import Path
import shutil
import uuid

import pycolmap

from hloc import extract_features, localize_sfm, match_features, pairs_from_retrieval


# --- CONFIGURATION (edit these) ---
outputs = Path('outputs/kulliye/')
sfm_dir = outputs / 'sfm'
query_dir = Path('query')
query_name = 'IMG_20251212_153550.jpg'
top_k = 50
OVERWRITE = False
KEEP_CACHE = False # if False, only the pose output is kept

# Must match your mapping pipeline
feature_conf = extract_features.confs['aliked-n16']
retrieval_conf = extract_features.confs['netvlad']
matcher_conf = match_features.confs['aliked+lightglue']

# Mapping artifacts (must already exist)
features_db = outputs / f"{feature_conf['output']}.h5"
global_db = outputs / f"{retrieval_conf['output']}.h5"

# Output directory
loc_outputs = outputs / 'localization'
loc_outputs.mkdir(exist_ok=True)
results = loc_outputs / 'poses.txt'


def _select_sfm_model(root: Path) -> Path:
models_dir = root / 'models'
if not models_dir.exists():
return root
best_dir = None
best_n = -1
for sub in sorted(models_dir.iterdir()):
if not sub.is_dir():
continue
required = ['cameras.bin', 'images.bin', 'points3D.bin']
if not all((sub / f).exists() for f in required):
continue
try:
rec = pycolmap.Reconstruction(sub)
except Exception:
continue
if len(rec.images) > best_n:
best_n = len(rec.images)
best_dir = sub
return best_dir or root


def main():
if not sfm_dir.exists():
raise FileNotFoundError(f"SfM model not found: {sfm_dir}")
if not features_db.exists():
raise FileNotFoundError(f"DB local features missing: {features_db}")
if not global_db.exists():
raise FileNotFoundError(
f"DB global descriptors missing: {global_db}\n"
"Run mapping once with NetVLAD to create it (global-feats-netvlad.h5)."
)

query_path = query_dir / query_name
if not query_path.exists():
raise FileNotFoundError(f"Query image not found: {query_path}")

sfm_model = _select_sfm_model(sfm_dir)
ref = pycolmap.Reconstruction(sfm_model)
if len(ref.images) == 0 or len(ref.cameras) == 0:
raise RuntimeError("Reference reconstruction is empty.")

db_names = [img.name for img in ref.images.values()]
k = min(top_k, len(db_names))

# Put intermediate artifacts into a temp directory unless the user wants caching.
# This keeps the workspace clean and prevents accumulation across different queries.
qtag = Path(query_name).stem
if KEEP_CACHE:
work_dir = loc_outputs
global_query = work_dir / f'global_query_{qtag}.h5'
features_query = work_dir / f'query_features_{qtag}.h5'
retrieval_pairs = work_dir / f'retrieval_{qtag}_top{k}.txt'
matches_query_db = work_dir / f'query_db_matches_{qtag}.h5'
queries_list = work_dir / f'queries_with_intrinsics_{qtag}.txt'
cleanup_dir = None
else:
work_dir = loc_outputs / f"tmp_localize_{qtag}_{uuid.uuid4().hex[:8]}"
if work_dir.exists():
shutil.rmtree(work_dir, ignore_errors=True)
work_dir.mkdir(parents=True, exist_ok=True)
global_query = work_dir / 'global_query.h5'
features_query = work_dir / 'query_features.h5'
retrieval_pairs = work_dir / f'retrieval_top{k}.txt'
matches_query_db = work_dir / 'query_db_matches.h5'
queries_list = work_dir / 'queries_with_intrinsics.txt'
cleanup_dir = work_dir

# Intrinsics: reuse first camera (minimal; replace with your real intrinsics if needed)
ref_cam = next(iter(ref.cameras.values()))
model_str = getattr(ref_cam, 'model_name', None) or str(ref_cam.model)
if '.' in model_str:
model_str = model_str.split('.')[-1]
params = ' '.join(map(str, ref_cam.params))
try:
queries_list.write_text(
f"{query_name} {model_str} {ref_cam.width} {ref_cam.height} {params}\n"
)

# 1) Retrieval (query descriptor is computed only if missing)
extract_features.main(
retrieval_conf,
query_dir,
image_list=[query_name],
feature_path=global_query,
overwrite=OVERWRITE,
)
pairs_from_retrieval.main(
descriptors=global_query,
output=retrieval_pairs,
num_matched=k,
db_model=sfm_model,
db_descriptors=global_db,
)

# 2) Query local features + matching to DB
extract_features.main(
feature_conf,
query_dir,
image_list=[query_name],
feature_path=features_query,
overwrite=OVERWRITE,
)
match_features.main(
matcher_conf,
retrieval_pairs,
features=features_query,
features_ref=features_db,
matches=matches_query_db,
overwrite=OVERWRITE,
)

# 3) PnP
localize_sfm.main(
reference_sfm=sfm_model,
queries=queries_list,
retrieval=retrieval_pairs,
features=features_query,
matches=matches_query_db,
results=results,
)
finally:
if cleanup_dir is not None:
shutil.rmtree(cleanup_dir, ignore_errors=True)

print(f"Pose written to: {results}")


if __name__ == '__main__':
main()
205 changes: 205 additions & 0 deletions src/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import sys
from pathlib import Path
import pycolmap

# HLoc yolunu ekle
current_dir = Path(__file__).resolve().parent
project_root = current_dir.parent
hloc_path = project_root / 'Hierarchical-Localization'
if str(hloc_path) not in sys.path:
sys.path.append(str(hloc_path))

from hloc import (
extract_features,
match_features,
pairs_from_retrieval,
pairs_from_covisibility,
pairs_from_exhaustive,
reconstruction,
visualization,
)


class FeatureExtractor:
def __init__(self, dataset_path, outputs_path):
self.dataset_path = Path(dataset_path)
self.outputs_path = Path(outputs_path)
self.outputs_path.mkdir(parents=True, exist_ok=True)

# DOSYA İSİMLERİ (Sabitler)
self.global_feat_name = "global-feats-netvlad"
# Senin elindeki dosya ismi
self.local_feat_name = "feats-superpoint-n4096-r1024"

# Tam Dosya Yolları
self.global_feats_path = self.outputs_path / (self.global_feat_name + ".h5")
self.local_feats_path = self.outputs_path / (self.local_feat_name + ".h5")

self.mapping_list = []
self.query_list = []
self._create_image_lists()

def _create_image_lists(self):
print("--- Resim Listeleri Taranıyor ---")
for p in (self.dataset_path / 'mapping').glob('**/*'):
if p.suffix.lower() in {'.jpg', '.png', '.jpeg'}:
self.mapping_list.append(p.relative_to(self.dataset_path).as_posix())

for p in (self.dataset_path / 'query').glob('**/*'):
if p.suffix.lower() in {'.jpg', '.png', '.jpeg'}:
self.query_list.append(p.relative_to(self.dataset_path).as_posix())

print(f"Mapping: {len(self.mapping_list)} | Query: {len(self.query_list)}")

def extract_global_features(self):
conf = extract_features.confs['netvlad']
conf['output'] = self.global_feat_name

if self.global_feats_path.exists():
print(f"✅ Global özellikler bulundu (ATLANDI): {self.global_feats_path.name}")
return self.global_feats_path

print(f"🚀 Global Özellikler Çıkarılıyor...")
return extract_features.main(
conf, self.dataset_path, self.outputs_path,
image_list=self.mapping_list + self.query_list
)

def extract_local_features(self):
conf = extract_features.confs['superpoint_aachen']
conf['model']['max_keypoints'] = 4096
conf['preprocessing']['resize_max'] = 1024
print(f"DEBUG: {self.local_feat_name} ")
conf['output'] = self.local_feat_name

if self.local_feats_path.exists():
print(f"✅ Yerel özellikler bulundu (ATLANDI): {self.local_feats_path.name}")
return self.local_feats_path

print(f"🚀 Yerel Özellikler Çıkarılıyor...")
return extract_features.main(
conf, self.dataset_path, self.outputs_path,
image_list=self.mapping_list + self.query_list
)
def match_mapping_images_for_sfm(extractor):
"""
3D harita oluşturmak için mapping görüntülerini non-sequential şekilde eşleştirir.
Aachen v1.1 SIFT modeli varsa covisibility kullanır, yoksa exhaustive'e düşer.
"""
outputs_path = extractor.outputs_path

# 1. Mapping-Mapping çiftlerini üret
sfm_pairs = outputs_path / "pairs-mapping.txt"
aachen_sift_model = extractor.dataset_path / "3D-models" / "aachen_v_1_1"

if not sfm_pairs.exists():
if aachen_sift_model.exists():
print("📄 Covisibility tabanli pair'ler uretiliyor...")
pairs_from_covisibility.main(aachen_sift_model, sfm_pairs, num_matched=20)
else:
print("📄 Covisibility modeli bulunamadi, exhaustive pair'lere dusuluyor...")
pairs_from_exhaustive.main(sfm_pairs, image_list=sorted(extractor.mapping_list))

else:
print(f"✅ Çift listesi zaten var: {sfm_pairs.name}")

# 2. SuperGlue ile Eşleştir
print("--- [SfM] SuperGlue ile Eşleştiriliyor (Mapping-Mapping) ---")
match_conf = match_features.confs['superglue']
match_conf['model']['weights'] = 'outdoor'

sfm_matches = outputs_path / 'matches-mapping-superglue.h5'

if not sfm_matches.exists():
# DÜZELTME BURADA: feature_path -> features, matches_path -> matches
match_features.main(
match_conf,
sfm_pairs,
features=extractor.local_feats_path,
matches=sfm_matches
)
else:
print(f"✅ Eşleşme dosyası zaten var: {sfm_matches.name}")

return sfm_pairs, sfm_matches


def match_and_visualize(extractor, global_feats, local_feats, visualize=True):
outputs_path = extractor.outputs_path
loc_pairs = outputs_path / 'pairs-query-netvlad.txt'

if not loc_pairs.exists():
print("\n--- Eşleşme Adayları Bulunuyor ---")
pairs_from_retrieval.main(
global_feats, loc_pairs, num_matched=5,
query_list=extractor.query_list, db_list=extractor.mapping_list
)
else:
print(f"\n✅ Çift listesi bulundu: {loc_pairs.name}")

print("--- SuperGlue ile Eşleştiriliyor (Query-Mapping) ---")
match_conf = match_features.confs['superglue']
match_conf['model']['weights'] = 'outdoor'

matches_path = outputs_path / (extractor.matches_name + ".h5")

# DÜZELTME BURADA DA YAPILDI
matches_path = match_features.main(
match_conf, loc_pairs,
features=local_feats,
matches=matches_path
)

if visualize:
print("\n🎨 Eşleşmeler Görselleştiriliyor...")
vis_path = outputs_path / 'viz_matches'
vis_path.mkdir(exist_ok=True)

visualization.visualize_loc_from_log(
outputs_path,
query_list=extractor.query_list,
n=5,
top_k_db=1,
seed=2,
)
print(f"👀 Görseller: {vis_path}")

return matches_path


def run_sfm_reconstruction(extractor, sfm_pairs, sfm_matches):
"""
COLMAP kullanarak 3D Harita (Sparse Reconstruction) oluşturur.
"""
sfm_dir = extractor.outputs_path / 'sfm_map'

if (sfm_dir / "cameras.bin").exists():
print(f"✅ Harita zaten oluşturulmuş: {sfm_dir}")
print("Yeniden oluşturmak istiyorsan 'outputs/sfm_map' klasörünü sil.")
return sfm_dir

sfm_dir.mkdir(exist_ok=True)

print(f"\n--- [COLMAP] 3D Harita Oluşturuluyor... ---")
print(f"Hedef Klasör: {sfm_dir}")

mapper_options = {
"num_threads": 16,
"min_num_matches": 8, # 🔥 çok önemli
"ba_local_max_num_iterations": 25,
"ba_global_max_num_iterations": 50,
}

model = reconstruction.main(
sfm_dir,
extractor.dataset_path,
sfm_pairs,
extractor.local_feats_path,
sfm_matches,
image_list=extractor.mapping_list,
camera_mode="AUTO",
mapper_options=mapper_options
)

print(f"✅ Harita başarıyla oluşturuldu!")
return model
Loading
Loading