PythonでRAW画像を読み込んで簡単な編集をしてJPEGに書き出す方法を解説します
RAW画像の読み込み
import rawpy
import numpy as np
from PIL import Image
from scipy import interpolate
from matplotlib import pyplot as plt
import matplotlib.colors as mcolors
def read_raw_image(file_path: str) -> np.ndarray:
# rawpyで読み込み
rawpy_object = rawpy.imread(file_path)
# rawpyでRAW画像を16bit RGB画像に変換
image_array: np.ndarray = rawpy_object.postprocess(use_camera_wb = True, output_bps=16).astype(np.float32)
# 0~1の範囲にする
image_array = (image_array / 65535.0).astype(np.float32)
return image_array
path = r"ファイルパス"
image_array = read_raw_image(path)
画像の表示
def display(image_array: np.ndarray):
# pillowのImgaeオブジェクトにして表示
Image.fromarray(np.clip(image_array * 255, 0, 255).astype(np.uint8)).show()
display(image_array)
色や明るさを調整するためHLS ⇔ RGB変換
opencvで変換を試してみましたがエラーは出ないもののうまく変換できませんでした。
uint8でしか動かないみたいです。
なのでAIに書いてもらいました
def rgb_to_hls_image(rgb_image: np.ndarray) -> np.ndarray:
"""
RGB画像をHLS画像に変換する関数
"""
# 入力が3次元配列(高さ、幅、チャンネル)であることを確認
if rgb_image.ndim != 3 or rgb_image.shape[2] != 3:
raise ValueError("入力は3チャンネルのRGB画像である必要があります")
rgb_normalized = rgb_image.copy()
r, g, b = rgb_normalized[:,:,0], rgb_normalized[:,:,1], rgb_normalized[:,:,2]
max_c = np.maximum(np.maximum(r, g), b)
min_c = np.minimum(np.minimum(r, g), b)
l = (max_c + min_c) / 2
s = np.zeros_like(l)
h = np.zeros_like(l)
delta = max_c - min_c
mask = (max_c != min_c)
s[mask] = np.where(l[mask] > 0.5,
delta[mask] / (2 - max_c[mask] - min_c[mask]),
delta[mask] / (max_c[mask] + min_c[mask]))
mask_r = (max_c == r) & mask
h[mask_r] = (g[mask_r] - b[mask_r]) / delta[mask_r] + np.where(g[mask_r] < b[mask_r], 6, 0)
mask_g = (max_c == g) & mask
h[mask_g] = (b[mask_g] - r[mask_g]) / delta[mask_g] + 2
mask_b = (max_c == b) & mask
h[mask_b] = (r[mask_b] - g[mask_b]) / delta[mask_b] + 4
h /= 6
return np.dstack((h, l, s)).astype(np.float32)
def hls_to_rgb_image(hls_image: np.ndarray) -> np.ndarray:
"""
HLS画像をRGB画像に変換する関数
"""
if hls_image.ndim != 3 or hls_image.shape[2] != 3:
raise ValueError("入力は3チャンネルのHLS画像である必要があります")
h = hls_image[:, :, 0]
l = hls_image[:, :, 1]
s = hls_image[:, :, 2]
q = np.where(l < 0.5, l * (1 + s), l + s - l * s)
p = 2 * l - q
def _hue2rgb(p, q, t):
t = t % 1.0
res = np.empty_like(t)
res[(t < 1/6)] = p[(t < 1/6)] + (q[(t < 1/6)] - p[(t < 1/6)]) * 6 * t[(t < 1/6)]
res[(1/6 <= t) & (t < 1/2)] = q[(1/6 <= t) & (t < 1/2)]
res[(1/2 <= t) & (t < 2/3)] = p[(1/2 <= t) & (t < 2/3)] + (q[(1/2 <= t) & (t < 2/3)] - p[(1/2 <= t) & (t < 2/3)]) * (4 - 6 * t[(1/2 <= t) & (t < 2/3)])
res[(2/3 <= t)] = p[(2/3 <= t)]
return res
r = _hue2rgb(p, q, h + 1/3)
g = _hue2rgb(p, q, h)
b = _hue2rgb(p, q, h - 1/3)
rgb = np.stack([r, g, b], axis=2)
return np.clip(rgb, 0, 1).astype(np.float32)
明るさ調整 トーンカーブ!!
def apply_tone_curve(hls_image_array: np.ndarray, control_points_x: list[int], control_points_y: list[int]):
"""明るさ調整トーンカーブ"""
if control_points_x is None or control_points_y is None:
raise ValueError("x, yの制御点を指定してください")
if len(control_points_x) != len(control_points_y):
raise ValueError("制御点の数が一致していません")
# scipyの補間で曲線トーンカーブを生成
f = interpolate.PchipInterpolator(control_points_x, control_points_y)
curve = np.clip(f(np.arange(65536)), 0, 65535).astype(np.uint16)
# トーンカーブのグラフを作る
plt.plot(list(range(0, 65536)), curve)
for x in control_points_x:
plt.plot(x, curve[x], marker='.', color='red')
plt.grid(True)
plt.savefig("明るさトーンカーブ.png")
plt.close()
# トーンカーブ適用
hls_image = (hls_image_array.copy() * 65535).astype(np.int32)
hls_image[:,:,1] = curve[hls_image[:, :, 1]]
return (hls_image / 65535.0).astype(np.float32)
# 読み込み
image_array = read_raw_image(path)
# 明るさ調整するためにHLSに変換
hls_image = rgb_to_hls_image(image_array)
# トーンカーブを適用
hls_image = apply_tone_curve(hls_image, [0, 23000, 65535], [0, 36000, 65535])
# RGBに戻す
rgb_image = hls_to_rgb_image(hls_image)
# 表示
display(rgb_image)
色調整 色ごとに彩度を調整!
def apply_hls_saturation_tone_curve(hls_image_array: np.ndarray, control_points_x: list[int], control_points_y: list[int]):
"""彩度調整トーンカーブ"""
if len(control_points_x) != len(control_points_y):
raise ValueError("制御点の数が一致していません")
# scipyの補間で曲線トーンカーブを生成
f = interpolate.PchipInterpolator(np.array(control_points_x) * 182, np.array(control_points_y) / 100 * 65535)
# 0〜65535の範囲で彩度調整カーブを生成
curve = np.clip(f(np.arange(65536)), 0, 131070).astype(np.float32)
# カーブをプロット
plt.figure(figsize=(12, 8))
# 背景に色相環を表示
for i in range(360):
color = mcolors.hsv_to_rgb([i/360, 1, 1])
plt.axvline(i, color=color, alpha=0.2)
# 彩度調整カーブをプロット
x = np.arange(65536) / 182
y = curve / 655.35
plt.plot(x, y, 'k-', linewidth=2)
if curve is None:
# 制御点をプロット
for x, y in zip(control_points_x, control_points_y):
color = mcolors.hsv_to_rgb([x/360, 1, 1])
plt.plot(x, y, 'o', color=color, markersize=10)
plt.xlabel('色相')
plt.ylabel('彩度 (%)')
plt.title('彩度調整カーブ')
plt.xlim(0, 360)
plt.ylim(0, 200)
plt.grid(True, alpha=0.3)
# y軸に目盛りを追加
y_ticks = [0, 50, 100, 150, 200]
plt.yticks(y_ticks)
# 100%のラインを追加
plt.axhline(100, color='r', linestyle='--', alpha=0.5)
plt.text(365, 100, '100%', va='center', ha='left', color='r')
plt.savefig("彩度調整カーブ.png", dpi=300, bbox_inches='tight')
plt.close()
# 彩度チャンネル(S)に調整を適用
hls_image = hls_image_array.copy() * 65535.0
hue = hls_image[:,:,0].astype(np.uint16)
hls_image[:,:,2] = np.clip(hls_image[:,:,2] * curve[hue] / 65535, 0, 65535)
return (hls_image / 65535.0).astype(np.float32)
hls_image = apply_hls_saturation_tone_curve(hls_image, [0, 360], [160, 160])
# [0, 360]は色相、[160, 160]はその色相の場所の彩度の倍数 (100で変更なし、50で半分、200で2倍)
明るさと彩度トーンカーブを作りましたが、彩度のほかに輝度や色相の調整トーンカーブも簡単に作れます。
JPEGに書き出し
Image.fromarray(np.clip(rgb_image * 255.0, 0.0, 255.0).astype(np.uint8)).save("編集した写真.jpeg", optimize=True, quality=90)
終わりに
Pythonはライブラリが強いのでraw画像読み込み用のrawpyやnumpyなどがあるので他の言語にくらべて比較的簡単にRAW現像が作れるのでやってみてください!