Image Similarity (CLIP & LPIPS)
Two Images, Two Similarity Scores — One of Them Is a Distance
- image1
- image2
- clip_similarity
- lpips_similarity
You've got a reference image and twelve generations, and you want to know which one actually nailed it. Eyeballing gets you maybe half the way. This node is the objective half: feed it any two images and it hands you two numbers - one telling you how semantically similar they are, one telling you how perceptually similar they are. Both are genuinely useful, and one of them is quietly backwards, which we'll get to before you wire anything up.
ImageSimilarityScores is a single small node from risunobushi's ComfyUI-Similarity-Score pack. It takes two IMAGE inputs and returns two FLOAT outputs: clip_similarity and lpips_similarity. That's the whole surface area - no settings, no sliders, no model picker. Drop in two images, run, read the numbers.
How it actually works
Under the hood the node runs two well-known metrics, both computed on your GPU if you have one:
- CLIP similarity embeds both images with OpenAI's CLIP ViT-B/32 and returns the cosine similarity between the embeddings. Cosine similarity runs from -1 to 1, and for real images you'll basically live between about 0.5 and 1.0. Higher means "about the same subject and composition," even if the pixels are totally different. This is the same trick LAION used to filter its dataset, and the same CLIP whose text encoder you already fight with in prompts - the image side of it is what's doing the work here.
- LPIPS is a perceptual distance, straight from the Zhang et al. paper ("The Unreasonable Effectiveness of Deep Features as a Perceptual Metric"). The node runs the AlexNet variant, resizes both images to 256×256, and compares deep features. Here's the trap: the output is named
lpips_similarity, but LPIPS is a distance. Lower is more similar. 0 means pixel-identical to the metric's eyes, and anything much above 0.3 is starting to be a different image. So the two outputs move in opposite directions - higher CLIP is good, lower LPIPS is good. Read them both before you conclude anything, and if you ever wire LPIPS into a "closest match wins" comparison, invert it.
Worth knowing because it's cute: you already use LPIPS without knowing it. The famous 840k VAE that most SD 1.5 checkpoints were trained against was fine-tuned on an L1 + LPIPS loss. This node is using the same perceptual yardstick that shaped your VAE.
What you'd actually use it for
- Seed hunting / variant picking: generate a dozen variations, compare each to a reference, keep the one with the highest CLIP score. Great for "closest to my sketch/reference" prompts.
- img2img strength checks: confirm that lowering denoise actually preserves content instead of trusting your eyes.
- Before/after comparisons: style-transfer or LoRA tests, where you want to prove the change is stylistic (CLIP drops, LPIPS holds) rather than wholesale replacement.
The floats don't show up inline by default - check the node's output panel after a run, or wire them into a text/display node (WAS's Display Any or rgthree's Show Text both work) so you can actually see them in the graph.
Installing it
ComfyUI Manager is the easy path: search for "Similarity" (or the pack title) and install. Manual route:
cd ComfyUI/custom_nodes/
git clone https://github.com/risunobushi/ComfyUI-Similarity-Score
cd ComfyUI-Similarity-Score
pip install -r requirements.txt # on the portable build: python_embeded\python.exe -m pip install -r requirements.txt
Then restart ComfyUI. Two things to know before you hit run:
- The first run downloads weights. The node loads CLIP ViT-B/32 (OpenAI's pretrained, a few hundred MB) and the tiny LPIPS AlexNet on first use. It's not hung, it's downloading - give it internet and a minute.
- The requirements list
clip-interrogator, but the shipped code never imports it. The node only needsopen-clip-torch,lpips,torch/torchvision, Pillow, and numpy. If you're pinning an environment, you can skip clip-interrogator and skip its dependency tree. And heads-up: each copy of the node in a graph loads its own CLIP + LPIPS models into VRAM, so don't scatter ten of them around one workflow - one node, fed repeatedly, is the sane setup.
Where people get burned
The LPIPS direction trap is the big one - trust the metric's meaning, not the output's name. After that, the classic failure is a "network error" or apparent hang on first run (missing weights), and on portable installs, forgetting that pip means the embedded Python. Feed it two ordinary IMAGE tensors - Load Image or a decoded batch - and you're fine; note it only compares the first frame of a batch. It's a small pack and a small node, but for "is this close to my reference?" it's a genuinely handy measuring stick.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image1 | IMAGE | — | |
| image2 | IMAGE | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| clip_similarity | FLOAT | — |
| lpips_similarity | FLOAT | — |