Guest User

Untitled

a guest
Jul 15th, 2026
116
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.97 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. """Render a motion-direction video from an MP4's own H.264 motion vectors.
  3.  
  4. Each pixel in the output is colored by the direction of the codec motion
  5. vector that produced it: hue encodes the angle of motion (full circle) and
  6. brightness encodes its magnitude. Sections that move differently from the
  7. dominant flow (e.g. diagonally vs. straight down) show up as a distinct hue.
  8.  
  9. Usage:
  10. python3 motion.py INPUT.mp4 [OUTPUT.mp4]
  11.  
  12. If OUTPUT is omitted, it defaults to "<input>_direction.mp4".
  13. """
  14.  
  15. import sys
  16. import os
  17. import numpy as np
  18. import cv2
  19. import av
  20. import imageio.v2 as iio
  21.  
  22.  
  23. def extract_motion(path):
  24. """Decode the video and return per-frame dense content-motion maps.
  25.  
  26. Returns (dx, dy) float32 arrays of shape (T, H, W) where each pixel holds
  27. the content displacement (px/frame) implied by the codec motion vector for
  28. its macroblock. Content displacement is -motion/scale (the MV points to the
  29. source the block was copied from).
  30. """
  31. container = av.open(path)
  32. stream = container.streams.video[0]
  33. # Ask the H.264 decoder to export per-macroblock motion vectors.
  34. stream.codec_context.options = {"flags2": "+export_mvs"}
  35. W = stream.codec_context.width
  36. H = stream.codec_context.height
  37.  
  38. dxmaps, dymaps = [], []
  39. for frame in container.decode(stream):
  40. dx = np.zeros((H, W), np.float32)
  41. dy = np.zeros((H, W), np.float32)
  42. for d in frame.side_data:
  43. if d.type.name != "MOTION_VECTORS":
  44. continue
  45. a = d.to_ndarray()
  46. cmx = -a["motion_x"] / a["motion_scale"] # content moves in +x
  47. cmy = -a["motion_y"] / a["motion_scale"] # content moves in +y
  48. for i in range(len(a)):
  49. x0 = int(a["dst_x"][i] - a["w"][i] / 2)
  50. y0 = int(a["dst_y"][i] - a["h"][i] / 2)
  51. x1 = x0 + int(a["w"][i])
  52. y1 = y0 + int(a["h"][i])
  53. x0 = max(0, x0); y0 = max(0, y0)
  54. x1 = min(W, x1); y1 = min(H, y1)
  55. dx[y0:y1, x0:x1] = cmx[i]
  56. dy[y0:y1, x0:x1] = cmy[i]
  57. dxmaps.append(dx)
  58. dymaps.append(dy)
  59. return np.array(dxmaps), np.array(dymaps)
  60.  
  61.  
  62. def render(dx, dy, out_path, fps=60, scale=6, temporal_window=15):
  63. """Color each pixel by motion direction and write the output video."""
  64. T, H, W = dx.shape
  65.  
  66. # Per-frame codec MVs on noisy content are jittery; smooth them in time so
  67. # the persistent motion structure is visible.
  68. if temporal_window > 1:
  69. ker = np.ones(temporal_window) / temporal_window
  70. smooth = lambda a: np.apply_along_axis(
  71. lambda m: np.convolve(m, ker, mode="same"), 0, a)
  72. dx, dy = smooth(dx), smooth(dy)
  73.  
  74. frames = []
  75. for t in range(T):
  76. fx = cv2.GaussianBlur(dx[t], (0, 0), 2)
  77. fy = cv2.GaussianBlur(dy[t], (0, 0), 2)
  78. ang = np.arctan2(fy, fx) # 0=right, +pi/2=down
  79. hue = ((ang / np.pi * 0.5 + 0.5) % 1.0 * 179).astype(np.uint8)
  80. mag = np.sqrt(fx ** 2 + fy ** 2)
  81. val = np.clip(mag / 3.5 * 255, 40, 255).astype(np.uint8)
  82. hsv = np.dstack([hue, np.full((H, W), 255, np.uint8), val])
  83. bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
  84. rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
  85. frames.append(cv2.resize(rgb, (W * scale, H * scale),
  86. interpolation=cv2.INTER_NEAREST))
  87.  
  88. iio.mimwrite(out_path, frames, fps=fps, codec="libx264", quality=8)
  89. return len(frames)
  90.  
  91.  
  92. def main(argv):
  93. if len(argv) < 2:
  94. print(__doc__)
  95. sys.exit(1)
  96. in_path = argv[1]
  97. if len(argv) >= 3:
  98. out_path = argv[2]
  99. else:
  100. base, _ = os.path.splitext(in_path)
  101. out_path = base + "_direction.mp4"
  102.  
  103. dx, dy = extract_motion(in_path)
  104. n = render(dx, dy, out_path)
  105. print("wrote %s (%d frames)" % (out_path, n))
  106.  
  107.  
  108. if __name__ == "__main__":
  109. main(sys.argv)
  110.  
Advertisement
Add Comment
Please, Sign In to add comment