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?

XMPメタデータを使用してPythonでRAW画像をJPEGに変換してみた

Last updated at Posted at 2024-07-14

※ 残念ながら、lightroom現像の再現には失敗しています。
※ もう少し実際の現像状況を見つつ、設定項目を再検証する必要あり(してからここに上げろよって話ではある)

前提条件

 RAW画像ファイルはカメラが撮影した未加工のデータを含むため、高品質な画像編集が可能ですが、そのままでは扱いにくく、実際に画像として使用する際には、JPEG形式に変換する必要があります。

 私は通常、lightroomにて現像を行っていますが、このプリセットファイルは出力することができ、各設定情報は簡単に読み解くことが可能です。また、簡単にJSONなどのファイルに変換することができます。

 今回は、lightroomのプリセットファイルであるXMPファイルを参照し、Pythonを用いてRAWファイルを現像してみます。RAWファイルはオリンパスのDNG形式、lightroomのプリセットファイルは私のオリジナルのものを使用します。

 以下は今回使用したファイルのリンクです。
 ・RAWファイル:https://drive.google.com/file/d/15miXXxP8mv_yEo8fiOLNYS-56k59ua5t/view?usp=drive_link
 ・プリセットファイル:https://drive.google.com/file/d/16B5S0_Lyb5mf3vZRJg4pMBZ2nClcfcqR/view?usp=drive_link

ライブラリ

今回のプログラムに使用するライブラリの一覧です。

・rawpy
LibRawライブラリのPythonラッパーであり、RAW画像の処理を簡単に行うことができます。
また、ホット/デッドピクセルの修復機能も備えています。
・imageio
imageioは、幅広い画像およびビデオデータを読み書きするための簡単なインターフェースを提供するPythonライブラリです。アニメーション画像や科学データなどもサポートしています。
・lxml
lxmlは、PythonでXMLおよびHTMLを処理するための最も機能豊富で使いやすいライブラリです。libxml2およびlibxsltのCライブラリに基づいており、高速で完全なXML機能を提供します。

プログラムの各パーツについて

1. XMPファイルをJSONに変換

XMPファイルを読み込み、XML形式のデータをパースしてJSON形式に変換します。

mushi.py
def xmp_to_json(xmp_path):
    with open(xmp_path, 'rb') as file:
        xmp_data = file.read()
    root = etree.fromstring(xmp_data)
    xmp_dict = {child.tag: child.text.strip() for child in root.iter() if child.text}
    return xmp_dict

2. JSONを保存

変換されたJSONデータを指定されたディレクトリに保存します。

mushi.py
def save_json(data, output_dir, xmp_filename):
    base_filename = os.path.splitext(os.path.basename(xmp_filename))[0]
    json_path = os.path.join(output_dir, f"{base_filename}.json")
    with open(json_path, 'w', encoding='utf-8') as json_file:
        json.dump(data, json_file, ensure_ascii=False, indent=4)

3. RAWファイルをJPEGに現像

RAWファイルを読み込み、XMPファイルの設定を適用してJPEG形式に変換します。

mushi.py
def convert_raw_to_jpeg(dng_path, output_dir, base_filename, xmp_data):
    with rawpy.imread(dng_path) as raw:
        params = rawpy.Params()

        # XMPデータを適用するための処理
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012' in xmp_data:
            params.exposure = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012'] else 0.0
        # 他のパラメータも同様に設定

        rgb = raw.postprocess(params=params)
        output_path = os.path.join(output_dir, f"{base_filename}.jpg")
        imageio.imwrite(output_path, rgb, quality=95)  # 高画質で保存

このコードでは、XMPファイルから取得したメタデータを使用して、RAW画像の現像パラメータを設定しています。
以下に、具体的な処理について詳しく説明します。

Exposure2012の適用

mushi.py
if '{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012' in xmp_data:
    params.exposure = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012'] else 0.0

 この部分のコードは、XMPファイル内のExposure2012というタグを探し、その値をparams.exposureに設定しています。Exposure2012は、Adobe Camera RawやLightroomで使用される露出補正の値を示します。この値を適用することで、RAW画像の露出を調整します。
 具体的には、以下の手順で処理が行われます
  1. XMPデータ内に"{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012"というキーが存在するか確認します。
  2. 存在する場合、その値を取得し、浮動小数点数に変換してparams.exposureに設定します。
  3. 値が存在しない場合は、デフォルト値として0.0を設定します。

