照片里一个字都没有,怎么算出它在哪拍的?用铁路、山脊线和桥墩定到 2 米

0 阅读6分钟

我手上有一张照片:手机拍的,EXIF 已经被抹掉。画面里是收割完的稻田、一个白色烤炉、远处一条长长的高架,右边一座陡山。没有招牌,没有车牌,没有一个字。

图1-照片.jpg

最后的定位结果:广东清远,广清城际高架西侧的一块稻田边,误差约 2 米。全程约 72 分钟,其中一半时间在等计算。

这篇讲这个过程是怎么做的。整套方法我做成了一个开源的 Agent Skill,叫 geo-sleuth(MIT 协议),装进 Claude Code、Codex、Cursor 等 agent 就能用:github.com/Oldcircle/g…

为什么不直接让大模型看图猜

现在的多模态模型看这张图,大概能说出「华南农村」。再往下到具体哪个村、哪块田,它只能猜,而且猜错了也不会告诉你。

所以我把工作拆成两部分:

  • 模型负责读画面、提线索、提出假设;
  • 搜索、打分、排序全部交给脚本,用真实的地理数据去比对:OpenStreetMap 的几何数据、高程数据、卫星图、街景。

每个结论都必须指向本次跑过的命令和它产出的文件。人口多、名气大都不算证据。

下面按步骤走一遍。

第一步:读图,先缩到「华南」

画面里能读出的东西:

  • 高架上立着一排杆子,是接触网支柱,说明这是电气化铁路;
  • 桥墩在画面里左边稀、右边密。高铁高架常见的跨距是 32 米,按 32 米算,左段离机位约 0.5 公里,右段在 1 公里以外;
  • 右边的山大约在 3 公里外;
  • 稻子已经收割,但草还是绿的,说明还没下过霜。

图2-桥墩.jpg

这一步能把范围缩到华南,但这是判断,不是证明。后面每一步都要用数据核对。

第二步:区域扫描,2.7 万段铁路桥筛到 171 处

从 OpenStreetMap 取出北纬 18–29°、东经 104–122.5° 范围内所有的铁路桥,一共 27,335 段,去掉不通电的还剩 25,402 段。每 400 米采一个点,用高程数据算出每个点周围 360° 的地平线,然后按照片里看到的情况筛:

  • 附近地势平坦;
  • 几公里内有明显的山峰;
  • 旁边的地平线是平的(照片左半边的天际线很低)。

第一次筛得太严,只剩 98 个点,答案被漏掉了。放宽条件后剩 715 个点,合并成 171 处候选。

图3-区域扫描.jpg

第三步:天际线拟合,算了 14,372 个机位

在每处候选周围摆一圈候选机位,用高程数据渲染每个机位看出去的山脊线,和照片里的山脊线比对打分,一共算了 14,372 个机位。

问题是前 20 名的得分差不到 0.1°,分不开。于是加了一条从照片里读出的约束:桥必须左近右远。筛完剩 22 个。

第四步:把山脊线画回照片,逐个核对

得分只是一个数字,最后要把前三名的山脊线画回照片上看:

图4-天际线前三名.jpg

  • 第 1 名福州:得分最高,是因为有一处凸起正好藏在烤炉后面,对不上的部分被挡住了,没被扣分;
  • 第 3 名惠州:照片里平坦的地方,它的山脊线却有坡度;
  • 第 2 名清远:从山脚到画面右边缘都贴合。

最后留下清远。排第一的不一定对,得分要画回画面核对。

第五步:数桥墩,从 300 米缩到一个点

照片里能数出 17 根桥墩。每根桥墩对应一条从机位出发的方位线。桥墩本来是等间距的,所以这 17 条线打在铁路线上的交点,间距也必须均匀,只有一个机位能满足。

结合天际线的结果,先把范围缩到铁路边约 300 米的一段,再缩到一个点,误差约 2 米。反算出来的桥墩间距是 32.0 米,和第一步的假设一致。

最后生成的证据图,标出了机位、视野范围、广清城际高架和北峡山:

图5-证据图.jpg

这套流程是怎么组织的

上面五步里,模型真正做判断的地方不多。整个 skill 分三层:

层谁做做什么
决策脚本 board.py(候选盘)记录所有候选、线索和证据,算排名,给出下一步扫哪里
感知脚本先排序OCR、查表、以图搜图、卫星图打分(CLIP)、街景比对(DINOv2 + SIFT)、天际线渲染
判断模型读画面、提线索、在排好的前几名里挑

有几条规则写在代码里强制执行,不靠模型自觉:

  • 没有「删除候选」的操作。排除只有一个入口,而且必须附上算出来的文件;
  • 推测出来的线索只能降低候选的权重,不能直接排除;
  • 排除的范围不能超过证据覆盖的范围。在一条路上只看过一个点,不能把整条路排除。

最后这条是测试里同样的错犯了两次之后加的:只看了路上一个点就排除了整条路,结果答案就在这条路的另一段。

脚本一共二十个,一个脚本只做一件事,比如:

  • sun.py:用太阳和影子推纬度带、拍摄时刻、街道走向;
  • osm.py:OpenStreetMap 要素共现查询,比如找「几百米内同时有铁路桥和水库」的地方;
  • terrain.py:用高程数据渲染山体视图、叠天际线、批量打分;
  • pose.py:用多个已知点反解机位的经纬度、高度和朝向;
  • match.py:把候选街景和照片比对排序。

针对国内做了适配

  • 车牌前缀、固话区号、行政区划查表;
  • 百度识图(包括相似图片,会拼成带编号的图片墙方便翻看);
  • 百度全景。

局限

  • 适合画面里有结构的照片:铁路、山脊、河道、有特点的建筑。纯室内、只有天空和植被的照片很难;
  • 以图搜图会把照片上传给百度和 Yandex,隐私照片请先裁剪、打码;
  • 一题要跑几十分钟到一个多小时,大半时间在等计算。

怎么用

需要 Python 3.10+、uv,以及一个能运行 shell 命令的 agent。

npx skills add Oldcircle/geo-sleuth

装好后把照片交给 agent,说一句「找出这张照片是在哪拍的」。

项目地址:github.com/Oldcircle/g…

欢迎拿你自己拍过、知道确切位置的照片来试,定对定错都欢迎在 issue 里告诉我。