0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでRAW現像!

Last updated at Posted at 2025-04-28

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現像が作れるのでやってみてください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?