如何在Python中使用HEIC图像文件类型



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

默认情况下,我可以修改手机设置以保存为JPG,但这并不能真正解决使用其他文件类型的问题。我仍然希望能够处理HEIC文件,以便进行文件转换、提取元数据等。(示例用例——地理编码)

枕头

以下是在尝试读取这种类型的文件时使用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枕头中的支持是被请求的(#2806),但存在许可证/专利问题。

ImageMagick+魔杖

看来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

是否有其他可用于以编程方式进行转换的替代方案?

考虑将PIL与枕头高度结合使用:

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

也就是说,我不知道有任何许可/专利问题会阻止HEIF在Pillow中的支持(见此或此)。libheif被广泛采用并免费使用,前提是您不将HEIF解码器与设备捆绑在一起,并满足LGPLv3许可证的要求。

你们应该看看这个库,它是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")
...

我使用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()

这里有另一个解决方案,可以将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}")

添加到danial的答案中,我只需要稍微修改字节数组,就可以获得有效的数据流来进行进一步的工作。前6个字节是"Exif\x0\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)

至少这对我有用。

您可以使用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")

这将从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)

从版本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和枕边高度处理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位文件

完美工作。。。(即使在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

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

我还没有成功地让这些人工作,但我会继续努力。我建议您检查这些依赖项是否可用于您的配置。

查看人们的多个响应后的简单解决方案
请在运行此代码之前安装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)

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

第一个答案是有效的,但由于它只是用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")

我使用pillow_heif库。例如,当我有一个包含要转换为png的heif文件的文件夹时,我会使用这个脚本。

from PIL import Image
import pillow_heif
import os 
from tqdm import tqdm 
import argparse
def get_images(heic_folder):
# Get all the heic images in the folder
imgs = [os.path.join(heic_folder, f) for f in os.listdir(heic_folder) if f.endswith('.HEIC')]
# Name of the folder where the png files will be stored
png_folder = heic_folder + "_png"
# If it doesn't exist, create the folder
if not os.path.exists(png_folder):
os.mkdir(png_folder)

for img in tqdm(imgs):
heif_file = pillow_heif.read_heif(img)
image = Image.frombytes(
heif_file.mode,
heif_file.size,
heif_file.data,
"raw",
)
image.save(os.path.join(png_folder,os.path.basename(img).split('.')[0])+'.png', format("png"))

if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert heic images to png')
parser.add_argument('heic_folder', type=str, help='Folder with heic images')
args = parser.parse_args()
get_images(args.heic_folder)

最新更新