记录一次手动读取BigTiff文件(Python)

BigTiff规范
http://bigtiff.org/#INTERNAL_OBJECT_CHANGES
https://www.awaresystems.be/imaging/tiff/bigtiff.html

错误信息

由于使用ENVIimageiogdal读取都会出错,因此尝试手动读取文件解析原因,尝试修复。
报错信息:

from osgeo import gdal

path = r'C:\img.tif'
dataset = gdal.Open(path)
img = dataset.ReadAsArray()
ERROR 1: LZWDecode:Corrupted LZW table at scanline 26848
ERROR 1: TIFFReadEncodedStrip() failed.
ERROR 1: C:\img.tif, band 1: IReadBlock failed at X offset 0, Y offset 26848: TIFFReadEncodedStrip() failed.

读取文件头

以二进制读取文件,由于一次读取全部数据(2.8G)会导致后续步骤严重卡顿,故这里只读取这一步所需的数据

# 读取tif文件
file_offset = 0    # 读取起始位置(Bytes)
file_len = 1000000     # 读取长度(Bytes)
with open(path, 'rb') as f:
    f.seek(file_offset, 0)
    b = f.read(file_len)

在这里插入图片描述
根据BigTiff标准,文件头的16位(bit),即2字节(byte),为0x4D4D,转为ASCII码即为II

In:  b[:2]
Out: b'II'

我本来以为是GeoTIFF,结果发现不对劲,绕回来看才发现版本号是43而不是42

In: b[2:4]
Out: b'+\x00'

由于版本号是整数格式,因此需要解码才能看到

import sys
import struct

# 解码二进制数据
def dec(x, type='i'):
    if type == 'i':  # int
        return int.from_bytes(x, byteorder=sys.byteorder)
    elif type == 'f':  # float
        if len(x) == 4:
            return struct.unpack('f', x)[0]
        elif len(x) == 8:
            return struct.unpack('d', x)[0]
    elif type == 's':  # string
        return struct.unpack('s', x)[0].decode()
    else:
        return struct.unpack(type, x)[0]
In:  dec(b[2:4])
Out: 43

头文件中除了确定版本号外,最重要的就是获取第一个目录位置(offset):

offset_dir1 = dec(b[8:8 + 8])	# 16

读取目录

在这里插入图片描述
BigTiff中每个目录头8个字节表示目录条目数量,一个条目占用20字节,每个条目中的信息及其分布如上图所示。

# 读取目录
len_num = 8
len_entry = 20
num_entries = dec(b[offset_dir1:offset_dir1 + len_num])
tags = []
for offset_entry in range(offset_dir1 + len_num, offset_dir1 + len_num + num_entries * len_entry, len_entry):
    entries = b[offset_entry:offset_entry + len_entry]
    tag = dec(entries[0:2])
    type = dec(entries[2:4])
    count = dec(entries[4:12])
    value = entries[12:20]
    tags.append((tag, type, count, value))

# 下一个目录的位置(0即无下个目录)
offset_dir = dec(b[offset_dir1 + len_num + num_entries * len_entry:
                   offset_dir1 + len_num + num_entries * len_entry + 8])

得到的目录中的条目如下所示

# Tag编号,数据类型,数据量,值
(256, 3, 1, b'd\xc9\x00\x00\x00\x00\x00\x00')
(257, 3, 1, b'[\xca\x00\x00\x00\x00\x00\x00')
(258, 3, 4, b'\x08\x00\x08\x00\x08\x00\x08\x00')
(259, 3, 1, b'\x05\x00\x00\x00\x00\x00\x00\x00')
(262, 3, 1, b'\x02\x00\x00\x00\x00\x00\x00\x00')
(273, 16, 51803, b'tT\x06\x00\x00\x00\x00\x00')
(277, 3, 1, b'\x04\x00\x00\x00\x00\x00\x00\x00')
(278, 3, 1, b'\x01\x00\x00\x00\x00\x00\x00\x00')
(279, 16, 51803, b'\x9c\x01\x00\x00\x00\x00\x00\x00')
(284, 3, 1, b'\x01\x00\x00\x00\x00\x00\x00\x00')
(305, 2, 12, b'L\xa7\x0c\x00\x00\x00\x00\x00')
(317, 3, 1, b'\x02\x00\x00\x00\x00\x00\x00\x00')
(338, 3, 1, b'\x02\x00\x00\x00\x00\x00\x00\x00')
(339, 3, 4, b'\x01\x00\x01\x00\x01\x00\x01\x00')
(33550, 12, 3, b'X\xa7\x0c\x00\x00\x00\x00\x00')
(33922, 12, 6, b'p\xa7\x0c\x00\x00\x00\x00\x00')
(34735, 3, 32, b'\xa0\xa7\x0c\x00\x00\x00\x00\x00')
(34737, 2, 80, b'\xe0\xa7\x0c\x00\x00\x00\x00\x00')
(42113, 2, 7, b'-10000\x00\x00')

