本文由 莫潇羽@复源AI社区(bbs.fuyuan7.com)撰写,转载请注明出处。
快速摘要
一句话结论:现在的 AI 已经可以在照片“没有定位信息、没有文字、没有著名地标”的情况下,靠画面里的几何线索和公开地理数据,把拍摄位置推到街道级甚至米级。GitHub 上的开源项目 geo-sleuth 就是一个典型代表,它把“看图找位置”拆成二十来个单一功能的 Python 脚本,让脚本去检索、打分、排序,大模型只在最后几个候选里做判断,每一个结论都必须对应一条真实跑过的命令和一份证据文件。
它最强的地方,不是“认出”某栋楼,而是“算出”拍摄点:远山的山脊轮廓可以和高程数据比对,等间距的桥墩可以当尺子测距,影子方向可以换算成镜头朝向。它的短板也很清楚:画面里特征越少、网上公开资料越少(比如野外无人区),准确率掉得越厉害。
对普通人来说,这件事真正的意义在于两点:第一,关掉相机定位、不发原图,已经挡不住“从像素里读位置”这一类技术;第二,这类工具只能用在自己的照片、或已获授权的照片上,拿来定位他人,在国内可能触碰《个人信息保护法》中关于“行踪轨迹”这类敏感个人信息的规定,甚至涉及刑事风险。
想看完整拆解,往下翻。
写在正文前:本文介绍 geo-sleuth 的技术原理,目的是帮助读者理解“照片为什么会暴露位置”,从而更好地保护自己。文中不提供任何针对特定个人的定位方法或案例演示。如将相关技术用于定位他人,可能触犯《个人信息保护法》《刑法》等法律法规,后果由使用者自行承担。
一、先把问题想清楚:一张照片里,位置信息藏在哪几层
我自己琢磨这类工具的时候,习惯先不看代码,而是先问一个朴素的问题:一张照片凭什么能暴露位置?
想明白这个,后面所有原理都好理解。我把它拆成四层,越往下越隐蔽,也越难防。
1.1 第一层:照片文件里的“隐形档案”——EXIF
EXIF 是“可交换图像文件格式”的缩写,你可以把它理解成照片的身份证:它不是画面的一部分,而是写在图片文件里的一段附加数据。手机拍照时,会自动往里塞拍摄时间、手机型号、光圈快门等参数,如果你给相机开了定位权限,经纬度坐标也会一并写进去。
这一层其实早就不是新闻了。几年前“微信发原图会不会泄露位置”就上过热搜,当时微信团队回应的意思是:任何智能手机拍的照片都带有 Exif 参数,可以记录位置、时间等信息,无论通过什么工具发送原图,这些附带信息都会一起发出去;而朋友圈发布的照片会经过系统压缩,不带位置信息。同一篇报道里,法律学者还总结过一个很实用的判断标准:原图泄露拍摄位置,需要同时满足手机定位打开、相机设置了保存地理位置、发送的是原图这三个条件。
所以第一层的结论很简单:EXIF 是最直接的泄露渠道,但也是最好防的。 关掉相机定位、不发原图,基本就堵住了。
问题在于,下面三层跟 EXIF 一点关系都没有。
1.2 第二层:画面里能“读”出来的字
招牌、路牌、车牌、公交站名、店铺门头上的电话号码……这些都是可以被 OCR(光学字符识别,就是让电脑把图片里的字认出来)读取的信息。
举个通俗的例子:一张街景照片里,远处有块不太清楚的门头,只露出半截电话号码,区号那几位就足以把范围缩到某个城市;车牌第一个字是省份简称、第二个字母对应地级市,这又是一条硬线索。
这一层比 EXIF 难防,因为很多人发照片时根本不会注意背景里有什么字。
1.3 第三层:能被“搜”出来的地物
即使画面里一个字都没有,只要出现了有辨识度的东西——一座造型特别的塔、一处网红打卡点、一个风格独特的门头——就可以拿去做反向搜图(以图搜图,把图片丢给搜索引擎,让它找相似的图)。
网上公开的旅游攻略、点评网站、短视频平台,积累了海量带地点标注的照片。你拍的景点,很可能早就有成百上千人拍过、标注过。反向搜图本质上就是在这个巨大的“已标注图库”里找最像的那几张。
这一层的特点是:越出名的地方越好找,越普通的地方越难找。
1.4 第四层:藏在几何关系里的“指纹”
这是最隐蔽、也是 geo-sleuth 这类新工具真正厉害的地方。
一张照片里,就算没有字、没有地标、搜图也搜不到,它依然包含大量几何信息:
- 远处山脊的起伏轮廓,从不同位置看过去形状完全不同,像指纹一样;
- 影子的长度和方向,和太阳位置有确定的数学关系;
- 一排等间距的电线杆、桥墩、路灯,在画面里“近大远小”的变化规律,能反推出它们离相机多远;
- 道路、河流、铁路、海岸线之间的相对位置关系,可以和地图数据逐一比对。
这些东西不靠“认”,靠“算”。我觉得这才是理解这类工具的关键:它不是一个更聪明的“看图猜地方”,而是把几何测量和地理数据库接到了一起。
下面这张表把四层信息做个对照,方便你建立整体印象:
|
信息层 |
典型线索 |
提取方式 |
普通人防护难度 |
|
文件元数据 |
GPS 坐标、拍摄时间、机型 |
读取 EXIF |
低(关定位、不发原图即可) |
|
画面文字 |
招牌、路牌、车牌、电话区号 |
OCR 文字识别 |
中(需要留意背景、打码) |
|
可搜地物 |
地标建筑、网红景点、特色门头 |
反向搜图 |
中偏高 |
|
几何关系 |
山脊线、影子、等间距结构、地物布局 |
几何计算 + 地理数据比对 |
高(几乎无法靠设置规避) |
用一张流程图表示,大概是这样一个“由浅入深”的过程:
flowchart TD
A[一张照片] --> B{有没有 EXIF 定位?}
B -- 有 --> Z[直接得到坐标]
B -- 没有 --> C{画面里有可识别文字?}
C -- 有 --> D[OCR 读取 → 区号/车牌/地名查表]
C -- 没有 --> E{有没有辨识度高的地物?}
E -- 有 --> F[反向搜图 → 候选地点]
E -- 没有 --> G[几何线索: 山脊线 / 影子 / 等间距结构]
D --> H[候选区域]
F --> H
G --> H
H --> I[地图与高程数据比对 → 收敛到少数候选]
I --> J[输出坐标 ± 误差半径]
理解了这四层,你再去看 geo-sleuth 的设计,就会发现它基本就是把这四层按顺序、用脚本一层层榨干。
二、geo-sleuth 是个什么项目:定位、技术栈和官方案例
2.1 一句话定位:给 AI 编程助手装的“地理侦探技能包”
geo-sleuth 是 GitHub 用户 Oldcircle 发布的开源项目,项目自我介绍是一个 Agent Skill(智能体技能),作用是找出一张照片的拍摄地点,并展示推理过程,手段包括 OpenStreetMap 几何数据、高程天际线、卫星影像和街景。
这里有个概念需要先说清楚:Agent Skill 是什么?
现在很多 AI 编程助手(比如在终端里帮你写代码、跑命令的那类工具)都支持“技能”机制:一个技能就是一个文件夹,里面有一份说明书 SKILL.md,告诉 AI“遇到什么任务、该按什么步骤做、可以调用哪些脚本”,再配上实际干活的脚本。AI 读了说明书,就会按规矩办事。
geo-sleuth 就是这样一个文件夹。按照官方说明,它是标准的 Agent Skill 格式,一个文件夹里装着 SKILL.md、scripts/、references/ 和 data/,因此可以在多款主流 AI 编程助手中通用。
项目的时间线也值得一提。有海外科技资讯网站在 2026 年 9 月 19 日报道过这个项目,据该报道,它上线首日星标即过百。截至 2026 年 10 月中旬写稿时,我查看仓库页面显示的星标约 1.2k,分支(Fork)一百多个。
开源项目的星标数、版本号变化很快,上面是写稿时的近似情况,请以 GitHub 仓库页面实时数据为准。
2.2 它的设计哲学:脚本负责算,模型只负责判
我读 README 时,印象最深的不是哪个脚本多牛,而是它对“AI 该干什么、不该干什么”的划分。
用大模型直接看图猜地点,最大的问题是幻觉:模型会非常自信地给你一个答案,而且讲得头头是道,但你根本不知道它是真看出来了,还是编的。geo-sleuth 的思路是反过来的——尽量不让模型“猜”。
官方把工作分成三层:由脚本来决定和感知、负责检索与排序,模型只在排在最前面的少数几个候选之间做判断。而且它立了一条硬规矩:每个结论都必须指向会话中真实运行过的某条命令,以及这条命令生成的文件;地点的人口多少、名气大小都不能算作证据。
最后这半句我特别认同。很多人用 AI 看图定位翻车,往往就是模型倾向于往“有名的地方”猜——因为训练数据里有名地方的照片多。把“名气”明确排除在证据之外,等于从制度上堵住了这类偏差。
用表格对比一下两种思路:
|
对比项 |
让大模型直接看图猜 |
geo-sleuth 的做法 |
|
谁在找候选 |
模型凭“印象”联想 |
脚本在地图、高程、卫星数据里系统检索 |
|
谁在排序 |
模型凭感觉 |
脚本按似然比、相似度打分 |
|
模型的角色 |
从头猜到尾 |
只在前几名里做最终判断 |
|
结论能否核查 |
基本不能 |
每条结论对应命令和证据文件 |
|
输出形式 |
一个地名 |
坐标 ± 误差半径、相机朝向、证据图、分级置信度 |
2.3 技术栈:二十个“一事一脚本”的小工具
README 里列了一张工具清单,我按功能把它们归成几类,方便新手理解每个脚本是干嘛的:
|
类别 |
代表脚本 |
干的事(白话版) |
|
环境自检 |
|
检查 Python、uv、浏览器、网络是否就绪 |
|
信息榨取 |
|
读元数据、认字、反向搜图、裁剪放大局部 |
|
查表推理 |
|
车牌前缀、电话区号、行政区划、地名查坐标 |
|
候选管理 |
|
维护候选清单,按证据加减权重、排除、排序 |
|
物理与几何 |
|
太阳与影子、方位与距离、相机姿态 |
|
地图与地形 |
|
查地图要素、算天际线、拼卫星瓦片 |
|
视觉比对 |
|
卫星图打分、街景全景图比对 |
|
出报告 |
|
生成卫星底图 + 相机视野扇形的证据图 |
其中有几个脚本背后用到的模型值得单独解释一下:
- CLIP:OpenAI 开源的图文匹配模型,能判断“一张图和一段文字描述有多像”。在这个项目里,它被用来给卫星图的每个格子打分,比如“这个格子里像不像有个田径跑道”。
- DINOv2:Meta 开源的视觉特征模型,擅长提取图像的整体“长相特征”,适合比较两张照片拍的是不是同一个场景。
- SIFT:一种经典的图像特征点算法,能在两张图里找出相互对应的角点、边缘,对应点越多,说明越可能是同一处。
官方说明里,match.py 正是把这两者结合起来,用 DINOv2 的全局相似度加上 SIFT 的匹配内点,对候选的地面照片进行排序。
数据源方面,项目致谢里列出了 OpenStreetMap、AWS 提供的高程瓦片、一份中国行政区划的开源数据集,以及上面两个视觉模型。这里有个细节:仓库本身不附带 OSM 数据,脚本是实时查询的,发布查询结果时需要注明“© OpenStreetMap 贡献者”。这一点对打算拿结果做内容的朋友很重要,后面合规那章我会再展开。
2.4 官方案例拆解:一张“什么都没有”的照片怎么被定位到米级
README 里放了一个完整案例,我觉得是理解整个项目最好的入口。我用自己的话把它的逻辑讲一遍。
照片的条件非常苛刻:EXIF 已被清除,画面里是一片收割过的稻田、远处一座长长的高架桥、右边一座陡峭的山,一个字都没有。
整个推理过程是一个“漏斗”,候选数量一路收窄:
flowchart LR
A["读图<br/>判断: 电气化铁路、南方、霜降前"] --> B["区域扫描<br/>2.7 万段铁路桥"]
B --> C["地平线筛选<br/>剩 171 处"]
C --> D["天际线拟合<br/>1.4 万个机位 → 22 个"]
D --> E["叠图人工复核<br/>剩 1 处"]
E --> F["桥墩测距<br/>定位到约 ±2 米"]
拆开来看,每一步用的都是前面说的“第四层几何线索”:
读图阶段,模型从画面里提取出几个关键判断:高架桥上的杆子是接触网立柱,说明是电气化铁路;稻子收了但草还绿,说明还没下霜。官方把这一步得出的“华南地区”明确标注为一个押注,而不是证明。这个措辞我很喜欢,推理的不确定性被如实记录了下来。
区域扫描阶段,脚本从 OpenStreetMap 里拉出该区域所有铁路桥,共 27,335 段,每隔几百米取一个点,用高程数据算出从这个点看出去的 360 度地平线,只保留“附近地面平坦、几公里内有座明显的山、旁边地平线又是平的”这类点位。
天际线拟合阶段,在每个候选点附近放置大量假想机位,渲染出从每个机位看过去的山脊线形状,和照片里的山脊线比对。
叠图复核阶段,把排名前三的山脊线画回照片上。官方提到,排第一的那个位置分数高,其实是因为有个山包恰好被画面里的一个物体挡住了,看着吻合;排第三的那个在照片是平的地方却是斜的;反倒是排第二的那个从山脚到画面边缘全程吻合。这一步特别能说明“脚本排序 + 人或模型复核”的必要性——纯靠分数会选错。
桥墩测距阶段,照片里能数出 17 个桥墩,每个桥墩对应一条从相机出发的方位线,这些方位线和铁路线相交,交点之间的间距理应是均匀的。只有一个相机位置能让间距均匀,于是位置被锁定。
按官方说法,整个过程端到端大约 72 分钟,其中差不多一半时间是在等计算。
我读完这个案例的感受是:这已经不是“AI 看图”了,更像是一个把测绘、天文、计算机视觉串起来的自动化调查流水线。下一章我就把其中几个关键原理掰开讲,尽量讲到新手也能自己在纸上算一算的程度。
三、核心原理逐层拆:从“看图猜地方”到“用几何算位置”
这一章是整篇文章的硬核部分。我尽量不堆术语,每个原理都配一个能在纸上算的小公式。公式统一用普通文字和符号来写,复制到金山文档、公众号后台或者网页里都能正常显示。
3.1 影子与太阳:一根电线杆就是一个“日晷”
先说最容易理解的一个。
太阳在天上的位置,可以用两个角度描述:高度角(太阳离地平线有多高)和方位角(太阳在正北顺时针转了多少度)。这两个角度只取决于三件事:地点的纬度和经度、日期、时刻。天文学上这是一套早就算得清清楚楚的公式,网上很多开源库都能直接调用。
反过来想,如果照片里有一根竖直的杆子和它的影子,我们就能量出太阳的高度角:
tan(太阳高度角) = 杆子高度 ÷ 影子长度
举个数字例子:一根大约 3 米高的路灯杆,影子大约 5 米长,那么 tan(高度角) ≈ 0.6,高度角约 31°。
拿到高度角能干嘛?如果你大致知道拍摄日期(比如 EXIF 里没有坐标,但时间还在;或者从植被、衣着推测出季节),那么同一个高度角只可能出现在特定的纬度带和特定的时段里。这就是 geo-sleuth 里 sun.py 干的事情之一:用影子推纬度范围、推一天中的时刻。
影子还有一个更实用的作用:定方向。影子总是背对太阳,所以只要知道大概时间,影子的指向就能换算成真北方向,进而推出镜头朝哪边拍。有了朝向,后面所有和地图比对的工作都能省掉一大半。
flowchart LR
A[影子长度与杆高] --> B[太阳高度角]
C[影子方向] --> D[太阳方位角]
B --> E{结合日期}
D --> E
E --> F[可能的纬度带]
E --> G[镜头朝向 / 街道走向]
我自己的理解是,影子这条线索单独用往往只能给出“一条带子”而不是“一个点”,它的价值在于和别的线索交叉,把范围切得更窄。
3.2 天际线:远山的轮廓就是地理“指纹”
这是 geo-sleuth 最有代表性的一招。
天际线,就是照片里天空和地面(山、楼)交界的那条轮廓线。在山区或丘陵地带,这条线的起伏形状高度依赖拍摄位置:你往左挪一公里,远处的山峰会换个角度,近处的小山包可能会把远处的主峰挡住,轮廓就完全变了。
而全球的地形起伏早就有公开的数字高程模型(DEM,可以理解成一张“每个格子记录海拔”的大表格)。有了 DEM,就能用计算机模拟“站在某个点,往四周看出去,地平线长什么样”。
算法上大致是这么个思路,我写成伪代码方便理解:
# 伪代码:从某个候选点渲染 360° 地平线
def render_horizon(lat, lon, eye_height, dem):
horizon = []
for azimuth in range(0, 360): # 每隔 1° 看一个方向
max_angle = -90
for dist in range(100, 30000, 100): # 沿这个方向每 100 米取一个点,看到 30 公里外
h = dem.elevation_at(lat, lon, azimuth, dist)
angle = atan((h - eye_height) / dist) # 该点相对眼睛的仰角
max_angle = max(max_angle, angle) # 只有最高的那个能被看到
horizon.append(max_angle)
return horizon # 360 个仰角,组成一条“地平线曲线”
然后把照片里提取出的那段山脊线(只占 360° 里镜头视野那几十度),在这条曲线上左右滑动,找最吻合的位置。吻合度可以简单理解为:
误差 = Σ |照片仰角(i) − 模拟仰角(i)| ÷ 采样点数
误差越小,说明这个候选点越可能是拍摄点。
这里有两个现实问题,我觉得值得说一下:
一是计算量大。一个省级范围随便撒点就是几万个候选点,每个点算 360 度、每个方向几百个采样,这就是为什么官方案例要先用“附近有铁路桥”“地面平坦”这类硬条件把点位砍到几百个,再做精细拟合。
二是容易被遮挡骗。前面讲官方案例时提过,分数第一的候选其实是因为一个山包恰好藏在前景物体后面。所以天际线拟合之后,必须把模拟的山脊线叠回照片上人工看一眼,这一步机器省不掉。
3.3 等间距结构:桥墩、电线杆、路灯就是一把尺子
这是我觉得最巧妙的一个思路。
高铁桥、高速高架桥的桥墩,跨度在设计上往往是标准化的,常见的就是几十米一跨;电线杆、路灯、行道树在一条路上也往往等间距排布。真实世界里等距的东西,拍到照片里会因为透视变得“近大远小”,越远挤得越密。
小孔成像模型给出一个很朴素的关系:
物体到相机的距离 ≈ 镜头焦距 × 物体实际间距 ÷ 画面上的间距
焦距可以从 EXIF 的“等效焦距”拿到;没有 EXIF,也可以从常见手机主摄的视角范围去估一个区间。只要再假设一个桥墩实际跨度(官方案例里也明确写了“跨度为假设值”),画面上相邻桥墩的像素间距就能换算成距离。
更进一步,geo.py 有个“从等间距结构反推相机位置”的功能,原理我用白话讲一下:
- 照片里每个桥墩对应一条从相机发出的视线,这条视线的方向(方位角)可以根据它在画面里的横向位置算出来;
- 把这些视线画到地图上,它们会和地图里的那条铁路线相交;
- 如果相机放对了位置,这些交点之间的距离应该刚好相等(因为桥墩本来就是等距的);
- 相机放错了,交点间距就会忽大忽小。
于是问题变成:在地图上找一个点,让所有视线和铁路线的交点尽量均匀。这是一个标准的最优化问题,计算机很擅长。
|
线索类型 |
能得出什么 |
依赖的前提 |
|
影子 |
纬度带、时刻、镜头朝向 |
大致日期、竖直参照物 |
|
天际线 |
候选机位排名 |
有明显山体、DEM 覆盖 |
|
等间距结构 |
距离、精确机位 |
结构确实等距、能在地图上找到对应线状物 |
|
地物共现 |
候选区域 |
地图数据标注完整 |
3.4 地物共现:让地图数据库替你“排除不可能”
OpenStreetMap(简称 OSM)是一个全球志愿者共同编辑的开放地图,里面不只有道路,还标注了铁路、河流、电力线、学校、加油站、水塔、跑道等等。它提供一个叫 Overpass 的查询接口,可以写类似“数据库查询语句”的东西,问它:
在这个范围内,找出所有“铁路桥 500 米内有河流、并且 2 公里内有一座山峰”的位置。
这就是地物共现:照片里同时出现了 A、B、C 三样东西,那拍摄点附近一定同时存在这三样。每多一个条件,候选位置就少一大截。geo-sleuth 的 osm.py 就是专门干这个的。
一个简化的查询思路长这样(示意,非项目原始代码):
[out:json];
// 在指定范围内找铁路桥
way["railway"="rail"]["bridge"="yes"](区域范围) -> .bridges;
// 找这些桥附近 500 米内的河流
way["waterway"="river"](around.bridges:500) -> .rivers;
// 输出同时满足条件的桥
.bridges out geom;
我觉得这个思路特别值得普通开发者学习:它不是在“找答案”,而是在“排除不可能”。排除得够狠,剩下的自然就是答案。
3.5 视觉比对:CLIP 扫卫星图,DINOv2 + SIFT 比街景
前面几种方法都是“算”,但总有一些线索没法用公式表达,比如“这里看起来像一个工厂”“那片屋顶的颜色排布”。这时候就轮到视觉模型出场了。
卫星图扫描这一步,项目把一大片区域切成很多小格子,用 CLIP 给每个格子打分,问的问题类似“这个格子里有没有田径跑道”。项目 README 的基准测试部分公布过一组数据:在深圳一片 4×8 公里、364 个格子的区域里,以 OSM 上标注的 40 个跑道为真值,排名前 100 的格子里能找回其中 32 个。这个成绩说明 CLIP 很适合做初筛,把需要人看的格子从几百个压到几十个,但离“一眼命中”还差得远。
街景比对这一步,项目会拉取候选点附近的全景街景图(国内用的是百度全景),渲染出不同朝向的画面,然后用 DINOv2 比整体像不像、用 SIFT 数能对上的特征点有多少,综合排名。同样来自 README 的基准测试:在深圳的 8 个测试案例里,正确答案全部排进了前 6 名,其中一半排在第 1。需要说明的是,这两组都是单个脚本的专项测试,不是整套流程的定位准确率,后面第五章会再提到这一点。
用一张图把视觉比对的两条线路画出来:
flowchart TD
A[候选区域] --> B[切成卫星瓦片网格]
B --> C[CLIP 打分: 像不像目标地物]
C --> D[前几十个格子]
A --> E[拉取候选点附近街景全景]
E --> F[按不同朝向渲染画面]
F --> G[DINOv2 整体相似度]
F --> H[SIFT 特征点匹配数]
G --> I[综合排序]
H --> I
D --> J[人或模型复核]
I --> J
3.6 候选看板:把“感觉”变成可计算的证据账本
最后讲一个容易被忽略、但我认为是整个项目灵魂的设计:board.py,也就是候选看板。
它用的思想叫似然比(Likelihood Ratio),说白了就是:“如果答案真是 A 地,看到这条线索的可能性,比答案不是 A 地时,高出多少倍?”
似然比 = P(看到这条线索 | 是该地点) ÷ P(看到这条线索 | 不是该地点)
每来一条线索,就给每个候选乘上对应的似然比:
更新后的几率 = 原来的几率 × 似然比₁ × 似然比₂ × … × 似然比ₙ
比如画面里有接触网立柱,那“有电气化铁路的区域”似然比就高,“没有铁路的区域”似然比接近 0。几条线索乘下来,候选之间的差距就拉开了。
这套方法有几个好处:
- 可追溯:每个候选为什么排第几,账本里写得清清楚楚;
- 有分寸:README 里定的规矩是,排除一个候选必须有读出来或算出来的硬证据,而观察和猜测只能降低权重,不能一票否决;
- 防止拍脑袋:模型想说“我觉得是这里”,必须先在账本里找到支持它的那几条记录。
我做内容这几年,最怕的就是“结论漂亮但说不清来路”。这个看板的设计,某种程度上就是把调查记者那套“每句话都要有出处”的纪律,写成了代码。莫潇羽在这里多说一句:这个思路远比定位本身更有迁移价值,做数据核查、做事实验证、甚至做自己的选题判断,都可以借鉴。
四、上手体验:环境准备、安装与一次“只用自己照片”的合规自测
讲完原理,聊聊怎么动手。先把丑话说在前面:这一章的所有操作,我只建议你用自己拍的照片、或者明确获得授权的照片来做。 原因第七章会细讲,这里先记住这条底线。
4.1 先确认你的“底座”:它不是一个独立软件
很多人一听“开源工具”,第一反应是下载个安装包双击运行。geo-sleuth 不是这样的东西。
它是一个技能包,必须装进一个能读 SKILL.md、能执行命令行的 AI 编程助手里才能工作。官方列出的兼容对象包括 Claude Code、Codex、Cursor、Gemini CLI、OpenCode、GitHub Copilot,以及其他遵循同样规范的智能体。
这里给国内朋友一个合规提醒:上面这些编程助手大多是境外厂商的产品,背后调用的也是境外大模型。境外模型使用需遵守国内网络与内容管理相关规定,涉及工作数据、个人照片时尤其要谨慎,不要把敏感内容上传到你无法确认数据去向的服务里。国内也有不少支持类似“技能/规则文件”机制的编程助手和大模型服务,选择时优先考虑数据存储在境内、有明确隐私政策的产品。
整体结构可以这样理解:
flowchart TD
A[你] -->|自然语言: 帮我看看这张照片在哪拍的| B[AI 编程助手]
B -->|读取| C[SKILL.md 操作规范]
B -->|执行| D[scripts/ 下的 Python 脚本]
D --> E[本地处理: EXIF / OCR / 裁剪]
D --> F[联网查询: 地图 / 高程 / 搜图 / 街景]
D --> G[本地模型: CLIP / DINOv2]
E --> H[证据文件]
F --> H
G --> H
H --> B
B -->|报告| A
4.2 运行环境清单
根据官方 README,跑起来需要这些东西:
|
依赖 |
作用 |
备注 |
|
Python 3.10 及以上 |
运行全部脚本 |
建议用独立虚拟环境 |
|
uv |
Python 包与环境管理工具 |
每个脚本自带依赖声明,首次 |
|
curl |
命令行下载工具 |
多数系统自带 |
|
Chrome 或 Playwright Chromium |
反向搜图时驱动浏览器 |
没有的话反向搜图不可用,其他功能照常 |
|
足够的磁盘空间 |
存放视觉模型权重 |
CLIP、DINOv2 首次使用时会下载,几百兆到上 G 不等 |
uv 这个词新手可能没见过,简单说它就是一个更快的 Python 包管理器,作用和 pip 类似,但能自动帮每个脚本隔离出独立的依赖环境,避免“装了这个包、那个脚本就坏了”的问题。
4.3 安装:两种方式任选
官方给的一键安装方式,是用一个叫 skills 的命令行工具把技能装进指定的编程助手:
# 交互式安装:运行后按提示选择你用的编程助手
npx skills add Oldcircle/geo-sleuth
npx 是 Node.js 自带的命令,意思是“临时下载并运行一个 npm 包”,所以你的电脑需要先装 Node.js。
安装前提示:上面的命令会从 npm 公共仓库和 GitHub 下载代码,脚本首次运行时还会下载依赖包和模型权重,部分资源托管在境外。该命令会从境外服务器下载代码和依赖,执行前请自行评估网络合规性;如访问不畅,可优先使用国内镜像源(npm、Python 依赖均可按官方文档配置),不要采用任何突破网络管理的手段,访问不了的资源就放弃,项目大部分本地功能不受影响。另外,任何第三方代码都建议先读一遍再运行,最好放在独立的测试环境里。
如果你只用其中一两个助手,交互式安装最省心,不必一次全装。习惯手动管理的话,也可以直接把仓库克隆下来,把 skills/geo-sleuth 这个文件夹复制到对应助手的技能目录里。不同助手的技能目录不一样,以官方 README 的对照表为准,比如 Claude Code 用户级目录是 ~/.claude/skills/,Codex 等几款则共用 ~/.agents/skills/。
开源项目的安装方式、目录约定迭代很快,上面是写稿时的情况,请以项目仓库 README 的最新说明为准。
4.4 第一步永远是“体检”
装好之后别急着丢照片。官方建议首次使用先跑环境自检脚本:
# 本地检查:Python、uv、curl、查表数据、目录写权限、浏览器能否启动
uv run skills/geo-sleuth/scripts/doctor.py
# 附加网络探测:只探测各服务能否连通,不上传任何照片
uv run skills/geo-sleuth/scripts/doctor.py --network
如果是通过安装命令装的,路径换成技能实际所在目录即可,也可以直接让编程助手替你跑。
我的经验是,这一步能帮你提前知道哪些功能在你的网络环境下用不了。项目里有些数据源和服务部署在境外(比如部分反向搜图引擎、海外街景服务、模型权重托管平台),在国内网络环境下可能连不上或不稳定。遇到这种情况,正确的做法是接受这个限制,或者改用国内可正常访问的服务(比如项目本身就支持的百度反向搜图和百度全景),不要去折腾任何突破网络管理的手段。
4.5 推荐的自测方式:先跑“纯本地”流程
如果你只是想理解这套流水线在干什么,我强烈建议先从不联网搜图的本地模式开始。官方提供了这样一条命令:
# 只做本地处理:读元数据、裁剪边角、生成变体、OCR,不做反向搜图
uv run skills/geo-sleuth/scripts/intake.py photo.jpg --out-dir intake --no-rev
跑完之后,打开 intake/intake.md,你会看到这张照片被“榨”出了哪些信息:EXIF 里有什么、哪些区域被单独裁剪放大、OCR 读出了哪些字。
我觉得这一步对普通人的意义,甚至比