[英]Reading a CR2 (Raw Canon Image) header using Python
我正在嘗試提取從CR2拍攝照片的日期/時間(原始照片的佳能格式)。
我知道CR2規范 ,我知道我可以使用Python struct模塊從二進制緩沖區中提取片段。
簡而言之,規范說在Tag 0x0132 / 306
我可以找到一個長度為20的字符串 - 日期和時間。
我嘗試使用以下方法獲取該標記:
struct.unpack_from(20*'s', buffer, 0x0132)
但我明白了
('\x00', '\x00', "'", '\x88, ...[and more crap])
有任何想法嗎?
編輯
非常感謝您的全力以赴! 答案是驚人的,我學到了很多關於處理二進制數據的知識。
你有沒有考慮到你所談論的IFD塊之前應該(根據規范)的標題?
我查看了規范,它說第一個IFD塊跟隨16字節頭。 因此,如果我們讀取字節16和17(偏移量為0x10十六進制),我們應該得到第一個IFD塊中的條目數。 然后我們只需搜索每個條目,直到找到匹配的標記ID(我讀它)給出了日期/時間字符串的字節偏移量。
這對我有用:
from struct import *
def FindDateTimeOffsetFromCR2( buffer, ifd_offset ):
# Read the number of entries in IFD #0
(num_of_entries,) = unpack_from('H', buffer, ifd_offset)
print "ifd #0 contains %d entries"%num_of_entries
# Work out where the date time is stored
datetime_offset = -1
for entry_num in range(0,num_of_entries-1):
(tag_id, tag_type, num_of_value, value) = unpack_from('HHLL', buffer, ifd_offset+2+entry_num*12)
if tag_id == 0x0132:
print "found datetime at offset %d"%value
datetime_offset = value
return datetime_offset
if __name__ == '__main__':
with open("IMG_6113.CR2", "rb") as f:
buffer = f.read(1024) # read the first 1kb of the file should be enough to find the date / time
datetime_offset = FindDateTimeOffsetFromCR2(buffer, 0x10)
print unpack_from(20*'s', buffer, datetime_offset)
我的示例文件的輸出是:
ifd #0 contains 14 entries
found datetime at offset 250
('2', '0', '1', '0', ':', '0', '8', ':', '0', '1', ' ', '2', '3', ':', '4', '5', ':', '4', '6', '\x00')
[編輯] - 修訂/更徹底的例子
from struct import *
recognised_tags = {
0x0100 : 'imageWidth',
0x0101 : 'imageLength',
0x0102 : 'bitsPerSample',
0x0103 : 'compression',
0x010f : 'make',
0x0110 : 'model',
0x0111 : 'stripOffset',
0x0112 : 'orientation',
0x0117 : 'stripByteCounts',
0x011a : 'xResolution',
0x011b : 'yResolution',
0x0128 : 'resolutionUnit',
0x0132 : 'dateTime',
0x8769 : 'EXIF',
0x8825 : 'GPS data'};
def GetHeaderFromCR2( buffer ):
# Unpack the header into a tuple
header = unpack_from('HHLHBBL', buffer)
print "\nbyte_order = 0x%04X"%header[0]
print "tiff_magic_word = %d"%header[1]
print "tiff_offset = 0x%08X"%header[2]
print "cr2_magic_word = %d"%header[3]
print "cr2_major_version = %d"%header[4]
print "cr2_minor_version = %d"%header[5]
print "raw_ifd_offset = 0x%08X\n"%header[6]
return header
def FindDateTimeOffsetFromCR2( buffer, ifd_offset, endian_flag ):
# Read the number of entries in IFD #0
(num_of_entries,) = unpack_from(endian_flag+'H', buffer, ifd_offset)
print "Image File Directory #0 contains %d entries\n"%num_of_entries
# Work out where the date time is stored
datetime_offset = -1
# Go through all the entries looking for the datetime field
print " id | type | number | value "
for entry_num in range(0,num_of_entries):
# Grab this IFD entry
(tag_id, tag_type, num_of_value, value) = unpack_from(endian_flag+'HHLL', buffer, ifd_offset+2+entry_num*12)
# Print out the entry for information
print "%04X | %04X | %08X | %08X "%(tag_id, tag_type, num_of_value, value),
if tag_id in recognised_tags:
print recognised_tags[tag_id]
# If this is the datetime one we're looking for, make a note of the offset
if tag_id == 0x0132:
assert tag_type == 2
assert num_of_value == 20
datetime_offset = value
return datetime_offset
if __name__ == '__main__':
with open("IMG_6113.CR2", "rb") as f:
# read the first 1kb of the file should be enough to find the date/time
buffer = f.read(1024)
# Grab the various parts of the header
(byte_order, tiff_magic_word, tiff_offset, cr2_magic_word, cr2_major_version, cr2_minor_version, raw_ifd_offset) = GetHeaderFromCR2(buffer)
# Set the endian flag
endian_flag = '@'
if byte_order == 0x4D4D:
# motorola format
endian_flag = '>'
elif byte_order == 0x4949:
# intel format
endian_flag = '<'
# Search for the datetime entry offset
datetime_offset = FindDateTimeOffsetFromCR2(buffer, 0x10, endian_flag)
datetime_string = unpack_from(20*'s', buffer, datetime_offset)
print "\nDatetime: "+"".join(datetime_string)+"\n"
0x0132不是偏移量,它是日期的標記號。 CR2或TIFF分別是基於目錄的格式。 您必須在給定您正在尋找的(已知)標簽的情況下查找條目。
編輯 :好的,首先,你必須閱讀文件數據是使用little或big-endian格式保存的。 前八個字節指定標頭,該標頭的前兩個字節指定字節順序。 Python的struct模塊允許您通過在格式字符串前加上“<”或“>”來處理小端和大端數據。 因此,假設data
是包含CR2圖像的緩沖區,則可以通過以下方式處理字節序
header = data[:8]
endian_flag = "<" if header[:2] == "II" else ">"
格式規范指出第一個圖像文件目錄以相對於文件開頭的偏移量開始,偏移量在標頭的最后4個字節中指定。 因此,要獲得第一個IFD的偏移量,您可以使用與此類似的行:
ifd_offset = struct.unpack("{0}I".format(endian_flag), header[4:])[0]
您現在可以繼續閱讀第一個IFD。 您將在目錄中找到指定偏移量的條目數,該文件寬度為兩個字節。 因此,您將使用以下方法讀取第一個IFD中的條目數:
number_of_entries = struct.unpack("{0}H".format(endian_flag), data[ifd_offset:ifd_offset+2])[0]
字段條目長度為12個字節,因此您可以計算IFD的長度。 在number_of_entries * 12個字節之后,將有另外4個字節的長偏移,告訴你在哪里尋找下一個目錄。 這基本上就是你如何處理TIFF和CR2圖像。
這里的“神奇”是要注意,對於每個12字節字段條目,前兩個字節將是標簽ID。 這就是你尋找你的標簽0x0132的地方。 因此,如果您知道第一個IFD從文件中的ifd_offset開始,您可以通過以下方式掃描第一個目錄:
current_position = ifd_offset + 2
for field_offset in xrange(current_position, number_of_entries*12, 12):
field_tag = struct.unpack("{0}H".format(endian_flag), data[field_offset:field_offset+2])[0]
field_type = struct.unpack("{0}H".format(endian_flag), data[field_offset+2:field_offset+4])[0]
value_count = struct.unpack("{0}I".format(endian_flag), data[field_offset+4:field_offset+8])[0]
value_offset = struct.unpack("{0}I".format(endian_flag), data[field_offset+8:field_offset+12])[0]
if field_tag == 0x0132:
# You are now reading a field entry containing the date and time
assert field_type == 2 # Type 2 is ASCII
assert value_count == 20 # You would expect a string length of 20 here
date_time = struct.unpack("20s", data[value_offset:value_offset+20])
print date_time
您顯然希望將解包重構為一個公共函數,並可能將整個格式包裝成一個很好的類,但這超出了本示例的范圍。 您還可以通過將多個格式字符串組合成一個來縮短解包,從而產生一個更大的元組,其中包含您可以解壓縮到不同變量的所有字段,為清楚起見,我省略了這些字節。
我發現來自https://github.com/ianare/exif-py的EXIF.py從.CR2文件中讀取EXIF數據。 似乎因為.CR2文件基於.TIFF文件EXIF.py是兼容的。
import EXIF
import time
# Change the filename to be suitable for you
f = open('../DCIM/100CANON/IMG_3432.CR2', 'rb')
data = EXIF.process_file(f)
f.close()
date_str = data['EXIF DateTimeOriginal'].values
# We have the raw data
print date_str
# We can now convert it
date = time.strptime(date_str, '%Y:%m:%d %H:%M:%S')
print date
這打印:
2011:04:30 11:08:44
(2011, 4, 30, 11, 8, 44, 5, 120, -1)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.