j12y
j12y

Reputation: 2432

How to work with HEIC image file types in Python

The High Efficiency Image File (HEIF) format is the default when airdropping an image from an iPhone to a OSX device. I want to edit and modify these .HEIC files with Python.

I could modify phone settings to save as JPG by default but that doesn't really solve the problem of being able to work with the filetype from others. I still want to be able to process HEIC files for doing file conversion, extracting metadata, etc. (Example Use Case -- Geocoding)

Pillow

Here is the result of working with Python 3.7 and Pillow when trying to read a file of this type.

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

It looks like support in python-pillow was requested (#2806) but there are licensing / patent issues preventing it there.

ImageMagick + Wand

It appears that ImageMagick may be an option. After doing a brew install imagemagick and pip install wand however I was unsuccessful.

$ 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

Any other alternatives available to do a conversion programmatically?

Upvotes: 87

Views: 120249

Answers (16)

Selcuk
Selcuk

Reputation: 59238

There is also the pi-heif project which is a more lightweight version of pillow-heif if you only need to read from HEIF files:

This is a light version of Pillow-Heif with more permissive license for binary wheels.

It includes only HEIF decoder and does not support save operations.

All codebase are the same, refer to pillow-heif docs.

from PIL import Image
from pi_heif import register_heif_opener

register_heif_opener()

im = Image.open("images/input.heic")  # do whatever need with a Pillow image
im.show()

Upvotes: 0

JJ Zeng
JJ Zeng

Reputation: 1

The first answer is correct, but it should add more args:

def decodeImage(bytesIo):
    fmt = whatimage.identify_image(bytesIo)
    if fmt in ['heic', 'avif']:
        i = pyheif.read_heif(bytesIo)
        pi = Image.frombytes(i.mode, i.size, i.data, "raw", i.mode, i.stride)
         
        s = open("test.jpg", mode="w")
        pi.save(s, format="JPEG")

Upvotes: 0

mara004
mara004

Reputation: 2339

Consider using PIL in conjunction with 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')

That said, I'm not aware of any licensing/patent issues that would prevent HEIF support in Pillow (see this or this). AFAIK, libheif is widely adopted and free to use, provided you do not bundle the HEIF decoder with a device and fulfill the requirements of the LGPLv3 license.

Upvotes: 87

Sushil Champ
Sushil Champ

Reputation: 257

Works perfectly... (Even on 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

Upvotes: 4

Alexander Piskun
Alexander Piskun

Reputation: 126

Starting from version 0.10.0, it becomes much simpler.

Saving 8/10/12 bit HEIF files to 8/16 bit PNG using OpenCV:

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)

For versions < 0.10.0

Example for working with HDR(10/12) bit HEIF files using OpenCV and pillow-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)

Input file for this example can be 10 or 12 bit file.

Upvotes: 6

DL876576
DL876576

Reputation: 11

I use the pillow_heif library. For example, I use this script when I have a folder of heif files I want to convert to png.

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)

Upvotes: 1

adel
adel

Reputation: 464

You can use the pillow_heif library to read HEIF images in a way compatible with PIL.

The example below will import a HEIF picture and save it in png format.

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

Upvotes: 8

aleksandereiken
aleksandereiken

Reputation: 520

Here is another solution to convert heic to jpg while keeping the metadata intact. It is based on mara004s solution above, however I was not able to extract the images timestamp in that way, so had to add some code. Put the heic files in dir_of_interest before applying the function:

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

Upvotes: 15

Kyle Roux
Kyle Roux

Reputation: 746

the first answer works, but since its just calling save with a BytesIO object as the argument, it doesnt actually save the new jpeg file, but if you create a new File object with open and pass that, it saves to that file ie:

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

Upvotes: 0

Qiyu Zhong
Qiyu Zhong

Reputation: 74

It looked like that there is a solution called heic-to-jpg, but I might be not very sure about how this would work in colab.

Upvotes: 0

danial
danial

Reputation: 617

You guys should check out this library, it's a Python 3 wrapper to the libheif library, it should serve your purpose of file conversion, extracting metadata:

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

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

Example usage:

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

  ...

Upvotes: 28

d1p3
d1p3

Reputation: 39

Simple solution after going over multiple responses from people.
Please install whatimage, pyheif and PIL libraries before running this code.


[NOTE] : I used this command for install.

python3 -m pip install Pillow

Also using linux was lot easier to install all these libraries. I recommend WSL for windows.


  • code
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)

Upvotes: 0

Richard Dvoř&#225;k
Richard Dvoř&#225;k

Reputation: 329

I was quite successful with Wand package : Install Wand: https://docs.wand-py.org/en/0.6.4/ Code for conversion:

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

Upvotes: 22

PidePython
PidePython

Reputation: 81

This will do go get the exif data from the heic file

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)

Upvotes: 7

Peter Kunszt
Peter Kunszt

Reputation: 131

Adding to the answer by danial, i just had to modify the byte array slighly to get a valid datastream for further work. The first 6 bytes are 'Exif\x00\x00' .. dropping these will give you a raw format that you can pipe into any image processing tool.

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)

At least this worked for me.

Upvotes: 7

Tropicalrambler
Tropicalrambler

Reputation: 707

I am facing the exact same problem as you, wanting a CLI solution. Doing some further research, it seems ImageMagick requires the libheif delegate library. The libheif library itself seems to have some dependencies as well.

I have not had success in getting any of those to work as well, but will continue trying. I suggest you check if those dependencies are available to your configuration.

Upvotes: 0

Related Questions