modify shift behavior, interpolate in parts when changing single point

This commit is contained in:
q
2025-09-03 14:12:32 +03:00
parent 58a30724db
commit b147e24bd3

View File

@@ -305,9 +305,7 @@ class Marker:
) )
# Show current track # Show current track
intrp_str = ( intrp_str = (
"" "" if not self.points_interpolation_enabled else " i*" if self.get_interpolation_required() else " i"
if not self.points_interpolation_enabled
else " i*" if True in self.points_interpolation_required.values() else " i"
) )
self.shadow_text( self.shadow_text(
frame, frame,
@@ -342,17 +340,13 @@ class Marker:
thick_y1 = current[nearest_wall] if "y" in nearest_wall else current["y1"] thick_y1 = current[nearest_wall] if "y" in nearest_wall else current["y1"]
cv2.line(frame, (thick_x0, thick_y0), (thick_x1, thick_y1), color, 5) cv2.line(frame, (thick_x0, thick_y0), (thick_x1, thick_y1), color, 5)
if self.mouse_flags["shift"]: else:
key_combos = (("x0", "y0"), ("x0", "y1"), ("x1", "y1"), ("x1", "y0")) if self.mouse_flags["shift"]:
dists = [ corners = (("x0", "y1"), ("x1", "y0"))
( else:
(px, py), corners = (("x0", "y0"), ("x1", "y1"))
abs(self.mouse_position[0] - current[px]) + abs(self.mouse_position[1] - current[py]), for corner in corners:
) cv2.circle(frame, (current[corner[0]], current[corner[1]]), 3, color, -1)
for i, (px, py) in enumerate(key_combos)
]
dists.sort(key=lambda d: d[1])
cv2.circle(frame, (current[dists[0][0][0]], current[dists[0][0][1]]), 5, color, -1)
except (KeyError, IndexError, TypeError): except (KeyError, IndexError, TypeError):
# print(self.get_interpolated_point(), self.nr) # print(self.get_interpolated_point(), self.nr)
@@ -410,6 +404,7 @@ class Marker:
if ts in self.points[self.point_index]: if ts in self.points[self.point_index]:
# Remove point # Remove point
del self.points[self.point_index][ts] del self.points[self.point_index][ts]
self.interpolate_set(self.point_index, ts)
else: else:
# Introduce point from interpolated # Introduce point from interpolated
ip = self.get_interpolated_point() ip = self.get_interpolated_point()
@@ -422,7 +417,7 @@ class Marker:
"y1": ip["y1"], "y1": ip["y1"],
"visible": ip["visible"], "visible": ip["visible"],
} }
self.interpolate_set(self.point_index) self.interpolate_set(self.point_index, self.nr)
except Exception: except Exception:
pass pass
@@ -540,7 +535,7 @@ class Marker:
} }
def modify_point(self, position, x, y): def modify_point(self, position, x, y):
"""position: tl topleft, br bottomright, c center, n nearest, snap to nearest wall""" """position: tl topleft, br bottomright, tr,bl, c center, n nearest, snap to nearest wall"""
def get_points_by_nearest(last_p, x, y, w=None, h=None): def get_points_by_nearest(last_p, x, y, w=None, h=None):
# Modifies in place! # Modifies in place!
@@ -615,6 +610,22 @@ class Marker:
"y1": y, "y1": y,
"visible": visibility, "visible": visibility,
} }
if position == "tr":
self.points[self.point_index][self.nr] = {
"x0": max(0, x - w),
"y0": y,
"x1": x,
"y1": min(self.video_res[1] - 1, y + h),
"visible": visibility,
}
if position == "bl":
self.points[self.point_index][self.nr] = {
"x0": x,
"y0": max(0, y - h),
"x1": min(self.video_res[0] - 1, x + w),
"y1": y,
"visible": visibility,
}
if position == "c": if position == "c":
self.points[self.point_index][self.nr] = { self.points[self.point_index][self.nr] = {
"x0": max(0, int(x - w / 2)), "x0": max(0, int(x - w / 2)),
@@ -643,10 +654,15 @@ class Marker:
elif position == "tl": elif position == "tl":
self.points[self.point_index][self.nr]["x0"] = x self.points[self.point_index][self.nr]["x0"] = x
self.points[self.point_index][self.nr]["y0"] = y self.points[self.point_index][self.nr]["y0"] = y
elif position == "br": elif position == "br":
self.points[self.point_index][self.nr]["x1"] = x self.points[self.point_index][self.nr]["x1"] = x
self.points[self.point_index][self.nr]["y1"] = y self.points[self.point_index][self.nr]["y1"] = y
elif position == "tr":
self.points[self.point_index][self.nr]["x1"] = x
self.points[self.point_index][self.nr]["y0"] = y
elif position == "bl":
self.points[self.point_index][self.nr]["x0"] = x
self.points[self.point_index][self.nr]["y1"] = y
elif position == "n": elif position == "n":
self.points[self.point_index][self.nr] = get_points_by_nearest( self.points[self.point_index][self.nr] = get_points_by_nearest(
@@ -674,7 +690,7 @@ class Marker:
self.points[self.point_index][self.nr]["y0"], self.points[self.point_index][self.nr]["y0"],
self.points[self.point_index][self.nr]["y1"], self.points[self.point_index][self.nr]["y1"],
) )
self.interpolate_set(self.point_index) self.interpolate_set(self.point_index, self.nr)
def modify_point_wh(self): def modify_point_wh(self):
@@ -695,7 +711,7 @@ class Marker:
self.points[self.point_index][self.nr]["x1"] = int(curr_point["cx"] + new_wh) self.points[self.point_index][self.nr]["x1"] = int(curr_point["cx"] + new_wh)
self.points[self.point_index][self.nr]["y1"] = int(curr_point["cy"] + new_hh) self.points[self.point_index][self.nr]["y1"] = int(curr_point["cy"] + new_hh)
self.points[self.point_index][self.nr]["visible"] = POINT_VISIBILITY[0] self.points[self.point_index][self.nr]["visible"] = POINT_VISIBILITY[0]
self.interpolate_set(self.point_index) self.interpolate_set(self.point_index, self.nr)
def toggle_point_visibility(self): def toggle_point_visibility(self):
@@ -722,7 +738,7 @@ class Marker:
except Exception as e: except Exception as e:
print(e) print(e)
pass pass
self.interpolate_set(self.point_index) self.interpolate_set(self.point_index, self.nr)
def track_point(self): def track_point(self):
@@ -738,7 +754,7 @@ class Marker:
if len(tracker_gui.points) > 0: if len(tracker_gui.points) > 0:
for nr in tracker_gui.points: for nr in tracker_gui.points:
self.points[self.point_index][nr] = tracker_gui.points[nr] self.points[self.point_index][nr] = tracker_gui.points[nr]
self.interpolate_set(self.point_index) self.interpolate_set(self.point_index, nr)
self.nr = max(tracker_gui.points) - 1 self.nr = max(tracker_gui.points) - 1
self.read_next = True self.read_next = True
@@ -787,11 +803,26 @@ class World:
if self.plugin: if self.plugin:
self.plugin() self.plugin()
def interpolate_set(self, point_index=None): def get_interpolation_required(self, point_index=None):
if point_index is None:
for key in self.points_interpolation_required:
if len(self.points_interpolation_required[key]) > 0:
return True
else:
if len(self.points_interpolation_required[point_index]) > 0:
return True
return False
def interpolate_set(self, point_index=None, nr=None):
if point_index is None: if point_index is None:
point_index = self.point_index point_index = self.point_index
self.points_interpolation_required[point_index] = True if nr is None:
nr = self.nr
if not point_index in self.points_interpolation_required:
self.points_interpolation_required[point_index] = []
self.points_interpolation_required[point_index].append(nr)
if not self.interpolate_thread_alive(): if not self.interpolate_thread_alive():
print("ERROR: Interpolator thread is not running!") print("ERROR: Interpolator thread is not running!")
self.interpolate_thread_start() self.interpolate_thread_start()
@@ -803,14 +834,11 @@ class World:
pre: before any keyframes pre: before any keyframes
post: after any keyframes post: after any keyframes
""" """
if point_index is None:
point_index = self.point_index
if not point_index in self.points:
return
try: try:
if not point_index in self.points:
return
if point_index is None:
point_index = self.point_index
self.points_interpolation_required[point_index] = False
def i_point(x0=None, y0=None, x1=None, y1=None, t=None, visible=None, age=None): def i_point(x0=None, y0=None, x1=None, y1=None, t=None, visible=None, age=None):
return {"x0": x0, "y0": y0, "x1": x1, "y1": y1, "type": t, "visible": visible, "age": age} return {"x0": x0, "y0": y0, "x1": x1, "y1": y1, "type": t, "visible": visible, "age": age}
@@ -818,8 +846,23 @@ class World:
def point2array(p): def point2array(p):
return [p["x0"], p["y0"], p["x1"], p["y1"]] return [p["x0"], p["y0"], p["x1"], p["y1"]]
def tryadd(s, key, l):
try:
s.add(l[key])
except IndexError:
pass
if point_index in self.points_interpolation_required:
seed_keys = (
[x for x in self.points_interpolation_required[point_index]]
if len(self.points_interpolation_required[point_index]) > 0
else []
)
else:
seed_keys = []
if not point_index in self.points_interpolated: if not point_index in self.points_interpolated:
self.points_interpolated[point_index] = {key: {} for key in range(self.frames)} self.points_interpolated[point_index] = {key: {} for key in range(self.frames + 1)}
new_points = {k: v for k, v in self.points_interpolated[point_index].items()} new_points = {k: v for k, v in self.points_interpolated[point_index].items()}
@@ -835,19 +878,55 @@ class World:
self.points_interpolated[point_index] = new_points self.points_interpolated[point_index] = new_points
else: # more points else: # more points
point_keys = list(sorted(list(self.points[point_index].keys()))) global_point_keys = list(sorted(list(self.points[point_index].keys())))
point_values = [point2array(self.points[point_index][k]) for k in point_keys]
xyxy = np.array(point_values).T if len(seed_keys) > 0:
point_keys = set()
min_seed = min(seed_keys)
max_seed = max(seed_keys)
min_found = False
max_found = False
for pi, nr in enumerate(global_point_keys):
if not min_found:
min_index = pi
if not max_found:
max_index = pi
if nr >= min_seed:
min_found = True
if nr <= max_seed:
tryadd(point_keys, pi, global_point_keys)
if nr > max_seed:
max_found = True
break
if min_found:
for shift in range(-3, 0):
tryadd(point_keys, min_index + shift, global_point_keys)
if max_found:
for shift in range(2):
tryadd(point_keys, max_index + shift, global_point_keys)
else:
for shift in range(-3, -1):
tryadd(point_keys, len(global_point_keys) + shift, global_point_keys)
point_keys = list(sorted(list(point_keys)))
else:
point_keys = global_point_keys
print(point_index, seed_keys, point_keys)
xyxy = np.array([point2array(self.points[point_index][k]) for k in point_keys]).T
spline = PchipInterpolator(point_keys, xyxy, axis=1) spline = PchipInterpolator(point_keys, xyxy, axis=1)
start_key = min(point_keys) start_key = min(global_point_keys)
end_key = max(point_keys) + 1 end_key = max(global_point_keys) + 1
t2 = np.arange(start_key, end_key)
# Pre points # Pre points
for key in range(0, start_key): for key in range(0, start_key):
new_points[key]["type"] = "pre" new_points[key]["type"] = "pre"
new_points[key].update(self.points[point_index][start_key]) new_points[key].update(self.points[point_index][start_key])
# interpolated points # interpolated points
visible = self.points[point_index][start_key]["visible"] visible = self.points[point_index][start_key]["visible"]
t2 = np.arange(min(point_keys), max(point_keys) + 1)
for row in np.vstack((t2, spline(t2))).T: for row in np.vstack((t2, spline(t2))).T:
if row[0] in point_keys: if row[0] in point_keys:
visible = self.points[point_index][row[0]]["visible"] visible = self.points[point_index][row[0]]["visible"]
@@ -861,15 +940,10 @@ class World:
} }
# post points # post points
global_last = self.points[point_index][max(global_point_keys)]
for key in range(end_key, self.frames + 1): for key in range(end_key, self.frames + 1):
new_points[key] = { new_points[key]["type"] = "post"
"type": "post", new_points[key].update(global_last)
"x0": int(row[1]),
"y0": int(row[2]),
"x1": int(row[3]),
"y1": int(row[4]),
"visible": visible,
}
# clicked points (not necessary, could determine at draw time!) # clicked points (not necessary, could determine at draw time!)
for key in point_keys: for key in point_keys:
new_points[key]["type"] = "key" new_points[key]["type"] = "key"
@@ -882,8 +956,15 @@ class World:
new_points[key]["age"] = age new_points[key]["age"] = age
self.points_interpolated[point_index] = new_points self.points_interpolated[point_index] = new_points
if point_index in self.points_interpolation_required:
self.points_interpolation_required[point_index] = [
x for x in self.points_interpolation_required[point_index] if x not in seed_keys
]
except Exception as e: except Exception as e:
print(f"Interpolation error: {e}") print(f"Interpolation error: {e}")
print(new_points)
print(point_keys)
raise (e)
def interpolate_thread_start(self): def interpolate_thread_start(self):
@@ -901,7 +982,7 @@ class World:
self.points_interpolation_thread_exit = True self.points_interpolation_thread_exit = True
for point_index in self.points_interpolation_required: for point_index in self.points_interpolation_required:
if self.points_interpolation_required[point_index]: if self.get_interpolation_required(point_index):
self.interpolate_points(point_index) self.interpolate_points(point_index)
def interpolate_thread_alive(self): def interpolate_thread_alive(self):
@@ -922,7 +1003,7 @@ class World:
continue continue
for point_index in self.points_interpolation_required: for point_index in self.points_interpolation_required:
if self.points_interpolation_required[point_index]: if self.get_interpolation_required(point_index):
self.interpolate_points(point_index) self.interpolate_points(point_index)
def toggle_interpolation(self, value=None): def toggle_interpolation(self, value=None):
@@ -1035,9 +1116,9 @@ class World:
x toggle (delete) key frame x toggle (delete) key frame
r convert interpolated points to points (no undo!) r convert interpolated points to points (no undo!)
u toggle automatic interpolation u toggle automatic interpolation
mouse left: set top-left corner of box. shift: modify nearest corner, ctrl: side to image edge mouse left: set top-left corner of box. shift: bottom-left, ctrl: side to image edge
mouse middle: set center of box mouse middle: set center of box
mouse right: set lower right corner of box mouse right: set lower right corner of box. shift: top-right
e set width/height of box symmetric around center e set width/height of box symmetric around center
z c Home End move between key-frames z c Home End move between key-frames
t start optical flow tracker t start optical flow tracker
@@ -1082,11 +1163,14 @@ class World:
if self.mouse_flags["ctrl"]: if self.mouse_flags["ctrl"]:
self.modify_point("snap", int(x), int(y)) self.modify_point("snap", int(x), int(y))
elif self.mouse_flags["shift"]: elif self.mouse_flags["shift"]:
self.modify_point("n", int(x), int(y)) self.modify_point("bl", int(x), int(y))
else: else:
self.modify_point("tl", int(x), int(y)) self.modify_point("tl", int(x), int(y))
elif event == cv2.EVENT_RBUTTONDOWN: elif event == cv2.EVENT_RBUTTONDOWN:
self.modify_point("br", int(x), int(y)) if self.mouse_flags["shift"]:
self.modify_point("tr", int(x), int(y))
else:
self.modify_point("br", int(x), int(y))
elif event == cv2.EVENT_MBUTTONDOWN: elif event == cv2.EVENT_MBUTTONDOWN:
self.modify_point("c", int(x), int(y)) self.modify_point("c", int(x), int(y))
@@ -1170,7 +1254,7 @@ class World:
) )
if not self.points[index][key].get("visible", "NA") in POINT_VISIBILITY: if not self.points[index][key].get("visible", "NA") in POINT_VISIBILITY:
self.points[index][key]["visible"] = POINT_VISIBILITY[0] self.points[index][key]["visible"] = POINT_VISIBILITY[0]
self.interpolate_set(index) self.interpolate_set(index, key)
print(f"Loaded points with index: {index}") print(f"Loaded points with index: {index}")
self.point_index = None self.point_index = None