跳至主要内容

用 Python 讀取 HEIC 圖檔

· 閱讀時間約 4 分鐘
Zephyr
Engineer

當你想要讀取一張影像時,你可能會使用 OpenCV 的 imread 函數。

可惜這個函數並不是萬能的,有時候可能會遇到一些問題。

基礎用法

imread 函數的基礎用法非常簡單,你只需要傳入影像的路徑即可:

import cv2

image = cv2.imread('path/to/image.jpg')

其中,你可以使用的影像格式包括:BMP, JPG, PNG, TIF 等常見影像格式。

限制一:HEIC 格式

在 iOS 裝置上拍攝的照片通常是 HEIC 格式,這種格式在 OpenCV 中是不支援的。如果你嘗試使用 imread 函數讀取 HEIC 格式的影像,你會得到一個 None 的返回值。

為了解決這個問題,我們得用 pyheif 這個套件來讀取 HEIC 格式的影像,然後再將其轉換成 numpy.ndarray 類型的變數。

首先,我們安裝必要套件:

sudo apt install libheif-dev
pip install pyheif

接著寫個簡單的函數:

import cv2
import pyheif
import numpy as np

def read_heic_to_numpy(file_path: str):
heif_file = pyheif.read(file_path)
data = heif_file.data
if heif_file.mode == "RGB":
numpy_array = np.frombuffer(data, dtype=np.uint8).reshape(
heif_file.size[1], heif_file.size[0], 3)
elif heif_file.mode == "RGBA":
numpy_array = np.frombuffer(data, dtype=np.uint8).reshape(
heif_file.size[1], heif_file.size[0], 4)
else:
raise ValueError("Unsupported HEIC color mode")
return numpy_array


img = read_heic_to_numpy('path/to/image.heic')
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

限制二:JPG 讀取慢

在某些情況下,imread 函數讀取 JPG 格式的影像速度會非常慢。這是因為 OpenCV 在讀取 JPG 格式的影像時,會使用 libjpeg 這個庫,而 libjpeg 本身的效能就不是很好。

在這邊,我們引入 TurboJPEG 這個套件,它是 libjpeg 的一個替代品,效能更好。我們可以使用 TurboJPEG 來加速讀取 JPG 格式的影像。

跟之前一樣,我們先安裝必要套件:

sudo apt install libturbojpeg exiftool
pip install PyTurboJPEG

再來我們寫一點程式,幫他加速一下:

一般來說,可以加速大約 2-3 倍。

import cv2
import piexif
from enum import IntEnum
from pathlib import Path
from turbojpeg import TurboJPEG


jpeg = TurboJPEG()


class ROTATE(IntEnum):
ROTATE_90 = cv2.ROTATE_90_CLOCKWISE
ROTATE_180 = cv2.ROTATE_180
ROTATE_270 = cv2.ROTATE_90_COUNTERCLOCKWISE


def imrotate90(img, rotate_code: ROTATE) -> np.ndarray:
return cv2.rotate(img.copy(), rotate_code)


def get_orientation_code(stream: Union[str, Path, bytes]):
code = None
try:
exif_dict = piexif.load(stream)
if piexif.ImageIFD.Orientation in exif_dict["0th"]:
orientation = exif_dict["0th"][piexif.ImageIFD.Orientation]
if orientation == 3:
code = ROTATE.ROTATE_180
elif orientation == 6:
code = ROTATE.ROTATE_90
elif orientation == 8:
code = ROTATE.ROTATE_270
finally:
return code


def jpgdecode(byte_: bytes) -> Union[np.ndarray, None]:
try:
bgr_array = jpeg.decode(byte_)
code = get_orientation_code(byte_)
bgr_array = imrotate90(
bgr_array, code) if code is not None else bgr_array
except:
bgr_array = None

return bgr_array


def jpgread(img_file: Union[str, Path]) -> Union[np.ndarray, None]:
with open(str(img_file), 'rb') as f:
binary_img = f.read()
bgr_array = jpgdecode(binary_img)

return bgr_array

img = jpgread('path/to/image.jpg')

這樣就可以加速讀取 JPG 格式的影像了。

結語

那如果我們希望這個程式可以聰明一點,自己選一個適合載入的方式呢?

我們可以寫一個函數,根據影像的格式來選擇合適的載入方式:

def imread(
path: Union[str, Path],
color_base: str = 'BGR',
verbose: bool = False
) -> Union[np.ndarray, None]:

if not Path(path).exists():
raise FileExistsError(f'{path} can not found.')

if Path(path).suffix.lower() == '.heic':
img = read_heic_to_numpy(str(path))
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
else:
img = jpgread(path)
img = cv2.imread(str(path)) if img is None else img

if img is None:
if verbose:
warnings.warn("Got a None type image.")
return

if color_base != 'BGR':
img = imcvtcolor(img, cvt_mode=f'BGR2{color_base}')

return img

詳細程式碼可以參考:imread.py