Pythonにてraw現像の処理をいろいろ調べたり、追加したりしてきましたが、最後JPEG保存するにあたり、やはりEXIFデータの付加は必須だろうと思い、いろいろ調べてみたメモです。ライブラリとしては、piexifというのを使ってみました。
結論から言ってしまうと、どのようなデータにも当てはまるような、汎用的な記述に行き着きませんでした。Canon限定でうまく動いているようですが、Olympus、Panasonic、iPhoneのrawデータでは一部書き換えが必要です。
piexif
Python上でEXIFを簡単に扱うことができるライブラリです。執筆時点では1.1.3というバージョンになってます。日本の方が作ったもののようです。PyCharmからのインストールはいつものように、File-> Setting-> Project Interpreter から右上の+ボタンを押して、検索のところからpiexifと検索すると見つかり、インストールできました。他にもいろいろ扱う手段はありそうです。
例えば、PIL (Pillow)でもこんな感じで、
from PIL import Image
exif = Image.open(filename).info['parsed_exif']
扱えるようですが、rawデータから取ってきてくれなかったり、取ってきた後の編集もやや回りくどかったです。個人的には。
また、他にも、ExifReadというライブラリもあるようです。こちらはrawでも一応開いてはくれましたが、書き込みの方法がぱっと見つからなかったので、あきらめました。こちらも
import exifread
f = open(filename, 'rb')
exif = exifread.process_file(f)
こんな感じで読み込めました。手段はいろいろあるようです。
読み書き(Canon)
基本的には読み取りをpiexifで行い、書き込みをPILで行う。という流れで扱います。サンプルコードがそうなっていたので。なので、処理フローは
1.piexifでrawを読み取り、exif取得
2.rawpyで現像 適宜exif書き換え
3.PILでexif付きで保存
こんな感じになります。Canonのrawデータには素直にEXIFがついているのか、するっとこのフローを通せました。コードとしては、
from PIL import Image
import piexif
import rawpy
exif_dict = piexif.load(filename)
raw = rawpy.imread(filename)
img = raw.postprocess(use_camera_wb=True,
user_flip=0)
# 画像サイズだけ修正
exif_dict["0th"][piexif.ImageIFD.ImageWidth] = img.shape[1]
exif_dict["0th"][piexif.ImageIFD.ImageLength] = img.shape[0]
exif_bytes = piexif.dump(exif_dict)
im = Image.fromarray(img)
im.save(savefile, "jpeg", exif=exif_bytes)
こんな感じになるかと思います。現像時のオプションで、user_flipをあえて無回転に設定してEXIFの情報と合わせています。なので本来サイズを変更する必要はないのですが、サンプルとして。本当はJPEGに変換しているので、それ関連の値も付与した方がいいのかもしれませんが、必要性を感じたことがないので、個人的にはこれで十分です。
ココで辞書に指定している[piexif.ImageIFD.ImageWidth]とかのキーですが、Web上に情報がなく、ソースコードを覗いて調べました。このソースにある数字とEXIFの仕様書から突き合わせて使います。いちいちソース覗くのが面倒なので、最後に該当コードの定義部だけ載せておきます。
読み書き(Panasonic)
上記のCanonのケースはレギュラーケースで、他の会社のRawデータはそのまま使えませんでした。Panasonic(DMC-GM1)のRawデータには不正なタグがあるのか、読み込めたのですが、書き込めなかったです。なので不正なタグを消して対応することになりました。
コードは
from PIL import Image
import piexif
import rawpy
exif_dict = piexif.load(filename)
raw = rawpy.imread(filename)
img = raw.postprocess(use_camera_wb=True,
user_flip=0)
# 画像サイズだけ修正
exif_dict["0th"][piexif.ImageIFD.ImageWidth] = img.shape[1]
exif_dict["0th"][piexif.ImageIFD.ImageLength] = img.shape[0]
# この3つのタグを消去(何がいけないのか不明)
del exif_dict["0th"][piexif.ImageIFD.ProcessingSoftware]
del exif_dict["0th"][piexif.ImageIFD.XResolution]
del exif_dict["0th"][piexif.ImageIFD.YResolution]
exif_bytes = piexif.dump(exif_dict)
im = Image.fromarray(img)
im.save(savefile, "jpeg", exif=exif_bytes)
これで動きました。この3つのタグが悪さをしているようです。なぜか不明です。ダンプすると確かにおかしな値が入ってます。
他にもJPEGにしてしまえば不要なタグはいろいろありそうですが、いったん放置しときます。
読み書き(Olympus)
これが厄介。完全に壊れた値が読み込まれています。機種はOMD E-M10 MkIIです。一部書き換えてどうにかなるレベルではないです。現像処理自体はできるのですごく残念ですが、諦めます。フォーマットの問題なのかなぁ…。
こいつを使う場合には、raw+JPEGで撮影しておいて、JPEGのEXIFをコピーすることで回避しようと思います。JPEGであればこのコードのままで読み込めました。なので、現像時にJPEGも用意しておく必要が出てきます。少しダサいですが、しょうがないです。
読み書き(iPhone SE)
iPhoneのRAWデータもいくつかタグを消す必要がありました。こいつの場合には、上記Panasonicのコードのように、6つのタグを消し去るとうまく行きました。
# この6つのタグを消去
del exif_dict["0th"][piexif.ImageIFD.TileWidth]
del exif_dict["0th"][piexif.ImageIFD.TileLength]
del exif_dict["0th"][piexif.ImageIFD.TileOffsets]
del exif_dict["0th"][piexif.ImageIFD.TileByteCounts]
del exif_dict["0th"][piexif.ImageIFD.BlackLevel]
del exif_dict["0th"][piexif.ImageIFD.AsShotNeutral]
他にも消して差し支えないようなタグ(現像してしまえば不要なタグ)もいっぱい入ってましたが、最低限は上記で済みそうです。
まとめ
いろいろ妥協をしていますが、ひとまず手元にあるrawデータのEXIFのコピーを取ることができました。現像したJPEGには付けときたいですよね。現像したいメーカーのRAWデータに応じたコードの修正が必要になりますが、うまい方法が見つかりませんでした。
ちまちま必須タグだけ取り出せばよいのかもしれませんが、メーカーオリジナルタグもあるし、レンズ情報もないオールドレンズの写真もあるので、めんどくさい。ので深追いはやめにします。
おまけ
piexifのタグ定義の箇所を抜粋しときます。よくにらめっこしたので。ファイルは、_exif.pyってのでした。(作者様にお断り入れた方がいいのかな…、こういうのって)
大きく[“0th”]のタグと、[“Exif”]のタグと、[“GPS”]のタグの3つをいじることが多いです。
class ImageIFD:
"""Exif tag number reference - 0th IFD"""
ProcessingSoftware = 11
NewSubfileType = 254
SubfileType = 255
ImageWidth = 256
ImageLength = 257
BitsPerSample = 258
Compression = 259
PhotometricInterpretation = 262
Threshholding = 263
CellWidth = 264
CellLength = 265
FillOrder = 266
DocumentName = 269
ImageDescription = 270
Make = 271
Model = 272
StripOffsets = 273
Orientation = 274
SamplesPerPixel = 277
RowsPerStrip = 278
StripByteCounts = 279
XResolution = 282
YResolution = 283
PlanarConfiguration = 284
GrayResponseUnit = 290
GrayResponseCurve = 291
T4Options = 292
T6Options = 293
ResolutionUnit = 296
TransferFunction = 301
Software = 305
DateTime = 306
Artist = 315
HostComputer = 316
Predictor = 317
WhitePoint = 318
PrimaryChromaticities = 319
ColorMap = 320
HalftoneHints = 321
TileWidth = 322
TileLength = 323
TileOffsets = 324
TileByteCounts = 325
SubIFDs = 330
InkSet = 332
InkNames = 333
NumberOfInks = 334
DotRange = 336
TargetPrinter = 337
ExtraSamples = 338
SampleFormat = 339
SMinSampleValue = 340
SMaxSampleValue = 341
TransferRange = 342
ClipPath = 343
XClipPathUnits = 344
YClipPathUnits = 345
Indexed = 346
JPEGTables = 347
OPIProxy = 351
JPEGProc = 512
JPEGInterchangeFormat = 513
JPEGInterchangeFormatLength = 514
JPEGRestartInterval = 515
JPEGLosslessPredictors = 517
JPEGPointTransforms = 518
JPEGQTables = 519
JPEGDCTables = 520
JPEGACTables = 521
YCbCrCoefficients = 529
YCbCrSubSampling = 530
YCbCrPositioning = 531
ReferenceBlackWhite = 532
XMLPacket = 700
Rating = 18246
RatingPercent = 18249
ImageID = 32781
CFARepeatPatternDim = 33421
CFAPattern = 33422
BatteryLevel = 33423
Copyright = 33432
ExposureTime = 33434
ImageResources = 34377
ExifTag = 34665
InterColorProfile = 34675
GPSTag = 34853
Interlace = 34857
TimeZoneOffset = 34858
SelfTimerMode = 34859
FlashEnergy = 37387
SpatialFrequencyResponse = 37388
Noise = 37389
FocalPlaneXResolution = 37390
FocalPlaneYResolution = 37391
FocalPlaneResolutionUnit = 37392
ImageNumber = 37393
SecurityClassification = 37394
ImageHistory = 37395
ExposureIndex = 37397
TIFFEPStandardID = 37398
SensingMethod = 37399
XPTitle = 40091
XPComment = 40092
XPAuthor = 40093
XPKeywords = 40094
XPSubject = 40095
PrintImageMatching = 50341
DNGVersion = 50706
DNGBackwardVersion = 50707
UniqueCameraModel = 50708
LocalizedCameraModel = 50709
CFAPlaneColor = 50710
CFALayout = 50711
LinearizationTable = 50712
BlackLevelRepeatDim = 50713
BlackLevel = 50714
BlackLevelDeltaH = 50715
BlackLevelDeltaV = 50716
WhiteLevel = 50717
DefaultScale = 50718
DefaultCropOrigin = 50719
DefaultCropSize = 50720
ColorMatrix1 = 50721
ColorMatrix2 = 50722
CameraCalibration1 = 50723
CameraCalibration2 = 50724
ReductionMatrix1 = 50725
ReductionMatrix2 = 50726
AnalogBalance = 50727
AsShotNeutral = 50728
AsShotWhiteXY = 50729
BaselineExposure = 50730
BaselineNoise = 50731
BaselineSharpness = 50732
BayerGreenSplit = 50733
LinearResponseLimit = 50734
CameraSerialNumber = 50735
LensInfo = 50736
ChromaBlurRadius = 50737
AntiAliasStrength = 50738
ShadowScale = 50739
DNGPrivateData = 50740
MakerNoteSafety = 50741
CalibrationIlluminant1 = 50778
CalibrationIlluminant2 = 50779
BestQualityScale = 50780
RawDataUniqueID = 50781
OriginalRawFileName = 50827
OriginalRawFileData = 50828
ActiveArea = 50829
MaskedAreas = 50830
AsShotICCProfile = 50831
AsShotPreProfileMatrix = 50832
CurrentICCProfile = 50833
CurrentPreProfileMatrix = 50834
ColorimetricReference = 50879
CameraCalibrationSignature = 50931
ProfileCalibrationSignature = 50932
AsShotProfileName = 50934
NoiseReductionApplied = 50935
ProfileName = 50936
ProfileHueSatMapDims = 50937
ProfileHueSatMapData1 = 50938
ProfileHueSatMapData2 = 50939
ProfileToneCurve = 50940
ProfileEmbedPolicy = 50941
ProfileCopyright = 50942
ForwardMatrix1 = 50964
ForwardMatrix2 = 50965
PreviewApplicationName = 50966
PreviewApplicationVersion = 50967
PreviewSettingsName = 50968
PreviewSettingsDigest = 50969
PreviewColorSpace = 50970
PreviewDateTime = 50971
RawImageDigest = 50972
OriginalRawFileDigest = 50973
SubTileBlockSize = 50974
RowInterleaveFactor = 50975
ProfileLookTableDims = 50981
ProfileLookTableData = 50982
OpcodeList1 = 51008
OpcodeList2 = 51009
OpcodeList3 = 51022
NoiseProfile = 51041
ZZZTestSlong1 = 60606
ZZZTestSlong2 = 60607
ZZZTestSByte = 60608
ZZZTestSShort = 60609
ZZZTestDFloat = 60610
class ExifIFD:
"""Exif tag number reference - Exif IFD"""
ExposureTime = 33434
FNumber = 33437
ExposureProgram = 34850
SpectralSensitivity = 34852
ISOSpeedRatings = 34855
OECF = 34856
SensitivityType = 34864
StandardOutputSensitivity = 34865
RecommendedExposureIndex = 34866
ISOSpeed = 34867
ISOSpeedLatitudeyyy = 34868
ISOSpeedLatitudezzz = 34869
ExifVersion = 36864
DateTimeOriginal = 36867
DateTimeDigitized = 36868
OffsetTime = 36880
OffsetTimeOriginal = 36881
OffsetTimeDigitized = 36882
ComponentsConfiguration = 37121
CompressedBitsPerPixel = 37122
ShutterSpeedValue = 37377
ApertureValue = 37378
BrightnessValue = 37379
ExposureBiasValue = 37380
MaxApertureValue = 37381
SubjectDistance = 37382
MeteringMode = 37383
LightSource = 37384
Flash = 37385
FocalLength = 37386
Temperature = 37888
Humidity = 37889
Pressure = 37890
WaterDepth = 37891
Acceleration = 37892
CameraElevationAngle = 37893
SubjectArea = 37396
MakerNote = 37500
UserComment = 37510
SubSecTime = 37520
SubSecTimeOriginal = 37521
SubSecTimeDigitized = 37522
FlashpixVersion = 40960
ColorSpace = 40961
PixelXDimension = 40962
PixelYDimension = 40963
RelatedSoundFile = 40964
InteroperabilityTag = 40965
FlashEnergy = 41483
SpatialFrequencyResponse = 41484
FocalPlaneXResolution = 41486
FocalPlaneYResolution = 41487
FocalPlaneResolutionUnit = 41488
SubjectLocation = 41492
ExposureIndex = 41493
SensingMethod = 41495
FileSource = 41728
SceneType = 41729
CFAPattern = 41730
CustomRendered = 41985
ExposureMode = 41986
WhiteBalance = 41987
DigitalZoomRatio = 41988
FocalLengthIn35mmFilm = 41989
SceneCaptureType = 41990
GainControl = 41991
Contrast = 41992
Saturation = 41993
Sharpness = 41994
DeviceSettingDescription = 41995
SubjectDistanceRange = 41996
ImageUniqueID = 42016
CameraOwnerName = 42032
BodySerialNumber = 42033
LensSpecification = 42034
LensMake = 42035
LensModel = 42036
LensSerialNumber = 42037
Gamma = 42240
class GPSIFD:
"""Exif tag number reference - GPS IFD"""
GPSVersionID = 0
GPSLatitudeRef = 1
GPSLatitude = 2
GPSLongitudeRef = 3
GPSLongitude = 4
GPSAltitudeRef = 5
GPSAltitude = 6
GPSTimeStamp = 7
GPSSatellites = 8
GPSStatus = 9
GPSMeasureMode = 10
GPSDOP = 11
GPSSpeedRef = 12
GPSSpeed = 13
GPSTrackRef = 14
GPSTrack = 15
GPSImgDirectionRef = 16
GPSImgDirection = 17
GPSMapDatum = 18
GPSDestLatitudeRef = 19
GPSDestLatitude = 20
GPSDestLongitudeRef = 21
GPSDestLongitude = 22
GPSDestBearingRef = 23
GPSDestBearing = 24
GPSDestDistanceRef = 25
GPSDestDistance = 26
GPSProcessingMethod = 27
GPSAreaInformation = 28
GPSDateStamp = 29
GPSDifferential = 30
GPSHPositioningError = 31
コメント