Anmation per angle
This commit is contained in:
parent
112cbd83da
commit
e67395c44f
2 changed files with 74 additions and 38 deletions
108
moonwalk.py
108
moonwalk.py
|
@ -36,16 +36,22 @@ colorset = [(255, 0, 0),
|
|||
]
|
||||
|
||||
|
||||
VELOCITY_FACTOR = 20*.05 # a pixels/second velocity of 400 gives a frame-duration of .05
|
||||
VELOCITY_FACTOR = 30*.05 # a pixels/second velocity of 400 gives a frame-duration of .05
|
||||
|
||||
images_nrs = range(125, 162)
|
||||
walk_images = []
|
||||
for nr in images_nrs:
|
||||
fn = f'walk-animation/OUT/{nr:03d}_frame{nr}.png'
|
||||
pic: pyglet.image.ImageData = pyglet.image.load(fn)
|
||||
walk_images.append(pic)
|
||||
walk_animation = pyglet.image.animation.Animation.from_image_sequence(walk_images, 2, True)
|
||||
angles = [0,15,30,45]
|
||||
|
||||
walk_animations = {}
|
||||
for a in angles:
|
||||
walk_images = []
|
||||
for nr in images_nrs:
|
||||
# smaller textures doesn't seem to matter much in speed
|
||||
fn = f'walk-animation/OUT/frame{nr:03d}_{a:02d}.png'
|
||||
pic: pyglet.image.ImageData = pyglet.image.load(fn)
|
||||
walk_images.append(pic)
|
||||
walk_animations[a] = pyglet.image.animation.Animation.from_image_sequence(walk_images, 1, True)
|
||||
|
||||
walk_animation = walk_animations[0]
|
||||
|
||||
# walk_image = pyglet.image.load('walk_bw.jpg') # TODO investigaet pyglet.resource: https://pyglet.readthedocs.io/en/latest/programming_guide/image.html#displaying-images
|
||||
# image_grid = pyglet.image.ImageGrid(walk_image, rows=1, columns=4)
|
||||
|
@ -63,10 +69,11 @@ class Params:
|
|||
# video_fps: float = 60
|
||||
tracker_fps: float = 5.6
|
||||
# iou = None
|
||||
emitter_speed: float = 2 # objects per second
|
||||
object_velocity: float = 200 # pixels/second
|
||||
emitter_speed: float = 1.2 # objects per second
|
||||
object_velocity: float = 100 # pixels/second
|
||||
velocity_decay: float = 1 # make system a bit springy
|
||||
iou_threshold: float = 0 # SORT Intersection over union
|
||||
movement_angle: float = 0 # angle at which the blobs move (-45 - +45 degrees)
|
||||
|
||||
def add_listener(self, attr: str, callback: callable):
|
||||
"""
|
||||
|
@ -112,11 +119,13 @@ class DetectedObject:
|
|||
self.w = 160 # width
|
||||
self.h = 320 # height
|
||||
self.l = -self.w # left
|
||||
self.t = (self.canvas.height - self.h)/2 # top
|
||||
self.t = (self.canvas.height - self.h)/2 - math.sin(self.canvas.params.movement_angle/360*math.pi*2) * self.canvas.width/2 # top
|
||||
|
||||
# self.shape = pyglet.shapes.Rectangle(self.l, self.t, self.w, self.h, color=(255, 22, 20), batch=self.canvas.batch_figures)
|
||||
|
||||
angle = min(angles, key=lambda x:abs(x-self.canvas.params.movement_angle))
|
||||
|
||||
self.shape = pyglet.sprite.Sprite(img=walk_animation,x=self.l, y=self.t, batch=self.canvas.batch_figures)
|
||||
self.shape = pyglet.sprite.Sprite(img=walk_animations[angle],x=self.l, y=self.t, batch=self.canvas.batch_figures)
|
||||
self.shape.scale_x = self.w/walk_animation_dim['x']
|
||||
self.shape.scale_y = self.h/walk_animation_dim['y']
|
||||
# rectangle.opacity = 128
|
||||
|
@ -127,8 +136,10 @@ class DetectedObject:
|
|||
"""
|
||||
Update position
|
||||
"""
|
||||
self.l += dt * self.canvas.params.object_velocity
|
||||
self.l += dt * self.canvas.params.object_velocity * math.cos(self.canvas.params.movement_angle/360*2 * math.pi)
|
||||
self.t += dt * self.canvas.params.object_velocity * math.sin(self.canvas.params.movement_angle/360*2 * math.pi)
|
||||
self.shape.x = self.l
|
||||
self.shape.y = self.t
|
||||
# TODO exponential decay with self.params.velocity_decay
|
||||
|
||||
class ObjectEmitter:
|
||||
|
@ -149,7 +160,7 @@ class ObjectEmitter:
|
|||
return [obj]
|
||||
return []
|
||||
|
||||
class MissingDict(dict):
|
||||
class MissingDict[T](dict):
|
||||
"""
|
||||
collections.defaultdict does not accept arguments, but this is what we want/need.
|
||||
This implementation should do the trick
|
||||
|
@ -158,7 +169,7 @@ class MissingDict(dict):
|
|||
self.update(values)
|
||||
self.factory = factory
|
||||
|
||||
def __missing__(self, key):
|
||||
def __missing__(self, key) -> T:
|
||||
self[key] = self.factory(key)
|
||||
return self[key]
|
||||
|
||||
|
@ -194,7 +205,7 @@ class Canvas:
|
|||
# Purple background color:
|
||||
pyglet.gl.glClearColor(230/255,230/255,230/255,230/255)
|
||||
self.draw_stats = True
|
||||
self.fps_display = pyglet.window.FPSDisplay(window=self.window, color=(255,255,255,255))
|
||||
self.fps_display = pyglet.window.FPSDisplay(window=self.window, color=(255,255,255,255), samples=100)
|
||||
self.fps_display.label.x = self.window.width - 150
|
||||
self.fps_display.label.y = self.window.height - 17
|
||||
self.fps_display.label.bold = False
|
||||
|
@ -214,7 +225,7 @@ class Canvas:
|
|||
self.labels[field.name] = pyglet.text.Label(f"{field.name}: {field.default}", x=20, y=30 + 20*(i+1), color=(255,255,255,255), batch=self.batch_info)
|
||||
|
||||
# TODO: Add a number next to the box using pyglet.graphics.Group()
|
||||
self.track_shapes = MissingDict(self.getTrackBboxShapes)
|
||||
self.track_shapes: MissingDict[np.float64, tuple[pyglet.shapes.Box, pyglet.text.Label]] = MissingDict(self.getTrackBboxShapes)
|
||||
|
||||
self.tracker = Sort(max_age=5, min_hits=1, iou_threshold=0) #DeepSort(max_age=5)
|
||||
|
||||
|
@ -224,16 +235,17 @@ class Canvas:
|
|||
self.params.add_listener('tracker_fps', self.on_tracker_fps_change)
|
||||
self.params.add_listener('iou_threshold', self.on_iou_threshold_change)
|
||||
self.params.add_listener('object_velocity', self.on_object_velocity_change)
|
||||
self.params.add_listener('movement_angle', self.on_movement_angle_change)
|
||||
# trigger first time around
|
||||
self.on_object_velocity_change('object_velocity', None, self.params.object_velocity)
|
||||
|
||||
def getTrackBboxShapes(self, track_id):
|
||||
def getTrackBboxShapes(self, track_id) -> tuple[pyglet.shapes.Box, pyglet.text.Label]:
|
||||
color = colorset[int(track_id) % len(colorset)]
|
||||
return [
|
||||
return (
|
||||
pyglet.shapes.Box(0,0,0,0,color=color,thickness=2, batch=self.batch_bounding_boxes),
|
||||
pyglet.text.Label(f"{track_id:.0f}", x=0, y=0, anchor_y='top', color=color, batch=self.batch_info),
|
||||
pyglet.text.Label(f"{track_id:.0f}", x=0, y=0, anchor_y='top', color=color, batch=self.batch_bounding_boxes),
|
||||
# pyglet.shapes.Lab(0,0,0,0,color=(0,255,0),thickness=2, batch=self.batch_bounding_boxes)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def on_tracker_fps_change(self, field, old_value, new_value):
|
||||
|
@ -250,14 +262,31 @@ class Canvas:
|
|||
"""
|
||||
duration = max(.001, VELOCITY_FACTOR / new_value)
|
||||
logger.debug(f"set frame duration to {duration=} (for {new_value} p/s, factor: {VELOCITY_FACTOR})")
|
||||
for frame in walk_animation.frames:
|
||||
# a velocity of
|
||||
frame.duration = duration
|
||||
for anim in walk_animations.values():
|
||||
for frame in anim.frames:
|
||||
# a velocity of
|
||||
frame.duration = duration
|
||||
|
||||
# for ii in self.interval_items:
|
||||
# ii.interval = 1/new_value
|
||||
# logger.debug(f"Set tracker interval to {ii.interval}")
|
||||
|
||||
def on_movement_angle_change(self, field, old_value, new_value):
|
||||
"""
|
||||
Param dataclass listener for changes to movement-angle
|
||||
"""
|
||||
angle = min(angles, key=lambda x:abs(x-new_value))
|
||||
|
||||
if hasattr(self, 'angle') and self.angle == angle:
|
||||
return
|
||||
|
||||
self.angle = angle
|
||||
|
||||
for obj in self.objects:
|
||||
idx = obj.shape._frame_index
|
||||
obj.shape.image = walk_animations[self.angle]
|
||||
obj.shape._frame_index = idx
|
||||
|
||||
def on_iou_threshold_change(self, field, old_value, new_value):
|
||||
"""
|
||||
Param dataclass listener for changes to iou_threshold
|
||||
|
@ -274,6 +303,7 @@ class Canvas:
|
|||
|
||||
def on_draw(self):
|
||||
self.window.clear()
|
||||
|
||||
self.batch_figures.draw()
|
||||
self.batch_bounding_boxes.draw()
|
||||
self.batch_info.draw()
|
||||
|
@ -379,6 +409,7 @@ class Canvas:
|
|||
self.prune()
|
||||
|
||||
self.lastSnapshot = now
|
||||
# print(f"duration: {time.monotonic()-now)
|
||||
return self.objects
|
||||
|
||||
|
||||
|
@ -394,21 +425,26 @@ class Canvas:
|
|||
for name, value in dataclasses.asdict(self.params).items():
|
||||
self.labels[name].text = f"{name}: {value}"
|
||||
|
||||
# BIG impact
|
||||
for track in self.tracks:
|
||||
nr = track[4]
|
||||
for shape in self.track_shapes[nr]:
|
||||
if shape.x == 0 and shape.y == 0:
|
||||
# set initial position
|
||||
shape.x = track[0]
|
||||
shape.y = track[1]
|
||||
shape.width = track[2] - track[0]
|
||||
shape.height = track[3] - track[1]
|
||||
else:
|
||||
# exponential decay
|
||||
shape.x = exponentialDecay(shape.x, track[0], 12, dt)
|
||||
shape.y = exponentialDecay(shape.y, track[1], 12, dt)
|
||||
shape.width = exponentialDecay(shape.width, track[2] - track[0], 12, dt)
|
||||
shape.height = exponentialDecay(shape.height, track[3] - track[1], 12, dt)
|
||||
box: pyglet.shape.Box = self.track_shapes[nr][0]
|
||||
label: pyglet.text.Label = self.track_shapes[nr][1]
|
||||
if box.x == 0 and box.y == 0:
|
||||
# set initial position
|
||||
box.x = track[0]
|
||||
box.y = track[1]
|
||||
label.x = track[0]
|
||||
label.y = track[1]
|
||||
box.width = track[2] - track[0]
|
||||
box.height = track[3] - track[1]
|
||||
else: # not really impact
|
||||
# exponential decay
|
||||
label.x = box.x = exponentialDecay(box.x, track[0], 12, dt)
|
||||
label.y = box.y = exponentialDecay(box.y, track[1], 12, dt)
|
||||
# setting width and height on label is not needed _and_ makes it super slow
|
||||
box.width = exponentialDecay(box.width, track[2] - track[0], 12, dt)
|
||||
box.height = exponentialDecay(box.height, track[3] - track[1], 12, dt)
|
||||
|
||||
|
||||
# TODO: shape in DetectedObject
|
||||
|
|
BIN
walk-animation/walking-body.blend
(Stored with Git LFS)
BIN
walk-animation/walking-body.blend
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in a new issue