【问题标题】:How to work with HEIC image file types in Python如何在 Python 中使用 HEIC 图像文件类型
【发布时间】:2019-06-21 02:04:16
【问题描述】:

High Efficiency Image File (HEIF) 格式是从 iPhone 向 OSX 设备空投图像时的默认格式。我想用 Python 编辑和修改这些 .HEIC 文件。

我可以修改手机设置以默认保存为 JPG,但这并不能真正解决能够使用其他文件类型的问题。我仍然希望能够处理 HEIC 文件以进行文件转换、提取元数据等 (Example Use Case -- Geocoding)

枕头

这是尝试读取此类文件时使用 Python 3.7 和 Pillow 的结果。

$ ipython
Python 3.7.0 (default, Oct  2 2018, 09:20:07)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from PIL import Image

In [2]: img = Image.open('IMG_2292.HEIC')
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-2-fe47106ce80b> in <module>
----> 1 img = Image.open('IMG_2292.HEIC')

~/.env/py3/lib/python3.7/site-packages/PIL/Image.py in open(fp, mode)
   2685         warnings.warn(message)
   2686     raise IOError("cannot identify image file %r"
-> 2687                   % (filename if filename else fp))
   2688
   2689 #

OSError: cannot identify image file 'IMG_2292.HEIC'

