Nhận kích thước hình ảnh KHÔNG tải hình ảnh vào bộ nhớ


113

Tôi hiểu rằng bạn có thể lấy kích thước hình ảnh bằng PIL theo cách sau

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

Tuy nhiên, tôi muốn lấy chiều rộng và chiều cao của hình ảnh mà không cần phải tải hình ảnh vào bộ nhớ. Điều đó có thể không? Tôi chỉ thống kê về kích thước hình ảnh và không quan tâm đến nội dung hình ảnh. Tôi chỉ muốn xử lý nhanh hơn.


8
Tôi không chắc chắn 100% nhưng tôi không tin rằng việc .open()đọc toàn bộ tệp vào bộ nhớ ... (đó là những gì .load()) làm - theo như tôi biết - điều này tốt như nó được sử dụngPIL
Jon Clements

5
Ngay cả khi bạn nghĩ rằng bạn có một chức năng chỉ đọc thông tin tiêu đề hình ảnh, mã đầu đọc hệ thống tệp vẫn có thể tải toàn bộ hình ảnh. Lo lắng về hiệu suất là không hiệu quả trừ khi ứng dụng của bạn yêu cầu.
stark

1
Tôi đã bị thuyết phục về câu trả lời của bạn. Cảm ơn @JonClements và stark
Sami A. Haija

9
Kiểm tra bộ nhớ nhanh bằng cách sử dụng pmapđể theo dõi bộ nhớ được sử dụng bởi một quá trình cho tôi thấy rằng thực sự PILkhông tải toàn bộ hình ảnh trong bộ nhớ.
Vincent Nivoliers

Câu trả lời:


63

Như các ý kiến ​​ám chỉ, PIL không tải hình ảnh vào bộ nhớ khi gọi .open. Nhìn vào tài liệu của PIL 1.1.7, chuỗi tài liệu cho .openbiết:

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

Có một số thao tác tệp trong nguồn như:

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

nhưng những điều này hầu như không cấu thành việc đọc toàn bộ tệp. Trong thực tế, .openchỉ cần trả về một đối tượng tệp và tên tệp khi thành công. Ngoài ra, các tài liệu nói:

mở (tệp, chế độ = ”r”)

Mở và xác định tệp hình ảnh đã cho.

Đây là một hoạt động lười biếng; chức năng này xác định tệp, nhưng dữ liệu hình ảnh thực tế không được đọc từ tệp cho đến khi bạn cố gắng xử lý dữ liệu (hoặc gọi phương thức tải ).

Tìm hiểu sâu hơn, chúng ta thấy rằng .opencác cuộc gọi _openlà một quá tải cụ thể ở định dạng hình ảnh. Mỗi triển khai _opencó thể được tìm thấy trong một tệp mới, ví dụ. tệp .jpeg có trong JpegImagePlugin.py. Hãy xem xét vấn đề đó một cách sâu sắc.

Ở đây mọi thứ dường như trở nên phức tạp một chút, trong đó có một vòng lặp vô hạn bị phá vỡ khi tìm thấy điểm đánh dấu jpeg:

    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")

Có vẻ như nó có thể đọc toàn bộ tệp nếu nó không đúng định dạng. Tuy nhiên, nếu nó đọc đánh dấu thông tin OK, nó sẽ bùng phát sớm. handlerCuối cùng, hàm thiết lập self.sizecác kích thước của hình ảnh.


1
Đúng vậy, nhưng nó openkích thước của hình ảnh hay đó là một thao tác lười biếng? Và nếu nó lười biếng, nó có đọc dữ liệu hình ảnh cùng một lúc không?
Mark Ransom vào

Liên kết tài liệu trỏ đến Gối một ngã ba từ PIL. Tuy nhiên, tôi không thể tìm thấy một liên kết tài liệu chính thức trên web. Nếu ai đó đăng nó dưới dạng bình luận, tôi sẽ cập nhật câu trả lời. Trích dẫn có thể được tìm thấy trong tệp Docs/PIL.Image.html.
Hooked

@MarkRansom Tôi đã cố gắng trả lời câu hỏi của bạn, tuy nhiên để chắc chắn 100%, có vẻ như chúng ta phải đi sâu vào từng cách triển khai hình ảnh cụ thể. Các .jpegđịnh dạng có vẻ OK miễn là tiêu đề được tìm thấy.
Hooked

@Hooked: Cảm ơn rất nhiều vì đã xem xét điều này. Tôi chấp nhận rằng bạn là chính xác mặc dù tôi khá giống như giải pháp khá tối thiểu Paulo dưới đây (mặc dù phải công bằng OP đã không đề cập muốn tránh sự phụ thuộc PIL)
Alex Flint

