DEV Community

Voor AI
Voor AI

Posted on

How to Build a Frame-by-Frame Hip-Drift Test for an AI Dance Clip

A generated dance clip can look fine as a thumbnail and still fail in motion: the face drifts, feet slide, the torso stretches, or the apparent hip movement comes from the camera rather than the body.

This tutorial turns that subjective review into a small OpenCV test. The input fixture and five-second result come from the public example on the Voor AI Hip Shake generator; no generation credits are needed to reproduce the analysis.

Real input and two extracted frames from the public five-second dance result

Define the signals

For each sampled frame, track four normalized values:

  • face-center drift from the baseline;
  • torso-width ratio;
  • left and right foot displacement;
  • horizontal hip-center amplitude.

Normalize pixel distances by frame width or subject height. That makes thresholds portable across exports.

from dataclasses import dataclass

@dataclass
class FrameMetric:
    t: float
    face_drift: float
    torso_ratio: float
    left_foot_drift: float
    right_foot_drift: float
    hip_x: float

def failed(m: FrameMetric) -> list[str]:
    errors = []
    if m.face_drift > 0.035:
        errors.append("face drift")
    if not 0.90 <= m.torso_ratio <= 1.10:
        errors.append("torso stretch")
    if max(m.left_foot_drift, m.right_foot_drift) > 0.04:
        errors.append("foot slide")
    return errors
Enter fullscreen mode Exit fullscreen mode

The values above are starter thresholds, not universal biological rules. Calibrate them against a small set of clips your team has already accepted.

Sample deterministically

OpenCV makes a reproducible one-frame-per-half-second pass straightforward:

import cv2

cap = cv2.VideoCapture("hip-result.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
step = max(1, round(fps * 0.5))
frames = []

i = 0
while True:
    ok, frame = cap.read()
    if not ok:
        break
    if i % step == 0:
        frames.append((i / fps, frame))
    i += 1

assert frames, "video produced no decodable frames"
Enter fullscreen mode Exit fullscreen mode

Keep the raw timestamp with every metric. “The face drifts at 3.5 seconds” is actionable; “the face looks weird” is not.

Separate intended motion from instability

Hip amplitude should oscillate. Face identity and torso proportions should not. A simple release rule can require:

def release_ok(metrics):
    drift_failures = sum(bool(failed(m)) for m in metrics)
    hip_span = max(m.hip_x for m in metrics) - min(m.hip_x for m in metrics)
    return drift_failures == 0 and hip_span >= 0.06
Enter fullscreen mode Exit fullscreen mode

This catches two opposite failures: unstable anatomy and a “dance” clip with almost no measurable lateral motion.

Configured generator evidence beside baseline and later frames

Preserve the generator state with the report

The current page shows a required image input, a 5-second default within a documented 4–30 second range, Public watermarked visibility, and 628 credits for the selected state. Save these observations with the test artifact because a model or setting change can invalidate old baselines.

Do not claim that a UI success toast equals a passing video. Decode the final MP4, count frames, and run the metric gate.

Turn failures into a compact CI artifact

Emit JSON plus a contact sheet of the worst timestamps. A pull request reviewer should be able to see the evidence without replaying the whole clip.

{
  "duration_s": 5.04,
  "sample_interval_s": 0.5,
  "release_ok": false,
  "failures": [{"t": 3.5, "type": "face drift", "value": 0.041}]
}
Enter fullscreen mode Exit fullscreen mode

Use the public fixture page and its real result as a reproducible starting point. The aim is not to automate taste; it is to make identity, anatomy, and motion regressions visible before a clip ships.

Top comments (0)