# 二维码与条形码识读｜从图片或摄像头扫二维码

把摄像头对准码，或者把拍下来的图丢进来。读这件事只发生在这里，别处都没有。

> 从照片、截图或摄像头读取二维码，在打开之前就看清这个链接到底通向哪里。EAN、UPC、Code 128、Code 39 和 ITF 条形码也能读。什么都不上传。

这是一个浏览器交互工具，地址是 https://abox.tools/zh/qr-barcode-reader/。文件在运行该浏览器的设备上处理。主动分享和可选联网功能都在页面上说明。下文是它的文字说明；使用工具请打开该地址。

## 你的图片和图片里的码**在浏览器里处理**，无须上传处理。

读一个码是对像素做算术，而像素本来就在这里。找到符号、把拍摄角度校正回来、把掩码解掉、用里德-所罗门把损坏的地方补上、再把比特读回来，这些全都发生在这一页里两千来行你能读的 JavaScript 中。**摄像头是同一个承诺，不是它的例外：**一帧画面以像素的形式进到这个标签页，被看一眼，然后就没了。什么都不录，什么都不留，而且这一页压根就没有任何能把东西送出去的网络功能。

- ✗ 不上传
- ✗ 不用账号
- ✗ 不做记录
- ✓ 断网可用
- ✓ 开源

## 怎样不上传图片就读出二维码

1. **把图给它。** 把照片或截图拖到那个框上，或者直接粘贴一张，或者按摄像头那个按钮。一次好几张也行，每张各读各的，各有各的答案。已经在你屏幕上的码，截个图是最快也最靠谱的路子，因为那里既没有镜头，也没有角度，更没有光。
2. **让整个符号连同留白一起进画面。** 码周围那圈白是码的一部分：识读器就是靠它知道符号到哪儿为止的。裁到方块边缘的照片，是一个码读不出来的最常见原因，没有之一。让码占到画面一半左右差不多正好；再靠近，四个角就跑到画面外面去了。
3. **在决定任何事情之前，先把地址读一遍。** 去扫海报、停车缴费机或者信件上的码，图的就是弄清楚它通向哪里；而这恰恰是手机摄像头真正不让你做的那一件事。这里主机单独占一行。要不是你预想中的那个名字，你要的东西其实已经到手了，也就没什么好打开的了。
4. **把提示当回事，尤其是那些不响的。** 光秃秃的 `http://` 地址、用看起来不是那套文字的字母写的名字、短链接服务，或者地址里 `@` 前面的任何东西——每一样都会在它出现的地方被点名。它们单拿出来谁也证明不了什么，但在你过去之前，每一样都值十秒钟。
5. **读不出来，先改光，别先改别的。** 几乎所有失败都是阈值的问题：横在中间的反光、压在一角的阴影，或者以某个恰好把背光收进来的角度拍下的屏幕。挪个位置让反光别落在码上，或者把摄像头的灯打开。还不行就正对着拍一张平的照片，把那张丢进来——一张静止的图会得到比实时画面细致得多的搜索。
6. **答案看着不对劲，就看看采样出来的那张图。** 把“这一个是怎么读出来的”展开，看那个小格子。它是这一页认为这个码长什么样，从采到的模块重新画回去的结果。一个把符号读错、又修补成了某种像模像样东西的识读器，只会在那里露出马脚，别处都不会。

## 说得更细的版本

