ming0531 commited on
Commit
57cc3c9
·
verified ·
1 Parent(s): aacd001

Remove renderer/infer_single_focus.py (replaced by the flashrefocus package)

Browse files
Files changed (1) hide show
  1. 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()