-
Notifications
You must be signed in to change notification settings - Fork 338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image interpolation method for downscaling (nearest neighbour, antialias) #128
Comments
Can you show a few example images and what their anti-aliased & nearest down-sampled (8x8pixel) image looks like? |
Here are the tests: TimingI've made some timing tests with the following snippet of code: from os.path import dirname, abspath
from os import listdir
from time import time
from PIL import Image
DATA_DIR = dirname(dirname(abspath(__file__))) + "/data/"
data = []
for image in listdir(DATA_DIR):
try:
data.append(Image.open(f"{DATA_DIR}{image}"))
except: pass
for image in data:
image.load()
start = time()
for image in data:
image.resize((8, 8), Image.NEAREST)
print("Time taken (Image.NEAREST):", time() - start, "sec.")
start = time()
for image in data:
image.resize((8, 8), Image.ANTIALIAS)
print("Time taken (Image.ANTIALIAS):", time() - start, "sec.")
data[0].save("image_1_noresize.png")
data[0].resize((8, 8), Image.NEAREST).save("image_1_nearest.png")
data[0].resize((8, 8), Image.ANTIALIAS).save("image_1_antialias.png")
data[4].save("image_5_noresize.png")
data[4].resize((8, 8), Image.NEAREST).save("image_5_nearest.png")
data[4].resize((8, 8), Image.ANTIALIAS).save("image_5_antialias.png")
data[33].save("image_34_noresize.png")
data[33].resize((8, 8), Image.NEAREST).save("image_34_nearest.png")
data[33].resize((8, 8), Image.ANTIALIAS).save("image_34_antialias.png") ResultsThis are the results printed out: Time taken (Image.NEAREST): 0.02369093894958496 sec.
Time taken (Image.ANTIALIAS): 0.367678165435791 sec.
The images are all from the Anime Faces dataset: https://www.kaggle.com/soumikrakshit/anime-faces There is a total of 21551 64x64 RGB images (.png with no alpha) Image Results (8x8)
Sorry I couldn't find the name of the first images because it kind of picked them randomly and I tried to find them using a really basic and simple image difference algorithm: class ImageHash():
def __init__(self, image, r=None, g=None, b=None) -> None:
self.path = image
self.original = Image.open(image)
image = self.original.resize((SIZE, SIZE), Image.NEAREST)
data = image.getdata()
self.r = ([d[0] for d in data] if r is None else r)
self.g = ([d[1] for d in data] if g is None else g)
self.b = ([d[2] for d in data] if b is None else b)
def difference(self, image):
r_diff = mean([abs(value - image.r[index]) for index, value in enumerate(self.r)]) / 255
g_diff = mean([abs(value - image.g[index]) for index, value in enumerate(self.g)]) / 255
b_diff = mean([abs(value - image.b[index]) for index, value in enumerate(self.b)]) / 255
return mean([r_diff, g_diff, b_diff]) But couldn't manage to find them Computer SpecsMacBook Air (M1, 2020) |
Yes, looking at the 8x8 pixels, the nearest method introduces some dark pixels and more noise. It looks like it is not a small effect. Probably the hashes of the two methods are even different. |
Yea I think that they will be different but isn't it accentuating the features? Like, if a human needed to see these images it would definitely be worth using ANTIALIAS (which is Lanczos) but here a computer is just gonna compute it. Or maybe add a parameter where you can define the resizing algorithm used? |
Could you also compare the speed of dhash/ahash/phash with the two methods? I am not sure this is the slowest part. |
TestsHere is the script used to test with the different algorithm: from time import time
from PIL import Image
from imagehash import average_hash, dhash, phash
img = Image.open("image_1_noresize.png")
img.load()
start = time()
for _ in range(100000):
average_hash(img)
print("Took", time() - start, "seconds to create 100000 hashes with average_hash")
start = time()
for _ in range(100000):
dhash(img)
print("Took", time() - start, "seconds to create 100000 hashes with dhash")
start = time()
for _ in range(100000):
phash(img)
print("Took", time() - start, "seconds to create 100000 hashes with phash") ResultsWith ANTIALIAS:
With NEAREST:
I changed the resize method directly in the source code between each test Using cProfile you can see that with ANTIALIAS:
With NEAREST:
|
I just determined that using Image.antialias makes imagehash NOT produce the same phash values as goimagehash in many cases. |
Having messed around with image resizing in Python, I have a few comments: Using A common approach to speed up image scaling in Python is to do it in two steps: scale down the image at 2x, 4x or 8x the target size using In addition to that PIL has a special method to downscale images quickly: thumbnail. Thumbnail is most effective if it is used before the image data is loaded, as it can tell the file loader to only load the needed data for the smaller image. This works very well for JPEG, less well for other formats. I did run some tests (code at the end). I used the same test images as @Animenosekai, but I did not preload the data. I did warm the file system cache by loading all images once and discarding them. Here is an example image and its 8x8 versions for comparison:
In this view the differences between AA, N+AA and TH seem to be almost invisible, but the hashes do find differences. I tested it with Interestingly I got fairly different performance numbers than @Animenosekai. In my tests without preloading data and with calculating the hash (from the scaled-down image) the differences between the algorithms were very small:
So, not very exciting. 😒 However, the test faces are only 64x64 pixels, which is very small. I tested it with some larger (~3000x4000) JPEG test images, and got more interesting results:
So So at this point I'm not sure what I would recommend. My use case is more like the second test: large images on disc. In that case Just my $.02... Test Code:
|
I guess if the user uses the Thumbnail4 method to pass the imagehash functions a file in the right size, the resize functions within will be noop. Is that right? In that case we can recommend users to use Thumbnail4. |
Yes, that's an option. At least pillow's resize checks and doesn't resize if the size is the same. The only price you pay is making a copy of the index image. Given that it's small that's probably not a very big deal. So yeah, recommending something like th4 may make sense for the large JPEG on disc use case. Maybe it would make sense to add it as a utility function to imagehash, to make using it simpler? It's not a lot of code, but I know people (me included ;) are lazy... |
If so, we should also include information about the ICC profiles, see #110 , so that these are correctly handled. If you can prepare a short script, that would be great. I would prefer to include it in the README as a section "How to best read image files" so the code is in plain sight. |
A subtlety is that some hashes do hashsize x hashsize, some do (hashsize + 1) x hashsize, but we can spell that out. |
I was testing different resizing algorithms and I noticed that the Nearest Neighbour algorithm is way faster than Antialiasing.
I was just wondering why ImageHash used
Image.ANTIALIAS
overImage.NEAREST
for something that will be processed by the program (we don't really care about how the image look if we have the features)The text was updated successfully, but these errors were encountered: