Structural Similarity Index (SSIM) in Python
Quantitatively check the quality of a compressed image using a simple Python code for calculating the Structural Similarity Index (SSIM) and Mean Square Errors (MSE) between two images.
When images are compressed, resized or converted to different formats, there can be a loss of fidelity between the original and the copy.
A common method for quantitatively checking the effect that format changes or compression may have had on an image is to use the peak signal-to-noise ratio (PSNR), which is the ratio between the maximum possible power of an image and the power of corrupting noise that affects the quality of its representation. However, there are two other methods that can also be used:
- The Mean Square Error (MSE)
- The Structural Similarity Index (SSIM)
MSE is a fairly old method with known limitations, but is still commonly used. Values of MSE closer to zero are better. MSE can be represented mathematically as follows:
\[MSE = \displaystyle \frac{1}{mn} \sum\limits_{i=0}^{m-1}\displaystyle\sum\limits_{j=0}^{n-1}\lVert f(i,j)-g(i,j)\lVert^2\]
where:
- f represents the matrix data of our original image
- g represents the matrix data of our copied or degraded image in question
- m represents the numbers of rows of pixels of the images and i represents the index of that row
- n represents the number of columns of pixels of the image and j represents the index of that column
The Structural Similarity Index (SSIM) is a much newer equation developed in 2004 by Wang et al. SSIM Index quality assessment index is based on the computation of three factors; luminance (l), contrast (c) and structure (s). The overall index is a multiplicative combination of the three:
\[SSIM(x, y) = [l(x,y)]^a\times[c(x,y)]^\beta\times[s(x,y)]^\gamma\]
where:
\[l(x,y)=\frac{2\mu_x\mu_y+C_1}{\mu^2_x+\mu^2_x+C_1},\]
\[c(x,y)=\frac{2\sigma_x\sigma_y+C_2}{\sigma^2_x+\sigma^2_x+C_2},\]
\[s(x,y)=\frac{\sigma_xy+C_3}{\sigma_x\sigma_y+C_3}\]
where μx, μy, σx,σy, and σxy are the local means, standard deviations, and cross-covariance for images x, y. If α = β = γ = 1 and C3 = C2/2 (both common assumptions) the equation simplifies to:
\[SSIM(x,y)=\frac{(2\mu_x\mu_y+C_1)(2\sigma_xy+C_2)}{(\mu^2_x+\mu^2_y+C_1)(\sigma^2_x+\sigma^2_y+C_2)}\]
The SSIM values range between 0 to 1 where 1 means a perfect match between the original image and the copy.
The mathematics for SSIM is horrendous, but thankfully both MSE and SSIM functions exist in the scikit-image package in Python. The following Python code can be used to calculate the MSE and SSIM for a pair of images:
#!/usr/bin/env python
from skimage.metrics import structural_similarity as ssim
import numpy as np
import cv2
import argparse
def options():
parser = argparse.ArgumentParser(description="Read image metadata")
parser.add_argument("-o", "--first", help="Input image file.", required=True)
parser.add_argument("-c", "--second", help="Input image file.", required=True)
args = parser.parse_args()
return args
def mse(imageA, imageB):
# the 'Mean Squared Error' between the two images is the sum of the squared difference between the two images
mse_error = np.sum((imageA.astype("float") - imageB.astype("float")) ** 2)
mse_error /= float(imageA.shape[0] * imageA.shape[1])
# return the MSE. The lower the error, the more "similar" the two images are.
return mse_error
def compare(imageA, imageB):
# Calculate the MSE and SSIM
m = mse(imageA, imageB)
s = ssim(imageA, imageB)
# Return the SSIM. The higher the value, the more "similar" the two images are.
return s
def main():
# Get options
args = options()
# Import images
image1 = cv2.imread(args.first)
image2 = cv2.imread(args.second, 1)
# Convert the images to grayscale
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
# Check for same size and ratio and report accordingly
ho, wo, _ = image1.shape
hc, wc, _ = image2.shape
ratio_orig = ho/wo
ratio_comp = hc/wc
dim = (wc, hc)
if round(ratio_orig, 2) != round(ratio_comp, 2):
print("\nImages not of the same dimension. Check input.")
exit()
# Resize first image if the second image is smaller
elif ho > hc and wo > wc:
print("\nResizing original image for analysis...")
gray1 = cv2.resize(gray1, dim)
elif ho < hc and wo < wc:
print("\nCompressed image has a larger dimension than the original. Check input.")
exit()
if round(ratio_orig, 2) == round(ratio_comp, 2):
mse_value = mse(gray1, gray2)
ssim_value = compare(gray1, gray2)
print("MSE:", mse_value)
print("SSIM:", ssim_value)
if __name__ == '__main__':
main()
The code takes two inputs on the command line:
-o
: A full path to the original image-c
: A full path to the comparison (copy or degraded) image
If the python file were saved as mse-ssim.py, implementation would be as follows: /path/to/mse-ssim.py -o /path/to/original.ext -c /path/to/copy.ext
Before calculating MSE and SSIM, the above code will check that the two images are the same size. If the copy is smaller, then the original will be reduced to the same dimensions using OpenCV’s cv2.resize()
function which uses a bilinear interpolation. (Ideally, the two images would have identical dimensions and this step would not be necessary but your application will determine this). The aspect ratio of each image is also checked before proceeding.
This code has been published to GitHub.
Comments
No comments have yet been submitted. Be the first!