Yolo Similarity Compare ππ ‘π £π
Did this frame change scene? Object-level frame comparison for live ComfyUI
- ULTRALYTICS_RESULTS1
- ULTRALYTICS_RESULTS2
- similarity_score
- above_threshold
- explanation
Most "is this frame different?" comparisons are pixel-based: diff the images, threshold the change. That works, but it can't tell you what changed, and it fires on lighting shifts you don't care about. Yolo Similarity Compare ππ ‘π £π does it semantically. Feed it the object-detection results of two frames and it tells you how similar the scenes are - same objects? same places? - and hands you a clean boolean for gating the rest of your real-time graph.
It's from ryanontheinside/ComfyUI_RealtimeNodes, the real-time pack built for once-per-frame runners like ComfyStream. It's also the pack's strangest citizen: while everything else lives under "Realtime Nodes", this one's category is literally ScavengerHunt - a leftover from whatever project it was extracted from. Functionally irrelevant, but it means you'll search for it by name rather than stumble on it in the menu.
How it works
The node takes two Ultralytics YOLO detection results and scores their similarity as a weighted sum of five facets, each compared on a 0β1 scale:
- Class overlap - a Jaccard-style measure of which object types appear in both images (0.3 weight by default). Two frames both showing "person, car, dog" score high even if the pixels differ.
- Spatial - where the detected objects sit, normalized to image coordinates (0.2). "Person on the left" vs "person on the right" drops the score.
- Confidence - how similar the detection confidences are (0.2).
- Size - average normalized box areas (0.15). A close-up vs a wide shot diverges here.
- Relationship - the average pairwise distance between detected objects (0.15). This one catches compositional changes that move objects relative to each other.
The weights default to 0.30 + 0.20 + 0.20 + 0.15 + 0.15 = 1.0, so the raw score stays a sensible 0β1 number. If the weighted sum clears threshold (default 0.5), above_threshold goes true.
Inputs and outputs
The two inputs that decide everything are ULTRALYTICS_RESULTS1 and ULTRALYTICS_RESULTS2 - that's the YOLO detection output type, produced by nodes like Impact Pack's UltralyticsDetectorProvider, not by this pack itself. Then five sliders (class_weight, spatial_weight, confidence_weight, size_weight, relationship_weight) and threshold.
Outputs - three, which is generous for a utility node:
similarity_score(FLOAT) - the 0β1 number.above_threshold(BOOLEAN) - your gate. Wire this into a switch or a lazy-condition node to skip downstream sampling when the scene hasn't changed.explanation(STRING) - a human-readable breakdown of each facet plus the detected class names in both images, in plain English. It's the best debugging tool the node has.
Installing it
Same pack install as everything else. ComfyUI Manager: search "Control Nodes" or "Realtime". Or:
cd ComfyUI/custom_nodes
git clone https://github.com/ryanontheinside/ComfyUI_RealtimeNodes
cd ComfyUI_RealtimeNodes
pip install -r requirements.txt
Then restart. Note the repo case: ComfyUI_RealtimeNodes (the README's own example says RealTimeNodes, which 404s on GitHub). The real gotcha for this node is upstream: its inputs only resolve if a node that outputs ULTRALYTICS_RESULTS is installed, and the pack's requirements don't pull in ultralytics at all. Practically that means installing Impact Subpack (Impact Pack's v8.0+ split-out detector provider) alongside it. Ultralytics itself is AGPL-licensed and was the vector for a December 2024 supply-chain cryptominer incident in this exact ecosystem - worth knowing when you're deciding whether to add a YOLO dependency, not a reason to panic.
Where people get burned
The classic failure is connecting it without a detection upstream and wondering why the inputs are red. Second is forgetting the facet weights - they're all normalized so they sum to 1.0, but they don't have to; if you push every weight to 1 the score balloons past 1 and threshold becomes meaningless. Keep the sum near 1.0. And like every node in this pack, it's built for continuous execution - in a one-shot batch run it compares once and your gate never re-evaluates. For its real job - "only re-render when the camera actually shows a new scene" - it's a genuinely clever filter, and the explanation output makes it painless to tune.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| ULTRALYTICS_RESULTS1 | ULTRALYTICS_RESULTS | β | |
| ULTRALYTICS_RESULTS2 | ULTRALYTICS_RESULTS | β | |
| class_weight | FLOAT | 0.300β1 | Weight for class similarity - how much to consider matching object types |
| spatial_weight | FLOAT | 0.200β1 | Weight for spatial similarity - how much to consider object positions |
| confidence_weight | FLOAT | 0.200β1 | Weight for confidence similarity - how much to consider detection confidence |
| size_weight | FLOAT | 0.150β1 | Weight for size similarity - how much to consider matching object sizes |
| relationship_weight | FLOAT | 0.150β1 | Weight for relationship similarity - how much to consider distances between objects |
| threshold | FLOAT | 0.500β1 | Threshold for similarity - scores above this value will return True |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| similarity_score | FLOAT | β |
| above_threshold | BOOLEAN | β |
| explanation | STRING | β |