Finding Valorant Clips With OpenCV: Template Matching a Kill Banner, Measured

2026-11-12来源 dev.toEnglish
Finding Valorant Clips With OpenCV: Template Matching a Kill Banner, Measured

A Valorant VOD is mostly setup. The moments people actually post are short, and a lot of them announce themselves in the HUD: a kill banner, a stack in the feed, an ace notification. Before you reach for a model, it is worth asking how far a 50-pixel template will get you.

This tutorial builds a small OpenCV detector around that idea, then measures it. I did not use a real Valorant recording. Riot's art, fonts, agent icons, and HUD are not mine to crop and republish, so I synthesized a 12-second 1080p clip with an original navy-and-cyan elimination banner in the top-right. The code is a teaching implementation. It is not a clone of anyone's production pipeline, and the scores below are not match-prediction accuracy on live games.

I work on FragCut, an AI gaming clip maker. The detector in this repo is deliberately simpler than that, on purpose: you can see every threshold.

Why a banner is a reasonable first signal

If you have already played the match, you often remember "there was a three-kill stretch around the mid-round." Finding it by scrubbing is slow. The kill feed is a visual event with a fairly stable shape: a small icon, a bar, a pair of names, sitting in a consistent corner.

Template matching is a blunt tool for that shape. cv2.matchTemplate slides a patch across a frame and returns a score map. On a normalized method such as TM_CCOEFF_NORMED, a perfect copy of the patch scores close to 1.0, and an empty corner scores much lower. It will not understand a clutch. It will not notice a knife kill that never drew the icon you cropped. It will also break if the HUD is scaled, which is the failure I built into the test.

If the goal is a set of candidate timestamps you could later cut as short Valorant highlights, a banner detector is a starting index, not an editor.

The synthetic VOD, honestly described

The generator, the template, and the measurement script live in detect_kill_feed.py beside this draft. I ran that file to produce every number in this article.

The clip is 1920×1080, 30 fps, 12.000 seconds, 360 frames, encoded with libx264 at CRF 28 and then decoded again before matching. Measuring the compressed frames matters. A PNG composite and an H.264 VOD are not the same picture, and the idle scores below would have looked cleaner if I had cheated and scored the pre-encode frames.

The banner is original: a dark navy bar, a cyan leading strip, and a black chevron I drew in code. Names are fictional (NOVA, PIXEL, and so on). The background is a procedural corridor with a crosshair and a meaningless minimap blob. It should not be mistaken for Valorant footage.

The template is not the full bar. It is a 54×48 crop of the cyan strip and chevron only, so the matcher can fire on a banner with different names. That is the version you would actually want in a real pipeline, assuming you had a legally obtained template.

I planted five banner events and one decoy:

Event Time (s) What I changed Expected?
E1 single 2.000–3.033 One banner, normal brightness and scale Detect
E2 stacked 4.000–5.033 Two banners, different names Detect
E3 darkened 6.000–7.033 Banner multiplied to 0.55 brightness Detect
E4 brief 8.000–8.533 Same banner, only 16 frames (0.533 s) Detect
Distractor 9.000–10.033 A cyan-and-navy bar in the bottom-left, no chevron Ignore
E5 scaled 10.500–11.533 Banner drawn at 1.12× scale Miss

E5 is the planned miss. Fixed-size template matching is not scale-invariant. If the article reported a hit there, the test would have been too easy.

The matcher

The loop is short. Search a top-right ROI rather than the whole 1080p frame, take the peak TM_CCOEFF_NORMED score, and later cluster frames that stay above a threshold.

import cv2 METHOD = cv2.TM_CCOEFF_NORMED ROI = (1424, 28, 580, 184) # x, y, w, h — the kill-feed corner def peak_score(frame, template, roi=ROI): x, y, w, h = roi crop = frame[y:y + h, x:x + w] result = cv2.matchTemplate(crop, template, METHOD) _, max_val, _, max_loc = cv2.minMaxLoc(result) return float(max_val), (max_loc[0] + x, max_loc[1] + y) 

I also ran the same method over the full frame, mainly to time it and to see what the bottom-left decoy does when the search is not constrained. Clustering is equally plain: hits separated by eight frames or fewer become one event. I scored a predicted interval as a true positive when its overlap with a ground-truth interval had an intersection-over-union of at least 0.30. Those two constants are editorial. They are in the script so you can change them.

What the run actually produced

