| name | gif-pinned-overlay-tracking |
| description | Use when a user wants a logo, sticker, image, or meme asset pinned, snapped, attached, or scaled with a moving object in a GIF; especially when static overlays drift, repeated patterns cause false jumps, or apparent zoom/scale changes matter. |
| version | 1.1.0 |
| author | Hermes Agent |
| license | Apache-2.0 |
| platforms | ["linux","macos","windows"] |
| metadata | {"hermes":{"tags":["GIF","Media","Pillow","OpenCV","Template Matching","Object Tracking"],"related_skills":["gif-editing","gif-search","youtube-video-download"]}} |
GIF Pinned Overlay Tracking
Overview
Tier 2 script-backed skill for pinning a transparent overlay to a moving object in a GIF. The canonical method tracks a distinctive frame-0 pixel signature, pastes the overlay at a stable offset from the matched patch, and optionally scales the overlay with the object's apparent size.
This skill owns the deterministic tracking scripts. Keep generic/static GIF editing in gif-editing; keep GIF discovery/download guidance in gif-search or youtube-video-download.
When to use
Use this when the user says the overlay should:
- stay pinned, snapped, attached, locked, or glued to a moving point;
- follow a sign, object, face, vehicle, screen, UI element, or other target;
- scale with a moving/snapped area instead of staying fixed-size;
- avoid jumps caused by repeated patterns or similar decorations.
Do not use it for a simple fixed watermark, crop, resize, palette cleanup, or speed change. Route those to gif-editing.
Canonical files
scripts/gif_pin_overlay.py — OpenCV matchTemplate(..., TM_CCOEFF_NORMED) renderer with Pillow fallback, local search, debug GIFs, and multiscale overlay rendering.
scripts/gif_snap_picker.py — local HTML picker for frame snapshots and overlay preview; use it to choose frame-0 placement and keyframe fallback points.
scripts/gif_keyframe_overlay.py — manual keyframe/interpolation fallback. Use only when true pixel tracking is unsuitable or the user accepts non-snapped interpolation.
references/scale-aware-repeated-pattern-example.md — observed parameter set from a repeated-pattern tracking run.
Future agents can run the scripts from the skill_dir returned by skill_view, or copy them into the active project:
SKILL_DIR="/path/from/skill_view/gif-pinned-overlay-tracking"
mkdir -p scripts gif_work
cp "$SKILL_DIR/scripts/gif_pin_overlay.py" scripts/
cp "$SKILL_DIR/scripts/gif_snap_picker.py" scripts/
cp "$SKILL_DIR/scripts/gif_keyframe_overlay.py" scripts/
Workflow
- Get a local source GIF.
- If the input is a page URL, resolve the real media first, e.g.
yt-dlp --no-playlist --force-overwrites -o 'gif_work/source.%(ext)s' '<url>'.
- If the download is MP4/WebM, convert to GIF with an ffmpeg palette pass before frame editing.
- Prepare the overlay as transparent RGBA. Crop/extract logos from screenshots when needed; do not flatten alpha.
- Use
gif_snap_picker.py to choose the frame-0 overlay placement.
- The picker is for visual placement, not final tracking.
gif_pin_overlay.py expects --overlay-anchor as the overlay top-left on frame 0.
- If the picker centers the overlay on a green point, compute
overlay_anchor = first_frame_point + overlay_offset.
- Choose a distinctive
--template-crop x,y,w,h on frame 0 near the target object.
- Good: unique corner, trim, edge, emblem, handle, high-contrast texture.
- Bad: flat color, motion blur, faces that deform, repeated ornaments, background.
- Render with
gif_pin_overlay.py and a tight local search radius.
- Inspect
debug_tracking.gif: green box must stay on the tracked patch; red box is the composited overlay.
- Iterate on crop, radius, scale range, and smoothing until the debug GIF proves stability.
Primary render command shape
uv run --with pillow --with numpy --with opencv-python python scripts/gif_pin_overlay.py \
--input gif_work/source.gif \
--overlay path/to/overlay.png \
--output output_snapped.gif \
--overlay-width 104 \
--template-crop x,y,w,h \
--overlay-anchor x,y \
--search-radius 18 \
--min-confidence 0.0 \
--scale-min 0.95 \
--scale-max 1.25 \
--scale-steps 13 \
--scale-penalty 0.08 \
--scale-smoothing 0.25 \
--debug-dir gif_work/snapped_debug
Use --scale-min 1 --scale-max 1 --scale-steps 1 for translation-only tracking.
Tuning guide
| Symptom | Adjustment |
|---|
| Tracker jumps to a similar decoration | Tighten --search-radius; try 12-30 px; use a more unique crop; avoid global fallback by setting low --min-confidence if debug looks stable. |
| Tracker lags fast motion | Increase --search-radius only enough to cover real frame-to-frame motion. |
| Confidence is low but debug is stable | Accept if visual verification is clean; GIF palette/dither can suppress scores. |
| Overlay looks fixed-size while object grows/shrinks | Enable multiscale matching with --scale-min, --scale-max, and --scale-steps. |
| Overlay size pulses | Increase --scale-penalty or lower --scale-smoothing alpha, e.g. 0.2-0.35. |
| Patch rotates, deforms, or is occluded | Use manual per-frame/keyframed placement, optical flow, or feature matching/homography instead. |
Verification checklist
- Output exists and is separate from the source.
- Frame count, dimensions, durations, and loop are preserved unless intentionally changed.
- Overlay transparency survived; no white/black matte box.
- Debug GIF shows the green crop staying on the intended object across hard frames.
- Script printout records matcher, confidence min/avg, and scale range when scale-aware mode is enabled.
- A mid/late frame is visually inspected; frame 0 alone does not prove tracking.
- CLI responses report absolute paths plainly and never emit
MEDIA:/path tags.
Public sharing hygiene
For a public repo, keep this skill as the script owner and make gif-editing a thin router instead of duplicating scripts. Replace local paths with placeholders, avoid committing generated GIFs unless licensing is clear, and include only CC0 or properly attributed sample media/assets.