まとめ

いちおう、コード自体の指示通りには現像できました。
が、やはり色味がかなり違っています…うーむ…
シャドウとホワイトバランスは明らかにトチッてますね。
他にもいろいろ再検証する必要があるようです。

未処理のjpeg
IMG_20240517_185403 (2).jpg

lightroomで現像したjpegファイル
IMG_20240517_185403 (1).jpg

本コードで現像したjpegファイル
IMG_20240517_185403.jpg

参考サイト

今回参考にしたサイト集です。

Adobe XMP Specification
 :XMPの仕様について詳細に説明しています。
Adobe Experience Manager
 :XMPメタデータの概要とエコシステムについて解説しています。
Accusoft Documentation
 :XMPメタデータの構造について説明しています。
Post Partner Blog
 :XMPファイルをRAW画像に適用する方法についてのステップバイステップガイド。
Damien Symonds Blog
 :XMPデータの基本とその役割について解説しています。

コード

mushi.py
import rawpy
import imageio
import json
import os
from lxml import etree

# ファイルパスの設定
dng_file = r"C:\Users\\Documents\.dng"
xmp_file = r"C:\Users\\Documents\.xmp"
output_dir = r"C:\Users\\Documents"

# 1. XMPファイルをJSONに変換
def xmp_to_json(xmp_path):
    with open(xmp_path, 'rb') as file:
        xmp_data = file.read()
    root = etree.fromstring(xmp_data)
    xmp_dict = {child.tag: child.text.strip() for child in root.iter() if child.text}
    return xmp_dict

# 2. JSONを保存
def save_json(data, output_dir, xmp_filename):
    base_filename = os.path.splitext(os.path.basename(xmp_filename))[0]
    json_path = os.path.join(output_dir, f"{base_filename}.json")
    with open(json_path, 'w', encoding='utf-8') as json_file:
        json.dump(data, json_file, ensure_ascii=False, indent=4)

# 3. RAWファイルをJPEGに現像
def convert_raw_to_jpeg(dng_path, output_dir, base_filename, xmp_data):
    with rawpy.imread(dng_path) as raw:
        params = rawpy.Params()

        # XMPデータを適用するための処理
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012' in xmp_data:
            params.exposure = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Exposure2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Contrast2012' in xmp_data:
            params.contrast = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Contrast2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Contrast2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Highlights2012' in xmp_data:
            params.highlight = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Highlights2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Highlights2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Shadows2012' in xmp_data:
            params.shadow = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Shadows2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Shadows2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Whites2012' in xmp_data:
            params.white = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Whites2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Whites2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Blacks2012' in xmp_data:
            params.black = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Blacks2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Blacks2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Clarity2012' in xmp_data:
            params.clarity = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Clarity2012']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Clarity2012'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Vibrance' in xmp_data:
            params.vibrance = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Vibrance']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Vibrance'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Saturation' in xmp_data:
            params.saturation = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Saturation']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Saturation'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Temperature' in xmp_data:
            params.temperature = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Temperature']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Temperature'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}Tint' in xmp_data:
            params.tint = float(xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Tint']) if xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}Tint'] else 0.0
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012' in xmp_data:
            params.tone_curve = [float(i) for i in xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012'].split(',') if i]
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012Red' in xmp_data:
            params.tone_curve_red = [float(i) for i in xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012Red'].split(',') if i]
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012Green' in xmp_data:
            params.tone_curve_green = [float(i) for i in xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012Green'].split(',') if i]
        if '{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012Blue' in xmp_data:
            params.tone_curve_blue = [float(i) for i in xmp_data['{http://ns.adobe.com/camera-raw-settings/1.0/}ToneCurvePV2012Blue'].split(',') if i]

        rgb = raw.postprocess(params=params)
        output_path = os.path.join(output_dir, f"{base_filename}.jpg")
        imageio.imwrite(output_path, rgb, quality=95)  # 高画質で保存

# メイン処理
def main():
    base_filename = os.path.splitext(os.path.basename(dng_file))[0]
    
    # XMPをJSONに変換
    xmp_data = xmp_to_json(xmp_file)
    
    # JSONを保存
    save_json(xmp_data, output_dir, xmp_file)
    
    # RAWファイルをJPEGに変換
    convert_raw_to_jpeg(dng_file, output_dir, base_filename, xmp_data)

if __name__ == "__main__":
    main()
0
0
1

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?