似乎请求了对 python-pillow 的支持 (#2806),但那里存在许可/专利问题阻止它。

ImageMagick + Wand

看来ImageMagick 可能是一个选项。在做了brew install imagemagickpip install wand 之后,我没有成功。

$ ipython
Python 3.7.0 (default, Oct  2 2018, 09:20:07)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from wand.image import Image

In [2]: with Image(filename='img.jpg') as img:
   ...:     print(img.size)
   ...:
(4032, 3024)

In [3]: with Image(filename='img.HEIC') as img:
   ...:     print(img.size)
   ...:
---------------------------------------------------------------------------
MissingDelegateError                      Traceback (most recent call last)
<ipython-input-3-9d6f58c40f95> in <module>
----> 1 with Image(filename='ces2.HEIC') as img:
      2     print(img.size)
      3

~/.env/py3/lib/python3.7/site-packages/wand/image.py in __init__(self, image, blob, file, filename, format, width, height, depth, background, resolution, pseudo)
   4603                     self.read(blob=blob, resolution=resolution)
   4604                 elif filename is not None:
-> 4605                     self.read(filename=filename, resolution=resolution)
   4606                 # clear the wand format, otherwise any subsequent call to
   4607                 # MagickGetImageBlob will silently change the image to this

~/.env/py3/lib/python3.7/site-packages/wand/image.py in read(self, file, filename, blob, resolution)
   4894             r = library.MagickReadImage(self.wand, filename)
   4895         if not r:
-> 4896             self.raise_exception()
   4897
   4898     def save(self, file=None, filename=None):

~/.env/py3/lib/python3.7/site-packages/wand/resource.py in raise_exception(self, stacklevel)
    220             warnings.warn(e, stacklevel=stacklevel + 1)
    221         elif isinstance(e, Exception):
--> 222             raise e
    223
    224     def __enter__(self):

MissingDelegateError: no decode delegate for this image format `HEIC' @ error/constitute.c/ReadImage/556

还有其他可用于以编程方式进行转换的替代方法吗?

【问题讨论】:

标签: python python-3.x heif heic


【解决方案1】:

我面临与您完全相同的问题,需要 CLI 解决方案。做一些进一步的研究,似乎 ImageMagick requires libheif 委托库。 libheif 库本身似乎也有一些依赖关系。

我没有成功让其中任何一个也能正常工作,但会继续尝试。我建议您检查这些依赖项是否可用于您的配置。

【讨论】:

  • 感谢您提供此标记,它将证明很有用。
【解决方案2】:

你们应该看看这个库,它是libheif 库的 Python 3 包装器,它应该可以用于文件转换、提取元数据:

https://github.com/david-poirier-csn/pyheif

https://pypi.org/project/pyheif/

示例用法:

 import io

 import whatimage
 import pyheif
 from PIL import Image


 def decodeImage(bytesIo):

    fmt = whatimage.identify_image(bytesIo)
    if fmt in ['heic', 'avif']:
         i = pyheif.read_heif(bytesIo)

         # Extract metadata etc
         for metadata in i.metadata or []:
             if metadata['type']=='Exif':
                 # do whatever
        
         # Convert to other file format like jpeg
         s = io.BytesIO()
         pi = Image.frombytes(
                mode=i.mode, size=i.size, data=i.data)

         pi.save(s, format="jpeg")

  ...

【讨论】:

  • 我对@9​​87654326@ 的体验是它成功读取了HEIC 文件,但我不明白Image.frombytes() 应该如何或为什么在上面的代码中工作。那不需要PIL理解HEIF吗?无论如何,当我运行它时,我得到的是一个严重损坏的 JPG 文件。
  • read_heif 需要的实际数据,所以该行实际上应该是:pyheif.read_heif(bytesIo.read())
  • 你能举一些do whatever的例子吗? metadata['data'] 这里似乎是 bytes 类型。但是当我尝试:metadata['data'].decode('utf-8')) 时,我看到:UnicodeDecodeError: 'utf-8' codec can't decode byte 0x86 in position 27: invalid start byte
  • fwiw,我尝试在 Windows 上安装 pyheif 并遇到 this 问题。原来pyheif 与 Windows 不兼容。
【解决方案3】:

添加 danial 的答案,我只需要稍微修改字节数组以获得有效的数据流以进行进一步的工作。前 6 个字节是 'Exif\x00\x00' .. 删除这些将为您提供原始格式,您可以将其输入任何图像处理工具。

import pyheif
import PIL
import exifread

def read_heic(path: str):
    with open(path, 'rb') as file:
        image = pyheif.read_heif(file)
        for metadata in image.metadata or []:
            if metadata['type'] == 'Exif':
                fstream = io.BytesIO(metadata['data'][6:])

    # now just convert to jpeg
    pi = PIL.Image.open(fstream)
    pi.save("file.jpg", "JPEG")

    # or do EXIF processing with exifread
    tags = exifread.process_file(fstream)

至少这对我有用。

【讨论】:

  • 使用您的代码,当我传递 HEIC 文件路径时,我得到 PIL.UnidentifiedImageError: cannot identify image file &lt;_io.BytesIO object at 0x109aefef0&gt;
  • 遇到和 Josh 一样的问题,PIL 无法使用此代码识别 HEIC 图像
【解决方案4】:

这样就可以从 heic 文件中获取 exif 数据了

import pyheif
import exifread
import io

heif_file = pyheif.read_heif("file.heic")

for metadata in heif_file.metadata:

    if metadata['type'] == 'Exif':
        fstream = io.BytesIO(metadata['data'][6:])

    exifdata = exifread.process_file(fstream,details=False)

    # example to get device model from heic file
    model = str(exifdata.get("Image Model"))
    print(model)

【讨论】:

    【解决方案5】:

    我使用 Wand 包非常成功: 安装魔杖: https://docs.wand-py.org/en/0.6.4/ 转换代码:

       from wand.image import Image
       import os
    
       SourceFolder="K:/HeicFolder"
       TargetFolder="K:/JpgFolder"
    
       for file in os.listdir(SourceFolder):
          SourceFile=SourceFolder + "/" + file
          TargetFile=TargetFolder + "/" + file.replace(".HEIC",".JPG")
        
          img=Image(filename=SourceFile)
          img.format='jpg'
          img.save(filename=TargetFile)
          img.close()
    

    【讨论】:

    • 似乎 ImageMagick(Wand 使用的低级库)在某些发行版的包管理器中不支持 heic 委托(例如:Centos 8)。
    • 谢谢!这对我有用,因为我无法让 pyheif 在 Windows 10 中工作。
    【解决方案6】:

    查看人们的多次回复后的简单解决方案。
    请在运行此代码之前安装whatimagepyheifPIL 库。


    [注意] : 我使用此命令进行安装。

    python3 -m pip install Pillow
    

    使用 linux 也更容易安装所有这些库。我推荐 WSL 用于 Windows。


    • 代码
    import whatimage
    import pyheif
    from PIL import Image
    import os
    
    def decodeImage(bytesIo, index):
        with open(bytesIo, 'rb') as f:
        data = f.read()
        fmt = whatimage.identify_image(data)
        if fmt in ['heic', 'avif']:
        i = pyheif.read_heif(data)
        pi = Image.frombytes(mode=i.mode, size=i.size, data=i.data)
        pi.save("new" + str(index) + ".jpg", format="jpeg")
    
    # For my use I had my python file inside the same folder as the heic files
    source = "./"
    
    for index,file in enumerate(os.listdir(source)):
        decodeImage(file, index)
    

    【讨论】:

    • 请解释你的代码是做什么的以及它是怎么做的。
    • 这是一个使用多个库的简单解决方案。它基本上会在与“.py”文件相同的文件夹中打开每个 .heic 文件并转换为 jpg。你是说代码解释吗?
    • 是的,每个答案都应该解释代码是如何工作的。
    • 您的代码缩进无效。请更正您的示例,以便它实际上是可测试的。
    【解决方案7】:

    看起来有一个名为 heic-to-jpg 的解决方案,但我可能不太确定它在 colab 中如何工作。

    【讨论】:

      【解决方案8】:

      第一个答案有效,但是由于它只是使用 BytesIO 对象作为参数调用 save,它实际上并没有保存新的 jpeg 文件,但是如果您使用 open 创建一个新的 File 对象并传递它,它会保存到该文件即:

      import whatimage
      import pyheif
      from PIL import Image
      
      
      def decodeImage(bytesIo):
      
          fmt = whatimage.identify_image(bytesIo)
          if fmt in ['heic', 'avif']:
               i = pyheif.read_heif(bytesIo)
              
               # Convert to other file format like jpeg
               s = open('my-new-image.jpg', mode='w')
               pi = Image.frombytes(
                      mode=i.mode, size=i.size, data=i.data)
      
               pi.save(s, format="jpeg")
      
      

      【讨论】:

        【解决方案9】:

        考虑将 PIL 与 pillow-heif 结合使用:

        pip3 install pillow-heif
        
        from PIL import Image
        from pillow_heif import register_heif_opener
        
        register_heif_opener()
        
        image = Image.open('image.heic')
        

        【讨论】:

          【解决方案10】:

          这是另一种将heic 转换为jpg 同时保持元数据完整的解决方案。它基于上面的mara004s 解决方案,但是我无法以这种方式提取图像时间戳,因此不得不添加一些代码。在应用函数之前将heic文件放入dir_of_interest

          import os
          from PIL import Image, ExifTags
          from pillow_heif import register_heif_opener
          from datetime import datetime
          import piexif
          import re
          register_heif_opener()
          
          def convert_heic_to_jpeg(dir_of_interest):
                  filenames = os.listdir(dir_of_interest)
                  filenames_matched = [re.search("\.HEIC$|\.heic$", filename) for filename in filenames]
          
                  # Extract files of interest
                  HEIC_files = []
                  for index, filename in enumerate(filenames_matched):
                          if filename:
                                  HEIC_files.append(filenames[index])
          
                  # Convert files to jpg while keeping the timestamp
                  for filename in HEIC_files:
                          image = Image.open(dir_of_interest + "/" + filename)
                          image_exif = image.getexif()
                          if image_exif:
                                  # Make a map with tag names and grab the datetime
                                  exif = { ExifTags.TAGS[k]: v for k, v in image_exif.items() if k in ExifTags.TAGS and type(v) is not bytes }
                                  date = datetime.strptime(exif['DateTime'], '%Y:%m:%d %H:%M:%S')
          
                                  # Load exif data via piexif
                                  exif_dict = piexif.load(image.info["exif"])
          
                                  # Update exif data with orientation and datetime
                                  exif_dict["0th"][piexif.ImageIFD.DateTime] = date.strftime("%Y:%m:%d %H:%M:%S")
                                  exif_dict["0th"][piexif.ImageIFD.Orientation] = 1
                                  exif_bytes = piexif.dump(exif_dict)
          
                                  # Save image as jpeg
                                  image.save(dir_of_interest + "/" + os.path.splitext(filename)[0] + ".jpg", "jpeg", exif= exif_bytes)
                          else:
                                  print(f"Unable to get exif data for {filename}")
          

          【讨论】:

            【解决方案11】:

            请注意: 今天是 pillow-heif 的第一个版本,支持 64 位窗口。

            现在它几乎支持所有平台,除了 windows arm 和 32 位系统。

            在这个主题中,有两个人展示了它的基本用法。

            【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-07-29
            • 2018-02-21
            • 2020-05-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多