1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
#![crate_name = "image_compare"]
//! # Comparing images
//! This crate allows to compare grayscale images
//! The easiest use is loading two images, converting them to grayscale and running a comparison:
//! ```no_run
//! use image_compare::Algorithm;
//! let image_one = image::open("image1.png").expect("Could not find test-image").into_luma8();
//! let image_two = image::open("image2.png").expect("Could not find test-image").into_luma8();
//! let result = image_compare::gray_similarity(Algorithm::MSSIMSimple, &image_one, &image_two).expect("Images had different dimensions");
//! ```
//!
//! Check the [`Algorithm`] enum for implementation details
//!
#![warn(missing_docs)]
#![warn(unused_qualifications)]
#![deny(deprecated)]
mod squared_error;
mod ssim;
mod utils;
#[doc(hidden)]
pub mod prelude {
pub use image::{GrayImage, ImageBuffer, Luma};
use thiserror::Error;
/// The enum for selecting a grayscale comparison implementation
pub enum Algorithm {
/// A simple RMSE implementation - will return: <img src="https://render.githubusercontent.com/render/math?math=1-\sqrt{\frac{(\sum_{x,y=0}^{x,y=w,h}\left(f(x,y)-g(x,y)\right)^2)}{w*h}}">
RootMeanSquared,
/// a simple MSSIM implementation - will run SSIM (implemented as on wikipedia) over 8x8 px windows and average the results
MSSIMSimple,
}
#[derive(Error, Debug)]
/// The errors that can occur during comparison of the images
pub enum CompareError {
#[error("The dimensions of the input images are not identical")]
DimensionsDiffer,
}
/// a single-channel f32 typed image containing a result-score for each pixel
pub type SimilarityImage = ImageBuffer<Luma<f32>, Vec<f32>>;
#[derive(Debug)]
/// A struct containing the results of a grayscale comparison
pub struct Similarity {
/// Contains the resulting differences per pixel.
/// The buffer will contain the resulting values of the respective algorithms:
/// - RMS will be between 0. for all-white vs all-black and 1.0 for identical
/// - SSIM usually is near 1. for similar, near 0. for different but can take on negative values for negative covariances
pub image: SimilarityImage,
/// the averaged resulting score
pub score: f64,
}
pub trait ToGrayScale {
/// Clamps each input pixel to (0., 1.) and multiplies by 255 before converting to u8.
/// See tests/data/*_compare.png images for examples
fn to_grayscale(&self) -> GrayImage;
}
impl ToGrayScale for SimilarityImage {
fn to_grayscale(&self) -> GrayImage {
let mut img_gray = GrayImage::new(self.width(), self.height());
for row in 0..self.height() {
for col in 0..self.width() {
let new_val = self.get_pixel(col, row)[0].clamp(0., 1.) * 255.;
img_gray.put_pixel(col, row, Luma([new_val as u8]));
}
}
img_gray
}
}
}
#[doc(inline)]
pub use prelude::Algorithm;
#[doc(inline)]
pub use prelude::CompareError;
#[doc(inline)]
pub use prelude::Similarity;
#[doc(inline)]
pub use prelude::SimilarityImage;
pub use prelude::ToGrayScale;
use prelude::*;
/// The current main function of the crate
///
/// # Arguments
///
/// * `algorithm` - The comparison algorithm to use
///
/// * `first` - The first of the images to compare
///
/// * `second` - The first of the images to compare
pub fn gray_similarity(
algorithm: Algorithm,
first: &GrayImage,
second: &GrayImage,
) -> Result<Similarity, CompareError> {
if first.dimensions() != second.dimensions() {
return Err(CompareError::DimensionsDiffer);
}
match algorithm {
Algorithm::RootMeanSquared => squared_error::root_mean_squared_error_simple(first, second),
Algorithm::MSSIMSimple => ssim::ssim_simple(first, second),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dimensions_differ_test() {
let first = GrayImage::new(1, 1);
let second = GrayImage::new(2, 2);
let result = gray_similarity(Algorithm::RootMeanSquared, &first, &second);
assert!(result.is_err());
}
}