Spaces:
Paused
Paused
Fix: graceful GPU/CPU fallback, remove spaces dependency requirement
Browse files- README.md +4 -6
- app.py +27 -29
- requirements.txt +0 -1
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: AnyCalib
|
| 3 |
emoji: "\U0001F4F7"
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: blue
|
|
@@ -14,15 +14,14 @@ tags:
|
|
| 14 |
- computer-vision
|
| 15 |
- lens-correction
|
| 16 |
- dinov2
|
| 17 |
-
- gpu
|
| 18 |
-
- zerogpu
|
| 19 |
---
|
| 20 |
|
| 21 |
-
# AnyCalib β Full-Resolution
|
| 22 |
|
| 23 |
-
Single-image camera calibration and lens distortion correction
|
| 24 |
|
| 25 |
No quantization, no resolution limits β full FP32 inference with the complete AnyCalib pipeline.
|
|
|
|
| 26 |
|
| 27 |
## What it does
|
| 28 |
|
|
@@ -45,5 +44,4 @@ No quantization, no resolution limits β full FP32 inference with the complete
|
|
| 45 |
- **Total**: ~320M parameters, full FP32
|
| 46 |
- **Weights**: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib)
|
| 47 |
- **ONNX**: [SebRincon/anycalib-onnx](https://huggingface.co/SebRincon/anycalib-onnx)
|
| 48 |
-
- **WASM demo**: [SebRincon/anycalib-wasm](https://huggingface.co/spaces/SebRincon/anycalib-wasm)
|
| 49 |
- **Source**: [github.com/javrtg/AnyCalib](https://github.com/javrtg/AnyCalib)
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AnyCalib
|
| 3 |
emoji: "\U0001F4F7"
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: blue
|
|
|
|
| 14 |
- computer-vision
|
| 15 |
- lens-correction
|
| 16 |
- dinov2
|
|
|
|
|
|
|
| 17 |
---
|
| 18 |
|
| 19 |
+
# AnyCalib β Full-Resolution Camera Calibration
|
| 20 |
|
| 21 |
+
Single-image camera calibration and lens distortion correction.
|
| 22 |
|
| 23 |
No quantization, no resolution limits β full FP32 inference with the complete AnyCalib pipeline.
|
| 24 |
+
Automatically uses GPU when available (ZeroGPU / dedicated), falls back to CPU.
|
| 25 |
|
| 26 |
## What it does
|
| 27 |
|
|
|
|
| 44 |
- **Total**: ~320M parameters, full FP32
|
| 45 |
- **Weights**: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib)
|
| 46 |
- **ONNX**: [SebRincon/anycalib-onnx](https://huggingface.co/SebRincon/anycalib-onnx)
|
|
|
|
| 47 |
- **Source**: [github.com/javrtg/AnyCalib](https://github.com/javrtg/AnyCalib)
|
app.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
"""
|
| 2 |
-
AnyCalib β Full-Resolution
|
| 3 |
|
| 4 |
-
Gradio Space running the full AnyCalib pipeline
|
| 5 |
1. DINOv2 ViT-L/14 backbone β LightDPT decoder β ConvexTangentDecoder head
|
| 6 |
2. RANSAC + Gauss-Newton calibrator β camera intrinsics [f, cx, cy, k1, ...]
|
| 7 |
3. Full-resolution undistortion via grid_sample
|
| 8 |
|
| 9 |
-
No resolution limits. No quantization. Full FP32
|
|
|
|
| 10 |
"""
|
| 11 |
from __future__ import annotations
|
| 12 |
|
|
@@ -15,11 +16,18 @@ import time
|
|
| 15 |
|
| 16 |
import gradio as gr
|
| 17 |
import numpy as np
|
| 18 |
-
import spaces
|
| 19 |
import torch
|
| 20 |
|
| 21 |
-
# ββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
|
|
|
| 23 |
from anycalib.model.anycalib_pretrained import AnyCalib
|
| 24 |
from anycalib.cameras.factory import CameraFactory
|
| 25 |
|
|
@@ -27,12 +35,10 @@ print("[anycalib] Loading model...")
|
|
| 27 |
t0 = time.time()
|
| 28 |
MODEL = AnyCalib(model_id="anycalib_gen")
|
| 29 |
MODEL.eval()
|
| 30 |
-
|
| 31 |
-
|
| 32 |
|
| 33 |
|
| 34 |
-
# ββ Undistortion grid builder ββ
|
| 35 |
-
|
| 36 |
def _build_undistort_grid(camera, params, h, w, scale=1.0, target_proj="perspective"):
|
| 37 |
"""Build undistortion sampling grid (mirrors AnyCalibRunner._undistort_grid)."""
|
| 38 |
params_b = params[None, ...] if params.ndim == 1 else params
|
|
@@ -61,9 +67,7 @@ def _build_undistort_grid(camera, params, h, w, scale=1.0, target_proj="perspect
|
|
| 61 |
return grid, valid
|
| 62 |
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
@spaces.GPU(duration=60)
|
| 67 |
@torch.no_grad()
|
| 68 |
def run_calibration(
|
| 69 |
input_image: np.ndarray,
|
|
@@ -74,7 +78,7 @@ def run_calibration(
|
|
| 74 |
interp_mode: str,
|
| 75 |
k1_threshold: float,
|
| 76 |
):
|
| 77 |
-
"""Full pipeline: predict
|
| 78 |
|
| 79 |
if input_image is None:
|
| 80 |
raise gr.Error("Please upload an image.")
|
|
@@ -85,19 +89,19 @@ def run_calibration(
|
|
| 85 |
h, w = input_image.shape[:2]
|
| 86 |
t_total = time.time()
|
| 87 |
|
| 88 |
-
#
|
| 89 |
x = input_image.astype("float32") / 255.0
|
| 90 |
-
x = np.transpose(x, (2, 0, 1))
|
| 91 |
x_t = torch.from_numpy(x).to(device)
|
| 92 |
|
| 93 |
-
#
|
| 94 |
t0 = time.time()
|
| 95 |
out = MODEL.predict(x_t, cam_id=cam_id)
|
| 96 |
intrinsics = out["intrinsics"]
|
| 97 |
pred_size = out.get("pred_size")
|
| 98 |
t_infer = time.time() - t0
|
| 99 |
|
| 100 |
-
#
|
| 101 |
camera = CameraFactory.create_from_id(cam_id)
|
| 102 |
num_f = int(camera.NUM_F)
|
| 103 |
intr_list = intrinsics.detach().cpu().numpy().astype(np.float64).tolist()
|
|
@@ -106,12 +110,10 @@ def run_calibration(
|
|
| 106 |
cx_val, cy_val = intr_list[num_f], intr_list[num_f + 1]
|
| 107 |
k1_val = intr_list[num_f + 2] if len(intr_list) > num_f + 2 else 0.0
|
| 108 |
|
| 109 |
-
# FOV
|
| 110 |
f_px = focal[0]
|
| 111 |
fov_h = float(2 * np.degrees(np.arctan(w / (2 * f_px)))) if f_px > 0 else 0
|
| 112 |
fov_v = float(2 * np.degrees(np.arctan(h / (2 * f_px)))) if f_px > 0 else 0
|
| 113 |
|
| 114 |
-
# Distortion type
|
| 115 |
if k1_val < -0.001:
|
| 116 |
dist_type = "Barrel (k1 < 0)"
|
| 117 |
elif k1_val > 0.001:
|
|
@@ -119,7 +121,6 @@ def run_calibration(
|
|
| 119 |
else:
|
| 120 |
dist_type = "Negligible"
|
| 121 |
|
| 122 |
-
# ββ k1 gating ββ
|
| 123 |
skip_undistort = k1_threshold > 0 and abs(k1_val) < k1_threshold
|
| 124 |
|
| 125 |
if skip_undistort:
|
|
@@ -127,7 +128,6 @@ def run_calibration(
|
|
| 127 |
valid_frac = 1.0
|
| 128 |
t_undistort = 0.0
|
| 129 |
else:
|
| 130 |
-
# ββ Undistortion at full resolution ββ
|
| 131 |
t0 = time.time()
|
| 132 |
grid, valid = _build_undistort_grid(
|
| 133 |
camera, intrinsics, h, w,
|
|
@@ -149,7 +149,8 @@ def run_calibration(
|
|
| 149 |
|
| 150 |
t_total_elapsed = time.time() - t_total
|
| 151 |
|
| 152 |
-
|
|
|
|
| 153 |
params_md = f"""
|
| 154 |
### Camera Intrinsics
|
| 155 |
|
|
@@ -174,17 +175,16 @@ def run_calibration(
|
|
| 174 |
| **Scale** | `{scale}` |
|
| 175 |
| **Target projection** | `{target_proj}` |
|
| 176 |
|
| 177 |
-
### Timing
|
| 178 |
|
| 179 |
| Stage | Time |
|
| 180 |
|-------|------|
|
| 181 |
| Neural net inference | `{t_infer*1000:.0f}` ms |
|
| 182 |
| Undistortion (grid_sample) | `{t_undistort*1000:.0f}` ms |
|
| 183 |
| **Total** | **`{t_total_elapsed*1000:.0f}` ms** |
|
| 184 |
-
|
|
| 185 |
"""
|
| 186 |
|
| 187 |
-
# ββ Raw JSON ββ
|
| 188 |
raw_json = json.dumps({
|
| 189 |
"intrinsics": {
|
| 190 |
"focal_length_px": focal,
|
|
@@ -225,12 +225,12 @@ def run_calibration(
|
|
| 225 |
with gr.Blocks() as demo:
|
| 226 |
|
| 227 |
gr.Markdown("""
|
| 228 |
-
# AnyCalib β Full-Resolution
|
| 229 |
|
| 230 |
Single-image lens calibration & distortion correction powered by
|
| 231 |
[AnyCalib](https://github.com/javrtg/AnyCalib) (DINOv2 ViT-L/14 + LightDPT + ConvexTangentDecoder, ~320M params).
|
| 232 |
|
| 233 |
-
|
| 234 |
|
| 235 |
Upload any image and get the **corrected (undistorted) image** at original resolution,
|
| 236 |
plus camera intrinsics, FOV, distortion parameters, and timing.
|
|
@@ -302,8 +302,6 @@ plus camera intrinsics, FOV, distortion parameters, and timing.
|
|
| 302 |
4. Image is **undistorted at full resolution** via differentiable grid_sample
|
| 303 |
5. All parameters and raw JSON output are displayed
|
| 304 |
|
| 305 |
-
Runs in ~100-500ms on GPU depending on image size.
|
| 306 |
-
|
| 307 |
### Links
|
| 308 |
|
| 309 |
- Raw weights: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib) (safetensors)
|
|
|
|
| 1 |
"""
|
| 2 |
+
AnyCalib β Full-Resolution Camera Calibration & Lens Correction
|
| 3 |
|
| 4 |
+
Gradio Space running the full AnyCalib pipeline:
|
| 5 |
1. DINOv2 ViT-L/14 backbone β LightDPT decoder β ConvexTangentDecoder head
|
| 6 |
2. RANSAC + Gauss-Newton calibrator β camera intrinsics [f, cx, cy, k1, ...]
|
| 7 |
3. Full-resolution undistortion via grid_sample
|
| 8 |
|
| 9 |
+
No resolution limits. No quantization. Full FP32 inference.
|
| 10 |
+
Runs on GPU if available (ZeroGPU / dedicated), falls back to CPU.
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
| 13 |
|
|
|
|
| 16 |
|
| 17 |
import gradio as gr
|
| 18 |
import numpy as np
|
|
|
|
| 19 |
import torch
|
| 20 |
|
| 21 |
+
# ββ GPU decorator (works on ZeroGPU Spaces, no-op elsewhere) ββ
|
| 22 |
+
try:
|
| 23 |
+
import spaces
|
| 24 |
+
gpu_decorator = spaces.GPU(duration=120)
|
| 25 |
+
except (ImportError, Exception):
|
| 26 |
+
# Not on a ZeroGPU Space β use identity decorator
|
| 27 |
+
def gpu_decorator(fn):
|
| 28 |
+
return fn
|
| 29 |
|
| 30 |
+
# ββ Load model at startup ββ
|
| 31 |
from anycalib.model.anycalib_pretrained import AnyCalib
|
| 32 |
from anycalib.cameras.factory import CameraFactory
|
| 33 |
|
|
|
|
| 35 |
t0 = time.time()
|
| 36 |
MODEL = AnyCalib(model_id="anycalib_gen")
|
| 37 |
MODEL.eval()
|
| 38 |
+
TOTAL_PARAMS = sum(p.numel() for p in MODEL.parameters())
|
| 39 |
+
print(f"[anycalib] Model loaded in {time.time() - t0:.1f}s ({TOTAL_PARAMS:,} params)")
|
| 40 |
|
| 41 |
|
|
|
|
|
|
|
| 42 |
def _build_undistort_grid(camera, params, h, w, scale=1.0, target_proj="perspective"):
|
| 43 |
"""Build undistortion sampling grid (mirrors AnyCalibRunner._undistort_grid)."""
|
| 44 |
params_b = params[None, ...] if params.ndim == 1 else params
|
|
|
|
| 67 |
return grid, valid
|
| 68 |
|
| 69 |
|
| 70 |
+
@gpu_decorator
|
|
|
|
|
|
|
| 71 |
@torch.no_grad()
|
| 72 |
def run_calibration(
|
| 73 |
input_image: np.ndarray,
|
|
|
|
| 78 |
interp_mode: str,
|
| 79 |
k1_threshold: float,
|
| 80 |
):
|
| 81 |
+
"""Full pipeline: predict -> fit -> undistort at original resolution."""
|
| 82 |
|
| 83 |
if input_image is None:
|
| 84 |
raise gr.Error("Please upload an image.")
|
|
|
|
| 89 |
h, w = input_image.shape[:2]
|
| 90 |
t_total = time.time()
|
| 91 |
|
| 92 |
+
# Preprocess
|
| 93 |
x = input_image.astype("float32") / 255.0
|
| 94 |
+
x = np.transpose(x, (2, 0, 1))
|
| 95 |
x_t = torch.from_numpy(x).to(device)
|
| 96 |
|
| 97 |
+
# Neural network inference
|
| 98 |
t0 = time.time()
|
| 99 |
out = MODEL.predict(x_t, cam_id=cam_id)
|
| 100 |
intrinsics = out["intrinsics"]
|
| 101 |
pred_size = out.get("pred_size")
|
| 102 |
t_infer = time.time() - t0
|
| 103 |
|
| 104 |
+
# Parse intrinsics
|
| 105 |
camera = CameraFactory.create_from_id(cam_id)
|
| 106 |
num_f = int(camera.NUM_F)
|
| 107 |
intr_list = intrinsics.detach().cpu().numpy().astype(np.float64).tolist()
|
|
|
|
| 110 |
cx_val, cy_val = intr_list[num_f], intr_list[num_f + 1]
|
| 111 |
k1_val = intr_list[num_f + 2] if len(intr_list) > num_f + 2 else 0.0
|
| 112 |
|
|
|
|
| 113 |
f_px = focal[0]
|
| 114 |
fov_h = float(2 * np.degrees(np.arctan(w / (2 * f_px)))) if f_px > 0 else 0
|
| 115 |
fov_v = float(2 * np.degrees(np.arctan(h / (2 * f_px)))) if f_px > 0 else 0
|
| 116 |
|
|
|
|
| 117 |
if k1_val < -0.001:
|
| 118 |
dist_type = "Barrel (k1 < 0)"
|
| 119 |
elif k1_val > 0.001:
|
|
|
|
| 121 |
else:
|
| 122 |
dist_type = "Negligible"
|
| 123 |
|
|
|
|
| 124 |
skip_undistort = k1_threshold > 0 and abs(k1_val) < k1_threshold
|
| 125 |
|
| 126 |
if skip_undistort:
|
|
|
|
| 128 |
valid_frac = 1.0
|
| 129 |
t_undistort = 0.0
|
| 130 |
else:
|
|
|
|
| 131 |
t0 = time.time()
|
| 132 |
grid, valid = _build_undistort_grid(
|
| 133 |
camera, intrinsics, h, w,
|
|
|
|
| 149 |
|
| 150 |
t_total_elapsed = time.time() - t_total
|
| 151 |
|
| 152 |
+
hw_label = "GPU" if device.type == "cuda" else "CPU"
|
| 153 |
+
|
| 154 |
params_md = f"""
|
| 155 |
### Camera Intrinsics
|
| 156 |
|
|
|
|
| 175 |
| **Scale** | `{scale}` |
|
| 176 |
| **Target projection** | `{target_proj}` |
|
| 177 |
|
| 178 |
+
### Timing ({hw_label})
|
| 179 |
|
| 180 |
| Stage | Time |
|
| 181 |
|-------|------|
|
| 182 |
| Neural net inference | `{t_infer*1000:.0f}` ms |
|
| 183 |
| Undistortion (grid_sample) | `{t_undistort*1000:.0f}` ms |
|
| 184 |
| **Total** | **`{t_total_elapsed*1000:.0f}` ms** |
|
| 185 |
+
| Hardware | `{device}` ({hw_label}) |
|
| 186 |
"""
|
| 187 |
|
|
|
|
| 188 |
raw_json = json.dumps({
|
| 189 |
"intrinsics": {
|
| 190 |
"focal_length_px": focal,
|
|
|
|
| 225 |
with gr.Blocks() as demo:
|
| 226 |
|
| 227 |
gr.Markdown("""
|
| 228 |
+
# AnyCalib β Full-Resolution Camera Calibration
|
| 229 |
|
| 230 |
Single-image lens calibration & distortion correction powered by
|
| 231 |
[AnyCalib](https://github.com/javrtg/AnyCalib) (DINOv2 ViT-L/14 + LightDPT + ConvexTangentDecoder, ~320M params).
|
| 232 |
|
| 233 |
+
Full FP32 inference, no quantization, no resolution limits. Automatically uses GPU when available.
|
| 234 |
|
| 235 |
Upload any image and get the **corrected (undistorted) image** at original resolution,
|
| 236 |
plus camera intrinsics, FOV, distortion parameters, and timing.
|
|
|
|
| 302 |
4. Image is **undistorted at full resolution** via differentiable grid_sample
|
| 303 |
5. All parameters and raw JSON output are displayed
|
| 304 |
|
|
|
|
|
|
|
| 305 |
### Links
|
| 306 |
|
| 307 |
- Raw weights: [SebRincon/anycalib](https://huggingface.co/SebRincon/anycalib) (safetensors)
|
requirements.txt
CHANGED
|
@@ -4,4 +4,3 @@ numpy>=1.26.0
|
|
| 4 |
opencv-python-headless>=4.9.0
|
| 5 |
anycalib @ git+https://github.com/javrtg/AnyCalib.git@3cf2e5dda92faf80f3548adaa0a8515f807848aa
|
| 6 |
safetensors>=0.4.0
|
| 7 |
-
gradio>=4.0.0
|
|
|
|
| 4 |
opencv-python-headless>=4.9.0
|
| 5 |
anycalib @ git+https://github.com/javrtg/AnyCalib.git@3cf2e5dda92faf80f3548adaa0a8515f807848aa
|
| 6 |
safetensors>=0.4.0
|
|
|