资讯动态

CARS开源摄影测量:从卫星立体像对到DSM的完整链路

发布时间:2026/9/11 2:58:47 来源:尧图企业网站定制
简介CARS 是一个基于 Python 的开源三维摄影测量工具专注于从卫星成像生成大规模数字表面模型可服务于遥感、地理信息系统、城市规划与环境监测等场景。资源包共 368 个文件大小约 47.23 MB除 Python 源码与 C 扩展外还包含 tif、nc 栅格样本、shp 矢量边界、json/npy 数据及 notebook 示例覆盖面广适合运行验证、算法研究与二次开发配套文档和安装配置展示了图像预处理、立体匹配、点云生成、表面建模的完整技术链路能帮助不同水平的读者缩短环境搭建与理解成本。目前已有 447 人学习下载适合希望借助卫星影像开展三维重建、深入掌握摄影测量原理并在 Python 项目中落地的工程技术人员。1. CARS 是什么从卫星影像到数字表面模型的那条链路接到一批立体卫星影像目标是拼出一幅 DSM最忌讳上来就背公式。第一件该做的事是放下文档里那些数学推导先找一份最小配置把一两景裁剪过的小图跑通。跑通之后你自然知道哪一步卡住再带着现象回头补理论。CARS 就是这条链路里值得先跑通的那个开源工具它把卫星摄影测量中从影像几何准备、核线重投影、稠密匹配到 DSM 生成的全流程做成了标准流水线让从业者不必从零实现半全局匹配和三角化。这个工具适合两类人。一类是被商业摄影测量软件的价格和密闭工作流限制住的遥感工程师另一类是想研究立体匹配算法、又不愿意重复造轮子的开发者和研究者。CARS 的价值不在于它比商业软件更准而在于它的每个中间产物都看得见、每个参数都可控、每一步都能用 Python 和 GDAL 单独验证。这篇文章顺着“从立体像对到 DSM”这条主线把安装、配置、调参与质量验证一次讲透。2. 从立体像对到 DSMCARS 的摄影测量流水线2.1 为什么单张卫星影像重建不出高程数字表面模型描述的是地表物体顶面到参考椭球或大地水准面的距离本质上是一个高程栅格。单张光学遥感影像是地表在焦平面上的中心投影像元亮度记录了辐射信息却没有直接记录地面点到传感器的距离。要恢复第三维必须有至少两个从不同视角观测同一区域的影像通过同一点在左右影像上的像方坐标差反算出地面坐标。这里需要先说清一个概念DSM 与 DEM 的差别。DEM 是裸地面高程DSM 则包含建筑物、树冠、桥梁等一切地表物体的顶部。用卫星影像做高分辨率 DSM 时树冠顶端之间的视差和建筑立面之间的视差可以被几十厘米或几米分辨率的传感器捕捉到这是 CARS 这类工具存在的基本前提。卫星影像上的几何关系通常不直接写外方位元素而是通过 RPC有理多项式系数模型来表达。RPC 是一组从像方坐标映射到地面坐标的多项式系数几乎所有现代高分辨率商业卫星影像都会随产品附带。CARS 在不做控制点平差的情况下就是利用这些 RPC 参数完成几何重建的。这也是为什么很多摄影测量处理流程的第一步永远是检查 RPC 是否完整。2.2 CARS 的四个核心处理阶段CARS 的设计思路可以概括为一句工程格言把不可控的匹配问题转化为可控的几何问题。整个流水线大致可以拆成四个阶段下面用表格列出每个阶段的职责和主要产出。阶段核心工作主要产出几何准备读取传感器模型RPC 或物理模型计算左右影像的地面覆盖范围与交会区域影像对交会区域掩膜、地面网格定义核线重采样将左右影像投影到核线几何下使同名点只沿扫描线方向偏移核线影像对稠密匹配对核线影像进行视差估计计算每个像元的水平位移视差图disparity map点云融合与 DSM 生成根据视差和 RPC 三角化地面点滤除粗差点并插值生成规则高程网格点云、DSM 栅格、置信度掩膜核线重采样是整个流水线里最具摄影测量特色的一步。原始左右影像的同名点可能同时存在行方向和列方向的偏移匹配时需要在二维平面搜索计算量大且容易误匹配。经过核线重采样后同名点被约束到同一条扫描线上立体匹配从二维搜索问题被降为一维搜索问题稳定性明显提升。CARS 在默认配置下会自动完成这一步但你需要在验证阶段检查输出的核线影像是否真的对齐我会在第四章和第五章详细写这一部分的检查方法。2.3 密集匹配的核心SGM 代价聚合CARS 默认使用的密集匹配算法是半全局匹配Semi-Global MatchingSGM。SGM 的思路分两步先计算每个像元在候选视差下的匹配代价再沿多个方向做动态规划形式的代价聚合抑制弱纹理区域的噪声。代价聚合这一步是最值得花时间理解的因为参数 P1 和 P2 直接影响 DSM 的平滑程度。下面是 SGM 代价聚合核心步骤的简化实现用 Numpy 表达“从左到右单路径聚合”的思路。import numpy as np def aggregate_cost(cost_volume, penalty11.0, penalty23.0): # cost_volume 形状: (height, width, disparity_range) h, w, d cost_volume.shape out cost_volume.copy() for row in range(h): # 从左向右扫描 for col in range(1, w): prev out[row, col - 1, :] cur cost_volume[row, col, :] best_prev np.min(prev) # SGM 聚合公式: 当前代价 min(prev, prevP1, prevP2) - best_prev smooth np.minimum(prev penalty1, np.minimum(prev penalty2, best_prev)) out[row, col, :] cur smooth - best_prev return out这段代码虽然只有不到十行但已经点出了 SGM 的两个关键设计。第一每个位置的聚合代价由相邻位置的匹配代价加上一个平滑惩罚构成惩罚1 惩罚微小视差变化惩罚2 惩罚视差不连续第二减去 best_prev 是为了防止代价数值沿路径无限增长。CARS 的实际实现会在多个方向上做类似聚合本段只是用于理解原理的最小示例。2.3.1 惩罚项参数对 DSM 形态的影响penalty1和penalty2的绝对大小不重要重要的是它们的比例关系。penalty2通常取penalty1的 3 到 10 倍这样能允许视差在物体边缘发生突变又能在平坦区域保持平滑。调参时如果 DSM 上出现大量细小毛刺应适当增大penalty2如果建筑物边缘被过度平滑、呈现“融化”感则应调小penalty2或适当减小窗口代价尺寸。3. 本地安装 CARS 的 Python 环境与最小化运行配置3.1 conda 环境与 Python 版本选择CARS 是一个 Python 包与 C 扩展混合的项目官方提供预编译发行包和 Docker 镜像两种主流使用方式。对于想改参数、看源码、调试匹配中间结果的开发者我一般推荐用 conda 建一个独立环境不要把它装进日常使用的 Python 环境里。conda create -n cars python3.10 gdal -c conda-forge -y conda activate cars pip install cars[full]第一行创建了一个 Python 3.10 环境同时把系统级 GDAL 的 Python 绑定一起装好。这里之所以直接用gdal而不是依赖后续 pip 自动安装是因为 CARS 依赖的 GDAL 版本必须与 numpy 和 rasterio 保持兼容conda-forge 会帮你解析好二进制依赖。第二行激活环境。第三行安装 CARS 及其完整扩展[full]通常表示附带可视化与性能分析相关的额外依赖。如果安装过程中发现网络源里的包版本过旧也应先确认是否有特殊渠道的构建版本再决定从源码构建。3.2 Docker 方式规避二进制依赖问题如果你不想在宿主机上折腾 GDAL 和 OpenCV 的二进制兼容Docker 是最省心的路径。官方镜像已经把编译好的可执行文件、Python 运行时和数据卷入口都封装好宿主机只需要装 Docker 引擎。docker run --rm -v $PWD:/work \ carslab/cars \ cars --config /work/config.json这条命令的含义是把当前目录挂载到容器内的/work路径--rm在容器退出后自动清理容器文件避免每次跑完留下废容器占磁盘。-v $PWD:/work中的$PWD会展开成当前目录的绝对路径容器内外的路径通过这个映射保持一致。注意carslab/cars只是一个示意镜像名实际拉取的镜像名应以你所依赖的发行渠道为准。Docker 方式的另一个好处是跑批量任务时资源隔离更可控但代价是宿主机与容器之间的大影像文件 IO 会成为瓶颈处理超大区域时优先把输入输出放在同一盘符下。3.3 三个最常见的安装与启动坑第一个坑是ImportError: libgdal.so.XX: cannot open shared object file。这说明系统里存在两个不同版本的 GDALconda 环境的库路径没有优先于系统路径。解决办法是确认在激活的 conda 环境内重新安装 gdal并检查LD_LIBRARY_PATH是否指向环境内 lib 目录。第二个坑是 Docker 容器默认内存不足。CARS 对较大影像对的内存消耗动辄以 GB 计默认容器内存限制会直接触发 OOM。运行前应在 docker run 命令中增加--shm-size4g和-m 8g之类的资源参数。第三个坑是命令行找不到cars可执行文件。安装完后如果提示 command not found优先改用python -m cars调用它不依赖.local/bin是否在 PATH 中。这一步不是逃避问题而是确认模块本身可以正常工作后再回过头修 PATH。4. 用真实卫星影像跑通一次 DSM 生成的配置与命令4.1 一套可直接复现的输入清单跑通 CARS 最少需要三样东西一对覆盖同一区域的立体影像、与该影像对应的 RPC 元数据、一个空的输出目录。它们的推荐组织方式如下。/data/base_stereo/ ├── left.tif # 左影像或前视影像 ├── right.tif # 右影像或后视影像 ├── left.rpc # 左影像 RPC 文件部分格式内嵌在 tif 中 └── output/这里并不是说 RPC 文件必须是独立文件。多数 GeoTIFF 发布产品会把 RPC 写在影像文件的元数据里使用gdalinfo就能读取。CARS 在加载影像时会自动检索内嵌 RPC只有遇到不含 RPC 的老旧影像才需要手动指定独立 RPC 文件。输入影像的位深建议保持一致如果一个是 16 bit 一个是 8 bit先统一转成 16 bit 再做匹配否则匹配代价的数值范围不一致会直接影响视差可靠性。4.2 用 gdalinfo 快速确认 RPC 是否完整在把影像交给 CARS 之前花十秒钟检查 RPC 是非常值得的。遇到没有 RPC 的影像后续所有步骤都会崩在几何准备阶段错误提示却往往滞后到三角化才出现。gdalinfo -listmdd left.tif | sed -n /RPC/,/^$/p如果输出中能看到LINE_OFF、SAMP_OFF、LAT_OFF、LONG_OFF、HEIGHT_OFF及三组多项式系数说明 RPC 信息完整。若输出为空则需要从影像分发方获取同级 RPC 文件或者用其他工具手写 RPC。对大多数用户来说缺少 RPC 的影像数据集就应该直接淘汰不要在此处浪费时间。4.3 最小可用配置文件与字段说明CARS 的配置是 JSON 格式下面这份配置可以算是最小可用骨架适合先跑小图验证环境。{ input: { sensor1: /data/base_stereo/left.tif, sensor2: /data/base_stereo/right.tif, no_data: 0 }, output: /data/base_stereo/output, pipeline: { resolution: 1.0, grid_points: 10000, mask: null } }配置里的input.sensor1和input.sensor2分别是左右影像的绝对路径。no_data的值要与影像中的无效像元值一致通常为 0 或 -9999这一步防止空值像元参与匹配。output是 DSM、点云和日志的存放目录。pipeline.resolution是 DSM 输出分辨率单位为米1.0表示输出像元大小为 1 米这个值不是随意设定的它决定了匹配点云的密度和内存占用。grid_points是地面网格划分的参数数值越大单个网格块内参与计算的像元越少内存压力越小但调度开销也会上升。如果影像文件较大建议先裁剪一小块 1024x1024 的区域来跑通这个配置确认输出文件和日志都正常后再逐渐扩大范围。注意不同版本对配置字段名可能略有调整运行前用cars --config 配置路径启动时如果报 schema 错误修改字段名即可。4.4 执行 DSM 生成与目录产物解读配置文件准备好后用下面的命令启动作业。python -m cars /data/base_stereo/config.jsonCARS 会在终端滚动输出日志关键阶段会打印类似extract、rectify、matching、triangulate的进度关键字。运行结束后输出目录下会至少出现以下几类产物。产物说明常见问题dsm_*.tif最终的 DSM 栅格边缘区域可能出现细长三角是无数据带point_cloud_*.laz彩色点云无色彩时会退化为黑白点云disparity_*.tif视差图水域镜面反射区视差多为 0各类日志文件全链路运行日志内存耗尽的日志出现在匹配阶段输出目录中的dsm_*.tif是你要的核心成果。建议第一时间用 QGIS 或 GDAL 打开检查值和范围是否合理。如果一个山谷区域的高程从 1700 米到 120 米跳变而实际地形是缓坡那么大概率是匹配粗差点没有被正确滤除。4.5 DSM 调参对照表跑通第一版后合理的下一步是针对影像特征微调参数。下表整理的是 CARS 的常见参数、作用与推荐调整方向。参数作用推荐做法与踩坑提示resolutionDSM 输出像元大小不宜小于原始影像地面分辨率的 2 倍否则大量空值像元grid_points调整匹配计算的并行粒度内存吃紧时加大但过大会增加调度延迟penalty1SGM 平滑小惩罚项默认值对建筑密集区可适当降低penalty2SGM 视差不连续惩罚项水域或平坦区域增大可减少毛刺max_iterations滤波或迭代次数输出仍有很多空洞时增大但耗时线性增长mask用户指定有效区域用矢量或栅格限制匹配范围能显著减少水域误匹配调参时需要牢牢记住一个原则每次只改一个参数。CARS 的匹配链路上参数之间往往存在相互牵制同时改两个参数后出了问题很难定位是哪一步导致的。5. 验证 DSM 质量的三个快速检查5.1 五分钟看视差直方图判断匹配是否发散DSM 是否可用不一定非要等全流程跑完。匹配阶段生成的视差图已经能反映大部分质量问题。用一个简单脚本读取视差图并输出分位数统计即可快速定位“整体偏移”与“局部毛刺”两种异常。import numpy as np from osgeo import gdal ds gdal.Open(output/disparity_0.tif) arr ds.GetRasterBand(1).ReadAsArray() nodata ds.GetRasterBand(1).GetNoDataValue() valid arr[arr ! nodata] print(np.nanpercentile(valid, [1, 5, 50, 95, 99]))如果中位数接近 0 而 99 分位数极大说明有零星粗糙错误点而非系统性错位如果 50 分位数和 95 分位数之间相差超过像元分辨率的数倍说明视差整体不稳定。此时优先检查左右影像的时相差异是否过大比如一边是夏季植被、另一边是冬季落叶后的同一区域。5.2 与参考 DSM 做点对点误差分析如果研究区域内有公开的更高精度 DSM 或 LiDAR 数据直接用差值统计来定量评估 CARS 输出。以下代码展示如何快速计算 MAE、RMSE 和误差分布。import numpy as np from osgeo import gdal def read_raster(path): ds gdal.Open(path) arr ds.GetRasterBand(1).ReadAsArray().astype(np.float32) ds None return arr dsm_cars read_raster(output/dsm.tif) dsm_ref read_raster(ref_dsm.tif) mask (dsm_cars 0) (dsm_ref 0) np.isfinite(dsm_cars) np.isfinite(dsm_ref) err dsm_cars[mask] - dsm_ref[mask] print(MAE:, np.abs(err).mean()) print(RMSE:, np.sqrt((err**2).mean())) print(95th percentiles:, np.percentile(np.abs(err), 95))这段代码将小于等于 0 的像素视为无效像素用有效区域计算误差。若 RMSE 大于 2 到 3 个输出分辨率则匹配质量大概率不可靠不必继续下游分析。5.3 把异常值栅格转成矢量人工判读复查定量指标合格不代表每个像元都正确。把 DSM 中高程异常突变的区域提取出来转成矢量叠加到原始影像上是成本最低的人工复核方式。gdal_calc.py -A output/dsm.tif --outfiledsm_valid.tif \ --calc1*(A0) --NoDataValue0 gdal_polygonize.py dsm_valid.tif -f GeoJSON dsm_valid.geojsongdal_calc.py将有效像元变成 1、无效像元变成 0gdal_polygonize.py把值为 1 的连通区域矢量化。把得到的 GeoJSON 叠加到 Google Earth 或 QGIS 影像底图上就能直接看到空值区是否主要落在湖泊、阴影或云遮挡区域这比看任何一个统计数字都更有说服力。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价