Get image size WITHOUT loading an image into memory

I understand that you can get the image size using PIL as follows

from PIL import Image im = Image.open(image_filename) width, height = im.size 

However, I would like to get the width and height of the image without having to load the image into memory. Is it possible? I just do statistics on image sizes and don't care about the content of the image. I just want to make my processing faster.

+48
python image image-processing
Apr 04 '13 at 0:42
source share
5 answers

As noted by comments, PIL does not load the image into memory when .open called. Looking at the PIL 1.1.7 docs, the docstring for .open says:

 def open(fp, mode="r"): "Open an image file, without loading the raster data" 

There are several file operations in the source, for example:

  ... prefix = fp.read(16) ... fp.seek(0) ... 

but they hardly constitute reading the entire file. In fact .open simply returns the file object and file name on success. In addition, docs say:

open (file, mode = "r")

Opens and identifies the given image file.

This is a lazy operation; this function identifies the file, but the actual image data is not read from the file until you try to process the data (or call the load method).

Digging deeper, we see that .open calls _open , which is a specific overload in image format. Each of the _open implementations can be found in a new file, for example JpegImagePlugin.py are in JpegImagePlugin.py . Let's look at it in depth.

Here, it seems a bit complicated, it has an infinite loop that breaks from when the jpeg marker is found:

  while True: s = s + self.fp.read(1) i = i16(s) if i in MARKER: name, description, handler = MARKER[i] # print hex(i), name, description if handler is not None: handler(self, i) if i == 0xFFDA: # start of scan rawmode = self.mode if self.mode == "CMYK": rawmode = "CMYK;I" # assume adobe conventions self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))] # self.__offset = self.fp.tell() break s = self.fp.read(1) elif i == 0 or i == 65535: # padded marker or junk; move on s = "\xff" else: raise SyntaxError("no marker found") 

It looks like he could read the whole file if it was garbled. If he reads the information marker OK, he should exit earlier. The handler function ultimately sets self.size , which are the dimensions of the image.

+31
Sep 26 '13 at 17:37
source share

If you don't care about the content of the image, PIL is probably redundant.

I suggest parsing the output of the python magic module:

 >>> t = magic.from_file('teste.png') >>> t 'PNG image data, 782 x 602, 8-bit/color RGBA, non-interlaced' >>> re.search('(\d+) x (\d+)', t).groups() ('782', '602') 

This is a libmagic shell that reads as few bytes as possible to identify a file type signature.

[update]

Hmm, unfortunately, when applied to jpegs, the above gives "JPEG image data, EXIF ​​2.21 standard." No picture! - Alex Flint

It seems that jpegs have a mask. :-)

I can understand why: to get image sizes for JPEG files, you may need to read more bytes than you like libmagic.

I rolled up my sleeves and came up with this very unverified fragment (get it from GitHub) , which does not require third-party modules.

Look, Ma! No deps!

 #------------------------------------------------------------------------------- # Name: get_image_size # Purpose: extract image dimensions given a file path using just # core modules # # Author: Paulo Scardine (based on code from Emmanuel VAÏSSE) # # Created: 26/09/2013 # Copyright: (c) Paulo Scardine 2013 # Licence: MIT #------------------------------------------------------------------------------- #!/usr/bin/env python import os import struct class UnknownImageFormat(Exception): pass def get_image_size(file_path): """ Return (width, height) for a given img file content - no external dependencies except the os and struct modules from core """ size = os.path.getsize(file_path) with open(file_path) as input: height = -1 width = -1 data = input.read(25) if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'): # GIFs w, h = struct.unpack("<HH", data[6:10]) width = int(w) height = int(h) elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n') and (data[12:16] == 'IHDR')): # PNGs w, h = struct.unpack(">LL", data[16:24]) width = int(w) height = int(h) elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'): # older PNGs? w, h = struct.unpack(">LL", data[8:16]) width = int(w) height = int(h) elif (size >= 2) and data.startswith('\377\330'): # JPEG msg = " raised while trying to decode as JPEG." input.seek(0) input.read(2) b = input.read(1) try: while (b and ord(b) != 0xDA): while (ord(b) != 0xFF): b = input.read(1) while (ord(b) == 0xFF): b = input.read(1) if (ord(b) >= 0xC0 and ord(b) <= 0xC3): input.read(3) h, w = struct.unpack(">HH", input.read(4)) break else: input.read(int(struct.unpack(">H", input.read(2))[0])-2) b = input.read(1) width = int(w) height = int(h) except struct.error: raise UnknownImageFormat("StructError" + msg) except ValueError: raise UnknownImageFormat("ValueError" + msg) except Exception as e: raise UnknownImageFormat(e.__class__.__name__ + msg) else: raise UnknownImageFormat( "Sorry, don't know how to get information from this file." ) return width, height 
