Dapatkan ukuran gambar TANPA memuat gambar ke memori

113

Saya memahami bahwa Anda bisa mendapatkan ukuran gambar menggunakan PIL dengan cara berikut

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

Namun demikian, saya ingin mendapatkan lebar dan tinggi gambar tanpa harus memuat gambar di memori. Apakah itu mungkin? Saya hanya melakukan statistik tentang ukuran gambar dan tidak peduli dengan konten gambar. Saya hanya ingin mempercepat proses saya.

Sami A. Haija
sumber
8
Saya tidak 100% yakin tetapi saya tidak percaya bahwa .open()membaca seluruh file ke dalam memori ... (itulah yang .load()) dilakukan - sejauh yang saya tahu - ini sebagus yang digunakanPIL
Jon Clements
5
Bahkan jika Anda merasa memiliki fungsi yang hanya membaca informasi header gambar, kode readahead sistem file mungkin masih memuat seluruh gambar. Khawatir tentang kinerja tidak produktif kecuali jika aplikasi Anda membutuhkannya.
mencolok
1
Saya menjadi yakin dengan jawaban Anda. Terima kasih @JonClements dan tegas
Sami A. Haija
9
Pengujian memori cepat yang digunakan pmapuntuk memantau memori yang digunakan oleh suatu proses menunjukkan kepada saya bahwa memang PILtidak memuat seluruh gambar dalam memori.
Vincent Nivoliers
Lihat juga: Dapatkan dimensi gambar dengan Python
Martin Thoma

Jawaban:

63

Seperti yang disinggung dalam komentar, PIL tidak memuat gambar ke dalam memori saat memanggil .open. Melihat dokumen dari PIL 1.1.7, docstring untuk .openmengatakan:

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

Ada beberapa operasi file di sumber seperti:

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

tapi ini bukan berarti membaca keseluruhan file. Sebenarnya .openhanya mengembalikan objek file dan nama file pada kesuksesan. Selain itu, dokumen tersebut mengatakan:

buka (file, mode = ”r”)

Membuka dan mengidentifikasi file gambar yang diberikan.

Ini adalah operasi yang malas; fungsi ini mengidentifikasi file, tetapi data gambar yang sebenarnya tidak dibaca dari file sampai Anda mencoba memproses data (atau memanggil metode pemuatan ).

Menggali lebih dalam, kita melihat bahwa .openpanggilan _openyang merupakan kelebihan beban khusus format gambar. Setiap implementasi _opendapat ditemukan di file baru, mis. File .jpeg ada dalam JpegImagePlugin.py. Mari kita lihat secara mendalam.

Di sini hal-hal tampaknya menjadi sedikit rumit, di dalamnya ada loop tak terbatas yang rusak saat penanda jpeg ditemukan:

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

Yang sepertinya bisa membaca seluruh file jika formatnya salah. Jika membaca penanda info OK, bagaimanapun, itu harus keluar lebih awal. Fungsi handlerakhirnya menetapkan self.sizedimensi gambar.

Doyan
sumber
1
Cukup benar, tetapi apakah openmendapatkan ukuran gambar atau apakah itu operasi yang malas juga? Dan jika malas, apakah ia membaca data gambar pada saat yang sama?
Mark Ransom
Tautan dokumen mengarah ke Pillow a fork dari PIL. Namun saya tidak dapat menemukan tautan dokumen resmi di web. Jika seseorang mempostingnya sebagai komentar, saya akan memperbarui jawabannya. Kutipan dapat ditemukan di file Docs/PIL.Image.html.
Ketagihan
@MarkRansom Saya telah mencoba menjawab pertanyaan Anda, namun untuk 100% yakin sepertinya kita harus menyelami setiap implementasi khusus gambar. The .jpegFormat terlihat OK selama header ditemukan.
Ketagihan
@Hooked: Terima kasih banyak telah melihat ini. Saya menerima bahwa Anda benar meskipun saya sangat menyukai solusi yang agak minim dari Paulo di bawah ini (meskipun agar adil, OP tidak menyebutkan keinginan untuk menghindari ketergantungan PIL)
Alex Flint
@AlexFlint Tidak masalah, selalu menyenangkan untuk melihat-lihat kode. Saya akan mengatakan bahwa Paulo mendapatkan hadiahnya, itu cuplikan bagus yang dia tulis untuk Anda di sana.
Ketagihan
88

