如何在Python中处理HEIC图像文件类型

74

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

我可以修改手机设置,将默认保存为JPG,但这并不能真正解决与他人共享文件类型问题。我仍然希望能够处理HEIC文件,以进行文件转换、提取元数据等操作(示例用例--地理编码

Pillow

这是在使用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

有没有其他可用的方法来进行程序转换?

1
同样地,Sindre Sorhus有一个出色的HEIC转换器,可以生成JPEG或PNG图像,但不是我正在寻找的灵活性。https://sindresorhus.com/heic-converter - j12y
1
ExifTool 提供了一个命令行界面,用于处理图像元数据并支持 HEIF 格式。在 Python 中使用应该很容易。 - buzjwa
这可能会有所帮助... https://stackoverflow.com/a/54558699/2836621 - Mark Setchell
1
仅供参考: 今天发布了pillow-heif的第一个版本,支持64位Windows。现在它几乎支持所有平台,除了Windows ARM和32位系统。在这个话题中,两个人展示了它的基本用法。 - Alexander Piskun
14个回答

57

考虑将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')

话虽如此,我不知道是否存在任何许可证/专利问题会阻止Pillow支持HEIF(请参见这个这个)。libheif已被广泛采用且免费使用,只要您不将HEIF解码器与设备捆绑在一起并满足LGPLv3许可证的要求即可。


2
这太棒了。我认为这是所有答案中最简单、最直接的方法,因此应该被接受为@j12y问题的答案。 - Andrew
1
这个能用来将HEIC转换成JPG吗? - Osama Bin Saleem
5
@OsamaBinSaleem 当然可以,只需像平常一样执行 image.save(filepath, format="jpg", ...) 即可。 - mara004

27

大家应该看看这个库,它是一个 Python 3 的包装器,用于 libheif 库,它应该可以满足您的文件转换和提取元数据的需求:

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

  ...

1
我使用 pyheif 的经验是它可以成功读取HEIC文件,但我不明白为什么上面的代码中 Image.frombytes() 应该如何工作。这不需要PIL理解HEIF吗?无论如何,当我运行它时得到的是一个严重损坏的JPG文件。 - Norm
1
read_heif required actual data, so the line should actually be: pyheif.read_heif(bytesIo.read()) - Cigogne Eveillée
3
你能否举一些“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。 - user9074332
4
我尝试在 Windows 上安装 pyheif,遇到了 这个 问题。结果发现 pyheif 不兼容 Windows。"fwiw" 可以翻译为 "顺便说一句"。 - Zhang18

21

我使用Wand包相当成功: 安装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()

2
似乎ImageMagick(Wand使用的低级库)在某些发行版的软件包管理器中默认不支持heic委托(例如:Centos 8)。 - Rodriguez
谢谢!这对我很有帮助,因为我在Windows 10中无法让pyheif正常工作。 - AlvaroFG

11

这里有另一个将heic转换为jpg并保持元数据完整的解决方案。它基于上面提到的mara004的解决方案,但是我无法以那种方式提取图像时间戳,因此必须添加一些代码。在应用函数之前,将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}")

很棒的努力! 我尝试了它,它救了我,在保持所有元数据完整的同时成功转换了所有图片。非常感谢你。 - Mohammad ElNesr
在我的情况下,它没有起作用。创建时间、修改时间和访问时间都被保存为保存(创建)该图片的时间。三个值都相同。 - M.K
在我看来,这是截至当前日期最完整的示例。 - DaReal

6
你可以使用pillow_heif库以与PIL兼容的方式读取HEIF图像。
以下示例将导入一个HEIF图片并将其保存为png格式。
from PIL import Image
import pillow_heif

heif_file = pillow_heif.read_heif("HEIC_file.HEIC")
image = Image.frombytes(
    heif_file.mode,
    heif_file.size,
    heif_file.data,
    "raw",
)

image.save("./picture_name.png", format="png")
    

不知道为什么,但这个解决方案在我的情况下失败了,并开始产生噪音图像。 - Farhan Hai Khan
1
可能更容易调用register_heif_opener()Image.open()而不是使用read_heif()和手动的frombytes() - mara004

6

补充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)

至少对我来说是有效的。


2
使用您的代码,当我传递一个HEIC文件路径时,我会得到PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x109aefef0>错误。 - Josh Clark
跟Josh有一样的问题,PIL用这段代码无法识别HEIC格式的图片。 - pedroprates

5

从版本 0.10.0 开始,变得更加简单。

使用 OpenCV 将 8/10/12 位 HEIF 文件保存为 8/16 位 PNG:

import numpy as np
import cv2
from pillow_heif import open_heif

heif_file = open_heif("images/rgb12.heif", convert_hdr_to_8bit=False, bgr_mode=True)
np_array = np.asarray(heif_file)
cv2.imwrite("image.png", np_array)

针对版本 < 0.10.0

使用OpenCV和pillow-heif处理HDR(10/12)位HEIF文件的示例:

import numpy as np
import cv2
import pillow_heif

heif_file = pillow_heif.open_heif("images/rgb12.heif", convert_hdr_to_8bit=False)
heif_file.convert_to("BGRA;16" if heif_file.has_alpha else "BGR;16")
np_array = np.asarray(heif_file)
cv2.imwrite("rgb16.png", np_array)

这个例子的输入文件可以是10位或12位文件。


到目前为止,这是我看过的最好的答案。非常简洁,谢谢! - jacktim

5
这将获取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)

1

完美运行...(甚至在Windows上)

import glob
from PIL import Image
from pillow_heif import register_heif_opener

register_heif_opener()

for heic_pic_name in glob.glob("*.heic"):   #searching .heic images in existing folder
    my_pic = Image.open(heic_pic_name)      #opening .heic images
    jpg_pic_name = heic_pic_name.split('.')[0]+'.jpg'   #creating new names for .jpg images
    my_pic.save(jpg_pic_name, format="JPEG", optimize = True, quality = 100)    #saving

0

第一个答案可行,但由于它只是使用BytesIO对象作为参数调用save方法,它实际上并没有保存新的JPEG文件。但是,如果您使用open创建一个新的File对象并将其传递给save方法,它会保存到该文件中:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接