+48
Sep 26 '13 at 18:06 on
source share

I often get image sizes online. Of course, you cannot upload an image and then upload it to analyze the information. This is too much time. My method is to feed the pieces into an image container and check if it can analyze the image every time. Stop the loop when I get the information I need.

I extracted the core of my code and modified it to analyze local files.

 from PIL import ImageFile ImPar=ImageFile.Parser() with open(r"D:\testpic\test.jpg", "rb") as f: ImPar=ImageFile.Parser() chunk = f.read(2048) count=2048 while chunk != "": ImPar.feed(chunk) if ImPar.image: break chunk = f.read(2048) count+=2048 print(ImPar.image.size) print(count) 

Output:

 (2240, 1488) 38912 

The actual file size is 1,543,580 bytes, and you only get 38,912 bytes to get the image size. Hope this helps.

+1
Feb 04 '17 at 19:02
source share

This answer has another good resolution, but the pgm format is missing. This answer allowed pgm. And I add bmp.

Cheats below

 import struct, imghdr, re, magic def get_image_size(fname): '''Determine the image type of fhandle and return its size. from draco''' with open(fname, 'rb') as fhandle: head = fhandle.read(32) if len(head) != 32: return if imghdr.what(fname) == 'png': check = struct.unpack('>i', head[4:8])[0] if check != 0x0d0a1a0a: return width, height = struct.unpack('>ii', head[16:24]) elif imghdr.what(fname) == 'gif': width, height = struct.unpack('<HH', head[6:10]) elif imghdr.what(fname) == 'jpeg': try: fhandle.seek(0) # Read 0xff next size = 2 ftype = 0 while not 0xc0 <= ftype <= 0xcf: fhandle.seek(size, 1) byte = fhandle.read(1) while ord(byte) == 0xff: byte = fhandle.read(1) ftype = ord(byte) size = struct.unpack('>H', fhandle.read(2))[0] - 2 # We are at a SOFn block fhandle.seek(1, 1) # Skip `precision' byte. height, width = struct.unpack('>HH', fhandle.read(4)) except Exception: #IGNORE:W0703 return elif imghdr.what(fname) == 'pgm': header, width, height, maxval = re.search( b"(^P5\s(?:\s*#.*[\r\n])*" b"(\d+)\s(?:\s*#.*[\r\n])*" b"(\d+)\s(?:\s*#.*[\r\n])*" b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", head).groups() width = int(width) height = int(height) elif imghdr.what(fname) == 'bmp': _, width, height, depth = re.search( b"((\d+)\sx\s" b"(\d+)\sx\s" b"(\d+))", str).groups() width = int(width) height = int(height) else: return return width, height 
0
Sep 24 '15 at 5:20
source share

Another short way to do this on Unix systems. It depends on the output of file , which I'm not sure is standardized on all systems. This should probably not be used in production code. Moreover, most JPEGs do not report image size.

 import subprocess, re image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1])) 
0
Jun 13 '17 at 9:08 on
source share



All Articles