On the ROI, every threshold I tried between 0.70 and 0.90 produced the same four events and no extras. I am reporting 0.80 as the operating point because it is the middle of that plateau, not because a sweep declared a unique winner.

Peak scores inside each ground-truth window:

Event Peak score Mean score in the window Detected at 0.80?
E1 single 0.99058 0.99044 Yes
E2 stacked 0.99064 0.99052 Yes
E3 darkened 0.98794 0.98749 Yes
E4 brief 0.99000 0.98981 Yes
E5 scaled 0.52239 0.52235 No, as planned

Event-level precision on the four detectable events was 1.000. Recall was 1.000. F1 was 1.000. That is a real measurement on this 12-second file. It is not a claim about ranked Valorant VODs, other HUDs, or a two-hour match.

The darkened banner barely moved the score. TM_CCOEFF_NORMED subtracts a local mean and is fairly stable under a global brightness change, so a 0.55 multiply was a weak test. I am leaving that in the table instead of pretending I stressed illumination. Scale was the actual break: 1.12× dropped the peak to 0.52239, well under every threshold I tried.

Idle frames, meaning no planted banner and no decoy, had a mean ROI score of 0.32389. That number is higher than the same run on a less-compressed encode, which is why I scored the CRF 28 file you can actually open. Compression leaves a residue. After a banner disappears, the score does not always fall straight back to the early-idle floor; you can see that plateau in the chart.

Line chart of ROI match scores over 12 seconds, with shaded ground-truth events and a threshold at 0.80

The decoy is the other useful result. In the top-right ROI, its peak score was 0.42670 — above idle, still under 0.80. On the full frame, the same decoy peaked at 0.77476. If I had searched the whole image with a 0.70 threshold, that bar would have been uncomfortably close to a false event. The ROI is not only faster. It is doing work the threshold cannot do alone.

Timing, measured in the same process on this machine:

Search area Seconds for 360 frames Milliseconds per frame
Full 1920×1080 72.0639 200.178
Top-right ROI (580×184) 1.8964 5.268

The ROI path was about 38 times faster here (200.178 / 5.268). At 5.268 ms/frame, 30 fps is comfortable. At 200 ms/frame, a two-hour VOD is a long wait for a banner index.

Zoomed crops of the synthetic kill-feed: idle, a single banner, a stack, a darkened banner, a brief banner, a bottom-left decoy, and a scaled banner that the matcher missed

Turning hits into clip windows

A banner timestamp is not a clip. Most short-form game clips need a few seconds of setup and a little room after the last elimination. I used a simple pad: three seconds before the first hit in a cluster, two seconds after the last, clipped to the file. Those pads are a taste choice, not a measured optimum.

On this short VOD the windows overlap, because the events are only a couple of seconds apart:

Detected event (s) Padded window (s) Duration (s)
2.000–3.033 0.000–5.033 5.033
4.000–5.033 1.000–7.033 6.033
6.000–7.033 3.000–9.033 6.033
8.000–8.533 5.000–10.533 5.533

On a real match those ranges would usually separate. You would still have to merge overlaps, drop feed events you would not post, and decide whether a stacked feed is one clip or two. The matcher does not know.

The 54-by-48 chevron template beside the matched region and a wider crop of the NOVA > PIXEL banner

What this does not do

I would not ship this as a Valorant clipper.

It will miss anything that does not draw this exact icon at this exact size: a scaled HUD, a different aspect crop, a blocked overlay, a replay cam, or a play that never printed a banner. It has no idea whether the elimination was an ace, a trade, or a team kill you would rather not post. Multi-scale matching, a second template, or a learned detector would address some of those gaps. None of them were measured here.

The legal constraint is also not a footnote. If you want to point this at your own VOD, crop the template from footage you are allowed to use. Do not extract Riot's icons into a public repo.

For a longer recording, FragCut is an AI gaming clip maker that accepts an upload or a Twitch VOD and looks for highlight moments after the match, including valorant clips. I checked the pricing page on 12 November 2026 (Asia/Shanghai): analysis of the first match is free, you pay when you want clips rendered, Creator is $19.99 a month, Pro is $49.99 a month, and payments have a 14-day refund. That is a different system than the 54×48 template above. The template is here so you can see a baseline fail in a predictable way.

If you run the script, change the threshold or the pad and see what the table does. The useful outcome is not a perfect F1 on a file I drew. It is a clear record of what a banner match can timestamp, and what it will never see.

原文:https://www.a8q9.cn/en/article/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured-7a315e.html

相关文章