รับขนาดภาพโดยไม่ต้องโหลดภาพลงในหน่วยความจำ


115

ฉันเข้าใจว่าคุณสามารถรับขนาดภาพโดยใช้ PIL ได้ในลักษณะต่อไปนี้

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

อย่างไรก็ตามฉันต้องการรับความกว้างและความสูงของภาพโดยไม่ต้องโหลดภาพในหน่วยความจำ เป็นไปได้หรือไม่ ฉันทำสถิติเกี่ยวกับขนาดภาพเท่านั้นและไม่สนใจเนื้อหาของภาพ ฉันแค่ต้องการทำให้การประมวลผลเร็วขึ้น


8
ฉันไม่แน่ใจ 100% แต่ฉันไม่เชื่อว่าการ.open()อ่านไฟล์ทั้งหมดลงในหน่วยความจำ ... (นั่นคือสิ่งที่.load()) ทำ - เท่าที่ฉันรู้ - นี่ดีพอ ๆ กับการใช้งานPIL
Jon Clements

5
แม้ว่าคุณจะคิดว่าคุณมีฟังก์ชั่นที่อ่านเฉพาะข้อมูลส่วนหัวของภาพ แต่รหัส readahead ของระบบไฟล์อาจยังโหลดภาพทั้งหมดได้ การกังวลเกี่ยวกับประสิทธิภาพจะไม่เกิดผลเว้นแต่แอปพลิเคชันของคุณจะต้องใช้
สิ้นเชิง

1
ฉันมั่นใจในคำตอบของคุณ ขอบคุณ @JonClements และสิ้นเชิง
Sami A. Haija

9
การทดสอบหน่วยความจำอย่างรวดเร็วโดยใช้pmapเพื่อตรวจสอบหน่วยความจำที่ใช้โดยกระบวนการแสดงให้ฉันเห็นว่าPILไม่โหลดภาพทั้งหมดในหน่วยความจำ
Vincent Nivoliers

ดูเพิ่มเติม: รับขนาดภาพด้วย Python
Martin Thoma

คำตอบ:


63

ในฐานะที่เป็นการแสดงความคิดเห็นพาดพิง, PIL .openไม่ได้โหลดภาพลงในหน่วยความจำเมื่อเรียก ดูเอกสารของPIL 1.1.7docstring สำหรับ.openพูดว่า:

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

มีการดำเนินการกับไฟล์บางส่วนในซอร์สเช่น:

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

แต่สิ่งเหล่านี้แทบจะไม่ถือเป็นการอ่านทั้งไฟล์ ในความเป็นจริง.openเพียงแค่ส่งคืนวัตถุไฟล์และชื่อไฟล์ที่ประสบความสำเร็จ นอกจากนี้เอกสารยังกล่าวว่า:

เปิด (ไฟล์โหมด =” r”)

เปิดและระบุไฟล์ภาพที่กำหนด

นี่เป็นการดำเนินการที่ขี้เกียจ ฟังก์ชันนี้ระบุไฟล์ แต่ข้อมูลรูปภาพจริงจะไม่ถูกอ่านจากไฟล์จนกว่าคุณจะพยายามประมวลผลข้อมูล (หรือเรียกวิธีการโหลด )

เมื่อเจาะลึกลงไปเราจะเห็นว่าการ.openโทร_openซึ่งเป็นการโอเวอร์โหลดเฉพาะรูปแบบรูปภาพ การใช้งานแต่ละรายการ_openสามารถพบได้ในไฟล์ใหม่เช่น ไฟล์. jpeg อยู่ในรูปแบบJpegImagePlugin.py. มาดูกันในเชิงลึก

ที่นี่ดูเหมือนจะยุ่งยากเล็กน้อยในนั้นมีลูปที่ไม่มีที่สิ้นสุดซึ่งแตกออกเมื่อพบเครื่องหมาย 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")

ซึ่งดูเหมือนว่ามันจะอ่านทั้งไฟล์ได้หากมันผิดรูปแบบ อย่างไรก็ตามหากอ่านเครื่องหมายข้อมูลตกลงก็ควรแยกออกก่อน handlerในท้ายที่สุดฟังก์ชันจะกำหนดself.sizeขนาดของรูปภาพ


1
จริงพอ แต่openได้ขนาดของภาพหรือว่าการดำเนินการที่ขี้เกียจเกินไป? แล้วถ้าขี้เกียจมันอ่านข้อมูลภาพพร้อมกันหรือเปล่า?
Mark Ransom

ลิงก์เอกสารชี้ไปที่หมอนส้อมจาก PIL อย่างไรก็ตามฉันไม่พบลิงก์เอกสารอย่างเป็นทางการบนเว็บ หากมีคนโพสต์เป็นความคิดเห็นฉันจะอัปเดตคำตอบ Docs/PIL.Image.htmlอ้างสามารถพบได้ในไฟล์
Hooked

@MarkRansom ฉันพยายามตอบคำถามของคุณแล้วอย่างไรก็ตามเพื่อให้แน่ใจ 100% ดูเหมือนว่าเราต้องดำน้ำในการใช้งานเฉพาะรูปภาพแต่ละรูปแบบ .jpegรูปแบบลักษณะตกลงตราบเท่าที่ส่วนหัวที่พบ
ติดยาเสพติด

@ ฮุค: ขอบคุณมากที่ดูเรื่องนี้ ฉันยอมรับว่าคุณถูกต้องแม้ว่าฉันจะชอบวิธีแก้ปัญหาที่ค่อนข้างน้อยที่สุดของ Paulo ด้านล่าง (แม้ว่าจะยุติธรรม แต่ OP ไม่ได้พูดถึงว่าต้องการหลีกเลี่ยงการพึ่งพา PIL)
Alex Flint

