Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/326.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用Python读取CR2(原始佳能图像)标题_Python_Image Processing_Metadata_Binary Data - Fatal编程技术网

使用Python读取CR2(原始佳能图像)标题

使用Python读取CR2(原始佳能图像)标题,python,image-processing,metadata,binary-data,Python,Image Processing,Metadata,Binary Data,我试图提取从CR2(原始图片的佳能格式)拍摄照片的日期/时间 我知道,我知道我可以使用Python模块从二进制缓冲区中提取片段 简单地说,规范中说,在Tag0x0132/306中,我可以找到一个长度为20的字符串—日期和时间 我尝试使用以下方法获取该标签: struct.unpack_from(20*'s', buffer, 0x0132) 但我明白了 ('\x00', '\x00', "'", '\x88, ...[and more crap]) 有什么想法吗 编辑 非

我试图提取从CR2(原始图片的佳能格式)拍摄照片的日期/时间

我知道,我知道我可以使用Python模块从二进制缓冲区中提取片段

简单地说,规范中说,在Tag
0x0132/306
中,我可以找到一个长度为20的字符串—日期和时间

我尝试使用以下方法获取该标签:

struct.unpack_from(20*'s', buffer, 0x0132)
但我明白了

('\x00', '\x00', "'", '\x88, ...[and more crap])
有什么想法吗

编辑


非常感谢您的努力!答案是惊人的,我学到了很多关于处理二进制数据的知识。

0x0132不是偏移量,而是日期的标签号。CR2或TIFF分别是基于目录的格式。您必须根据您要查找的(已知)标记查找条目

编辑: 好的,首先,您必须读取文件数据是使用小端还是大端格式保存的。头八个字节指定标头,头两个字节指定endianness。Python的struct模块允许您通过在格式字符串前面加上“”或“”来处理小的和大端数据。因此,假设
data
是一个包含CR2图像的缓冲区,您可以通过

header = data[:8]
endian_flag = "<" if header[:2] == "II" else ">"
您现在可以继续阅读第一篇IFD。您将在文件中指定的偏移量处找到目录中的条目数,该偏移量为两个字节宽。因此,您可以使用以下方法读取第一个IFD中的条目数:

number_of_entries = struct.unpack("{0}H".format(endian_flag), data[ifd_offset:ifd_offset+2])[0]
字段条目的长度为12字节,因此可以计算IFD的长度。在\u条目数*12字节之后,将有另一个4字节长的偏移量,告诉您在哪里查找下一个目录。这就是处理TIFF和CR2图像的基本方法

这里的“魔力”是要注意,对于每个12字节的字段条目,前两个字节将是标记ID。这就是您查找标记0x0132的地方。因此,如果您知道第一个IFD从文件中的IFD_偏移量开始,您可以通过以下方式扫描第一个目录:

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

显然,您希望将该解包重构为一个公共函数,并可能将整个格式封装到一个漂亮的类中,但这超出了本示例的范围。您还可以通过将多个格式字符串组合成一个字符串来缩短解包过程,从而生成一个较大的元组,其中包含可以解包为不同变量的所有字段,为了清楚起见,我省略了这些字段。

您是否考虑了应该(根据规范)位于您所谈论的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"
从结构导入*
已识别的_标记={
0x0100:“图像宽度”,
0x0101:“imageLength”,
0x0102:“bitsPerSample”,
0x0103:'压缩',
0x010f:“制造”,
0x0110:'型号',
0x0111:“条带偏移量”,
0x0112:'方向',
0x0117:'条带字节计数',
0x011a:“X分辨率”,
0x011b:“yResolution”,
0x0128:“resolutionUnit”,
0x0132:“日期时间”,
0x8769:“EXIF”,
0x8825:“GPS数据”};
def GetHeaderFromCR2(缓冲区):
#将标题解包为元组
页眉=从('hhlhbl',缓冲区)解包
打印“\n字节顺序=0x%04X”%header[0]
打印“tiff\U magic\U word=%d”%header[1]
打印“tiff_偏移量=0x%08X”%标头[2]
打印“cr2\u magic\u word=%d”%header[3]
打印“cr2\U主版本=%d”%header[4]
打印“cr2次要版本=%d”%header[5]
打印“原始\u ifd\u偏移量=0x%08X\n”%header[6]
回流集管
def FindDateTimeOffsetFromCR2(缓冲区、ifd_偏移、endian_标志):
#读取IFD#0中的条目数
(条目的数量,)=从(endian_标志+'H',缓冲区,ifd_偏移量)解包
打印“图像文件目录0包含%d个条目\n”%num\u个条目
#计算出日期和时间的存储位置
datetime_偏移量=-1
#检查所有条目以查找datetime字段
打印“id |类型|编号|值”
对于范围内的项目数量(0,项目数量):
#抓住这个IFD条目
(标记id、标记类型、值的个数、值)=从(endian标记+HHLL',缓冲区、ifd偏移量+2+条目个数*12)解包
#打印出条目以获取信息
打印“%04X |%04X |%08X |%08X”%(标记id、标记类型、值的数量、值),
如果识别的标签中有标签id:
打印识别的标签[标签id]
#如果这是我们要找的日期时间,记下偏移量
如果标记_id==0x0132:
断言标记类型==2
断言值的数量=20
datetime_offset=值
返回日期时间偏移量
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
打开(“IMG_6113.CR2”、“rb”)作为f:
#读取文件的前1kb应该足以找到日期/时间
缓冲区=f.read(1024)
#抓住收割台的各个部分
(字节顺序、tiff魔术字、tiff魔术字偏移量、cr2魔术字、cr2魔术字版本、cr2魔术字版本、cr2魔术字版本、原始ifd魔术字偏移量)=GetHeaderFromCR2(缓冲区)
#设置endian标志
endian_标志=“@”
如果字节_顺序==0x4d:
#摩托罗拉格式
endian_标志='>'
elif字节_顺序==0x4949:
#英特尔格式
endian_flag='我发现EXIF.py-from从.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)

谢谢@Jon Cage。恐怕我不知道怎么做。我怎样才能找出哪个块在前面?根据Jim的答案进行endian检查是值得的,以获得更可靠的解决方案,但我的示例有效
    2011:04:30 11:08:44
    (2011, 4, 30, 11, 8, 44, 5, 120, -1)