nelikCode commited on
Commit
32fb829
·
verified ·
1 Parent(s): f0ac3b1

Make fast mode about twice as fast

Browse files
Files changed (5) hide show
  1. README.md +1 -1
  2. main.py +1 -1
  3. segment.py +35 -22
  4. static/index.html +1 -1
  5. test_segment.py +3 -1
README.md CHANGED
@@ -80,7 +80,7 @@ options:
80
  path to store the output video
81
  --background_color BACKGROUND_COLOR
82
  background color for the output (hex)
83
- --fast faster, but may lose the subject in long videos
84
  --device {cpu,mps,cuda}
85
  default: best available
86
  ```
 
80
  path to store the output video
81
  --background_color BACKGROUND_COLOR
82
  background color for the output (hex)
83
+ --fast about twice as fast, slightly less precise
84
  --device {cpu,mps,cuda}
85
  default: best available
86
  ```
main.py CHANGED
@@ -28,7 +28,7 @@ def main():
28
  parser.add_argument(
29
  "--fast",
30
  action="store_true",
31
- help="faster, but may lose the subject in long videos",
32
  )
33
  parser.add_argument(
34
  "--device", choices=["cpu", "mps", "cuda"], help="default: best available"
 
28
  parser.add_argument(
29
  "--fast",
30
  action="store_true",
31
+ help="about twice as fast, slightly less precise",
32
  )
33
  parser.add_argument(
34
  "--device", choices=["cpu", "mps", "cuda"], help="default: best available"
segment.py CHANGED
@@ -66,20 +66,19 @@ def box_iou(a, b):
66
  return w * h / union if union > 0 else 0.0
67
 
68
 
69
- def closest(subjects, box):
70
- """The subject nearest to `box` in position and size."""
71
-
72
- def distance(s):
73
- b = s["box"]
74
- dx = (b[0] + b[2] - box[0] - box[2]) / 2
75
- dy = (b[1] + b[3] - box[1] - box[3]) / 2
76
- size = np.log(
77
- ((b[2] - b[0]) * (b[3] - b[1]) + 1)
78
- / ((box[2] - box[0]) * (box[3] - box[1]) + 1)
79
- )
80
- return np.hypot(dx, dy) / np.hypot(box[2] - box[0], box[3] - box[1]) + abs(size)
81
 
82
- return min(subjects, key=distance)
 
 
83
 
84
 
85
  def mask_box(alpha):
@@ -237,44 +236,57 @@ class Segmenter:
237
  reader.start()
238
  writer.start()
239
 
 
240
  self.tracker.num_maskmem = 3 if fast else 7
 
241
  session = self.processor.init_video_session(
242
  inference_device=self.device, dtype=self.dtype
243
  )
244
  keep = max(
245
  self.tracker.num_maskmem, self.tracker.config.max_object_pointers_in_encoder
246
  )
247
- done, item, lost, last_box = 0, True, 0, box
248
  try:
249
  while not errors and (item := frames.get()) is not None:
250
  if cancel is not None and cancel.is_set():
251
  raise Cancelled()
252
  rgb, inputs = item
 
 
 
 
 
 
253
  prompt = box if done == 0 else None
254
  if (
255
  label
256
  and lost >= LOST_AFTER
257
  and (lost - LOST_AFTER) % RETRY_EVERY == 0
258
  ):
259
- found = [s for s in self.detect(rgb, 0.3) if s["label"] == label]
260
- if found:
261
- prompt = closest(found, last_box)["box"]
 
 
 
 
262
  if prompt:
263
  self.processor.add_inputs_to_inference_session(
264
  inference_session=session,
265
- frame_idx=done,
266
  obj_ids=1,
267
  input_boxes=[[[float(v) for v in prompt]]],
268
  original_size=inputs.original_sizes[0],
269
  )
270
  result = self.tracker(
271
  inference_session=session,
272
- frame_idx=done,
273
  frame=inputs.pixel_values[0].to(self.device, self.dtype),
274
  )
275
- session.processed_frames.pop(done - 1, None)
276
  for outputs in session.output_dict_per_obj.values():
277
- outputs["non_cond_frame_outputs"].pop(done - keep, None)
 
278
  logits = self.processor.post_process_masks(
279
  [result.pred_masks],
280
  original_sizes=inputs.original_sizes,
@@ -283,8 +295,9 @@ class Segmenter:
283
  alpha = (
284
  (torch.sigmoid(logits.float()) * 255).to(torch.uint8).cpu().numpy()
285
  )
286
- masks.put((rgb, alpha[..., None].astype(np.uint16)))
287
  found_box = mask_box(alpha)
 
 
288
  lost, last_box = (0, found_box) if found_box else (lost + 1, last_box)
289
  done += 1
290
  if progress:
 
66
  return w * h / union if union > 0 else 0.0
67
 
68
 
69
+ def box_distance(b, box):
70
+ """Center offset in units of `box`'s diagonal, plus the log size ratio."""
71
+ dx = (b[0] + b[2] - box[0] - box[2]) / 2
72
+ dy = (b[1] + b[3] - box[1] - box[3]) / 2
73
+ size = np.log(
74
+ ((b[2] - b[0]) * (b[3] - b[1]) + 1)
75
+ / ((box[2] - box[0]) * (box[3] - box[1]) + 1)
76
+ )
77
+ return np.hypot(dx, dy) / np.hypot(box[2] - box[0], box[3] - box[1]) + abs(size)
 
 
 
78
 
79
+
80
+ def closest(subjects, box):
81
+ return min(subjects, key=lambda s: box_distance(s["box"], box))
82
 
83
 
84
  def mask_box(alpha):
 
236
  reader.start()
237
  writer.start()
238
 
239
+ # fast: shorter memory, and the model skips every other frame while it tracks
240
  self.tracker.num_maskmem = 3 if fast else 7
241
+ stride = 2 if fast else 1
242
  session = self.processor.init_video_session(
243
  inference_device=self.device, dtype=self.dtype
244
  )
245
  keep = max(
246
  self.tracker.num_maskmem, self.tracker.config.max_object_pointers_in_encoder
247
  )
248
+ done, item, lost, last_box, t = 0, True, 0, box, 0
249
  try:
250
  while not errors and (item := frames.get()) is not None:
251
  if cancel is not None and cancel.is_set():
252
  raise Cancelled()
253
  rgb, inputs = item
254
+ if done % stride and not lost:
255
+ masks.put((rgb, alpha))
256
+ done += 1
257
+ if progress:
258
+ progress(done, max(total, done))
259
+ continue
260
  prompt = box if done == 0 else None
261
  if (
262
  label
263
  and lost >= LOST_AFTER
264
  and (lost - LOST_AFTER) % RETRY_EVERY == 0
265
  ):
266
+ # the detector often renames an object seen from a new angle,
267
+ # so a different label is fine if it is where the subject was
268
+ found = self.detect(rgb, 0.3)
269
+ same = [s for s in found if s["label"] == label]
270
+ near = [s for s in found if box_distance(s["box"], last_box) < 1]
271
+ if same or near:
272
+ prompt = closest(same or near, last_box)["box"]
273
  if prompt:
274
  self.processor.add_inputs_to_inference_session(
275
  inference_session=session,
276
+ frame_idx=t,
277
  obj_ids=1,
278
  input_boxes=[[[float(v) for v in prompt]]],
279
  original_size=inputs.original_sizes[0],
280
  )
281
  result = self.tracker(
282
  inference_session=session,
283
+ frame_idx=t,
284
  frame=inputs.pixel_values[0].to(self.device, self.dtype),
285
  )
286
+ session.processed_frames.pop(t - 1, None)
287
  for outputs in session.output_dict_per_obj.values():
288
+ outputs["non_cond_frame_outputs"].pop(t - keep, None)
289
+ t += 1
290
  logits = self.processor.post_process_masks(
291
  [result.pred_masks],
292
  original_sizes=inputs.original_sizes,
 
295
  alpha = (
296
  (torch.sigmoid(logits.float()) * 255).to(torch.uint8).cpu().numpy()
297
  )
 
298
  found_box = mask_box(alpha)
299
+ alpha = alpha[..., None].astype(np.uint16)
300
+ masks.put((rgb, alpha))
301
  lost, last_box = (0, found_box) if found_box else (lost + 1, last_box)
302
  done += 1
303
  if progress:
static/index.html CHANGED
@@ -164,7 +164,7 @@ button:disabled { opacity: 0.4; cursor: default; pointer-events: none; }
164
  </div>
165
  <label class="check">
166
  <input type="checkbox" id="fast">
167
- <span>Fast mode<br><span class="soft mono">Faster, but may lose the subject in long videos.</span></span>
168
  </label>
169
  </section>
170
 
 
164
  </div>
165
  <label class="check">
166
  <input type="checkbox" id="fast">
167
+ <span>Fast mode<br><span class="soft mono">About twice as fast, slightly less precise.</span></span>
168
  </label>
169
  </section>
170
 
test_segment.py CHANGED
@@ -1,6 +1,6 @@
1
  import numpy as np
2
 
3
- from segment import box_iou, closest, hex_to_rgb, mask_box, rank_subjects
4
 
5
 
6
  def test_rank_subjects():
@@ -24,6 +24,8 @@ def test_boxes():
24
  near, far, big = [100, 100, 120, 120], [500, 500, 520, 520], [95, 95, 300, 300]
25
  subjects = [{"box": b} for b in (far, big, near)]
26
  assert closest(subjects, [102, 98, 122, 118])["box"] == near
 
 
27
  alpha = np.zeros((50, 80), np.uint8)
28
  assert mask_box(alpha) is None
29
  alpha[10:20, 30:45] = 255
 
1
  import numpy as np
2
 
3
+ from segment import box_distance, box_iou, closest, hex_to_rgb, mask_box, rank_subjects
4
 
5
 
6
  def test_rank_subjects():
 
24
  near, far, big = [100, 100, 120, 120], [500, 500, 520, 520], [95, 95, 300, 300]
25
  subjects = [{"box": b} for b in (far, big, near)]
26
  assert closest(subjects, [102, 98, 122, 118])["box"] == near
27
+ assert box_distance(near, near) == 0
28
+ assert box_distance(far, near) > 1 > box_distance([105, 100, 125, 120], near)
29
  alpha = np.zeros((50, 80), np.uint8)
30
  assert mask_box(alpha) is None
31
  alpha[10:20, 30:45] = 255