I'm finding that in PIL I can load an image from disk substantially more quickly than I can copy it. Is there a faster way to copy an image than by calling image.copy()? (and how is this even possible?)
Sample code:
import os, PIL.Image, timeit
test_filepath = os.path.expanduser("~/Test images/C.jpg")
load_image_cmd = "PIL.Image.open('{}')".format(test_filepath)
print((PIL.Image.open(test_filepath)).__class__)
print(min(timeit.repeat(load_image_cmd, setup='import PIL.Image', number=10000)))
print(min(timeit.repeat("img.copy()", setup='import PIL.Image; img = {}'.format(load_image_cmd), number=10000)))
Produces:
PIL.JpegImagePlugin.JpegImageFile
0.916192054749
1.85366988182
Adding gc.enable to the setup for timeit doesn't change things much.
According to the PIL documentation, open() is a lazy operation, which means that it's not really doing all the work to use the image yet.
To do a copy() however, it almost certainly has to read the whole thing in and process it.
EDIT:
To test whether this is true, you should access a pixel in each image as part of your timeit.
EDIT 2:
Another glance at the doc shows that a load() after the open() ought to do the trick of making it do all its work.
Related
I would like to read several png images by utilizing the ThreadPoolExecutor and cv2.imread.
Problem is that I don't know where to place cv2.IMREAD_UNCHANGED tag/argument to preserve alpha channel (transparency).
The following code works but alpha channel is lost. Where should I place the cv2.IMREAD_UNCHANGED argument?
import cv2
import concurrent.futures
images=["pic1.png", "pic2.png", "pic3.png"]
images_list=[]
with concurrent.futures.ThreadPoolExecutor() as executor:
images_list=list(executor.map(cv2.imread,images))
For example, the following return an error:
SystemError: <built-in function imread> returned NULL without setting an error
import cv2
import concurrent.futures
images=["pic1.png", "pic2.png", "pic3.png"]
images_list=[]
with concurrent.futures.ThreadPoolExecutor() as executor:
images_list=list(executor.map(cv2.imread(images,cv2.IMREAD_UNCHANGED)))
Use a lambda that accepts one argument img and pass the argument to the imread function along with the cv2.IMREAD_UNCHANGED.
import cv2
import concurrent.futures
images=["pic1.png", "pic2.png", "pic3.png"]
images_list=[]
with concurrent.futures.ThreadPoolExecutor() as executor:
images_list=list(executor.map(lambda img: cv2.imread(img, cv2.IMREAD_UNCHANGED),images))
One way of doing this is using functools.partial() which you can consider to be a function with its parameters "partially pre-filled":
#!/usr/bin/env python3
import cv2
import glob
from functools import partial
from multiprocessing.pool import ThreadPool
# List of image names
imageNames=glob.glob("*.png")
# Define a partially complete function where some parameters are pre-filled
loader = partial(cv2.imread, flags=cv2.IMREAD_UNCHANGED)
with ThreadPool() as pool:
images = list(pool.map(loader, imageNames))
Note that, in general, especially with images which tend to take a lot of memory, it is a poor idea to load large numbers of images all at the same time into lists in order to process them because you create exceptional strain on the memory of your computer.
So, say you want to identify exceptionally dark or light images, or images with lots of red in them, it is better to run a bunch of threads that each load one image, process it and then move to the next image than to accumulate all images in memory before processing them.
I'm trying to use opencv to open an image size 4864 x 382565 and it is bigger than CV_IO_MAX_IMAGE_PIXELS limitation which is 2^30 pixels.
img = cv2.cvtColor(cv2.imread(path),cv2.COLOR_BGR2GRAY)
You can do the trick of calling set CV_IO_MAX_IMAGE_PIXELS=18500000000 from the shell before running python script to bypass this check, but I wonder is there a better solution?
Thanks
I think I found the solution
os.environ["OPENCV_IO_MAX_IMAGE_PIXELS"] = pow(2,40).__str__()
import cv2 # import after setting OPENCV_IO_MAX_IMAGE_PIXELS
This will change the limitation to 2^40
Just remember to import opencv AFTER setting the environment variable, otherwise it wouldn't work
I'm struggling with a memory issue on Heroku when running a Django application (with gunicorn).
I have the following code that takes a user-uploaded image, removes all EXIF data, and returns the image ready for it to be uploaded to S3. This is used both as a form data cleaner and when reading base64 data into memory.
def sanitise_image(img): # img is InMemoryUploadedFile
try:
image = Image.open(img)
except IOError:
return None
# Move all pixel data into a new PIL image
data = list(image.getdata())
image_without_exif = Image.new(image.mode, image.size)
image_without_exif.putdata(data)
# Create new file with image_without_exif instead of input image.
thumb_io = StringIO.StringIO()
image_without_exif.save(thumb_io, format=image.format)
io_len = thumb_io.len
thumb_file = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type,
io_len, None)
# DEL AND CLOSE EVERYTHING
thumb_file.seek(0)
img.close()
del img
thumb_io.close()
image_without_exif.close()
del image_without_exif
image.close()
del image
return thumb_file
I basically take an InMemoryUploadedFile and return a new one with just the pixel data.
del and closes may be redundant, but they represent my attempt to fix the situation where Heroku memory usage keeps growing and is not released every time this function terminates, even remaining overnight:
Running this on localhost with Guppy and following the tutorial, there are no remaining InMemoryUploadedFiles nor StringIOs nor PIL Image left in the heap, leaving me puzzled.
My suspicion is Python does not release the memory back to the OS, as I've read in multiple threads on SO. Has anyone played around with InMemoryUploadedFile and can give me an explanation as to why this memory is not being released?
When I do not perform this sanitisation, the issue does not occur.
Thanks a lot!
I think the issue is creating the temporary list object:
data = list(image.getdata())
Try:
image_without_exif.putdata(image.getdata())
This is why I think that is the issue:
>>> images = [Image.new('RGBA', (100, 100)) for _ in range(100)]
Python memory usage increased ~4Mb.
>>> get_datas = [image.getdata() for image in images]
No memory increase.
>>> pixel_lists = [list(image.getdata()) for image in images]
Python memory usage increased by ~85Mb.
You probably don't want to make getdata() into a list unless you need the numbers explicitly. From the Pillow docs:
Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for printing), use list(im.getdata()).
I found my own answer eventually. Huge thanks to Ryan Tran for pointing me in the right direction. list() does indeed cause the leak.
Using the equivalent split() and merge() method (docs) this is the updated code:
with Image.open(img) as image:
comp = image.split()
image_without_exif = Image.merge(image.mode, comp)
thumb_io = StringIO.StringIO()
image_without_exif.save(thumb_io, format=image.format)
io_len = thumb_io.len
clean_img = InMemoryUploadedFile(thumb_io, None, strip_tags(img.name), img.content_type,
io_len, None)
clean_img.seek(0)
return clean_img
I have the following minimal code that gets the bytes from an image:
import Image
im = Image.open("kitten.png")
im_data = [pix for pixdata in im.getdata() for pix in pixdata]
This is rather slow (I have gigabytes of images to process) so how could this be sped up? I'm also unfamiliar with what exactly that code is trying to do. All my data is 1280 x 960 x 8-bit RGB, so I can ignore corner cases, etc.
(FYI, the full code is here - I've already replaced the ImageFile loop with the above Image.open().)
You can try
scipy.ndimage.imread()
If you mean speeding up by algorythamically i can suggest you accessing file with multiple threads simultaneously (only if you don't have a connection between processing sequence)
divide file logically by few sections and access each part simultaneously with threads (you have to put your operation inside a function and call it with threads)
here is a link to tutorial about threading in python
threding in python
I solved my problem, I think:
>>> [pix for pixdata in im.getdata() for pix in pixdata] ==
numpy.ndarray.tolist(numpy.ndarray.flatten(numpy.asarray(im)))
True
This cuts down the runtime by half, and with a bit of bash magic I can run the conversion on the 56 directories in parallel.
I want to do some image processing with OpenCV (in Python), but I have to start with a PIL Image object, so I can't use the cvLoadImage() call, since that takes a filename.
This recipe (adapted from http://opencv.willowgarage.com/wiki/PythonInterface) does not work because cvSetData complains argument 2 of type 'void *' . Any ideas?
from opencv.cv import *
from PIL import Image
pi = Image.open('foo.png') # PIL image
ci = cvCreateImage(pi.size, IPL_DEPTH_8U, 1) # OpenCV image
data = pi.tostring()
cvSetData(ci, data, len(data))
I think the last argument to the cvSetData is wrong too, but I am not sure what it should be.
The example you tried to adapt is for the new python interface for OpenCV 2.0. This is probably the source of the confusion between the prefixed and non-prefixed function names (cv.cvSetData() versus cv.SetData()).
OpenCV 2.0 now ships with two sets of python bindings:
The "old-style" python wrapper, a python package with the opencv.{cv,highgui,ml} modules
The new interface, a python C extension (cv.pyd), which wraps all the OpenCV functionalities (including the highgui and ml modules.)
The reason behind the error message is that the SWIG wrapper does not handle conversion from a python string to a plain-old C buffer. However, the SWIG wrapper comes with the opencv.adaptors module, which is designed to support conversions from numpy and PIL images to OpenCV.
The following (tested) code should solve your original problem (conversion from PIL to OpenCV), using the SWIG interface :
# PIL to OpenCV using the SWIG wrapper
from opencv import cv, adaptors, highgui
import PIL
pil_img = PIL.Image.open(filename)
cv_img = adaptors.PIL2Ipl(pil_img)
highgui.cvNamedWindow("pil2ipl")
highgui.cvShowImage("pil2ipl", cv_img)
However, this does not solve the fact that the cv.cvSetData() function will always fail (with the current SWIG wrapper implementation).
You could then use the new-style wrapper, which allows you to use the cv.SetData() function as you would expect :
# PIL to OpenCV using the new wrapper
import cv
import PIL
pil_img = PIL.Image.open(filename)
cv_img = cv.CreateImageHeader(pil_img.size, cv.IPL_DEPTH_8U, 3) # RGB image
cv.SetData(cv_img, pil_img.tostring(), pil_img.size[0]*3)
cv.NamedWindow("pil2ipl")
cv.ShowImage("pil2ipl", cv_img)
A third approach would be to switch your OpenCV python interface to the ctypes-based wrapper. It comes with utility functions for explicit data conversion between e.g. python strings and C buffers. A quick look on google code search seems to indicate that this is a working method.
Concerning the third parameter of the cvSetData() function, size of the image buffer, but the image step. The step is the number of bytes in one row of your image, which is pixel_depth * number_of_channels * image_width. The pixel_depth parameter is the size in bytes of the data associated to one channel. In your example, it would be simply the image width (only one channel, one byte per pixel).
It's really confusing to have both swig and new python binding. For example, in the OpenCV 2.0, cmake can accept both BUILD_SWIG_PYTHON_SUPPORT and BUILD_NEW_PYTHON_SUPPORT. But anyway, I kinda figured out most pitfalls.
In the case of using "import cv" (the new python binding), one more step is needed.
cv.SetData(cv_img, pil_img.tostring(), pil_img.size[0]*3)
cv.CvtColor(cv_img, cv_img, cv.CV_RGB2BGR)
The conversion is necessary for RGB images because the sequence is different in PIL and IplImage. The same applies to Ipl to PIL.
But if you use opencv.adaptors, it's already taken care of. You can look into the details in adaptors.py if interested.
I did this using the python2.6 bindings of OpenCV2.1:
...
cv_img = cv.CreateImageHeader(img.size, cv.IPL_DEPTH_8U, 3)
cv.SetData(cv_img, img.rotate(180).tostring()[::-1])
...
The image rotation and reversion of the string is to swap RGB into BGR, that is used in OpenCV video encoding. I assume that this would also be necessary for any other use of an image converted from PIL to OpenCV.
I'm not an expert but I managed to get a opencv image from a PIL image with this code:
import opencv
img = opencv.adaptors.PIL2Ipl(pilimg)