{"slug": "finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured", "title": "Finding Valorant Clips With OpenCV: Template Matching a Kill Banner, Measured", "summary": "A developer at FragCut built a small OpenCV kill-banner detector using cv2.matchTemplate with TM_CCOEFF_NORMED on a 54×48 crop of a synthesized 12-second 1080p clip, searching a top-right region of interest rather than the full frame. The detector caught four of five planted banner events, including a darkened banner and a 16-frame brief one, ignored a cyan-and-navy decoy, and missed a banner drawn at 1.12× scale, confirming fixed-size template matching is not scale-invariant. The author notes the scores are not match-prediction accuracy on live games and that a banner detector is a starting index for candidate timestamps, not an editor.", "body_md": "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.\n\nThis 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.\n\nI 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.\n\nIf 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.\n\nTemplate 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.\n\nIf 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.\n\nThe 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.\n\nThe 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.\n\nThe 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.\n\nThe 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.\n\nI planted five banner events and one decoy:\n\n| Event | Time (s) | What I changed | Expected? | \n|---|---|---|---|\n| E1 single | 2.000–3.033 | One banner, normal brightness and scale | Detect | \n| E2 stacked | 4.000–5.033 | Two banners, different names | Detect | \n| E3 darkened | 6.000–7.033 | Banner multiplied to 0.55 brightness | Detect | \n| E4 brief | 8.000–8.533 | Same banner, only 16 frames (0.533 s) | Detect | \n| Distractor | 9.000–10.033 | A cyan-and-navy bar in the bottom-left, no chevron | Ignore | \n| E5 scaled | 10.500–11.533 | Banner drawn at 1.12× scale | Miss | \n\nE5 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.\n\nThe 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.\n\n``` python\nimport cv2\n\nMETHOD = cv2.TM_CCOEFF_NORMED\nROI = (1424, 28, 580, 184)  # x, y, w, h — the kill-feed corner\n\ndef peak_score(frame, template, roi=ROI):\n    x, y, w, h = roi\n    crop = frame[y:y + h, x:x + w]\n    result = cv2.matchTemplate(crop, template, METHOD)\n    _, max_val, _, max_loc = cv2.minMaxLoc(result)\n    return float(max_val), (max_loc[0] + x, max_loc[1] + y)\n```\n\nI 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.\n\nOn 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.\n\nPeak scores inside each ground-truth window:\n\n| Event | Peak score | Mean score in the window | Detected at 0.80? | \n|---|---|---|---|\n| E1 single | 0.99058 | 0.99044 | Yes | \n| E2 stacked | 0.99064 | 0.99052 | Yes | \n| E3 darkened | 0.98794 | 0.98749 | Yes | \n| E4 brief | 0.99000 | 0.98981 | Yes | \n| E5 scaled | 0.52239 | 0.52235 | No, as planned | \n\nEvent-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.\n\nThe 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.\n\nIdle 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.\n\nThe 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.\n\nTiming, measured in the same process on this machine:\n\n| Search area | Seconds for 360 frames | Milliseconds per frame | \n|---|---|---|\n| Full 1920×1080 | 72.0639 | 200.178 | \n| Top-right ROI (580×184) | 1.8964 | 5.268 | \n\nThe 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.\n\nA 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.\n\nOn this short VOD the windows overlap, because the events are only a couple of seconds apart:\n\n| Detected event (s) | Padded window (s) | Duration (s) | \n|---|---|---|\n| 2.000–3.033 | 0.000–5.033 | 5.033 | \n| 4.000–5.033 | 1.000–7.033 | 6.033 | \n| 6.000–7.033 | 3.000–9.033 | 6.033 | \n| 8.000–8.533 | 5.000–10.533 | 5.533 | \n\nOn 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.\n\nI would not ship this as a Valorant clipper.\n\nIt 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.\n\nThe 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.\n\nFor 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](https://fragcut.io/games/valorant?utm_source=devto&utm_medium=community&utm_campaign=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.\n\nIf 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.", "url": "https://wpnews.pro/news/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured", "canonical_source": "https://dev.to/jacobpark/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured-2g6d", "published_at": "2026-11-12 03:37:47+00:00", "updated_at": "2026-11-12 03:51:05.179632+00:00", "lang": "en", "topics": ["computer-vision", "ai-tools", "developer-tools"], "entities": ["OpenCV", "Valorant", "FragCut", "Riot Games"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured", "markdown": "https://wpnews.pro/news/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured.md", "text": "https://wpnews.pro/news/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured.txt", "jsonld": "https://wpnews.pro/news/finding-valorant-clips-with-opencv-template-matching-a-kill-banner-measured.jsonld"}}