I think I understand what you are trying to do. I have no idea of the relative performance of our two machines so maybe you can benchmark it yourself.
from PIL import Image
import numpy as np
# Load images, convert to RGB, then to numpy arrays and ravel into long, flat things
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()
# Calculate the sum of the absolute differences divided by number of elements
MAE = np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
The only "tricky" thing in there is the forcing of the result type of np.subtract() to a float which ensures I can store negative numbers. It may be worth trying with dtype=np.int16 on your hardware to see if that is faster.
A fast way to benchmark it is as follows. Start ipython and then type in the following:
from PIL import Image
import numpy as np
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()
Now you can time my code with:
%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
6.72 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Or, you can try an int16 version like this:
%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.int16))) / a.shape[0]
6.43 µs ± 30.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
If you want to time your code, paste in your function then use:
%timeit compare_images_pil(img1, img2)
Answer from Mark Setchell on Stack OverflowI think I understand what you are trying to do. I have no idea of the relative performance of our two machines so maybe you can benchmark it yourself.
from PIL import Image
import numpy as np
# Load images, convert to RGB, then to numpy arrays and ravel into long, flat things
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()
# Calculate the sum of the absolute differences divided by number of elements
MAE = np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
The only "tricky" thing in there is the forcing of the result type of np.subtract() to a float which ensures I can store negative numbers. It may be worth trying with dtype=np.int16 on your hardware to see if that is faster.
A fast way to benchmark it is as follows. Start ipython and then type in the following:
from PIL import Image
import numpy as np
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()
Now you can time my code with:
%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
6.72 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Or, you can try an int16 version like this:
%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.int16))) / a.shape[0]
6.43 µs ± 30.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
If you want to time your code, paste in your function then use:
%timeit compare_images_pil(img1, img2)
Digging a bit, I found this repository that takes a different approach that is more based on Pillow itself and seems to give similar results.
from PIL import Image
from PIL import ImageChops, ImageStat
def compare_images_pil(img1, img2):
'''Calculate the difference between two images of the same size
by comparing channel values at the pixel level.
`delete_diff_file`: removes the diff image after ratio found
`diff_img_file`: filename to store diff image
Adapted from Nicolas Hahn:
https://github.com/nicolashahn/diffimg/blob/master/diffimg/__init__.py
'''
# Don't compare if images are of different modes or different sizes.
if (img1.mode != img2.mode) \
or (img1.size != img2.size) \
or (img1.getbands() != img2.getbands()):
return None
# Generate diff image in memory.
diff_img = ImageChops.difference(img1, img2)
# Calculate difference as a ratio.
stat = ImageStat.Stat(diff_img)
# Can be [r,g,b] or [r,g,b,a].
sum_channel_values = sum(stat.mean)
max_all_channels = len(stat.mean) * 255
diff_ratio = sum_channel_values / max_all_channels
return diff_ratio * 100
For my test images sample, the results seem to be the same (except for a few minor float rounding errors) and it runs considerably faster than the first version I had above.