diff --git a/moonwalk.py b/moonwalk.py index 1841449..94cbca2 100644 --- a/moonwalk.py +++ b/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 diff --git a/walk-animation/walking-body.blend b/walk-animation/walking-body.blend index 5ba8d5c..f92776c 100644 --- a/walk-animation/walking-body.blend +++ b/walk-animation/walking-body.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91eacad30840358d7f38015529edb0c4d2f07bf8ac57bee6e95ef4e744855741 -size 70646980 +oid sha256:9658215bbebfec3044eb8eb9fa428ec15244cc94a1a18d697f34f00e7510ce90 +size 70663196