遥感小白必看:RPC 和 RPB 文件到底是什么?怎么用 Python 轻松读取它们?

遥感小白必看:RPC 和 RPB 文件到底是什么?怎么用 Python 轻松读取它们?
ytkz大家好,我是你们的老朋友——遥感码农小白。
之前有人问我:“RPC 文件是什么鬼?RPB 又是啥?为什么有的卫星给 .rpc,有的是 .rpb?读取的时候总是报错怎么办?”
今天这篇就来手把手帮你搞懂这件事,保证零基础也能看懂、能上手!
一、先搞清楚:RPC 是什么?为什么遥感离不开它?
简单说,RPC = Rational Polynomial Coefficients(有理多项式系数)。
卫星拍下来的影像,原始的样子是“歪的、斜的、扭曲的”,因为卫星在飞、地球是圆的、镜头有畸变……直接拿来用会叠不对地图。
RPC 就是卫星厂家提前帮你算好的一组“魔法公式”,告诉你:
“图像上的这个像素(行、列),对应地球上的哪个经纬度(+高度)?”
用这组系数,你就能做:
- 几何校正(让影像“躺平”)
- 正射纠正(去掉地形起伏影响)
- 影像配准、镶嵌、入库……
一句话:没有 RPC,你就没法把卫星照片精准叠到 Google 地图上。
我遇到不提供RPC文件的卫星影像有:
风云4号静止卫星
日本葵花8静止卫星
sentinel3海洋卫星
它们的特点是nc数据,它们把定位信息直接写在nc文件内部,要通过插值实现几何定位。
landsat系列也不提供rpc文件,因为usgs直接做好了系统级的几何粗校正,也就是说,我们下载的landsat影像已经做好了粗几何校正无需rpc文件。
二、RPC 文件 vs RPB 文件:长得一样,其实差不多
| 项目 | RPC 文件 | RPB 文件 |
|---|---|---|
| 全称 | Rational Polynomial Coefficients | Rapid Positioning Binary(其实也是文本) |
| 常见卫星 | 北京三号、吉林一号 | 高分1号、高分2号、高分3号、高分6号、高分7号、DigitalGlobe(WorldView、QuickBird、GeoEye) |
| 文件扩展名 | .rpc / .txt | .rpb |
| 格式 | 键值对 + 系数列表 | 直接四个括号系数列表 + 少量元数据 |
| 内容示例 | LINE_OFF = 2457.5 SAMP_NUM_COEFF = (1.23, -0.45, …) | satId = “WV02”; LINE_NUMCOEF = ( … ); |
核心结论:两者本质都是存同一套 RPC 系数,只是写法不同。
- RPC 更规范,像配置文件
- RPB 更“简陋”,像数组
.rpc文件如下:
.rpb文件如下:
截图没截全,不过不影响理解。你们对比这两张图就能看出区别了。
所以写代码的时候,得同时兼容两种格式。
三、新手最头疼的:怎么用 Python 把它们读出来?
好消息:用几行代码就能搞定。
坏消息:不同厂家写法五花八门,一不小心就解析失败。
下面给你们一个我自己写的“万能解析器”,直接复制就能用。
def extract_coefficients(text: str):
"""
从 RPB 或类似格式的文本中提取四个系数数组(每个应有 20 个浮点数)。
返回:
四个浮点数列表,按顺序对应:
[LINE_NUM_COEFF, LINE_DEN_COEFF, SAMP_NUM_COEFF, SAMP_DEN_COEFF]
"""
# 匹配括号内的内容(非贪婪,允许多行)
pattern = r'\(\s*([^)]+?)\s*\)'
matches = re.findall(pattern, text, re.DOTALL)
if len(matches) != 4:
raise ValueError(f"预期找到 4 个系数数组,实际找到 {len(matches)} 个")
result = []
for block in matches:
# 清理多余空白、换行、逗号分隔
cleaned = re.sub(r'\s+', ' ', block.strip())
numbers_str = [x.strip() for x in cleaned.split(',') if x.strip()]
if len(numbers_str) != 20:
raise ValueError(f"系数块长度不为 20,得到 {len(numbers_str)} 个元素:\n{cleaned[:100]}...")
try:
floats = [float(x) for x in numbers_str]
result.append(floats)
except ValueError as e:
raise ValueError(f"系数转换失败:{e}\n原始块:{cleaned[:200]}...")
return result
def parse_rpc_file(rpc_path: str):
"""
通用解析 RPC 或 RPB 文件,返回标准化的字典。
支持标准 RPC 键值格式、带索引格式、RPB 纯括号格式。
返回的 data 字典中至少包含:
- LINE_OFF, SAMP_OFF, LAT_OFF, LONG_OFF, HEIGHT_OFF
- LINE_SCALE, SAMP_SCALE, LAT_SCALE, LONG_SCALE, HEIGHT_SCALE
- LINE_NUM_COEFF, LINE_DEN_COEFF, SAMP_NUM_COEFF, SAMP_DEN_COEFF (均为 list[float])
"""
with open(rpc_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
data = {}
# ------------------- 步骤1:清理文本 -------------------
lines = content.splitlines()
cleaned_lines = []
for line in lines:
# 去注释、单位、结尾分号
line = line.split('//')[0].split(';')[0]
line = re.sub(r'(pixels|degrees|meters)', '', line, flags=re.I)
cleaned_lines.append(line.strip())
# ------------------- 步骤2:标准键值对解析 -------------------
key_map = {
'LINEOFFSET': 'LINE_OFF', 'LINE_OFF': 'LINE_OFF',
'SAMPOFFSET': 'SAMP_OFF', 'SAMP_OFF': 'SAMP_OFF', 'SAMPLEOFFSET': 'SAMP_OFF',
'LATOFFSET': 'LAT_OFF', 'LAT_OFF': 'LAT_OFF',
'LONGOFFSET': 'LONG_OFF', 'LONG_OFF': 'LONG_OFF',
'HEIGHTOFFSET': 'HEIGHT_OFF', 'HEIGHT_OFF': 'HEIGHT_OFF',
'LINESCALE': 'LINE_SCALE', 'LINE_SCALE': 'LINE_SCALE',
'SAMPSCALE': 'SAMP_SCALE', 'SAMP_SCALE': 'SAMP_SCALE',
'LATSCALE': 'LAT_SCALE', 'LAT_SCALE': 'LAT_SCALE',
'LONGSCALE': 'LONG_SCALE', 'LONG_SCALE': 'LONG_SCALE',
'HEIGHTSCALE': 'HEIGHT_SCALE', 'HEIGHT_SCALE': 'HEIGHT_SCALE',
# 系数键(部分厂商用不同写法)
'LINENUMCOEF': 'LINE_NUM_COEFF', 'LINE_NUM_COEFF': 'LINE_NUM_COEFF',
'LINEDENCOEF': 'LINE_DEN_COEFF', 'LINE_DEN_COEFF': 'LINE_DEN_COEFF',
'SAMPNUMCOEF': 'SAMP_NUM_COEFF', 'SAMP_NUM_COEFF': 'SAMP_NUM_COEFF',
'SAMPDENCOEF': 'SAMP_DEN_COEFF', 'SAMP_DEN_COEFF': 'SAMP_DEN_COEFF',
}
for line in cleaned_lines:
if not line:
continue
parts = re.split(r'[:=]', line, maxsplit=1)
if len(parts) == 2:
k = parts[0].strip().upper()
v = parts[1].strip()
if k in key_map:
std_key = key_map[k]
# 如果是系数,保留原始字符串,后续统一处理
data[std_key] = v
# ------------------- 步骤3:系数兜底解析(正则匹配括号) -------------------
coeff_keys = ['LINE_NUM_COEFF', 'LINE_DEN_COEFF', 'SAMP_NUM_COEFF', 'SAMP_DEN_COEFF']
for std_key in coeff_keys:
if std_key in data and isinstance(data[std_key], str) and len(data[std_key]) > 50:
# 已经有内容,且看起来像系数字符串,跳过
continue
# 尝试匹配 XXX = ( ... )
for raw_key in [k for k, v in key_map.items() if v == std_key]:
pattern = re.compile(
re.escape(raw_key) + r'\s*[:=]\s*[(\[]\s*(.*?)\s*[)\]]',
re.DOTALL | re.IGNORECASE
)
match = pattern.search(content)
if match:
val = match.group(1).replace('\n', ' ').replace('\r', ' ')
data[std_key] = val
break
# ------------------- 步骤4:带索引的逐行解析(极少数情况) -------------------
for std_key in coeff_keys:
if std_key in data:
continue
collected = {}
for line in cleaned_lines:
parts = re.split(r'[:=]', line, maxsplit=1)
if len(parts) != 2:
continue
k = parts[0].strip().upper()
v = parts[1].strip()
m = re.match(r'^([A-Z]+(?:NUM|DEN)COEF)_?(\d+)$', k)
if m and key_map.get(m.group(1)) == std_key:
idx = int(m.group(2))
if 1 <= idx <= 20:
collected[idx] = v
if len(collected) == 20:
data[std_key] = " ".join(str(collected[i]) for i in range(1, 21))
# ------------------- 步骤5:RPB 格式兜底 -------------------
# 如果四个系数都没解析出来,或者 LINE_NUM_COEFF 明显太短
missing_coeffs = any(k not in data for k in coeff_keys)
short_coeff = 'LINE_NUM_COEFF' in data and isinstance(data['LINE_NUM_COEFF'], str) and len(
data['LINE_NUM_COEFF'].split()) < 15
if missing_coeffs or short_coeff:
try:
coef_lists = extract_coefficients(content)
if len(coef_lists) == 4:
data['LINE_NUM_COEFF'] = coef_lists[0]
data['LINE_DEN_COEFF'] = coef_lists[1]
data['SAMP_NUM_COEFF'] = coef_lists[2]
data['SAMP_DEN_COEFF'] = coef_lists[3]
print("检测到 RPB 格式,已从括号中提取系数")
except Exception as e:
print(f"尝试解析 RPB 格式失败:{e}")
# ------------------- 最终转换:字符串 → float 列表 -------------------
for key in coeff_keys:
if key in data and isinstance(data[key], str):
try:
nums = [float(x) for x in re.findall(r'[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?', data[key])]
if len(nums) == 20:
data[key] = nums
else:
print(f"警告:{key} 解析得到 {len(nums)} 个系数(应为20)")
except:
print(f"警告:无法将 {key} 转换为浮点数列表")
# 偏移和尺度保持字符串或转 float
for k in ['LINE_OFF', 'SAMP_OFF', 'LAT_OFF', 'LONG_OFF', 'HEIGHT_OFF',
'LINE_SCALE', 'SAMP_SCALE', 'LAT_SCALE', 'LONG_SCALE', 'HEIGHT_SCALE']:
if k in data and isinstance(data[k], str):
try:
data[k] = float(data[k])
except:
pass
return data
最后送新人一句话
遥感入门最坑的不是算法,而是数据格式千奇百怪。
喜欢就点个在看+分享给同路的遥感小伙伴吧!







