ImageSimilarity Class

Measures how similar two frames look: the Structural Similarity Index (SSIM), the peak signal-to-noise ratio (PSNR), the SSIMULACRA 2 score and the Butteraugli distance, and the distance between two 64-bit perceptual hashes, the difference hash (dHash) and the perceptual hash (pHash); and draws where two frames differ.

public static class ImageSimilarity

Remarks

Every member reads the pixels as 8-bit RGBA, whatever the pixel format (except the overloads of SSIM and PSNR that take ImageSimilarityOptions, and ImageSimilarity.CreateDifferenceMap), with the conversion of Image.CloneAs: 16-bit samples are rounded to 8 bits, gray samples are copied to the three color channels, and formats without alpha are opaque. Floating-point samples (RgbaFloat, RgbaHalf) are rounded to 8 bits as they are stored, whatever their transfer function label, like the samples of the other formats: a sample outside [0, 1], or that is not a number, has no 8-bit value and is rejected with an UnsupportedImageFeatureException, never clipped (convert such an image with Image.CloneAs and PixelConversionOptions.OutOfRangeSamples or PixelConversionOptions.ToneMapping first). Two frames of different pixel formats can be compared. Nothing is written to the pixels; the frames are leased for the duration of the call, so a frame cannot be measured while a lease is active on it. A frame can be compared with itself.

The results are deterministic: SSIM and the hashes depend only on the pixels, not on the hardware. PSNR, SSIMULACRA 2 and Butteraugli use the logarithm, power and cube root functions of the platform, so their last digits can differ between platforms. The temporary buffers are charged to the allocation scope of the image of the frames. An animated image is compared frame by frame.

SSIM. The score is computed in every 7×7 window that fits inside the frame, moved one pixel at a time, with uniform weights, the sample variance and covariance, and K1 = 0.01, K2 = 0.03, L = 255: for opaque frames, it is the mean over the three color channels of what scikit-image's structural_similarity(expected, actual, data_range=255, channel_axis=-1) computes. A frame narrower or shorter than 7 pixels uses a window as wide or as tall as the frame. The color channels are premultiplied by alpha, so a fully transparent pixel scores the same whatever color it hides; the alpha channel is scored on its own, and the result is the lower of the color score and the alpha score.

Hashes. Both hashes start from an exact, area-weighted luminance thumbnail (BT.601 weights; 9×8 cells for the dHash, 32×32 for the pHash), so a change anywhere in the frame affects it. A dHash bit is set when a cell is brighter than its right neighbor by more than half a luminance level; a pHash bit is set when a low-frequency DCT coefficient of the thumbnail exceeds the median coefficient by more than one level. These tolerances keep flat areas from flipping bits on invisible noise. The hashes describe the structure, not the brightness: a hash distance is therefore the Hamming distance plus the difference between the mean luminances on the same scale (about 4 levels out of 255 per unit). A frame that is not fully opaque is evaluated composited over black and over white, and the larger distance is kept. The distance is capped at ImageSimilarity.MaxHashDistance.

PSNR. The ratio, in decibels, of the peak value (255) to the mean squared error of the R, G and B channels premultiplied by alpha (as for SSIM): for opaque frames, it is what scikit-image's peak_signal_noise_ratio(expected, actual, data_range=255) computes. The alpha channel gives its own ratio, and the result is the lower of the two. Identical frames have an infinite ratio.

SSIMULACRA 2. A port of the ssimulacra2 tool of libjxl 0.12.0, a perceptual metric tuned on subjective scores: the frames are compared in the XYB color space of JPEG XL at 6 scales, with an SSIM map and two edge maps (new artifacts and lost details). The planes are 32-bit floats, and the arithmetic of the tool is reproduced (its sRGB curve, cube root and blur, with fused multiply-adds): the scores agree with the 8 decimals it prints. A frame that is not fully opaque is composited over a dark and a bright gray, and the lower score is kept, as the tool does.

Butteraugli. A port of the Butteraugli metric of libjxl 0.12.0 (by Jyrki Alakuijala), with the parameters of its butteraugli_main tool for SDR images (80 nits): a psychovisual model of the differences per frequency band, masked by the activity of the frames, that gives a distance for every pixel. The results are within about 0.01% of the tool. A frame that is not fully opaque is composited over black and over white, and every pixel keeps the larger distance, as libjxl does. A frame smaller than 8×8 is extended to 8×8 by repeating its edges, as libjxl's ButteraugliDiffmap does. It is the slowest member, and it needs about 120 bytes per pixel.