@AlexFlint Không sao, thật thú vị khi xem mã. Tôi muốn nói rằng Paulo đã kiếm được tiền thưởng của mình, đó là một đoạn trích hay mà anh ấy đã viết cho bạn ở đó.
Hooked

88

Nếu bạn không quan tâm đến nội dung hình ảnh, PIL có thể là một sự quá mức cần thiết.

Tôi đề nghị phân tích cú pháp đầu ra của mô-đun ma thuật python:

>>> 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')

Đây là một trình bao bọc xung quanh libmagic đọc càng ít byte càng tốt để xác định chữ ký loại tệp.

Phiên bản có liên quan của tập lệnh:

https://raw.githubusercontent.com/scardine/image_size/master/get_image_size.py

[cập nhật]

Rất tiếc, thật không may, khi áp dụng cho jpegs, phần trên cho "'Dữ liệu hình ảnh JPEG, tiêu chuẩn EXIF ​​2,21'". Không có kích thước hình ảnh! - Alex Flint

Có vẻ như jpegs có khả năng kháng phép thuật. :-)

Tôi có thể hiểu lý do tại sao: để có được kích thước hình ảnh cho tệp JPEG, bạn có thể phải đọc nhiều byte hơn so với yêu cầu của libmagic.

Cuộn tay áo của tôi và đi kèm với đoạn mã chưa được thử nghiệm này (lấy nó từ GitHub) mà không yêu cầu mô-đun của bên thứ ba.

Nhìn kìa, Mẹ!  Không có điểm!

#-------------------------------------------------------------------------------
# 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

[cập nhật 2019]

Kiểm tra triển khai Rust: https://github.com/scardine/imsz


3
Tôi cũng đã thêm khả năng truy xuất số kênh (không bị nhầm lẫn với độ sâu / bit) trong nhận xét sau phiên bản @EJEHardenberg cung cấp ở trên .
Greg Kramida

2
Điều tuyệt vời. Tôi đã thêm hỗ trợ cho bitmap trong dự án GitHub. Cảm ơn!
Mallard,

2
LƯU Ý: phiên bản hiện tại không hoạt động đối với tôi. @PauloScardine có phiên bản làm việc cập nhật trên github.com/scardine/image_size
DankMasterDan

2
Nhận UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start bytetrên hệ điều hành MacOS, python3 trên data = input.read(25), filetrên hình ảnh choPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom

3
Có vẻ như mã từ raw.githubusercontent.com/scardine/image_size/master/… hoạt động.
mrgloom

24

Có một gói trên pypi có tên imagesizehiện đang hoạt động đối với tôi, mặc dù có vẻ như nó không hoạt động lắm.

Tải về:

pip install imagesize

Sử dụng:

import imagesize

width, height = imagesize.get("test.png")
print(width, height)

Trang chủ: https://github.com/shibukawa/imagesize_py

PyPi: https://pypi.org/project/imagesize/


3
Tôi đã so sánh tốc độ imagesize.get, magic.from_file và hình ảnh PIL để có được kích thước hình ảnh thực tế theo thời gian. Kết quả cho thấy tốc độ imagesize.get (0,019 giây)> PIL (0,104 giây)> ma thuật với regex (0,1699 giây).
RyanLiu ngày

9

Tôi thường tìm nạp kích thước hình ảnh trên Internet. Tất nhiên, bạn không thể tải xuống hình ảnh và sau đó tải nó để phân tích thông tin. Nó quá tốn thời gian. Phương pháp của tôi là nạp các phần vào một vùng chứa hình ảnh và kiểm tra xem nó có thể phân tích cú pháp hình ảnh mỗi lần hay không. Dừng vòng lặp khi tôi nhận được thông tin tôi muốn.

Tôi đã trích xuất lõi mã của mình và sửa đổi nó để phân tích cú pháp các tệp cục bộ.

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)

Đầu ra:

(2240, 1488)
38912

Kích thước tệp thực tế là 1,543,580 byte và bạn chỉ đọc 38,912 byte để có kích thước hình ảnh. Hy vọng điều này sẽ giúp ích.


1

Một cách làm ngắn gọn khác trên hệ thống Unix. Nó phụ thuộc vào đầu ra filemà tôi không chắc đã được chuẩn hóa trên tất cả các hệ thống. Điều này có thể không được sử dụng trong mã sản xuất. Hơn nữa, hầu hết các JPEG không báo cáo kích thước hình ảnh.

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))

Cung cấpIndexError: list index out of range
mrgloom

0

Đây câu trả lời có một độ phân giải tốt, nhưng thiếu PGM định dạng. Đây câu trả lời đã giải quyết được PGM . Và tôi thêm bmp .

Các mã bên dưới

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

imghdrtuy nhiên xử lý các jpegs nhất định khá kém.
martixy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.