读取条目

这里查找条目对应的含义并读取:


# 读取目录条目
def get_TLO_by_tag_num(tag_num):
    for tag, type, count, value in tags:
        if tag == tag_num:
            if type == 2:   # ASCII
                if count * 1 > 8:   # value 作为偏置定位数据
                    offset = dec(value)
                    value = b[offset:offset + 1 * count]
                s = [dec(value[i:i+1], 's') for i in range(count-1)]
                string = ''
                for i in s:
                    string += i
                return string

            elif type == 3:   # SHORT # 16-bit unsigned integer
                if count * 2 > 8:
                    offset = dec(value)
                    value = b[offset:offset + 2 * count]
                return [dec(value[i*2:i*2+2]) for i in range(count)]

            elif type == 4:   # LONG # 32-bit unsigned integer
                if count * 4 > 8:
                    offset = dec(value)
                    value = b[offset:offset + 4 * count]
                return [dec(value[i*4:i*4+4]) for i in range(count)]

            elif type == 11:    # FLOAT # 32-bit IEEE floating point
                if count * 4 > 8:
                    offset = dec(value)
                    value = b[offset:offset + 4 * count]
                return [dec(value[i*4:i*4+4], 'f') for i in range(count)]

            elif type == 12:    # DOUBLE # 64-bit IEEE floating point
                if count * 8 > 8:
                    offset = dec(value)
                    value = b[offset:offset + 8 * count]
                return [dec(value[i*8:i*8+8], 'd') for i in range(count)]

            elif type == 16:  # RATIONAL # 64-bit unsigned fraction
                if count * 8 > 8:
                    offset = dec(value)
                    value = b[offset:offset + 8 * count]
                return [dec(value[i*8:i*8+8], 'i') for i in range(count)]

            else:
                raise NotImplementedError
    raise Exception('tag not found')


# 解析目录条目
width = get_TLO_by_tag_num(256)[0]
height = get_TLO_by_tag_num(257)[0]
bits_per_sample = get_TLO_by_tag_num(258)
compression = get_TLO_by_tag_num(259)[0]   # 图像压缩方案
PhotometricInterpretation = get_TLO_by_tag_num(262)[0]     # 图像色彩模型
StripOffsets = get_TLO_by_tag_num(273)  # 每个条带的字节偏移量(数据存储位置)
SamplesPerPixel = get_TLO_by_tag_num(277)[0]  # 每个像素的通道数
RowsPerStrip = get_TLO_by_tag_num(278)[0]  # 每个条带的行数
StripByteCounts = get_TLO_by_tag_num(279)  # 每个条带的字节数(压缩后)
PlanarConfiguration = get_TLO_by_tag_num(284)[0]  # 图像数据的存储方式
Software = get_TLO_by_tag_num(305)  # 软件信息
Predictor = get_TLO_by_tag_num(317)[0]  # 差分预测模式(用于LZW编码)
ExtraSamples = get_TLO_by_tag_num(338)[0]  # 在每个像素的颜色通道中,额外的通道数
SampleFormat = get_TLO_by_tag_num(339)  # 指定如何解释像素中的每个数据样本
ModelPixelScaleTag = get_TLO_by_tag_num(33550)  # 像素大小   用于可互换的 GeoTIFF 文件
ModelTiepointTag = get_TLO_by_tag_num(33922)  # 像素总体偏移   用于可互换的 GeoTIFF 文件
GeoKeyDirectoryTag = get_TLO_by_tag_num(34735)  # 地理信息关键字目录   用于可互换的 GeoTIFF 文件
GeoAsciiParamsTag = get_TLO_by_tag_num(34737)  # 地理信息 ASCII 参数   用于可互换的 GeoTIFF 文件
GDAL_NODATA = get_TLO_by_tag_num(42113)  # 由 GDAL 库使用,包含 ASCII 编码的 nodata 或背景像素值。

结果如下:

width: 51556
height: 51803
bits_per_sample: [8, 8, 8, 8]
compression: 5
PhotometricInterpretation: 2
StripOffsets: [829488, 830262, 831057, 832155, 833266, ...]
SamplesPerPixel: 4
RowsPerStrip: 1
StripByteCounts: [774, 795, 1098, 1111, 1122, ...]
PlanarConfiguration: 1
Software: pix4dmapper
Predictor: 2
ExtraSamples: 2
SampleFormat: [1, 1, 1, 1]
ModelPixelScaleTag: [0.030000000000000000, 0.030000000000000000, 0.0]
ModelTiepointTag: [0.0, 0.0, 0.0, 123456.78901, 234567.89012, 0.0]
GeoKeyDirectoryTag: [1, 1, 0, 7, 1024, 0, 1, 1, 1025, 0, 1, 1, 1026, 34737, 41, 0, 2049, 34737, 38, 41, 2054, 0, 1, 9102, 3072, 0, 1, 4546, 3076, 0, 1, 9001]
GeoAsciiParamsTag: CGCS2000 / 3-degree Gauss-Kruger CM 111E|China Geodetic Coordinate System 2000|
GDAL_NODATA: -10000

