ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Metashape 1.8.2 Python API 参考手册:批处理与自动化实战指南

Metashape 1.8.2 Python API 参考手册:批处理与自动化实战指南

简介:面向 Metashape Professional 用户的 Python 脚本开发参考文档,聚焦 Agisoft 官方 1.8.2 版 Python API 的完整说明,适合已具备 Python 基础、希望将摄影测量流程自动化的测绘、三维重建与视觉工程从业者。资源包仅含 1 个 PDF 文件,约 1.16MB,内容涵盖 API 概览、应用模块、变更日志与 Python 模块索引,并给出从打开项目、对齐照片、构建密集云与网格到导出成果的示例代码。读者可据此掌握 Document 类与 Metashape.app.document 的用法,了解相机校准、地面控制、坐标投影及网络任务控制等接口,快速搭建批量处理脚本并查阅各函数参数。已有 311 人学习,适合作为日常开发时随手查阅的案头手册。

1. 拿到这份 1.8.2 参考手册,先搞清楚它能替你省掉哪些手工活

如果你正在用 Metashape Professional 做航测或近景摄影测量,手上有一批项目要反复走「对齐照片 → 稠密点云 → 网格 → 纹理 → 导出」这套流程,那这份 1.8.2 的 Python API 参考手册就是给你准备的。它不是教程,是一本把Metashape模块里每个类、每个方法、每个枚举值摊开给你看的工具书——Document、Chunk、Camera、Calibration、Application、ModelView这些对象到底有哪些属性、方法签名长什么样、参数类型是什么,全在里面。适合两类人:一类是想把重复劳动脚本化的从业者,另一类是需要在批处理管线里嵌入摄影测量步骤的开发者。前提是 Professional 版,Standard 版没有脚本引擎,这一点手册开篇就写死了。

2. 脚本引擎与三种执行入口:先让代码跑起来再谈自动化

2.1 Python 3.8 引擎与 Professional 版的硬门槛

手册第一章明确写了,Metashape Professional 内置的脚本引擎是 Python 3.8。这个信息很关键,因为它决定了你写脚本时能用哪些语法、哪些标准库特性。比如海象运算符:=是 3.8 才有的,可以用;但 3.9 之后才有的dict合并运算符|、list[int]这种内置泛型写法就不能用,写了在控制台里直接语法报错。我一般会在脚本开头加一行版本断言,避免把脚本拷到别的机器上跑出莫名其妙的错误:

import sys import Metashape # 引擎版本兜底检查,低于 3.8 直接退出,避免语法层面翻车 if sys.version_info < (3, 8): raise RuntimeError("需要 Python 3.8 及以上,当前为 %s" % sys.version) # 顺手确认是 Professional 版,Standard 版没有 Metashape 模块 print("Metashape version:", Metashape.app.version)

这段代码的逻辑很简单:sys.version_info拿当前解释器版本做元组比较,Metashape.app.version打印软件版本字符串。参数上没什么可调的,但要注意Metashape.app这个全局实例只有在 Professional 版里才存在,Standard 版导入模块就会失败。所以如果你在别人的机器上跑脚本报ModuleNotFoundError: No module named 'Metashape',先别怀疑代码,先确认版本。

2.2 控制台、Run Script、命令行 -r 三种入口怎么选

手册列了三种执行方式,实际用起来各有各的场合。控制台面板适合调试,一行一行敲,看变量、看返回值,跟标准 Python REPL 差不多,但它的历史记录不会持久化,关掉就没了。Tools → Run script...适合跑已经写好的完整脚本,缺点是每次都要点菜单、选文件,批量场景下很烦。命令行-r参数才是自动化的正路:

# Windows 下典型调用,-r 后面跟脚本绝对路径 "C:\Program Files\Agisoft\Metashape Pro\metashape.exe" -r "D:\scripts\batch_align.py" # Linux 下同理,注意路径引号,避免空格截断 metashape -r /home/user/scripts/batch_align.py

-r的含义是「启动后立即执行指定脚本」,脚本跑完软件不会自动退出,除非你在脚本末尾调Metashape.app.quit()。这一点很多人第一次用会愣住——以为命令行跑完就返回了,结果进程挂在那里。如果你要做无人值守批处理,记得在脚本最后加退出逻辑,或者在外部用任务计划配合超时控制。另外命令行模式下没有 GUI 交互,所有getOpenFileName、getCoordinateSystem这类弹窗方法都会阻塞或返回空值,脚本里要提前用参数把路径写死,别指望弹窗。

2.3 Document 与 Chunk:项目数据模型的骨架

手册第二章的核心是Document类,它代表一个.psz项目文件。一个 Document 下可以挂多个 Chunk,每个 Chunk 是一套独立的处理单元——相机组、对齐结果、稠密点云、网格都挂在 Chunk 上。理解这层关系,后面所有 API 调用才不会迷路。下面这段是手册里给的完整处理链示例,我加了注释和异常处理,让它更接近能直接用的形态:

