这些年凡是搞过倾斜摄影内业的人,应该都体会过那种感觉:外业飞完一个架次,带回来的数据动辄几十G甚至上百G,真正进入建模流程的影像其实只有一部分。剩下的那些——起飞降落的连拍、转弯时的甩拍、姿态突变导致的大倾角片子、飞出了建模范围的冗余影像——全得靠人一张张去翻、去选、去删。我在2020年前后认真做了一套用FME做倾斜摄影影像筛选的流程,思路不算复杂,就是把"人工框选"变成"空间计算",但实际效果非常可观。这篇文字,就当作对整个方案的完整复盘。
我整理这套流程的初衷很简单:当时手里有个测区的倾斜摄影数据要送空三和建模,影像大概有八千多张,项目时间紧,靠人工筛选既不现实也没法交代。仔细想想,筛选这件事的本质其实非常清晰——每张影像都有它对应的外方位元素,也就是曝光瞬间相机的位置和姿态,再加上已知的相机内参,完全可以算出这张照片到底覆盖了地面上的哪一块区域。那么,是否保留这张影像,只需要看它的覆盖范围与建模范围有没有交集,再叠加姿态角、曝光质量这些硬性指标。这正是FME最擅长的批量空间运算。这篇内容比较适合被影像筛选、数据预处理这类问题困扰的内业人员,也适合想用FME把"凭感觉"变成"按规则"的测绘数据处理工程师。
1. 倾斜摄影影像筛选:为什么值得用FME认真做
很多人一听"用FME筛影像",第一反应是:这东西有必要专门做个流程吗?直接在软件里一个个看不就完了?几千张图,拉进看图软件,扫一眼,把不行的删掉,两三个小时也就干完了。听起来确实不复杂,但真正做过大项目的人都知道,这条路子其实是"看着省事,后面全是坑"。
1.1 人工筛选的问题:不是慢,而是不可控
人工筛选最大的问题不是慢,是标准不稳定。同一个测区,上午精神状态好的时候,你觉得某张片子倾斜角度15度还能用;下午犯困的时候,同样角度的片子你可能就直接删了。换个同事来筛,标准又不一样。哪怕你在项目开始前定了规则"俯仰角超过20度就删、翻滚角超过15度就删",真到一张张看片子时,你很难拿个量角器去对每条片子,最后全凭感觉,结果就是不同人筛出来的成果差异很大。
更麻烦的是可追溯性。人工筛选到最后,如果有人问"这批片子你为什么删了?为什么删的是这300张不是那280张?"你是答不上来的。最怕的是空三跑完发现某个区域连接点不够,需要补片,结果删掉的影像找不回来了,只能外业重飞。而且几千张影像连续看两三个小时,眼睛疲劳之后误判率明显上升,这种错误很难自查。
1.2 FME介入后工作流的变化
FME处理这类批量空间决策可以说是"杀鸡用牛刀",但胜在真正对症。它擅长的事情刚好覆盖了影像筛选的每一个环节:批量读取POS数据、按坐标系转换坐标、用空间关系判断覆盖范围、按属性规则筛选记录、最后输出清单和统计。整个筛选不再依赖人眼,而是依赖一套明确的空间规则。
我这套流程跑起来之后,筛选环节从"别人问我为什么删"变成"所有删除理由都在字段里写着"。哪些是超出测区范围,哪些是姿态角超限,哪些是起降阶段重复曝光,每条被筛掉的影像都有原因,可追溯、可复核。而且流程搭完一遍,后面再来任意一个新架次的数据,只要POS格式和相机参数不变,改个路径直接就能跑,几分钟出结果。这才是FME做这件事的真正价值——把一次性的筛选行为,沉淀成一个可以反复调用的批量处理工具。
2. 筛选背后的几何逻辑:如何推算出影像的"地面脚印"
想把筛选从人工变成自动,先要把"这张照片拍的是什么地方"这个问题用数学方式回答清楚。在倾斜摄影里,每张照片对应一组外方位元素——曝光瞬间相机镜头中心的三维坐标和三个姿态角度,加上相机固有的内参参数,就可以解算出影像在地面上覆盖的四边形范围。这个四边形,我习惯叫它"地面脚印"。
2.1 外方位元素与空间姿态:每张照片在空中的唯一状态
外方位元素通常由POS系统(GPS+惯导)记录,或者由空三解算得到。它包含三个位置参数和三个姿态参数。位置参数就是曝光瞬间相机镜头中心的坐标X、Y、Z;姿态参数是相机相对水平面的俯仰角、翻滚角和航偏角,不同软件里叫法略有差异,有的叫Omega、Phi、Kappa,有的叫Pitch、Roll、Yaw,本质是一回事,只是旋转顺序约定不同。
可以这样理解:外方位元素记录了拍照那一刻相机在空中的完整状态——它在哪个位置、朝哪个方向、机身歪了多少。有了这六个参数,就等于知道了一台相机在三维空间里的全部信息。再加上相机本身的传感器尺寸、镜头焦距,就能算出视场角,进而算出一张照片在地面上投出了多大的范围。这就像你站在楼上往下看,站的位置、头的朝向、眼睛的视野决定了你看到的地面区域;相机的外方位元素和镜头参数,决定的就是影像覆盖的地面区域。
2.2 从相机投影方程到地面覆盖范围
大多数情况下,我们不需要精确到像素级的摄影测量解算,只需要一个足够可靠的覆盖范围近似值。行业里有一套很成熟的简化算法,核心是两个公式。
第一个公式是地面分辨率(GSD)估算:GSD = H × p / f,其中H是相对航高,p是像元尺寸,f是焦距。第二个公式是地面覆盖尺寸估算:覆盖宽度 = H × 传感器边长 / f。
举个例子,某测区用相对航高120米飞行,相机焦距35毫米(等效35mm全画幅),传感器尺寸36mm × 24mm,像元尺寸大约3.9微米。那么GSD大约为:120 × 0.0039 / 35 ≈ 0.0134米,也就是约1.3厘米的精度。覆盖范围呢,长边方向为120 × 36 / 35 ≈ 123米,短边方向为120 × 24 / 35 ≈ 82米。也就是说,下视影像在地面上大约覆盖一个123米×82米的矩形区域。但注意,倾斜摄影的侧视镜头不是正对地面的,它与竖直方向有一个夹角,实际物距比航高更大,地面覆盖范围也会相应扩大。侧视镜头的等效覆盖需要根据倾斜角度修正,实践中通常会明显大于下视镜头。
如果不想做这么细的几何解算,还有个更简单粗暴的办法:对每个曝光点做缓冲区。根据航高和镜头视场角估算一个大致的地面覆盖半径,比如下视影像覆盖半径60到70米,那就在曝光点周围生成一个半径60到70米的缓冲区,用这个缓冲区近似代表影像的覆盖范围。这种方式精度略差,但胜在流程简单,尤其适合大批量粗筛。
2.3 判定规则:什么影像该保留,什么影像该剔除
有了影像地面覆盖范围,筛选规则就非常清晰了。我把规则分成三类,在FME里用不同的转换器组合去实现。
第一类是范围规则:影像的地面覆盖范围与建模范围有交集,就保留;完全落在建模范围之外,就剔除。更严格一点的做法,是要求影像中心点落在建模范围外扩一定比例的范围之内,因为倾斜影像的边缘部分畸变较大,参与建模时权重低,中心点才是真正有效的部分。
第二类是姿态规则:俯仰角、翻滚角超过设定阈值的剔除。不同飞行平台、不同建模软件对姿态角容忍度不一样,一般我习惯把阈值设在15度到25度之间,超过这个范围,影像边缘畸变和投影差会明显影响空三解算。
第三类是重复度规则:多镜头数据中,同一架次、同一个目标区域往往被多张影像覆盖。如果某个位置的曝光点过于密集,可以按中心点间距做抽稀。比如设定中心点间距小于5米的影像,只保留其中一张,减少冗余。
这三类规则叠加,基本能覆盖倾斜摄影影像筛选的主要场景。关键在于把规则量化成参数,而不是依赖人工经验判断。
3. 完整工作流搭建:从原始POS数据到筛选成果
前面是原理,这部分直接给可落地的方案。整个工作流在FME里由几个模块串联而成:数据输入与预处理、影像覆盖范围计算、空间筛选判断、结果输出与可视化。我按实际搭建顺序一步步说。
3.1 准备阶段:数据清单与坐标系确认
做流程之前,先把手头的数据理清楚。通常需要三类输入:原始影像文件对应的POS记录,包括文件路径、文件名、曝光点坐标X/Y/Z、姿态角Omega/Phi/Kappa(或Pitch/Roll/Yaw);相机参数表,包括焦距、传感器尺寸或像元尺寸;建模范围面,也就是本次需要参与建模的测区边界。
最容易被忽略但最致命的一步,是坐标系统一。POS系统输出的通常是WGS84经纬度坐标,而建模范围往往在地方坐标系或UTM投影坐标系下。直接拿经纬度和投影坐标做空间计算,结果必然错误。所以在FME里第一步就必须用Reprojector转换器,把POS数据统一转换到建模范围所在的坐标系。这一步千万不能省,否则后面所有分析都是在错误的空间参考下进行的。
输入数据的字段检查也很关键。POS记录的影像文件名必须与影像目录中的实际文件一一对应,不能有缺漏;相机参数表要逐项核对,镜头ID要能对应到每个影像文件。我见过不止一次因为POS文件里影像编号与目录不一致,导致最终生成的清单里有一部分文件根本不存在的情况。
3.2 解算影像足迹:两种实现方式
影像覆盖范围的计算,在FME里有两种实现路径,我分别说一下适用场景。
方式A是快速近似法:用AttributeCreator把每张影像的X、Y、航高、焦距、传感器尺寸等参数读入,用VertexCreator根据X/Y生成曝光点几何,再用Bufferer按估算的覆盖半径生成缓冲区,用这个圆形缓冲区近似代表影像地面覆盖。这种方法的优点是转换器组合简单,计算量小,几万条数据跑起来也没压力;缺点是覆盖形状是圆形,和真实的地面矩形覆盖有差异,尤其在斜视镜头上误差更大。适合做快速粗筛,或者对精度要求不高的项目前期评估。
方式B是精确计算法:用PythonCaller写一小段脚本,根据外方位元素和相机内参,算出影像四个角点在地面的坐标,构建一个四边形代表影像覆盖范围。核心逻辑是先把传感器四个角点在相机坐标系中的方向向量算出来,再结合姿态角做旋转,最后投影到地面。
import math from fmeobjects import FMEFeature, FMEPolygon, FMELine def calcFootprint(feature): # 读取外方位元素和相机参数 cx = float(feature.getAttribute('X')) cy = float(feature.getAttribute('Y')) cz = float(feature.getAttribute('Z')) omega = float(feature.getAttribute('Omega')) # 俯仰角,弧度 phi = float(feature.getAttribute('Phi')) # 翻滚角,弧度 kappa = float(feature.getAttribute('Kappa')) # 航偏角,弧度 focal = float(feature.getAttribute('focal')) # 焦距,mm width = float(feature.getAttribute('sensor_w')) # 传感器宽度,mm height = float(feature.getAttribute('sensor_h')) # 传感器高度,mm # 简化的覆盖估算:以曝光点为地面中心,按航高和焦距估算覆盖尺寸 ground_w = cz * width / focal ground_h = cz * height / focal # 四个角点相对中心点的偏移,按航偏角旋转 corners = [] dx_list = [ground_w/2, ground_w/2, -ground_w/2, -ground_w/2] dy_list = [ground_h/2, -ground_h/2, -ground_h/2, ground_h/2] for i in range(4): x = cx + dx_list[i] * math.cos(kappa) - dy_list[i] * math.sin(kappa) y = cy + dx_list[i] * math.sin(kappa) + dy_list[i] * math.cos(kappa) corners.append((x, y)) corners.append(corners[0]) # 构建多边形几何 line = FMELine(corners) polygon = FMEPolygon(line) feature.setGeometry(polygon)这段代码是简化示意,实际使用时需要根据相机姿态做完整的旋转矩阵变换,侧视镜头还要考虑物距修正。但它给出了基本思路:借助PythonCaller把数学计算直接嵌入FME流程,结果就是一个真正的覆盖多边形。这种方式比缓冲区近似更接近真实情况,而且后续如果要做"覆盖范围与建模范围的相交面积比例"这种精筛,也只有精确多边形才支撑得起。
3.3 空间筛选判断:SpatialFilter与属性条件组合
计算出影像覆盖足迹之后,就进入筛选环节。我一般用SpatialFilter转换器做核心的空间判断:把影像覆盖多边形设为候选要素,建模范围面设为过滤器,判断方式是"候选要素与过滤器相交"或者"候选要素中心点落在过滤器范围内"。
这里有一个关键技巧。如果直接要求"影像覆盖范围与建模范围相交才保留",可能会保留一批只覆盖到测区边缘一点点、实际价值很低的影像。更合理的做法是双条件组合:先用覆盖多边形与建模范围求交,判断"有交集";再用曝光点中心点与建模范围的外扩缓冲区判断"中心点落在范围内"。两个条件同时满足才保留。这样既保住了边缘区域的覆盖连续性,又剔除了那些纯粹靠边缘搭到测区的废片。
外扩缓冲区的比例需要根据航高和重叠度确定。我常用的默认值是外扩20%到30%,具体根据重叠率调整。做完空间判断,再叠加姿态角属性筛选,用TestFilter把姿态角超限的影像单独标记为"姿态异常剔除",把中心点间距过密的数据标记为"重复曝光抽稀"。所有条件之间用怎样的逻辑组合,建议一开始就想清楚:保留条件是"范围有效且姿态正常",剔除条件是"超范围或姿态异常或重复曝光"。
3.4 成果输出:筛选清单、统计表与可视化
筛选结果输出到三个成果文件,对应不同使用场景。第一个是详细的影像筛选清单,输出为CSV或Excel,包含影像文件名、完整路径、镜头类型、曝光坐标、姿态角、筛选结论、剔除原因、备注等字段,供后续在建模软件里直接使用。第二个是统计汇总表,按架次、镜头、筛选结论分组统计张数,方便写项目报告的时候直接引用。第三个是筛选后的影像足迹空间数据,输出为Shapefile,包含保留影像的覆盖多边形和曝光点。
这里给一个我常用的筛选成果字段表设计:
| 字段名 | 类型 | 说明 |
|---|---|---|
| ImageName | 文本 | 影像文件名 |
| FilePath | 文本 | 影像完整路径 |
| CameraID | 文本 | 所属镜头ID |
| X / Y / Z | 浮点 | 曝光点坐标 |
| Omega / Phi / Kappa | 浮点 | 姿态角(角度制) |
| Footprint | 几何 | 影像覆盖范围多边形 |
| KeepFlag | 整数 | 1=保留,0=剔除 |
| RejectReason | 文本 | 剔除原因:超范围/姿态异常/重复曝光/其他 |
| Remark | 文本 | 备注信息 |
可视化复核这一步我强烈建议保留。把所有影像足迹叠加到建模范围图层上,输出成KML或者直接加载到GIS平台里快速扫一遍。重点看两类位置:测区边缘的影像是否有足够的向外覆盖余量,航带衔接处是否存在覆盖空洞。自动化的空间计算再严谨,也替代不了最后这一眼人工核查,这一步能帮你发现相当多的规则设计漏洞。
4. 真实项目里的变体处理:多镜头、大区域与低重叠度
线上演示的流程跑通了,不代表真实项目就能直接套用。实际测区总有一些"意外"情况需要调整方案,我挑三个最常见的变体具体说明。
4.1 五镜头、三镜头等多相机数据的合并处理
现在倾斜摄影基本都上多镜头相机,五镜头是标配,还有三镜头、四镜头。不同镜头各有各的焦距和传感器尺寸,POS数据里也各自带镜头ID标识。处理这类数据,核心思路是"参数表驱动":不要在每个转换器里写死焦距数值,而是把每个镜头的内参建一张参数表,用FeatureJoiner把影像POS数据与镜头内参表按镜头ID关联起来,每个镜头自动匹配自己的焦距和传感器尺寸,再进入覆盖范围解算。
不同镜头的判定标准也应略有差异。下视镜头是建模主力,姿态角和覆盖范围要求从严;斜视镜头覆盖范围更大、边缘畸变更大,姿态角容忍度可以适度放宽,因为斜视影像本身就是用来补充立面纹理的,保留多一些对后续建模更稳妥。我的习惯是对斜视镜头只做超范围剔除和姿态极端异常剔除,不做过于激进的抽稀。
4.2 大测区性能优化:分块、裁剪与并行
当影像数量到几万甚至十几万张时,性能瓶颈往往出在空间求交环节。我的经验是,不要把所有影像足迹一次性丢给SpatialFilter去和建模范围做全量求交。先用建模范围做一个外扩缓冲区,把POS点按坐标粗筛一遍,只保留落在缓冲区范围内的曝光点;然后再对剩下的数据做精细的足迹计算和空间判断。这个过程叫"先粗筛,再精算",能大幅减少无效计算。
FME的并行处理在这里也很有用。按架次或者按镜头ID对数据进行分组,配置多个并行处理流,让不同分组的筛选任务同时跑。我实际测试下来,几万张影像的量级在普通工作站上几分钟就能完成全部分析,性能瓶颈基本不会出现在这张工作量上,更多是出现在你前期数据清洗没做干净导致的后端报错。
4.3 空三解算后的回调修正
影像筛选不是终点,筛选成果送进空三软件解算,还会产生新的反馈信息。空三解算出的影像外方位元素有时候会和POS原始记录有出入,尤其是姿态角,差值可能达到几度。这种偏差对粗筛结果影响不大,但如果项目精度要求很高,建议在空三成果出来之后,用解算后的外方位元素替换POS里的原始值,重新生成影像足迹和筛选清单,做一次"精筛校验"。
另外要强调一点,影像筛选和空三软件内部的连接点筛选是两回事。这篇文章讨论的是进入空三之前的预处理筛选,目的是减少无效影像进入解算;空三软件里针对连接点质量做的筛选,是解算过程中的另一套逻辑,两者不要混为一谈。前期做影像筛选时保留一些边缘质量稍差的影像,并不影响空三最终结果,空三会在连接点层面自动剔除不合格约束,但前期删除太多影像,空三就真的无处着力了。
5. 容易踩的坑与个人经验
这套流程我反复调过好几版,一路踩了不少坑,下面这几条对后来做类似工作的人来说,应该能省一些时间。
5.1 坐标系隐患:最贵的一堂课
第一次跑通流程时,我把POS数据的坐标系定义和转换搞得过于随意,结果筛选结果中出现了一个非常隐蔽的问题:测区边缘有一大片影像被误删,因为POS经纬度被当作投影坐标直接参与了求交。事后排查了很久才发现,Reprojector转换器虽然把坐标数值转过去了,但目标坐标系没有正确设定,后续读取该数据的模块拿到的仍然是没有坐标系的要素。我当时的教训是:在FME里做任何坐标转换,不仅要看数值,还要检查输出要素的坐标系定义是否更新成功。现在我的流程里会在转换之后加一个CoordinateSystemSetter做强制校正,彻底杜绝这类隐患。
5.2 容差原则:宁可多留,不要误删
影像筛选有一条我很强调的原则:容差宁可放大,不要缩小。因为筛选阶段删掉的影像,后期如果发现测区出现空洞,只能外业补飞,代价极大;而多保留一些质量稍差的影像,顶多让空三和建模阶段多消耗一点计算资源,这个代价小得多。所以我设计的规则里,建模范围外扩比例一般留足20%以上,姿态角阈值也倾向于宽松。空三软件和建模软件自身具有相当的鲁棒性,它们能够处理一部分姿态偏差较大的影像,真正需要人工干预的极端情况很少。
5.3 让筛选结果有"解释"
最后一条经验,也是让我从一个"手动筛图的人"变成"能交代清楚流程的人"的关键:每一个剔除决定,都要写下原因。不要只输出一个"是否保留"的标记,而是用TestFilter把剔除原因逐一分类写清楚——超范围、姿态异常、重复曝光、其他。这样做的直接好处是,项目例会上有人质疑"为什么这批影像没过",你可以直接调出清单,每一张都有明确理由;后续如果发现筛选规则有问题,也只需要按原因字段去查,不用重新翻原始数据。
如果还想再往前一步,可以在流程末尾加一个小环节:在输出清单里合并一段"影像文件是否真实存在"的文件系统校验,逐张检查影像路径可访问性。有些时候POS记录里明明有某张图,但拷贝数据时漏拷了,传统流程里这个问题要到空三报错才能发现,在这个流程里它就会被提前暴露出来。
这套FME流程做完之后,我最大的感受是:影像筛选这类工作,终于从"凭经验拍脑袋"变成了"按参数讲道理"。换任何一个人来操作,只要会用FME跑工作流,拿到手就能出结果,而且结果完全可复现、可审计。后续如果要进一步扩展,还可以把飞行轨迹实时生成、POS后处理成果自动回灌、分区块差异化筛选这些功能加进来,整个预处理管线会更加完整。最后一个建议是,跑完任何批量任务,一定要留出时间看一眼输出成果,FME跑通了只代表流程没报错,不代表示范结果就是对的,那一眼人工核查的作用,目前还没有任何自动化工具能完全替代。