@AlexFlint ไม่มีปัญหามันสนุกเสมอที่จะแหย่รหัส ฉันจะบอกว่า Paulo ได้รับค่าตอบแทนจากเขานั่นเป็นตัวอย่างที่ดีที่เขาเขียนให้คุณที่นั่น
ติดยาเสพติด

88

หากคุณไม่สนใจเกี่ยวกับเนื้อหาของรูปภาพ PIL น่าจะเป็นโอเวอร์คิล

ฉันขอแนะนำให้แยกวิเคราะห์ผลลัพธ์ของโมดูล python magic:

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

นี่คือ wrapper รอบ libmagic ซึ่งอ่านไบต์น้อยที่สุดเพื่อระบุลายเซ็นประเภทไฟล์

เวอร์ชันที่เกี่ยวข้องของสคริปต์:

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

[อัปเดต]

อืมน่าเสียดายที่เมื่อนำไปใช้กับ jpegs ด้านบนจะให้ "ข้อมูลภาพ JPEG มาตรฐาน EXIF ​​2.21" ไม่มีขนาดภาพ! - อเล็กซ์ฟลินท์

ดูเหมือนว่า jpegs จะทนทานต่อเวทมนตร์ :-)

ฉันเห็นสาเหตุ: เพื่อให้ได้ขนาดภาพสำหรับไฟล์ JPEG คุณอาจต้องอ่านไบต์มากกว่าที่ libmagic ชอบอ่าน

พับแขนเสื้อของฉันขึ้นและมาพร้อมกับตัวอย่างข้อมูลที่ยังไม่ผ่านการทดสอบ (รับจาก GitHub)ที่ไม่ต้องใช้โมดูลของบุคคลที่สาม

ดูมะ!  ไม่มี 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

[อัปเดต 2019]

ตรวจสอบการใช้งาน Rust: https://github.com/scardine/imsz


3
ฉันยังเพิ่มความสามารถในการดึงจำนวนช่อง (เพื่อไม่ให้สับสน w / ลึกบิต) ในความคิดเห็นหลังจากที่รุ่น @EJEHardenberg ให้ข้างต้น
Greg Kramida

2
สิ่งที่ดี. ฉันเพิ่มการรองรับบิตแมปในโครงการ GitHub ขอบคุณ!
Mallard

2
หมายเหตุ: เวอร์ชันปัจจุบันใช้ไม่ได้สำหรับฉัน @PauloScardine มีการอัปเดตเวอร์ชันการทำงานบนgithub.com/scardine/image_size
DankMasterDan

2
รับUnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteบน MacOS, python3 บนdata = input.read(25), fileบนภาพให้PNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom

3
ดูเหมือนว่ารหัสจากraw.githubusercontent.com/scardine/image_size/master/…ใช้งานได้
mrgloom

25

มีแพ็คเกจใน pypi ที่เรียกimagesizeว่าปัจจุบันใช้งานได้สำหรับฉันแม้ว่ามันจะดูไม่ได้ใช้งานมากนัก

ติดตั้ง:

pip install imagesize

การใช้งาน:

import imagesize

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

โฮมเพจ: https://github.com/shibukawa/imagesize_py

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


3
ฉันเปรียบเทียบความเร็ว imagesize.get, magic.from_file และรูปภาพ PIL เพื่อให้ได้ขนาดภาพที่แท้จริงตามเวลา ผลการวิจัยพบว่า speed imagesize.get (0.019s)> PIL (0.104s)> magic with regex (0.1699s)
RyanLiu

9

ฉันมักจะดึงข้อมูลขนาดรูปภาพทางอินเทอร์เน็ต แน่นอนว่าคุณไม่สามารถดาวน์โหลดภาพแล้วโหลดเพื่อแยกวิเคราะห์ข้อมูลได้ มันเสียเวลาเกินไป วิธีของฉันคือป้อนชิ้นส่วนไปยังคอนเทนเนอร์รูปภาพและทดสอบว่าสามารถแยกวิเคราะห์รูปภาพได้ทุกครั้งหรือไม่ หยุดการวนซ้ำเมื่อฉันได้รับข้อมูลที่ต้องการ

ฉันแตกคอร์ของโค้ดและแก้ไขเพื่อแยกวิเคราะห์ไฟล์ในเครื่อง

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)

เอาท์พุต:

(2240, 1488)
38912

ขนาดไฟล์จริงคือ 1,543,580 ไบต์และคุณอ่านเพียง 38,912 ไบต์เพื่อรับขนาดภาพ หวังว่านี่จะช่วยได้


1

อีกวิธีสั้น ๆ ในการทำบนระบบ Unix มันขึ้นอยู่กับผลลัพธ์fileที่ฉันไม่แน่ใจว่าเป็นมาตรฐานในทุกระบบ ซึ่งไม่ควรใช้ในรหัสการผลิต ยิ่งไปกว่านั้น JPEG ส่วนใหญ่ไม่รายงานขนาดของภาพ

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

ให้IndexError: list index out of range
mrgloom

0

คำตอบนี้มีความละเอียดที่ดี แต่ไม่มีรูปแบบpgm คำตอบนี้ได้แก้ไขPGM และฉันจะเพิ่มBMP

รหัสอยู่ด้านล่าง

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

imghdrอย่างไรก็ตามจัดการกับ jpegs บางตัวได้ค่อนข้างแย่
martixy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.