What is the difference between reading an image as grayscale and converting 3-channel image into a grayscale image?
To make it clear, if I read an image as follows:
gray_1 = cv2.imread("model.jpg", 0)
colored = cv2.imread("model.jpg")
gray_2 = cv2.cvtColor(colored, cv2.COLOR_RGB2GRAY)
print(gray_1.shape) #(1152,1536)
print(gray2.shape) #(1152, 1536)
Now, if I check the equality of the two numpy arrays gray_1
and gray_2
, they are not equal.
np.array_equal(gray_1, gray_2)
The above statement returns False
. Why is that? What is the difference between gray_1
and gray_2
?
Please note that this answer does not state, and has never stated, there is no difference between loading in greyscale versus loading in colour and subsequently converting to greyscale. It merely states that:
1) OP would need to use cv2.COLOR_BGR2GRAY
rather than cv2.COLOR_RGB2GRAY
to make a correct comparison, and
2) the difference is likely negligible for anyone who is prepared to use lossy JPEG to store their images.
OpenCV natively stores in BGR order, so the true comparison would actually be:
gray_2 = cv2.cvtColor(colored, cv2.COLOR_BGR2GRAY)
as opposed to using cv2.COLOR_RGB2GRAY
.
It might be helpful to quantify "how different" the two images are as a result of being loaded directly in greyscale versus being loaded in colour and then subsequently converted, so I calculated the following statistics:
Absolute Error - simply the number of pixels that differ
Peak Absolute Error - the largest absolute difference between any two corresponding pixels
Mean Absolute Error - the average absolute difference between corresponding pixels
Mean Squared Error - the average squared difference between corresponding pixels
Root Mean Square Error - square root of the above
If you use the standard 512x512 Lena image, and compare the image loaded directly as greyscale with the image loaded as colour and subsequently converted, you will get these results:
AE: 139
PAE: 4
MAE: 0.00072479248046875
MSE: 0.001220703125
RMSE: 0.034938562148434216
So, of the 262,144 pixels, only 139 pixels differ and the maximum difference between any two pixels is just 4 on a range of 0..255, i.e. less than 1.6%
By comparison, if you compare the Lena image saved with JPEG quality of 90 versus quality 89, you get the following difference:
AE: 158575
PAE: 13
MAE: 0.9766883850097656
MSE: 2.2438392639160156
RMSE: 1.4979450136490378
So, I am saying that a 1% difference in JPEG quality causes 100x as many pixels to differ by up to 3x as much. So, the fact you choose to store your data as JPEG has a massively larger impact than the difference in the two greyscale conversion methods, and if you really care about accuracy you should rather use PNG/TIFF/PPM or some other lossless format.
#!/usr/bin/env python3
import math
import numpy as np
from PIL import Image
import cv2
def compare(im1, im2, metric):
"""
Compare two images in terms of given metric.
'AE' Absolute Error. Simply the number of pixels that are different.
'PAE' Peak Absolute Error. The largest absolute difference between two corresponding pixels.
'MAE' Mean Absolute Error. The average difference between correspondng pixels.
'MSE' Mean Squared Error.
'RMSE' Root Mean Squared Error.
"""
assert(im1.shape==im2.shape)
im1 = np.ravel(im1).astype(np.int64)
im2 = np.ravel(im2).astype(np.int64)
if metric == 'AE':
# Return count of pixels that differ
res = (im1 != im2).sum()
return res
if metric == 'PAE':
# Return largest absolute difference
res = np.abs((im1-im2)).max()
return res
if metric == 'MAE':
# Return average absolute difference between corresponding pixels
res = np.abs((im1-im2)).mean()
return res
# Calculate mean squared difference between corresponding pixels
res = ((im1-im2)*(im1-im2)).mean()
if metric == 'MSE':
return res
if metric == 'RMSE':
return math.sqrt(res)
# Uncomment any one of the three following blocks
# Create greyscale image 640x480 filled with mid-grey
#w,h = 640,480
#im1 = np.zeros([h,w,1], dtype=np.uint8) + 128
#im2 = im1.copy()
#im2[1,1]=7
# Load first image as greyscale, second as colour but then convert to greyscale afterwards
#gray_1 = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
#coloured = cv2.imread('lena.jpg',cv2.IMREAD_COLOR)
#gray_2 = cv2.cvtColor(coloured, cv2.COLOR_BGR2GRAY)
# Load Lena in 89 and 90 JPEG quality
gray_1 = cv2.imread('lena89.jpg',cv2.IMREAD_GRAYSCALE)
gray_2 = cv2.imread('lena90.jpg',cv2.IMREAD_GRAYSCALE)
res = compare(gray_1, gray_2, 'AE')
print('AE: {}'.format(res))
res = compare(gray_1, gray_2, 'PAE')
print('PAE: {}'.format(res))
res = compare(gray_1, gray_2, 'MAE')
print('MAE: {}'.format(res))
res = compare(gray_1, gray_2, 'MSE')
print('MSE: {}'.format(res))
res = compare(gray_1, gray_2, 'RMSE')
print('RMSE: {}'.format(res))