import Metashape doc = Metashape.app.document doc.open(r"D:\projects\site_a.psz") # 取当前活跃 chunk,多 chunk 项目里要确认取的是哪一个 chunk = doc.chunk print("当前 chunk 标签:", chunk.label) # 照片对齐:downscale=1 表示不降采样,精度最高但最慢 chunk.matchPhotos( downscale=1, generic_preselection=True, # 通用预选,适合无序照片集 reference_preselection=False # 没有 POS 数据时关掉 ) chunk.alignCameras() # 深度图降采样 4 倍,过滤模式用激进,适合纹理丰富的场景 chunk.buildDepthMaps( downscale=4, filter_mode=Metashape.AggressiveFiltering ) chunk.buildDenseCloud() chunk.buildModel( surface_type=Metashape.Arbitrary, interpolation=Metashape.EnabledInterpolation ) chunk.buildUV(mapping_mode=Metashape.GenericMapping) chunk.buildTexture( blending_mode=Metashape.MosaicBlending, texture_size=4096 ) doc.save()

逐段说参数。matchPhotos的downscale取值 0/1/2/4/8,数值越大越粗越快,1 是原分辨率,适合小数据集精修;generic_preselection打开后会用图像内容做预筛选,无序航片必须开;reference_preselection依赖相机位置信息,没有 POS 或 GCP 就关掉,否则会误筛。buildDepthMaps的downscale=4是速度与质量的折中,AggressiveFiltering对弱纹理区域更狠,能压掉不少飞点,但代价是边缘可能被削。buildModel的surface_type=Arbitrary适合复杂地形和建筑,HeightField只适合近似平面的大范围地形,选错了网格会糊成一片。buildTexture的texture_size=4096是单张纹理贴图边长,项目大、细节多就往上加,但显存和导出体积跟着涨。

提示:doc.chunk返回的是当前激活的 chunk,多 chunk 项目里如果脚本依赖特定 chunk,用doc.chunks[i]按索引取,或者遍历比对chunk.label,别默认第一个就是你要的。

3. 相机标定与坐标系统:把内参和外参钉死在脚本里

3.1 Calibration 对象的读写与畸变系数映射

Calibration类是手册里信息密度很高的一块。它承载了相机内参:焦距f、主点cx/cy、径向畸变k1~k4、偏心畸变p1~p4、仿射与非正交b1/b2,还有width/height图像尺寸。做多相机、多镜头批处理时,把标定文件读进来统一套用,比每台相机手动填靠谱得多。手册给了load和save两个方法,格式默认是 XML:

import Metashape # 从 XML 标定文件加载内参 calib = Metashape.Calibration() calib.load(r"D:\calib\camera_a.xml", Metashape.CalibrationFormatXML) print("焦距 f =", calib.f) print("主点 cx, cy =", calib.cx, calib.cy) print("径向畸变 k1~k3 =", calib.k1, calib.k2, calib.k3) # 把标定套到 chunk 里所有相机上(同型号镜头批量场景) chunk = Metashape.app.document.chunk for camera in chunk.cameras: camera.sensor.calibration = calib

load的第二个参数是格式枚举,手册里默认CalibrationFormatXML,如果你的标定来自其他工具导出的格式,先确认枚举里有没有对应项,没有就得自己解析后逐字段赋值。camera.sensor.calibration是赋值入口,注意是挂在sensor上而不是camera上——同一个 sensor 下的多张照片共享一套内参,这个层级关系搞错了会报属性不存在。calib.error(point, proj)和calib.project(point)可以用来验证标定质量:前者算重投影误差,后者把三维点投到像素坐标,拿已知控制点跑一遍,误差在亚像素级才算标定可用。

3.2 CoordinateSystem 与 GCP:投影和地面控制点的脚本化

手册里Application.getCoordinateSystem是弹窗选坐标系,但批处理场景下弹窗是灾难。更稳的做法是直接构造或从已有 chunk 继承 CRS,再配合 GCP 做绝对定向。下面这段演示了从参考 chunk 拷 CRS、批量赋给其他 chunk 的写法:

import Metashape doc = Metashape.app.document ref_chunk = doc.chunks[0] target_crs = ref_chunk.crs # 从参考 chunk 继承坐标系 for chunk in doc.chunks[1:]: chunk.crs = target_crs # 标记(GCP)也要跟着换投影,否则坐标对不上 for marker in chunk.markers: marker.projections # 访问投影点,确认已关联 print("已同步 CRS 到 chunk:", chunk.label)

chunk.crs赋值后,chunk 内所有以地理坐标存储的标记、相机位置都会按新 CRS 解释。这里有个容易忽略的点:GCP 的projections是它在各张照片上的像素位置,跟 CRS 无关;真正受 CRS 影响的是marker.position这类三维坐标。如果你只换 CRS 不重新导入 GCP 坐标,绝对定向会偏。手册里Marker相关方法没在这段摘录里展开,但add/remove markers在功能列表里明确列了,实际用法是chunk.addMarker()建标记,再给marker.projections[camera]赋像素坐标。

3.3 用 Application 做参数收集与结果落盘

Application类除了全局属性,还提供了一堆getInt/getFloat/getString/getSaveFileName之类的交互方法。做内部工具时,用它们快速搭一个参数输入界面很省事,但记住这些方法在命令行-r模式下不可用。下面是一个「选保存路径 + 异常兜底」的典型写法,手册示例里也有类似结构:

import Metashape doc = Metashape.app.document path = Metashape.app.getSaveFileName("另存项目为", r"D:\projects") if not path: Metashape.app.messageBox("未选择路径,操作取消") else: try: doc.save(path) print("已保存到:", path) except RuntimeError as e: # 磁盘满、权限不足、文件被占用都会走到这里 Metashape.app.messageBox("保存失败: %s" % e)

getSaveFileName返回空字符串表示用户取消,这个判断不能省,否则doc.save("")会抛 RuntimeError。try/except RuntimeError是手册示例里的写法,Metashape 的底层错误基本都包成 RuntimeError,捕获后弹框提示比直接崩掉友好。Metashape.app.update()在长循环里定期调用可以刷新界面,避免软件看起来假死,但别在 tight loop 里每轮都调,会拖慢处理速度。

4. 批处理管线搭建:从单项目脚本到多项目队列

4.1 遍历目录批量建项目与对齐

单项目脚本跑通后,下一步就是把它套进目录遍历。下面这段是我常用的批处理骨架:扫一个根目录下的所有子文件夹,每个子文件夹当独立项目处理,照片全加进去、对齐、存盘。关键点是每个项目新建Document而不是复用app.document,避免状态串味:

import os import Metashape root = r"D:\survey_batch" for name in os.listdir(root): proj_dir = os.path.join(root, name) if not os.path.isdir(proj_dir): continue # 每个项目独立 Document,互不干扰 doc = Metashape.Document() chunk = doc.addChunk() chunk.label = name # 收集该目录下所有 jpg,按文件名排序保证可复现 photos = sorted( os.path.join(proj_dir, f) for f in os.listdir(proj_dir) if f.lower().endswith((".jpg", ".jpeg")) ) if not photos: print("跳过空目录:", name) continue chunk.addPhotos(photos) chunk.matchPhotos(downscale=2, generic_preselection=True) chunk.alignCameras() out = os.path.join(proj_dir, name + ".psz") doc.save(out) print("完成:", out)

Metashape.Document()直接实例化是手册明确支持的——「Multiple Document instances can be created simultaneously if needed」。addChunk()建空 chunk,addPhotos接收路径列表,内部会按文件创建 Camera 对象。downscale=2在批处理里比 1 更务实,速度快一倍、精度损失可接受,先出粗对齐结果再决定哪些项目值得精修。sorted那一步别省,文件系统返回顺序不保证稳定,排序后同样的输入每次跑出同样的结果,排查问题时才有可比性。

4.2 处理链的参数分级与失败重试

批处理最怕的是某个项目中途失败把整个队列带崩。我的做法是把处理链拆成阶段函数,每阶段包 try/except,失败记录到日志继续下一个。参数上做分级:粗对齐用downscale=4,精修再downscale=1重跑。下面是一个带阶段日志的片段:

import traceback def run_stage(chunk, stage_name, func): try: func() print("[OK]", stage_name) return True except Exception: # 记录完整堆栈,方便事后定位是哪个项目哪一步翻车 print("[FAIL]", stage_name) traceback.print_exc() return False # 用法:阶段失败不中断,继续下一阶段或下一项目 run_stage(chunk, "match", lambda: chunk.matchPhotos(downscale=4)) run_stage(chunk, "align", chunk.alignCameras) run_stage(chunk, "depth", lambda: chunk.buildDepthMaps(downscale=4))

lambda包一层是为了把带参调用塞进统一接口,traceback.print_exc()输出完整调用栈,比只打印异常消息有用得多——Metashape 的 RuntimeError 消息有时很笼统,堆栈能告诉你到底卡在哪个 API。阶段之间是否要继续,取决于业务:对齐失败后面建云建网格基本没意义,可以判断align返回 False 就continue跳过该项目;但深度图失败有时网格还能用别的路径出,可以继续。

4.3 导出环节:模型、正射、DEM 的格式选择

