Skip to content
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

Pillow vs cv2 resize #2718

Closed
TropComplique opened this issue Sep 8, 2017 · 11 comments
Closed

Pillow vs cv2 resize #2718

TropComplique opened this issue Sep 8, 2017 · 11 comments

Comments

@TropComplique
Copy link

TropComplique commented Sep 8, 2017

Results of reading and resizing can be different in cv2 and Pilllow. This creates problems when you want to reuse a model (neural network) trained using cv2 with Pillow.

Case 1

Different results of resizing an image.

import cv2
from PIL import Image
import numpy as np

# read images
a = cv2.imread('images/dogs-and-cats.jpg', cv2.IMREAD_COLOR)
b = Image.open('images/dogs-and-cats.jpg')

# they are equal
print((np.asarray(b)[:, :, [2, 1, 0]] == a).all())
# True

# resize images
b = b.resize((300, 300), Image.NEAREST)
a = cv2.resize(a, (300, 300), interpolation=cv2.INTER_NEAREST)

# results of resizing are different, but visually they are equal
b = np.asarray(b)[:, :, [2, 1, 0]]
print((b - a).mean())
# 71.1218444444

Case 2

Different results of reading an image.

import cv2
from PIL import Image
import numpy as np

a = cv2.imread('images/cat_train.jpg', cv2.IMREAD_COLOR)

b = Image.open('images/cat_train.jpg')
b = np.asarray(b)[:, :, [2, 1, 0]]

# results of reading are not equal
print((b == a).all())
# False
print((b == a).mean())
# 0.999996744792

I use: Python 3.6, Pillow 4.2.1, opencv-python 3.3

@wiredfool
Copy link
Member

Case 1: Nearest neighbor is a fast, low quality, best effort interpolation. That said, I believe that our tests show our implementation is reasonably correct. a mean difference of 71 would fall into the range of seriously visually different, and probably the result of either a rotation or channel swapping.

Case 2. That appears to be 1 pixel level difference in 10^6 pixels. That's pretty close, and well within what I'd consider reasonable for a lossy format with potentially different implementations and settings on the decoder.

Feel free to point out where you feel that the implementation is incorrect.

@TropComplique
Copy link
Author

For case 1 Image.fromarray(a) and Image.fromarray(b) show the same image.
Also, their values are kinda close:

print(a[41:55, 60, :])
# [[255 255 255]
#  [117 124 144]
#  [ 96 117 145]
#  [ 70  90 121]
#  [ 68  88 119]
#  [ 78  96 127]
#  [ 70  88 119]
#  [ 71  87 116]
#  [ 71  89 118]
#  [ 77  98 126]
#  [116 151 185]
#  [135 167 202]
#  [142 170 205]
#  [126 162 200]]
print(b[41:55, 60, :])
# [[225 215 215]
#  [113 120 140]
#  [ 83 105 141]
#  [ 74  95 133]
#  [ 80 102 138]
#  [ 53  68  94]
#  [ 52  65  91]
#  [ 42  49  69]
#  [ 43  52  72]
#  [ 69  86 107]
#  [114 149 183]
#  [132 168 198]
#  [137 170 203]
#  [128 160 196]]

I use this image:
https://github.com/TropComplique/ssd-pytorch/blob/master/images/dogs-and-cats.jpg

@wiredfool
Copy link
Member

Which one do you think is correct?

@TropComplique
Copy link
Author

I do not know. I am just stating a fact that there is a difference.

@homm
Copy link
Member

homm commented Oct 2, 2017

visually they are equal

Visually they are also significantly different:

output_vedgno

Please, look at the sample:

_in

This image with the uniform gradient (from 100% white to 100% black) allows us to find out which pixels are used by each library. This is the same image after resizing to (3, 3). Left is CV2, right is Pillow:

_out cv2 _out pil

OpenCV uses the topmost left white pixel from the source image, but the bottommost right pixel on the result is too bright. Here are maps of pixels on the source image:

_in cv2 _in pillow

It's clear to me there are some problems with rounding in OpenCV there.

@TropComplique
Copy link
Author

Hi. Nice visualization.

But I find it strange that sometimes neural network's predictions
change a lot because of an image resizing method.

@homm
Copy link
Member

homm commented Oct 3, 2017

I'm not familiar with neural network's, but as I understand this is called "overlearning". This is the case when the network shows good results on the test data, but doesn't accept any deviations.

@biaojiang
Copy link

biaojiang commented Nov 20, 2017

I tested that Pillow resize has the same result as Matlab function "resize", The output image size is a little bit difference, maybe Matlab and Pillow use different rounding operation from float to int. But this doesn't matter.
So, it is Skimage and Opencv that have weird resized outputs.

I guess Pillow used an anti-aliasing filter together with down-sampling filter, because by default Matlab will apply an anti-aliasing filter.
I reported this issue to Skimage, their document said it will include anti-aliasing filter in new version.

@ghost
Copy link

ghost commented Mar 12, 2019

Just for completeness, as described at https://stackoverflow.com/questions/21997094/why-opencv-cv2-resize-gives-different-answer-than-matlab-imresize/21998119, Matlab's imresize by default performs antialiasing when downsampling, which is why you see different results.

@wanxinzzz
Copy link

wanxinzzz commented Dec 10, 2019

In case 1

print((b - a).mean())
# 71.1218444444

This is because the numpy array's dtype is uint8

a = np.array([1], dtype='uint8')
b = np.array([2], dtype='uint8')
a - b
# array([255], dtype=uint8)
# 1 - 2 = -1 = 255 in uint8

So, we can transform it to int16

a_i = np.int16(a)
b_i = np.int16(b)
print(np.abs(b_i - a_i).mean())
# 0.028748148148148148
# which is slight difference

@BarCodeReader
Copy link

BarCodeReader commented Aug 14, 2020

I tested that Pillow resize has the same result as Matlab function "resize", The output image size is a little bit difference, maybe Matlab and Pillow use different rounding operation from float to int. But this doesn't matter.
So, it is Skimage and Opencv that have weird resized outputs.

I guess Pillow used an anti-aliasing filter together with down-sampling filter, because by default Matlab will apply an anti-aliasing filter.
I reported this issue to Skimage, their document said it will include anti-aliasing filter in new version.

for me PIL BICUBIC downsampling and MatLab imresize gives different result, though the difference is not very big.
but I believe for those people working on image restoration tasks, this small error is big enough to impact their model accuracy.

image
above image only has PSNR of 57.54...and you can see from the image, the error seems more random instead of having some structure related properties...and I think even with neural network, it may not be able to learn this extra "error"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants