Spaces:
Running on Zero
Running on Zero
Remove renderer/infer_single_focus.py (replaced by the flashrefocus package)
Browse files- renderer/infer_single_focus.py +0 -178
renderer/infer_single_focus.py
DELETED
|
@@ -1,178 +0,0 @@
|
|
| 1 |
-
"""Stage II on one image: Depth Anything V2 depth, a focal point and a blur strength -> rendered bokeh.
|
| 2 |
-
This is the reference implementation (depth post-processed on the CPU as for the training data); pipeline/fast_refocus.py
|
| 3 |
-
computes the same on the GPU for repeated edits of one image.
|
| 4 |
-
|
| 5 |
-
python renderer/infer_single_focus.py -i aif.png -o out.png --focus-x 640 --focus-y 360 --bokeh-strength 0.5 --amp"""
|
| 6 |
-
import argparse
|
| 7 |
-
import sys
|
| 8 |
-
from pathlib import Path
|
| 9 |
-
|
| 10 |
-
import cv2
|
| 11 |
-
import numpy as np
|
| 12 |
-
import torch
|
| 13 |
-
from PIL import Image, ImageDraw
|
| 14 |
-
from torchvision.transforms.functional import to_pil_image, to_tensor
|
| 15 |
-
|
| 16 |
-
sys.path.insert(0, str(Path(__file__).resolve().parents[1])) # repository root
|
| 17 |
-
import paths # noqa: E402
|
| 18 |
-
from method.model import load_renderer # noqa: E402
|
| 19 |
-
|
| 20 |
-
DA2_CONFIGS = {
|
| 21 |
-
"vits": {"encoder": "vits", "features": 64, "out_channels": [48, 96, 192, 384]},
|
| 22 |
-
"vitb": {"encoder": "vitb", "features": 128, "out_channels": [96, 192, 384, 768]},
|
| 23 |
-
"vitl": {"encoder": "vitl", "features": 256, "out_channels": [256, 512, 1024, 1024]},
|
| 24 |
-
"vitg": {"encoder": "vitg", "features": 384, "out_channels": [1536, 1536, 1536, 1536]},
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
def parse_args(argv=None):
|
| 29 |
-
ap = argparse.ArgumentParser(description="Render bokeh for one all-in-focus image and one focal point.")
|
| 30 |
-
ap.add_argument("--input", "-i", type=Path, required=True)
|
| 31 |
-
ap.add_argument("--output", "-o", type=Path, required=True)
|
| 32 |
-
ap.add_argument("--marked-output", type=Path, default=None, help="copy with the focal point marked "
|
| 33 |
-
"(default <output>_focus.<ext>)")
|
| 34 |
-
ap.add_argument("--focus-x", type=float, required=True)
|
| 35 |
-
ap.add_argument("--focus-y", type=float, required=True)
|
| 36 |
-
ap.add_argument("--bokeh-strength", type=float, required=True, help="blur strength K in [0, 1]")
|
| 37 |
-
ap.add_argument("--resize", type=int, default=None, help="resize the long side (--resize-short: the short side) "
|
| 38 |
-
"first; the focal point is given in input coordinates")
|
| 39 |
-
ap.add_argument("--resize-short", action="store_true")
|
| 40 |
-
ap.add_argument("--checkpoint", type=Path, default=paths.RENDERER)
|
| 41 |
-
ap.add_argument("--da2-checkpoint", type=Path, default=paths.DA2)
|
| 42 |
-
ap.add_argument("--da2-encoder", default="vitl", choices=list(DA2_CONFIGS))
|
| 43 |
-
ap.add_argument("--da2-input-size", type=int, default=1024)
|
| 44 |
-
ap.add_argument("--mark-size", type=int, default=None)
|
| 45 |
-
ap.add_argument("--amp", action="store_true", help="bf16 autocast for the renderer (used for all results)")
|
| 46 |
-
return ap.parse_args(argv)
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def load_source_image(path, resize=None, resize_short=False):
|
| 50 |
-
image = Image.open(path).convert("RGB")
|
| 51 |
-
w, h = image.size
|
| 52 |
-
if resize:
|
| 53 |
-
scale = resize / (min(w, h) if resize_short else max(w, h))
|
| 54 |
-
image = image.resize((max(1, int(round(w * scale))), max(1, int(round(h * scale)))), Image.BILINEAR)
|
| 55 |
-
return image, (w, h)
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def load_da2_model(encoder, checkpoint_path, device):
|
| 59 |
-
if str(paths.DA2_ROOT) not in sys.path:
|
| 60 |
-
sys.path.insert(0, str(paths.DA2_ROOT))
|
| 61 |
-
from depth_anything_v2.dpt import DepthAnythingV2
|
| 62 |
-
model = DepthAnythingV2(**DA2_CONFIGS[encoder])
|
| 63 |
-
model.load_state_dict(torch.load(checkpoint_path, map_location="cpu"))
|
| 64 |
-
return model.to(device).eval()
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
def predict_da2_depth(image, da2_model, input_size, device):
|
| 68 |
-
"""Relative inverse depth of a PIL image (fp16 autocast, as for the cached training depth)."""
|
| 69 |
-
bgr = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
|
| 70 |
-
with torch.amp.autocast(device_type=device.type, enabled=device.type == "cuda", dtype=torch.float16):
|
| 71 |
-
depth = da2_model.infer_image(bgr, input_size)
|
| 72 |
-
return depth.astype(np.float32)
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
def process_da2_depth(da2_depth, size_wh):
|
| 76 |
-
"""Resize to the image, min-max normalize and invert: D in [0, 1], 1 = far (1 x H x W)."""
|
| 77 |
-
depth_img = Image.fromarray(da2_depth.astype(np.float32), mode="F")
|
| 78 |
-
if depth_img.size != tuple(size_wh):
|
| 79 |
-
depth_img = depth_img.resize(tuple(size_wh), Image.BILINEAR)
|
| 80 |
-
d = np.nan_to_num(np.asarray(depth_img).astype(np.float32), nan=0.0, posinf=0.0, neginf=0.0)
|
| 81 |
-
low, high = np.percentile(d.reshape(-1), (0.0, 100.0))
|
| 82 |
-
d = np.clip(d, low, high)
|
| 83 |
-
d = (d - low) / (high - low) if abs(high - low) > 1e-12 else np.zeros_like(d, dtype=np.float32)
|
| 84 |
-
return torch.from_numpy(1.0 - d).unsqueeze(0).float().clamp(0, 1)
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
def default_marked_output_path(output_path):
|
| 88 |
-
return output_path.with_name(f"{output_path.stem}_focus{output_path.suffix}")
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
def draw_focus_marker(image, xy, mark_size=None, color=(0, 255, 0)):
|
| 92 |
-
marked = image.convert("RGB").copy()
|
| 93 |
-
draw = ImageDraw.Draw(marked)
|
| 94 |
-
width, height = marked.size
|
| 95 |
-
x, y = xy
|
| 96 |
-
if mark_size is None:
|
| 97 |
-
mark_size = max(22, min(width, height) // 8)
|
| 98 |
-
mark_size = max(6, int(mark_size))
|
| 99 |
-
half = mark_size // 2
|
| 100 |
-
line_width = max(1, round(mark_size / 18))
|
| 101 |
-
segment = max(3, round(mark_size * 0.32))
|
| 102 |
-
|
| 103 |
-
x0 = max(0, x - half)
|
| 104 |
-
x1 = min(width - 1, x + half)
|
| 105 |
-
y0 = max(0, y - half)
|
| 106 |
-
y1 = min(height - 1, y + half)
|
| 107 |
-
|
| 108 |
-
lines = [
|
| 109 |
-
((x0, y0), (min(x0 + segment, x1), y0)),
|
| 110 |
-
((x0, y0), (x0, min(y0 + segment, y1))),
|
| 111 |
-
((x1, y0), (max(x1 - segment, x0), y0)),
|
| 112 |
-
((x1, y0), (x1, min(y0 + segment, y1))),
|
| 113 |
-
((x0, y1), (min(x0 + segment, x1), y1)),
|
| 114 |
-
((x0, y1), (x0, max(y1 - segment, y0))),
|
| 115 |
-
((x1, y1), (max(x1 - segment, x0), y1)),
|
| 116 |
-
((x1, y1), (x1, max(y1 - segment, y0))),
|
| 117 |
-
]
|
| 118 |
-
for start, end in lines:
|
| 119 |
-
draw.line([start, end], fill=color, width=line_width)
|
| 120 |
-
|
| 121 |
-
dot_radius = max(2, line_width + 1)
|
| 122 |
-
draw.ellipse(
|
| 123 |
-
[
|
| 124 |
-
max(0, x - dot_radius),
|
| 125 |
-
max(0, y - dot_radius),
|
| 126 |
-
min(width - 1, x + dot_radius),
|
| 127 |
-
min(height - 1, y + dot_radius),
|
| 128 |
-
],
|
| 129 |
-
fill=color,
|
| 130 |
-
)
|
| 131 |
-
return marked
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
class RenderEngine:
|
| 135 |
-
"""Depth Anything V2 and the renderer, loaded once."""
|
| 136 |
-
|
| 137 |
-
def __init__(self, checkpoint=paths.RENDERER, da2_checkpoint=paths.DA2, da2_encoder="vitl", da2_input_size=1024,
|
| 138 |
-
device="cuda", amp=True):
|
| 139 |
-
self.device = torch.device(device)
|
| 140 |
-
self.da2_input_size = da2_input_size
|
| 141 |
-
self.amp = amp and self.device.type == "cuda"
|
| 142 |
-
self.da2_model = load_da2_model(da2_encoder, da2_checkpoint, self.device)
|
| 143 |
-
self.model = load_renderer(checkpoint, self.device)
|
| 144 |
-
|
| 145 |
-
def infer_image(self, input_path, output_path, focus_x, focus_y, bokeh_strength, marked_output=None,
|
| 146 |
-
resize=None, resize_short=False, mark_size=None):
|
| 147 |
-
if not 0.0 <= bokeh_strength <= 1.0:
|
| 148 |
-
raise ValueError("the bokeh strength must be in [0, 1]")
|
| 149 |
-
source_pil, (ow, oh) = load_source_image(input_path, resize, resize_short)
|
| 150 |
-
pw, ph = source_pil.size
|
| 151 |
-
depth = process_da2_depth(predict_da2_depth(source_pil, self.da2_model, self.da2_input_size, self.device),
|
| 152 |
-
source_pil.size)
|
| 153 |
-
x = max(0, min(pw - 1, int(round(focus_x * pw / ow))))
|
| 154 |
-
y = max(0, min(ph - 1, int(round(focus_y * ph / oh))))
|
| 155 |
-
source = to_tensor(source_pil).float().clamp(0, 1).unsqueeze(0).to(self.device)
|
| 156 |
-
strength = torch.tensor([[bokeh_strength]], device=self.device)
|
| 157 |
-
focus = torch.tensor([[float(depth[0, y, x])]], device=self.device)
|
| 158 |
-
with torch.inference_mode(), torch.amp.autocast("cuda", enabled=self.amp, dtype=torch.bfloat16):
|
| 159 |
-
pred = self.model(source, strength, depth.unsqueeze(0).to(self.device), focus)
|
| 160 |
-
output_path = Path(output_path)
|
| 161 |
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 162 |
-
image = to_pil_image(pred.clamp(0, 1).squeeze(0).float().cpu())
|
| 163 |
-
image.save(output_path)
|
| 164 |
-
marked = Path(marked_output) if marked_output else default_marked_output_path(output_path)
|
| 165 |
-
draw_focus_marker(image, (x, y), mark_size=mark_size).save(marked)
|
| 166 |
-
return image
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
def main():
|
| 170 |
-
a = parse_args()
|
| 171 |
-
eng = RenderEngine(a.checkpoint, a.da2_checkpoint, a.da2_encoder, a.da2_input_size, amp=a.amp)
|
| 172 |
-
eng.infer_image(a.input, a.output, a.focus_x, a.focus_y, a.bokeh_strength, a.marked_output, a.resize,
|
| 173 |
-
a.resize_short, a.mark_size)
|
| 174 |
-
print(f"Saved {a.output}")
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
if __name__ == "__main__":
|
| 178 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|