Perspec 1.0

Hacker News Top 产品

摘要

Adrian Sieber 宣布 Perspec 1.0 发布,这是一款用于校正图像透视的桌面应用,适用于文档和收据照片。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/04 19:47

# 发布 Perspec 1.0 来源:https://adriansieber.com/announcing-perspec-1-0/ 2026\-07\-29— 3020 字 — 16 分钟 我非常兴奋地宣布 [Perspec](https://github.com/ad-si/Perspec) 1.0 版本正式发布! Perspec 是一款用于校正图像透视的桌面应用。它主要面向文档和收据照片,但也适用于任何类型的图像。 Perspec 截图 这终于成为了我在 9 年前开始这个项目时所设想的那款应用。我没想过会花这么长时间才走到这一步,但我对结果非常满意,希望你们也会喜欢! ## 最初的动机 你可能很熟悉手机上可用的扫描应用,比如 [Adobe Scan](https://www.adobe.com/acrobat/mobile/scanner-app.html)、[vFlat](https://www.vflat.com/)、[SwiftScan](https://swiftscan.app/) 等等。扫描功能也集成到了 [Dropbox](https://www.dropbox.com/features/productivity/document-scanner) 中,如今甚至原生集成到了 [iOS](https://support.apple.com/en-us/108963) 本身。 但是,我不喜欢在手机上工作,我更愿意只拍摄文档和收据的照片,然后改天在电脑上处理清理和整理工作。在电脑上,我有大屏幕、键盘和精准的鼠标,编辑起来更快也更准确。 此外,这些移动应用为了给用户提供他们熟悉的东西,做出了一些令人恼火的技术决策。 例如:如果你将文档存储为灰度 PNG,就可以在不引入压缩伪影的情况下获得较小的文件大小。然而,所有流行的应用都会给你一个灰度 JPEG 图像,文件大小大得多,图像质量却更差,仅仅因为 JPEG 是人们熟悉的格式。 又或许我把它们想得太好了,它们实际上不知道:如果图像包含大面积的均匀颜色,PNG 可以比 JPEG 更小;而对于普通照片,JPEG 通常比 PNG 更小。而且,之后再转换为 PNG 并不可行,因为那时图像已经包含了所有的 JPEG 压缩伪影。 例如,让我们比较一下扫描以下文档的结果: 放置在桌面上的研究论文照片 其他应用产生的文件更大,而且你可以清楚地看到降低质量的压缩伪影。 | 应用 | 结果 | 预览 | 备注 | |------|------|------|------| | Perspec | ~110 kB,PNG [查看结果](https://adriansieber.com/announcing-perspec-1-0/result_perspec.png) | Perspec 结果细节 | | | Scanner Pro | ~190 kB,JPEG [查看结果](https://adriansieber.com/announcing-perspec-1-0/result_scanner_pro.jpeg) | Scanner Pro 结果细节 | 从导出的 PDF 中提取的 JPEG | | iOS | ~300 kB,JPEG [查看结果](https://adriansieber.com/announcing-perspec-1-0/result_ios.jpeg) | iOS 结果细节 | 从导出的 PDF 中提取的 JPEG | 另一件让我比应有的程度更恼火的事情是,现在几乎每个应用都包含的荒谬检测预览: 当你拍照时,应用会向你展示一个实时叠加层,显示它检测到的文档位置。然而,这对你完全没有帮助。仅仅因为它能在预览视频流中正确检测文档,并不意味着它能在最终照片中正确检测。由于分辨率更高、光照不同(曝光时间、闪光灯等)以及对比度不同,最终照片中的检测结果往往会大相径庭。 所以,这个预览只是告诉你镜头前确实有一份文档,而这一点你早就知道了,因为是你把它放在那里的。🤦♂️ 最后,也是最重要的一点,我知道我可以为我自己拍摄的那种照片构建一个更好的文档检测算法。现有应用中的检测经常会有轻微偏差,即使你有一张文档与背景对比度良好的好照片。 大多数应用在其流程中使用了某种边缘检测步骤,正如 Dropbox 在 [这里](https://dropbox.tech/machine-learning/fast-and-accurate-document-detection-for-scanning) 所解释的那样。但我知道,文档和收据通常没有笔直的边缘,而是有皱褶或弯曲的边缘。当你试图将甚至只是轻微的曲线与直线匹配时,端点会偏离得很远。因此,应用应该尝试检测角点,并从这里构建文档。文章后面会有计算机视觉技术的详细解释。 ## 通往 1.0 的漫漫长路 我开始开发 Perspec 时还是一名学生,为了学业必须扫描很多东西,所以我有足够的动力去构建这样的东西。 当然,你也可以用 Photoshop、[Affinity Photo](https://www.affinity.studio/photo-editing-software) 或 [GIMP](https://www.gimp.org/) 来修正透视。但开销很大:打开每张照片,找到透视工具,拖动角点,选择正确的导出设置,对下一张照片重复同样的操作,如此循环。这些工具是为处理任意图像而设计的,而不是为了尽可能快速地处理 50 张收据。我想要一个专注于这一项任务的应用,工作流程尽可能精简。 我的第一次迭代是一个完全自动化的命令行应用,名为 [Perspectra](https://github.com/ad-si/Perspectra),使用 Python 和 [scikit-image](https://scikit-image.org/) 实现。你传入图像,它会尝试为你检测并提取文档。就这么简单。 虽然我其实很喜欢 [scikit-image](https://scikit-image.org/)——功能丰富,而且比 [OpenCV](https://opencv.org/) 更直观——但我很快意识到我绝对不喜欢 Python。但更重要的是,我意识到我还需要一个图形界面来修正检测错误的文档边界,因为全自动的计算机视觉流程永远不可能 100% 正确地处理所有文档。 那么,如何构建一个带有图形界面的桌面应用呢?显然要用 Haskell。😝 开个玩笑。实际上,我当时最近开始学习 Haskell,并且完全爱上了它。所以,自然而然,我想看看它能否用于构建桌面应用。 由于我不想再使用 Python,我的下一个直觉是使用 ImageMagick 来完成计算机视觉和图像处理任务,因为我对它的功能有一些经验。现有的 [Haskell 绑定](https://hackage.haskell.org/packages/search?terms=magick) 相当欠缺,所以我选择直接调用 `magick` 作为外部进程。虽然这在大多数情况下都能工作,但在不同平台上正确安装和链接它一直很痛苦,而且对于较大的图像,性能出奇地差。 另一个显而易见的选择是 OpenCV,但我在大学时使用它留下了一些糟糕的回忆(也许只是 C++ 的环境问题……),而且 Haskell 绑定看起来相当痛苦。 所以,我的下一个实验是使用原生的 Haskell 图像处理库 [Hip](https://github.com/lehins/hip)。在它的作者 [@lehins](https://github.com/lehins) 本人和 [@HanStolpo](https://github.com/hanstolpo) 的帮助下,我们在 ZuriHac 上成功让它运行了起来!*(再次感谢!)* 然而,它仍然缺少我想要的一些功能,比如使用大津法进行二值化。虽然这肯定可以在 Hip 中实现,但我(这一次)觉得 Haskell 的抽象对于手头的任务并没有什么帮助,只会不必要地使事情复杂化。相比之下,C 语言中的 `for` 循环在概念上非常简单,并且和 Haskell 代码一样快。幸运的是,C 语言在 Haskell 中是一等公民,将一些 C 代码打包并通过 Haskell 的 FFI 调用非常容易。 不幸的是,似乎没有一个可以直接接入 Perspec 而不会带来太多 FFI 麻烦的简单 C 库,所以我开始开发 [FlatCV](https://github.com/ad-si/FlatCV)——一个纯 C 语言实现的计算机视觉和图像处理库。 我可能在这里过度“磨刀”了,但既然整个项目本就是出于热爱,何不全力以赴呢?😅 我对使用 C 语言实现图像处理算法的体验感到非常满意,并且我能够快速构建一个功能完整的版本,并配上必要的 Haskell 绑定。就在最近,我发布了 [版本 0.3.0](https://github.com/ad-si/FlatCV/releases/tag/v0.3.0),到目前为止,它已经具备了图像处理库所应有的大部分基本操作。我还移植了一些更高级的计算机视觉操作,比如 [自适应二值化](https://flatcv.ad-si.com/binarize.html) 和 [角点检测](https://flatcv.ad-si.com/corner-detection.html),这些我最初是在 [Perspectra](https://github.com/ad-si/Perspectra) 中实现的。 FlatCV 的性能仍有很大的提升空间:SIMD、GPU 使用、[流式处理](https://github.com/libvips/libvips/wiki/Why-is-libvips-quick) 等等。不过,由于 FlatCV 并非用于实时场景(即 60 帧/秒),目前的性能已经绰绰有余了。 有了 FlatCV,我终于可以实现 1.0 的最后一个缺失部分:直接在 Perspec 中进行自动角点检测。 ## 边缘检测 vs. 角点检测 大多数扫描应用通过类似 Dropbox [描述](https://dropbox.tech/machine-learning/fast-and-accurate-document-detection-for-scanning) 的流程来检测文档: 1. 缩小图像 2. 运行边缘检测算法(例如 [Canny](https://en.wikipedia.org/wiki/Canny_edge_detector)) 3. 使用 [霍夫变换](https://en.wikipedia.org/wiki/Hough_transform) 找到最突出的直线 4. 从这些直线的交点构建四边形,并为它们评分以选出最佳结果 这对于放在高对比度背景上的完全平整纸张效果很好。但真实的文档很少是完全平整的:收据有皱褶,书页有弯曲,折叠过的纸张也不可能完全平展。当你将一条直线拟合到弯曲的边缘时,直线的交点(即重建的角点)可能会偏差很大,即使边缘检测本身是完美的。 因此,Perspec 从另一个角度来处理这个问题:它不是寻找直线边缘,而是将照片分割为文档和背景,然后从文档的轮廓中推导出角点。这是 FlatCV 的 [角点检测](https://flatcv.ad-si.com/corner-detection.html) 流程的详细步骤: 1. 将图像转换为灰度并缩小到 256×256 像素。(检测不需要全分辨率,并且这样速度更快。) 2. 对图像进行模糊处理,以去除噪声和纸张纹理。 3. 使用 [Sobel 滤波器](https://en.wikipedia.org/wiki/Sobel_operator) 创建高程图。(强边缘变成山脊。) 4. 使用 [分水岭分割](https://en.wikipedia.org/wiki/Watershed_(image_processing)) 填充高程图:图像中心作为文档盆地的种子,图像边界作为背景盆地的种子。结果是一张文档的二值掩码。 5. 使用二值闭运算平滑掩码。 6. 在掩码上运行 [Förstner 角点检测器](https://en.wikipedia.org/wiki/Corner_detection#The_F%C3%B6rstner_corner_detector)。(与更流行的 Harris 检测器不同——其角点会向内偏移——Förstner 检测器能够产生亚像素精度的角点位置。) 7. 对角点候选进行排序,并保留角度最大的 4 个角点。 8. 将角点坐标缩放回原始分辨率。 | 输入 | 检测到的角点 | |------|--------------| | 收据照片 | 标出检测到角点的收据 | 这种方法的好处在于,它从不假设边缘是直的。分水岭算法能够很好地沿着有皱褶的文档边界行进,即使是在皱巴巴的收据上,角点在局部也依然定义良好。 如果检测结果确实有误,你只需将选区多边形拖拽到正确的大小和位置即可。自动检测和手动修正两全其美。 ## 二值化算法 修正透视只是成功的一半。对于文档和收据,另一半是将照片转换为干净的黑白图像。Perspec 中的 `Save BW` 和 `Save BW Smooth` 按钮就是用来做这件事的。 这个任务听起来很简单:每个比某个阈值暗的像素变成黑色,其他所有像素变成白色。棘手的地方在于选择阈值。 经典的解决方案是 [大津法](https://en.wikipedia.org/wiki/Otsu%27s_method):它构建图像中所有灰度值的直方图,然后选择能够最好地分离暗像素(文字)和亮像素(纸张)的阈值。这在……光照均匀的图像上效果很好。 不幸的是,照片很少是光照均匀的。通常会有亮度渐变,或者来自拿着相机的人或相机本身的阴影。 文档扫描领域的文献中充满了局部自适应算法(例如 [Niblack 和 Sauvola](https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_niblack_sauvola.html)),它们根据每个像素的邻域为其计算单独的阈值。 然而,FlatCV 的 [智能黑白转换](https://flatcv.ad-si.com/binarize.html) 使用了一个更简单的技巧,只需要一个全局阈值:它在阈值化*之前*去除阴影。 1. 将图像转换为灰度。 2. 创建一张高度模糊的副本(模糊半径约为图像尺寸的 10%)。所有文字和细节都会被平均掉,剩下的基本上就是光照:亮度渐变和柔和的阴影。 3. 从灰度图像中减去模糊副本。这保留了高频部分(文字),去除了低频部分(阴影)。结果就是一张光照均匀的图像。 4. 应用通过大津法计算出的全局阈值。 对于打印文档的照片,我发现这个方法的效果与更复杂的局部自适应算法一样好,甚至更好,同时实现起来更快、更简单。 `Save BW` 按钮正是应用这一流程,并将结果存储为真正的 1 位黑白图像,其中每个像素要么是全黑,要么是全白。 新的 `Save BW Smooth` 按钮则更进一步,使用两个阈值(大津阈值 ± 一个小偏移量):低于较低阈值的像素变为黑色,高于较高阈值的像素变为白色,介于两者之间的像素保留缩放的灰度值。这会产生抗锯齿边缘,因此文字看起来不会参差不齐,同时文件大小几乎仍然很小。这就是为什么它是处理文档、收据和白板的推荐选项。 | 输入 | 透视校正后的局部 | Save BW | Save BW Smooth | |------|------------------|---------|----------------| | | | 转换为黑白的局部 | 转换为抗锯齿黑白的局部 | ## 1.0 中的其他新特性 自动角点检测是主要亮点,但 [1.0 版本](https://github.com/ad-si/Perspec/releases/tag/v1.0.0.0) 还带来了不少其他改进: - 支持 Windows。加上已有的 macOS 和 Linux,Perspec 现在可以在三大主流桌面操作系统上运行。 - 选区多边形的边缘现在也可以拖拽(以前只能拖拽角点),网格线使选区对齐更加容易。 - 新增“选择文件”视图,支持按钮和拖放方式选择图像。 - 新的 `Save BW Smooth` 导出选项,可将图像转换为抗锯齿黑白。这现在成为处理文档、收据和白板的推荐选项。 - PNG 文件现在也会处理 EXIF 旋转数据。 - 升级到最新版本的 [Brillo](https://github.com/ad-si/Brillo),带来了改进的应用设计、按钮悬停效果和每操作系统默认字体。 查看 [更新日志](https://github.com/ad-si/Perspec/blob/master/changelog.md) 获取完整变更列表。 ## 安装 macOS、Windows 和 Linux 的预编译二进制文件也可以在 [发布页面](https://github.com/ad-si/Perspec/releases) 上获取。

相似文章

@jefffhj:试试 Imagine Image 2.0!

X AI KOLs Following

Grok 发布了 Imagine Image 2.0,这是一款下一代图像模型,具备精准编辑、清晰的文字渲染以及改进的事实准确性,适用于实际应用。

Immich v3.0.0 发布

Lobsters Hottest

Immich v3.0.0 是开源自托管照片管理平台的一个主要版本,引入了移动端非破坏性编辑、工作流(预览版)、后台备份改进等功能,同时带来了破坏性 API 变更和新周边商品。