Jika Anda tidak peduli dengan konten gambar, PIL mungkin berlebihan.

Saya menyarankan untuk mengurai output dari modul ajaib 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')

Ini adalah pembungkus libmagic yang membaca sesedikit mungkin byte untuk mengidentifikasi tanda tangan tipe file.

Versi skrip yang relevan:

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

[memperbarui]

Hmmm, sayangnya, ketika diterapkan ke jpegs, di atas memberikan "'data gambar JPEG, standar EXIF ​​2.21'". Tidak ada ukuran gambar! - Alex Flint

Sepertinya jpeg tahan sihir. :-)

Saya bisa mengerti alasannya: untuk mendapatkan dimensi gambar untuk file JPEG, Anda mungkin harus membaca lebih banyak byte daripada libmagic yang suka membaca.

Menyingsingkan lengan baju saya dan datang dengan cuplikan yang sangat belum teruji ini (dapatkan dari GitHub) yang tidak memerlukan modul pihak ketiga.

Lihat, Bu!  Tidak ada 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

[perbarui 2019]

Lihat implementasi Rust: https://github.com/scardine/imsz

Paulo Scardine
sumber
3
Saya juga menambahkan kemampuan untuk mengambil jumlah saluran (jangan bingung dengan kedalaman bit) di komentar setelah versi yang disediakan @EJEHardenberg di atas .
Greg Kramida
2
Hebat. Saya menambahkan dukungan untuk bitmap di proyek GitHub. Terima kasih!
Mallard
2
CATATAN: versi saat ini tidak berfungsi untuk saya. @PauloScardine memiliki versi kerja yang diperbarui di github.com/scardine/image_size
DankMasterDan
2
Dapatkan UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start bytedi MacOS, python3 aktif data = input.read(25), filepada gambar memberiPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom
3
Sepertinya kode dari raw.githubusercontent.com/scardine/image_size/master/… bekerja.
mrgloom
24

Ada sebuah paket di pypi bernama imagesizeyang saat ini berfungsi untuk saya, meskipun sepertinya tidak sangat aktif.

Install:

pip install imagesize

Pemakaian:

import imagesize

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

Beranda: https://github.com/shibukawa/imagesize_py

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

Jonathan
sumber
3
Saya membandingkan kecepatan gambar imagesize.get, magic.from_file, dan gambar PIL untuk mendapatkan ukuran gambar sebenarnya berdasarkan waktu. Hasil penelitian menunjukkan bahwa kecepatan imagesize.get (0.019s)> PIL (0.104s)> magic dengan regex (0.1699s).
RyanLiu
9

Saya sering mengambil ukuran gambar di Internet. Tentu saja, Anda tidak dapat mengunduh gambar dan memuatnya untuk mengurai informasi. Itu terlalu memakan waktu. Metode saya adalah memberi makan potongan ke wadah gambar dan menguji apakah itu dapat mengurai gambar setiap saat. Hentikan pengulangan ketika saya mendapatkan informasi yang saya inginkan.

Saya mengekstrak inti kode saya dan memodifikasinya untuk mengurai file lokal.

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)

Keluaran:

(2240, 1488)
38912

Ukuran file sebenarnya adalah 1.543.580 byte dan Anda hanya membaca 38.912 byte untuk mendapatkan ukuran gambar. Semoga ini bisa membantu.

lovetl2002
sumber
1

Cara singkat lain untuk melakukannya di sistem Unix. Itu tergantung pada output fileyang saya tidak yakin telah distandarisasi pada semua sistem. Ini mungkin tidak boleh digunakan dalam kode produksi. Selain itu, kebanyakan JPEG tidak melaporkan ukuran gambar.

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))
Lenar Hoyt
sumber
MemberikanIndexError: list index out of range
mrgloom
0

Jawaban ini memiliki resolusi bagus lainnya, tetapi format pgm tidak ada . Jawaban ini telah menyelesaikan pgm . Dan saya menambahkan bmp .

Kode di bawah

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
Yantao Xie
sumber
imghdrnamun menangani jpeg tertentu dengan sangat buruk.
martixy