Parallelism. SSIM, PSNR, SSIMULACRA 2 and Butteraugli use up to ImageConfiguration.MaxDegreeOfParallelism workers (the configuration of the image of the expected frame) for frames of at least 65,536 pixels, and the result is identical whatever the number of workers: the floating-point sums are made by bands of 64 rows, added in order. With several workers, each one has its own row buffers.

Methods

NameDescription
ComputeButteraugliDistance(ImageFrame, ImageFrame, CancellationToken)Computes the Butteraugli distance between two frames of the same size.
ComputeDifferenceHash(ImageFrame, CancellationToken)Computes the 64-bit difference hash (dHash) of a frame composited over a black background.
ComputeDifferenceHashDistance(ImageFrame, ImageFrame, CancellationToken)Computes the difference hash (dHash) distance between two frames of the same size: the Hamming distance between their hashes plus the difference between their mean luminances, over black and over white for frames that are not opaque.
ComputePerceptualHash(ImageFrame, CancellationToken)Computes the 64-bit perceptual hash (pHash) of a frame composited over a black background.
ComputePerceptualHashDistance(ImageFrame, ImageFrame, CancellationToken)Computes the perceptual hash (pHash) distance between two frames of the same size: the Hamming distance between their hashes plus the difference between their mean luminances, over black and over white for frames that are not opaque.
ComputePsnr(ImageFrame, ImageFrame, ImageSimilarityOptions, CancellationToken)Computes the peak signal-to-noise ratio (PSNR) of two frames of the same size, at the precision of options.
ComputePsnr(ImageFrame, ImageFrame, CancellationToken)Computes the peak signal-to-noise ratio (PSNR) of two frames of the same size.
ComputeSsim(ImageFrame, ImageFrame, ImageSimilarityOptions, CancellationToken)Computes the mean Structural Similarity Index (SSIM) of two frames of the same size, at the precision of options.
ComputeSsim(ImageFrame, ImageFrame, CancellationToken)Computes the mean Structural Similarity Index (SSIM) of two frames of the same size.
ComputeSsimulacra2(ImageFrame, ImageFrame, CancellationToken)Computes the SSIMULACRA 2 score of two frames of the same size, at least 8×8.
CreateDifferenceMap(ImageFrame, ImageFrame, DifferenceMapOptions?, CancellationToken)Draws where two frames of the same size differ: a new opaque image of their size (see DifferenceMapKind), to look at or to save next to the frames.

