benjaminc-tech/free-reverse-image-search
GitHub: benjaminc-tech/free-reverse-image-search
一个零依赖、零上传的纯浏览器端以图搜图工具,同时提供 EXIF 解析和感知哈希计算能力。
Stars: 0 | Forks: 0
# 免费以图搜图
**[立即体验:freereverseimagesearch.netlify.app](https://freereverseimagesearch.netlify.app)**
同时在 Google Lens、Yandex、Bing 和 TinEye 中搜索同一张图片,读取文件中隐藏的 EXIF 和 GPS 数据,并使用三种感知哈希(perceptual hash)对其进行指纹提取。
完全免费,无需账号,无广告,并且**不上传任何数据**。整个工具就是一个 HTML 文件,完全在您的浏览器中运行。这个承诺正是本仓库存在的意义:您可以亲自阅读源码并确认这里没有任何上传路径,这是任何托管工具都无法提供给您的保证。

- 单文件,无构建步骤,无依赖,无服务器
- 无需任何库,直接从原始字节解析 EXIF,包括将 GPS 转换为十进制
- 包含 aHash、dHash 和 pHash,甚至包含手动实现的 DCT
- 标记出关于照片值得了解的两件事:是否携带位置信息,以及最后一次保存是否由编辑软件(而非相机)完成
## 功能说明
- **多引擎搜索。** 一键将图片提交给 Google Lens、Yandex、Bing Visual Search 和 TinEye。
- **EXIF 提取。** 解析相机、镜头、时间戳、编辑软件和 GPS 坐标,无需任何库,直接从原始字节中解析。适用于本地文件,以及允许跨域读取的远程 URL。
- **感知哈希(Perceptual hashing)。** 在页面内计算 aHash、dHash 和 pHash,让您能够辨别两张图片在缩放或重新压缩后是否仍为同一张画面。
## 决定一切的设计约束
反向图片搜索引擎必须能够**自行获取图片**。这将问题分成了两类:
- **网络上已有的图片**拥有 URL,因此可以直接提交给所有四个引擎。这条路径完全可行。
- **您电脑上的文件**没有公开地址。通常的解决方案是将其上传到服务器以便引擎访问,这意味着需要承担托管成本、存储开销,以及对陌生人上传内容的责任。本工具拒绝这种妥协:它将图片复制到您的剪贴板,以便您自行粘贴到搜索引擎中。
正是这一选择,意味着没有后端、没有数据库、没有账单,也没有内容审核问题。
## 功能限制
它本身并没有图片索引,因此无法自行回答“这张图片还出现在哪里”。这种能力属于 Google、Yandex 和 TinEye,它们花费了 15 年以上的时间和数百万的资金,构建了包含数十亿张图片的抓取索引。本工具只是它们优秀的访问入口,并额外提供了它们不会给您的分析功能。
## 设计说明
全程使用 Monaco 字体,以 20px 为基准,配有粗黑线条和 `4px 4px 0` 偏移(无模糊)阴影。按钮和图块在点击时会有物理下陷效果:元素向右下方平移,阴影收缩为零。
Marvel 风格主导了调色板。标志文字像漫画刊头一样以纯红色底板反白显示,红色和黑色承担了结构构建的工作,金色作为辅助强调色。
刚好保留了一种 Google 的颜色,用在唯一真正属于 Google 的那个图块上。
| 变量 | Hex | 搭配色 | 对比度 | 用途 |
|---|---|---|---:|---|
| `--hero` | `#EC1D24` | 仅纯色块 | n/a | 分区标签、强调元素 |
| `--hero-ink` | `#C7151C` | 白色 | 5.91:1 | 刊头底板、主按钮、Yandex 图块 |
| `--gold` | `#F0B323` | 黑色 | 10.21:1 | TinEye 图块、编辑标记 |
| `--ink` | `#0d0f13` | 白色 | 19.18:1 | Bing 图块、隐私徽章 |
| `--g-blue` | `#4285F4` | 黑色 | 5.38:1 | Google Lens 图块、位置标记 |
以下规则基于实际测量,而非主观臆测:
- **Marvel 红 `#EC1D24` 无论搭配白色还是黑色都不合格**(对比度分别为 4.40:1 和 4.35:1),因为它的亮度居中。它仅被用作不含文字的纯色块。所有包含文字的红色表面均使用 `#C7151C` 的 `--hero-ink`,其在白底上的对比度超过了 4.5:1。
- **铭牌自带文字颜色。** 它们不再全都是浅色,因此每个引擎在声明其 `tile` 的同时也会声明对应的 `ink`,并且每种搭配的对比度都被验证至少达到了 4.5:1。
- 金色配红色的对比度仅为 3.15:1,仅能达到大号粗体文本的合格标准。它只用于 35px 刊头中的一个词,除此之外不用于其他任何地方。
### Favicon
在 Marvel 红色底板上的放大镜:白色镜片,金色手柄,所有轮廓像 UI 的其余部分一样用黑色勾勒。在真实尺寸下测试它而不是将其放大欣赏后,得出了三个设计决策:
- **镜片是实心圆盘,而不是圆环。** 圆环在 16px 尺寸下会糊成一团并失去辨识度。
- **金色手柄带有黑色下划线。** 金色配红色的对比度仅为 3.15:1,如果没有黑色墨水将它们隔开,手柄在小尺寸下会与底板模糊地融为一体。
- **直接否决了图片边框的概念。** 它在 128px 时看起来还不错,但在 16px 时就会变成无法辨认的污迹。
源文件是 `favicon.svg`;它也作为 data URI 内联到了 `index.html` 中,因此该工具始终是一个完全独立的单文件。
### 在手机上
已在 393px 尺寸下进行测试。有三个只有在这个宽度下才会暴露的问题需要修复:
- 引擎网格之前使用的是 `minmax(400px, 1fr)`,这会导致即使在 345px 的容器中也保持 400px 的轨道宽度,从而使得图块溢出,并被 `overflow-x: hidden` 防护措施悄无声息地裁剪掉。现在已改为 `minmax(min(400px, 100%), 1fr)`。
- 表格值上的 `word-break: break-all` 对于 16 个字符的哈希值来说是正确的,但对于“TestCam Industries”这样的文字就不合适了。现在改为 `overflow-wrap: anywhere`,它会优先在空格处换行,只有在迫不得已时才会截断单词。
- 在宽度低于 560px 时,键值表会垂直堆叠,标签位于值上方,两者均占据全宽。否则,不换行的标签列会占据屏幕的大部分空间。
在深色模式下,颜色块保持其准确的色相,就像印刷页面上的墨水一样。只有它们周围的表面会发生反转,同时 `--edge` 会切换为接近白色,以保证线条轮廓依然清晰可见。
## 测试说明
```
./tests/run.sh
```
所有内容均已通过独立的参考实现进行了验证,并且测试会直接从 `index.html` 中提取真实代码,因此它们不会与实际上线的内容产生脱节。
- **EXIF** 的检查依赖于一个测试夹具,其字节是在 `tests/make_fixtures.py` 中手工组装的,因此每个预期值都是精确已知的。覆盖了 ASCII、SHORT、LONG 和 RATIONAL 类型,包含两个 sub-IFD,以及 GPS 的度/分/秒到十进制的转换。同时也覆盖了各种失败路径:无 EXIF、非 JPEG、空缓冲区以及截断的文件(在此情况下绝不能抛出异常)。
- **哈希值** 与单独的 Python 实现进行了逐位比对,向其输入完全相同的灰度像素阵列,从而确保测试衡量的是算法本身,而不是两种不同的重采样器。
预计浏览器输出的哈希值与测试输出的哈希值会有几位的差异。这并不是一个 bug:Canvas 的下采样缩放和 Pillow 的 `BILINEAR` 并不是同一个滤镜,因此它们会向完全相同的算法传递略微不同的像素。真正重要的是图像之间的距离保持稳定,而这正是鲁棒性测试所检查的内容。
- **鲁棒性** 测量了经过真实编辑后的真实 Hamming 距离。
测得的 pHash 行为(距离原图的距离,0 = 完全相同):
| 编辑操作 | pHash | dHash |
|---|---:|---:|
| 缩放至 25% | 0 | 0 |
| 缩放至 200% | 0 | 0 |
| JPEG 质量 20 | 0 | 1 |
| 亮度 +25% | 0 | 2 |
| 转换为灰度图 | 0 | 2 |
| 四周边缘各裁剪 5% | 2 | 4 |
| 旋转 90 度 | 24 | 8 |
| 完全不同的一张图片 | 32 | 46 |
**已知局限:旋转会使其失效。** 旋转 90 度会被识别为不同的图像。这是此类哈希算法的工作原理所固有的特性,而不是 bug,但这意味着您应该在比较之前先将图像旋转复原。
## 文件说明
```
index.html the entire tool
favicon.svg favicon source (also inlined into index.html)
tests/run.sh runs everything
tests/make_fixtures.py builds test-exif.jpg with a hand-built EXIF block
tests/test_exif.mjs EXIF parser assertions
tests/test_hash.mjs hash cross-check against Python
tests/test_robustness.py Hamming distances across real edits
```
`test-exif.jpg` 和 `_base.jpg` 是由测试生成的,可以删除。
## 本地运行
```
python3 -m http.server 8765
# 然后打开 http://localhost:8765/
```
直接从磁盘打开 `index.html` 也可以运行,尽管在某些浏览器中,`file://` 协议下的剪贴板写入功能可能会受到限制。
## 自行运行
直接打开 `index.html`,或者部署该服务:
```
python3 -m http.server 8765
```
在某些浏览器中,`file://` 协议下的剪贴板写入可能会受到限制,这也是在本地部署服务器的唯一理由。
## 部署说明
`publish/` 目录包含了确切要发布到网络上的内容:页面、图标、`robots.txt`、`sitemap.xml` 和 `_headers`。测试夹具和测试套件被刻意排除在外。
```
cp index.html favicon.svg publish/
netlify deploy --prod --dir=publish
```
图标是根据与 `favicon.svg` 相同的几何结构生成的:
```
python3 tools/make-icons.py
```
## 开源协议
MIT。请随心所欲地使用它。
标签:EXIF解析, 后端开发, 图像搜索, 图片元数据提取, 多模态安全, 感知哈希, 数据可视化, 纯前端工具, 网络安全, 逆向工具, 隐私保护