[怎么做一个二维码，并证明它扫得出来](https://abox.tools/zh/guides/make-a-qr-code-and-prove-it-scans/): 生成二维码，再用同一个网站的识别器验证：一字不差的内容、真正的链接，按印刷尺寸、用一张照片，在批量印刷之前。全在浏览器里，什么都不上传。

## 工具箱里还有

- [哈希与校验和](https://abox.tools/zh/hash-checksum/): 把下载来的文件和发布方公布的那串数字对一下，不用把它发给任何人。
- [密码与密码短语生成器](https://abox.tools/zh/password-generator/): 在这里生成，由你自己的浏览器生成，不发往任何地方。什么都不保存，也没有历史记录。
- [JSON 格式化](https://abox.tools/zh/json-formatter/): JSON、XML、HTML、CSS 和 YAML，格式化或转换。没有一个字被贴进别人的服务器。
- [YAML 转 JSON](https://abox.tools/zh/yaml-to-json/): 两个方向都有，而且会告诉你各自的代价。这些都不会粘进别人的服务器。

## 常见问题

### 图片会被上传到什么地方去吗？

不会，从图里读出来的东西也不会。图片在这一页的画布上解码，然后就在那里被读，用的是这个站点提供的 JavaScript。这个工具压根没有任何网络功能——它从不去取东西，也从不发东西——而这一页的 `Content-Security-Policy` 把它可以联系的地址一个不落地写了出来，其中没有一个是我们的。最简单的证明就是断网：它照样能用。

### 摄像头会录下什么吗？

不会。摄像头的一帧以像素的形式进到这个标签页，画到画布上，被看一眼，大约十分之一秒后就被下一帧盖掉。没有东西写到硬盘上，也没有东西留下来。你一按停止，流就断了；标签页退到后台时会断，你离开这一页时也会断。而值得信的指示是你摄像头上的那盏灯，因为没有哪一个网页能把它关掉。

### 为什么它只把链接给我看，不直接打开？

因为有用的正是这一步。二维码是一个你读不出来的地址，贴在停车缴费机码上的那张贴纸之所以管用，靠的就是这一点：等你知道它去了哪儿，你已经在那儿了。这里整串字符原样印出来，主机被拎出来单独占一行，打开是另一个按钮，读完之后你自己按。这是多一次点击，而这一次点击正是这个格式一直都缺的。

### 它能读什么？

二维码从版本 1 到 40 全都能读，四个纠错等级都能读，数字、字母数字、字节和汉字模式都能读，ECI 字符集和属于一组连拼符号的码会被报出来，而不是悄悄扔掉。条纹那一边：EAN-13、EAN-8、UPC-A、UPC-E、ITF-14、交叉二五码、Code 128 和 Code 39。Data Matrix、PDF417、Aztec 和 MaxiCode 它不读。

### 我的码读不出来，是哪里不对？

十次里有九次是三件事里的一件。白色留白被裁掉了，而识读器要靠它知道符号到哪儿为止。码有一块被反光或阴影盖住了，于是没有哪个阈值能把深色方块和浅色方块分开。或者码在画面里太小，模块只有一两个像素宽。换个光，让码占到画面一半左右，正对着拍而不是斜着拍。

### 破损的或被挡住一部分的码，它能读吗？

有时可以。Reed-Solomon 纠错可在符号的纠错能力内恢复损坏的码字。H 级约 30% 指全部码字的比例，并非任意模块或图像面积的 30%；定位图形受损可能导致完全无法识别。结果下方的修复数表示本读取器实际纠正的数量。[DENSO 解释了各级别](https://www.qrcode.com/en/about/error_correction.html)。

### 为什么它说不出一个 `bit.ly` 链接通向哪里？

因为要弄清楚就得去问 `bit.ly`，而那是一次网络请求。这一页别的所有说法都立在“这里没有任何代码会去联系任何东西”上面，为这件事偷偷开个口子，比这个答案本身亏得多。所以短链接服务的名字会被点出来，它藏起来的东西则老老实实地留作未知。你真想解开，就把它粘到一个愿意去问的地方。

### 扫二维码这件事本身安全吗？

扫是安全的。照着它做才是风险，而且是真实的风险：贴在真码上面的假码，在停车缴费机上、在餐馆桌上、在快递取件单上，已经常见到有了专门的名字。它们之所以管用，是因为没人能靠看把一个码读出来。读它但不打开它——也就是这一页在做的事——把那点便宜整个拿掉了，而看一眼主机所花的十秒钟就是全部的防守。

### 每个结果下面那个小格子是什么？

是这一页真正从你的图里采到的那些模块，按一个模块一格重新画出来的。它放在那里，是为了让读出来的结果可以用眼睛核对，而不是只能相信：要是那个格子看起来就是你拍的那个码，上面的答案就来自正确的像素。几乎没有识读器给你看这个，而这正是“能查的工具”和“只能信的工具”之间的差别。

### 一张图里有好几个码，它都能读吗？

眼下一张图一个。一次拖进来好几张，每张各读各的；开着摄像头慢慢移，它会一个接一个地读，把每个还没见过的新码留下来。一张照片里拍下一整页码，那是该去裁图，或者把摄像头一个一个对过去的场合。

### 为什么它给我的条形码起了个跟我要的不一样的名字？

因为条形码不带着自己的名字走。UPC-A 就是首位为零的 EAN-13，ITF-14 就是十四位、校验位对得上的交叉二五码，而 Code 128 在数字模式下不像别的任何东西。这一页报的是那些条纹说了什么，加上校验位确认了什么——符号自己知道的也就这么多。

### 断网能用吗？

能。把页面加载一次，然后断网，它照样能用，摄像头也一样。这同时也是证明什么都没被上传的最简单办法：一个要把你的图送出去解码的识读器，在你拔线的那一刻就停了。

## 这条隐私承诺怎么核实

- **图片永远不会被上传，摄像头也不例外。** 你丢进来的照片会在这一页的画布上解码，然后就在那里被读。摄像头的一帧是同一件事，只是每秒发生三十次：画到那块画布上，被看一眼，再被下一帧盖掉。没有哪一帧被录下来，也没有哪一帧留下来；你按下停止时摄像头的灯灭掉，整件事就到此为止。
- **这里没有任何东西去取任何东西。** 解码文字和链接在本地显示，不会自动打开。工具不查询短链接的终点，因为这要联系对方。检查处理源码及 `fetch`、`XMLHttpRequest`、`sendBeacon` 调用，看它们拿到什么数据。页面的 `Content-Security-Policy` 限制连接目的地，也包括已允许的平台主机；它不是“没有网络目的地”的声明，也不能证明每个载荷。
- **它把地址给你看。它从不替你打开。** 印出来的二维码是一个谁也读不出来的地址，而这正是在它上面贴一张贴纸对某些人划得来的原因。这里什么都不打开。整串字符原样印出来给你看，你真正会落到的主机被单独拎出来放一行，那些让一个地址看起来像另一个地址的花招也在它们出现的地方被点名：`@` 前面的用户名、用形状和我们的字母一模一样的另一套文字写的名字、还有转跳。
- **它连采到的样子也一并给你看。** 每个二维码结果下面都有一张图，是这一页真正从你照片里读到的那些模块。如果它看起来就是你扫的那个码，上面的答案就站得住；如果它看起来像雪花点，那就站不住。一个只把一串字符丢给你就完事的识读器，没法这样查。
- **谷歌加载了什么，又拿不到什么。** 广告和统计脚本来自谷歌，捐助按钮来自 Buy Me a Coffee。它们谁也拿不到一张图片、一帧画面，或者从里面读出来的任何东西。把像素变成字符的每一行代码都由这个源自己提供，也都在仓库里摆着。
- **它断网也能用。** 把网断掉，这个工具一点不变，摄像头也一样，因为它里面本来就没有过一步走网络。这是所有证明里最简单的一个。

**你可以自己核对。**上面这些不用你信。这个页面是仓库里的模板和配置生成出来的，构建脚本你能读，也能自己跑一遍，跑出来的结果就提交在 `dist` 分支上。拿实际发给你的内容，跟你自己从源码构建出来的结果对一遍就知道了： https://github.com/A-Box-of-Tools/website

最值得先读的几个文件：`config/site.toml` 里是 Content-Security-Policy，其中 `src/binarize.js` 和 `src/detect.js` 负责在照片里找到符号，也就是阈值、定位图形和透视变换；`src/qr-decode.js` 负责把它读回来；`src/reed-solomon.js` 负责修补读错的地方；`src/linear.js` 负责那些带条纹的；而 `src/camera.js` 就是这一页里所有碰到摄像头的行。