ComputeButteraugliDistance(ImageFrame, ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the Butteraugli distance between two frames of the same size.

public static ButteraugliDistance ComputeButteraugliDistance(ImageFrame expected, ImageFrame actual, CancellationToken cancellationToken = default)

Parameters

expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
cancellationToken CancellationToken
The token checked between the steps of the computation.

Returns

ButteraugliDistance The largest distance of the map and its 3-norm: 0 for identical pixels, below about 1 when the difference is not visible.

Exceptions

ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The planes of the computation exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputeDifferenceHash(ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the 64-bit difference hash (dHash) of a frame composited over a black background.

public static ulong ComputeDifferenceHash(ImageFrame frame, CancellationToken cancellationToken = default)

Parameters

frame ImageFrame
The frame.
cancellationToken CancellationToken
The token checked between rows.

Returns

ulong The hash. Compare hashes with BitOperations.PopCount of their exclusive or.

Exceptions

ArgumentNullException
frame is null.
InvalidOperationException
A lease is active on the frame.
ObjectDisposedException
The frame is no longer attached to a live image.
ImageResourceLimitException
The row buffer exceeds the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputeDifferenceHashDistance(ImageFrame, ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the difference hash (dHash) distance between two frames of the same size: the Hamming distance between their hashes plus the difference between their mean luminances, over black and over white for frames that are not opaque.

public static int ComputeDifferenceHashDistance(ImageFrame expected, ImageFrame actual, CancellationToken cancellationToken = default)

Parameters

expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
cancellationToken CancellationToken
The token checked between rows.

Returns

int A distance from 0 (same structure and brightness) to ImageSimilarity.MaxHashDistance.

Exceptions

ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The row buffers exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputePerceptualHash(ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the 64-bit perceptual hash (pHash) of a frame composited over a black background.

public static ulong ComputePerceptualHash(ImageFrame frame, CancellationToken cancellationToken = default)

Parameters

frame ImageFrame
The frame.
cancellationToken CancellationToken
The token checked between rows.

Returns

ulong The hash. Compare hashes with BitOperations.PopCount of their exclusive or.

Exceptions

ArgumentNullException
frame is null.
InvalidOperationException
A lease is active on the frame.
ObjectDisposedException
The frame is no longer attached to a live image.
ImageResourceLimitException
The row buffer exceeds the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputePerceptualHashDistance(ImageFrame, ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the perceptual hash (pHash) distance between two frames of the same size: the Hamming distance between their hashes plus the difference between their mean luminances, over black and over white for frames that are not opaque.

public static int ComputePerceptualHashDistance(ImageFrame expected, ImageFrame actual, CancellationToken cancellationToken = default)

Parameters

expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
cancellationToken CancellationToken
The token checked between rows.

Returns

int A distance from 0 (same structure and brightness) to ImageSimilarity.MaxHashDistance.

Exceptions

ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The row buffers exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputePsnr overloads

ComputePsnr(ImageFrame, ImageFrame, ImageSimilarityOptions, CancellationToken)

ImageSimilarity.cs

Computes the peak signal-to-noise ratio (PSNR) of two frames of the same size, at the precision of options.

public static double ComputePsnr(ImageFrame expected, ImageFrame actual, ImageSimilarityOptions options, CancellationToken cancellationToken = default)
Parameters
expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
options ImageSimilarityOptions
The precision of the samples.
cancellationToken CancellationToken
The token checked between rows.
Returns

double The ratio in decibels: Double.PositiveInfinity for identical samples, higher values meaning smaller differences.

Exceptions
ArgumentNullException
expected, actual or options is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The row buffers exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample cannot be read at the precision: outside [0, 1] or not a number for 8 and 16 bits, not finite for fractions.
Remarks

The ratio of ImageSimilarity.ComputePsnr on other samples: with SimilarityPrecision.SixteenBit, 16-bit samples and the peak 65,535, which is scikit-image's peak_signal_noise_ratio(expected, actual, data_range=65535) on opaque frames (the squared errors are added as integers, so the mean squared error is exact); with SimilarityPrecision.FloatingPoint, fractions in double precision and the peak ImageSimilarityOptions.DataRange for the colors (1 for alpha). The colors are premultiplied by alpha, alpha gives its own ratio, and the result is the lower of the two.

ComputePsnr(ImageFrame, ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the peak signal-to-noise ratio (PSNR) of two frames of the same size.

public static double ComputePsnr(ImageFrame expected, ImageFrame actual, CancellationToken cancellationToken = default)
Parameters
expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
cancellationToken CancellationToken
The token checked between rows.
Returns

double The ratio in decibels: Double.PositiveInfinity for identical pixels; about 30 to 50 for a lossy encoding, higher values meaning smaller differences.

Exceptions
ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The row buffers exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputeSsim overloads

ComputeSsim(ImageFrame, ImageFrame, ImageSimilarityOptions, CancellationToken)

ImageSimilarity.cs

Computes the mean Structural Similarity Index (SSIM) of two frames of the same size, at the precision of options.

public static double ComputeSsim(ImageFrame expected, ImageFrame actual, ImageSimilarityOptions options, CancellationToken cancellationToken = default)
Parameters
expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
options ImageSimilarityOptions
The precision of the samples.
cancellationToken CancellationToken
The token checked between rows.
Returns

double A score from -1 to 1: 1 for identical samples, around 0 for unrelated frames.

Exceptions
ArgumentNullException
expected, actual or options is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The row buffers exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample cannot be read at the precision: outside [0, 1] or not a number for 8 and 16 bits, not finite for fractions.
Remarks

The score of ImageSimilarity.ComputeSsim on other samples: with SimilarityPrecision.SixteenBit, 16-bit samples and L = 65,535, which is scikit-image's structural_similarity(expected, actual, data_range=65535, channel_axis=-1) on uint16 arrays of opaque frames; with SimilarityPrecision.FloatingPoint, fractions in double precision and L = for the colors (1 for alpha), which is structural_similarity(expected, actual, data_range=L, channel_axis=-1) on float64 arrays. The colors are premultiplied by alpha (16-bit samples rounded to the nearest), the alpha channel is scored on its own, and the result is the lower of the two scores.

The window statistics are sums of the values of the window, without running sums: with 16-bit samples they are exact integers, so the score depends only on the pixels, as with 8-bit samples. The workers and the buffers are those of the 8-bit score, with 8 bytes per sample of the row buffers.

ComputeSsim(ImageFrame, ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the mean Structural Similarity Index (SSIM) of two frames of the same size.

public static double ComputeSsim(ImageFrame expected, ImageFrame actual, CancellationToken cancellationToken = default)
Parameters
expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
cancellationToken CancellationToken
The token checked between rows.
Returns

double A score from -1 to 1: 1 for identical pixels, around 0 for unrelated frames.

Exceptions
ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The row buffers exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

ComputeSsimulacra2(ImageFrame, ImageFrame, CancellationToken)

ImageSimilarity.cs

Computes the SSIMULACRA 2 score of two frames of the same size, at least 8×8.

public static double ComputeSsimulacra2(ImageFrame expected, ImageFrame actual, CancellationToken cancellationToken = default)

Parameters

expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
cancellationToken CancellationToken
The token checked between the steps of the computation.

Returns

double A score of at most 100: 100 for identical pixels, about 90 when the difference is not visible, 70 for a high quality, 50 for a medium quality, and negative values for very different frames.

Exceptions

ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size, or are smaller than 8×8.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The planes of the computation exceed the allocation limit.
UnsupportedImageFeatureException
A floating-point sample is outside [0, 1] or is not a number.

CreateDifferenceMap(ImageFrame, ImageFrame, DifferenceMapOptions?, CancellationToken)

ImageSimilarity.cs

Draws where two frames of the same size differ: a new opaque image of their size (see DifferenceMapKind), to look at or to save next to the frames.

public static Image<Rgba32> CreateDifferenceMap(ImageFrame expected, ImageFrame actual, DifferenceMapOptions? options = null, CancellationToken cancellationToken = default)

Parameters

expected ImageFrame
The reference frame.
actual ImageFrame
The frame compared with the reference.
options DifferenceMapOptions?
What the map shows, or null for the highlighted differences.
cancellationToken CancellationToken
The token checked between rows.

Returns

Image<Rgba32> The map, an image of the size of the frames.

Exceptions

ArgumentNullException
expected or actual is null.
ArgumentException
The frames do not have the same size.
InvalidOperationException
A lease is active on one of the frames.
ObjectDisposedException
A frame is no longer attached to a live image.
ImageResourceLimitException
The map or the row buffers exceed a limit.
UnsupportedImageFeatureException
A floating-point sample is not finite.

Remarks

The frames are read as fractions of their sample range, in double precision (16-bit samples keep their precision, floating-point samples must be finite), with the colors premultiplied by alpha, so a fully transparent pixel does not differ from another one whatever color it hides. DifferenceMapKind.Highlight composites DifferenceMapOptions.HighlightColor over the expected pixels whose largest sample difference exceeds DifferenceMapOptions.Threshold, and DifferenceMapOptions.LowlightColor over the others, which is what ImageMagick's compare expected actual map draws; the map has the alpha of the composite. DifferenceMapKind.Absolute draws the largest difference of every pixel, and DifferenceMapKind.Ssim the dissimilarity 1 - SSIM of the window centered on every pixel (the lower of the mean color score and the alpha score, as ImageSimilarity.ComputeSsim), with the rows and columns beyond the edges mirrored: the map of scikit-image's structural_similarity(full=True). Both are gray levels multiplied by DifferenceMapOptions.Gain and clamped to white.

The map has the configuration of the image of the expected frame, and the caller disposes it. The frames are read on the calling thread.

Constants

NameDescription
MaxHashDistanceThe largest distance between two hashes: a hash has 64 bits.

MaxHashDistance

The largest distance between two hashes: a hash has 64 bits.

public const int MaxHashDistance = 64