手册功能列表里明确列了导出模型、纹理、正射影像、DEM。导出 API 在摘录里没展开细节,但按这个模块的组织方式,常见做法是chunk.exportModel、chunk.exportOrthomosaic、chunk.exportDEM这几个方法,格式参数用枚举或字符串。实操上要注意两点:一是导出前确认对应产物已经生成,没建网格就导模型会报错;二是正射和 DEM 的分辨率参数跟项目坐标系挂钩,地理坐标系下单位是米,局部坐标系下是任意单位,搞混了导出的图尺寸会离谱。批处理里我一般把导出放在最后单独一个阶段,前面任何一步失败就跳过导出,避免产出半成品文件污染下游。

5. 避坑与排查:那些手册不会明说但一定会遇到的坑

5.1 坑一:Standard 版导入模块直接失败

现象:脚本在控制台里import Metashape报ModuleNotFoundError,或者Metashape.app属性不存在。原因:Python 脚本功能只在 Professional 版提供,Standard 版根本没有这个模块。解决:确认授权版本,Standard 版只能走 GUI 手工操作,没有脚本替代方案。团队协作时把这条写进环境检查清单,别让新人拿着 Standard 版折腾半天。

5.2 坑二:命令行 -r 跑完进程不退出

现象:用-r跑批处理脚本,脚本逻辑都执行完了,但进程一直挂着不返回,任务计划里显示「运行中」。原因:-r只负责执行脚本,不负责退出应用,GUI 进程会一直等用户操作。解决:脚本末尾显式调Metashape.app.quit(),或者在外部调度层加超时强杀。注意quit()前确保doc.save()已经完成,否则可能丢结果。

5.3 坑三:downscale 参数理解反了导致精度崩

现象:对齐结果稀稀拉拉,连接点很少,重建出来缺块。原因:把matchPhotos的downscale当成「降采样倍数越大越精细」,实际是数值越大图像被缩得越狠、特征越少。解决:精修用 1 或 2,快速预览用 4 或 8;批处理先粗后精两遍跑,别一上来就用 8 还指望出好模型。

5.4 坑四:多 chunk 项目里 doc.chunk 取错对象

现象:脚本对 A 区域的处理结果跑到了 B 区域的项目里,或者报「chunk 没有相机」。原因:doc.chunk返回当前激活 chunk,GUI 里点选过哪个就是哪个,脚本无法感知你的意图。解决:显式用doc.chunks[i]或按label匹配取 chunk,处理前打印chunk.label确认,别依赖隐式激活状态。

5.5 坑五:标定赋值挂错层级

现象:camera.calibration = calib报属性错误,或者赋值成功但处理结果没变化。原因:标定挂在camera.sensor上,不是camera上;同一 sensor 多相机共享内参,改一个全变。解决:用camera.sensor.calibration,批量赋值前先确认相机分组是否符合预期,不同镜头别共用一个 sensor。

6. 用 captureView 做结果快照与脚本自检

手册里ModelView.captureView和OrthoView.captureView这两个方法容易被忽略,但它们在自动化验收里很好用。参数是width、height、transparent、hide_items,返回一个Image对象。批处理跑完几十个项目,人工一个个打开看模型不现实,用脚本在每个项目结束时抓一张模型视图快照存成图片,事后翻图就能快速筛出明显翻车的项目——比如网格破面、纹理错位、模型整个是空的,一眼就能看出来。

import Metashape doc = Metashape.app.document chunk = doc.chunk # 切到模型视图,设置着色模式便于观察几何 view = Metashape.app.model_view view.view_mode = Metashape.ModelViewShaded # 抓 1024x768 快照,透明背景关掉,隐藏辅助项让画面干净 img = view.captureView(1024, 768, False, True) img.save(r"D:\snapshots\%s_preview.png" % chunk.label)

view_mode设成ModelViewShaded看几何最直观,ModelViewTextured看纹理贴图有没有接缝,ModelViewWireframe查网格密度。hide_items=True会把相机视锥、标记这些辅助元素藏掉,快照更像成品预览。img.save的路径要提前确保目录存在,Metashape 不会自动建目录,路径不存在直接抛异常。我一般会在批处理开头统一os.makedirs建好快照目录。

另一个自检手段是用Calibration.error算重投影误差,对齐完成后遍历相机算一遍平均误差,超过阈值(比如 1.5 像素)就在日志里标红该项目。这比肉眼看连接点靠谱,也能量化对比不同downscale参数的效果。手册里error(point, proj)接收三维点和像素坐标返回二维误差向量,配合chunk.tie_points和相机投影矩阵可以批量算,具体遍历方式取决于项目数据结构,思路是先取连接点、再逐相机投影、最后统计。

从那以后我每次搭批处理管线,都会在第一个项目上先手动跑一遍完整流程、抓快照、看误差,确认参数组合没问题再放开跑全量——这个习惯帮我省过好几次因为downscale设错导致整批返工的麻烦。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进