Perceptual image similarity for Rust, built primarily around SSIM (the Structural Similarity Index).
Compare two image files and read the similarity score:
use std::path::Path;use perception::ImagePair;fnmain() -> Result<(),Box<dyn std::error::Error>>{let comparison = ImagePair::new(Path::new("original.png"),Path::new("distorted.png"),).compare()?;// 1.0 means identical; lower means more structural difference.println!("similarity: {}", comparison.similarity());Ok(())}ImagePair always uses the CPU backend. To select a backend explicitly — including CUDA —
use Engine:
use std::path::Path;use perception::Engine;fnmain() -> Result<(),Box<dyn std::error::Error>>{let engine = Engine::cpu();let comparison = engine.compare(Path::new("original.png"),Path::new("distorted.png"),)?;println!("similarity: {}", comparison.similarity());Ok(())}The CUDA backend requires the cuda feature and a working CUDA toolkit and GPU:
[dependencies]
perception = { version = "0.1", features = ["cuda"] }# use std::path::Path;use perception::Engine;let engine = Engine::cuda()?;// creates the device context and compiles the kernelslet comparison = engine.compare(Path::new("original.png"),Path::new("distorted.png"))?;
# Ok::<(),Box<dyn std::error::Error>>(())| Feature | Default | Effect |
|---|---|---|
cpu | yes | Rayon-parallel CPU backend; enables Engine::cpu(). |
cuda | no | CUDA/GPU backend via cudarc; enables Engine::cuda(). |
Every comparison also yields a per-pixel SimilarityMap. Values are close to 1.0 where the
images match and lower where they differ. It can be written out as a grayscale PNG heatmap in
which differing regions appear brighter:
use std::path::Path;use perception::ImagePair;fnmain() -> Result<(),Box<dyn std::error::Error>>{let map = ImagePair::new(Path::new("original.png"),Path::new("distorted.png"),).compare()?
.into_map();println!("{}x{} map", map.width(), map.height());let _values:&[f32] = map.values();
map.write(Path::new("difference.png"))?;Ok(())}For a pixelmatch-style visual report, ImagePair::diff writes three separate PNG files — the
expected image, the current image, and a diff panel that paints red every pixel whose dissimilarity
is at or above a configurable threshold, over a faded grayscale of the expected image:
use std::path::Path;use perception::DiffOutputPaths;use perception::DissimilarityThreshold;use perception::ImagePair;fnmain() -> Result<(),Box<dyn std::error::Error>>{ImagePair::new(Path::new("original.png"),Path::new("distorted.png"),).diff()?
.write(// Highlight every region at or above 80% dissimilarity.DissimilarityThreshold::new(0.8)?,&DiffOutputPaths::new(Path::new("expected.png"),Path::new("current.png"),Path::new("diff.png"),),)?;Ok(())}Each of the three output paths is configured independently. Engine::diff produces the same
ThreeWayDiff on an explicitly chosen backend.
perception is a thin facade over a small family of crates:
| Crate | Role |
|---|---|
perception | High-level API (ImagePair, Engine, Comparison, …). |
perception_metric | Backend-agnostic multi-scale SSIM metric engine. |
perception_backend | Backend trait and shared types. |
perception_backend_cpu | CPU implementation of the backend. |
perception_backend_cuda | CUDA/GPU implementation of the backend. |
Licensed under the Apache License, Version 2.0.