目录中有GeoKeyDirectoryTag,这代表该文件还有地理信息拓展,这里不展开。

读取图像数据

使用gdal读取出现错误的位置是26848行,因此读取该行二进制数据

# 读取tif文件
file_offset = 1600000000    # 读取起始位置(Bytes)
file_len = 100000000     # 读取长度(Bytes)
with open(path, 'rb') as f:
    f.seek(file_offset, 0)
    b = f.read(file_len)

# 错误位置(行)
errp = 26848
# 读取错误行
err_line = b[StripOffsets[l]-file_offset:StripOffsets[l]+StripByteCounts[l]-file_offset]

通过解码(compression: 5,为LZW压缩)得到该行数据。

### 回答1: GDAL (Geospatial Data Abstraction Library)是一个在GIS领域非常流行的开源库,用于读写几乎所有的栅格和矢量数据格式。在这其中,GDAL也支持读取BigTIFF格式的栅格数据。 BigTIFF是一种扩展了TIFF(Tagged Image File Format)格式的大型栅格数据格式,用于存储高分辨率、多波段的卫星遥感影像等大型栅格数据。正常的TIFF格式规定了文件的大小不超过4GB,而BigTIFF则支持存储超出4GB的数据,使得对于大型栅格数据集的读取和处理成为可能。 要使用GDAL读取BigTIFF,需要安装GDAL库,并且使用GDAL提供的命令行工具或API接口进行文件读取。可以直接使用gdalinfo命令查看BigTIFF文件的相关信息: ```gdalinfo filename.tif``` 其中,filename.tif就是要查看的BigTIFF文件的名称。可以在命令行中看到该文件的基本信息,包括数据类型、地理坐标等信息。如果要在代码中使用GDAL库读取BigTIFF,则需要根据GDAL提供的API进行编程操作。 总之,GDAL为读取BigTIFF格式的栅格数据提供了底层支持,通过它可以方便地获取地图数据中的地理信息,进行地图数据的预处理、图像处理和应用开发。 ### 回答2: GDAL是一个用于读取和写入各种GIS格式文件的开源库。当需要读取大于4GB大小的TIFF格式文件时,需要用到GDAL读取BigTIFF。 首先,确保已经安装了最新版本的GDAL库。然后,使用以下Python代码来读取BigTIFF文件: ```python import gdal #打开BigTIFF文件 dataset = gdal.Open('bigtiff_file.tif', gdal.GA_ReadOnly) #获取文件信息 cols = dataset.RasterXSize rows = dataset.RasterYSize bands = dataset.RasterCount print("图像尺寸:{} x {}, 波段数:{}".format(cols, rows, bands)) #读取数据 band = dataset.GetRasterBand(1) data = band.ReadAsArray(0, 0, cols, rows) #释放资源 dataset = None ``` 此代码打开名为“bigtiff_file.tif”的文件,并打印出图像的尺寸和波段信息。然后,它读取第一个波段的数据,并将其存储在一个名为“data”的数组中。最后,它释放了所有资源。 需要注意的是,由于BigTIFF文件的尺寸巨大,读取和处理数据时需要充分利用计算机的内存和处理能力。建议使用64位的Python版本和大容量的计算机硬件。 ### 回答3: GDAL(Geospatial Data Abstraction Library)是一个在操作各种格式的遥感数据时常用的软件库。当遇到Bigtiff格式时,我们可以使用GDAL来轻松读取数据。 要使用GDAL读取Bigtiff格式的数据,我们可以按照以下步骤进行操作: 1. 安装GDAL。可以从官方网站下载适用于不同操作系统的安装程序或源代码。安装后,在命令行中输入“gdalinfo”可以检查是否安装成功。 2. 准备Bigtiff格式的文件。确保文件名称符合规范,并且文件中包含合法的数据。如果文件过大,可能需要额外的软件支持。 3. 在命令行中输入“gdal_translate filename outputname”命令,将Bigtiff格式的文件转换为GDAL支持的格式,比如GeoTIFF格式。 4. 在代码中使用GDAL的相关函数读取数据。比如使用GDALOpen函数打开已经转换为GeoTIFF格式的文件,使用GDALReadBlock函数读取数据块等。 总的来说,GDAL是一个非常强大和灵活的工具,可以轻松地读取各种遥感数据格式,包括Bigtiff。通过上述步骤,我们可以用GDAL读取Bigtiff文件,并进行必要的数据处理和分析。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值