把铁路网当成平板扫描仪:用工业线阵相机拍超宽照片

查看原文 HN 讨论

文章摘要

作者 Philo 用一台工业线阵扫描相机,从行驶的火车和渡轮窗口拍出极宽的照片,这篇 4,600 字的长文(2026 年 8 月 17 日)记录了整个折腾过程。开篇的样片是 2026 年 2 月在旧金山到奥克兰渡轮上拍的,56,894 × 2,048 像素灰度图。她在 EMFcamp 2026 做过同题演讲。

原理。 相机对着窗外,持续只捕捉一条竖直的线(比示意图里的还要细得多)。随着相机移动,它看到的内容不断变化;只要采线足够快再拼起来,就能得到一张看起来完整的图。「比这复杂一点,让结果好看相当棘手,但这就是主要思路。」

先例。 1990 年代数字传感器还赶不上中大画幅胶片的尺寸和有效分辨率,于是有了数字扫描后背——靠移动一条(彩色则三条)像素线扫过画面来获得高分辨率,而不需要一整块巨大的像素阵列。如今传感器已经很大(甚至有覆盖 4×5 英寸大画幅的),但这个思路对大画幅仍然更便宜。作者一直想给自己的大画幅相机做个扫描后背却始终没动手(买现成的:1990 年代的机器在 ebay 上仍要数千美元,还得复原同时代的计算环境)。去年底她看 Gigawipf 的中画幅扫描相机视频时忽然想到:「如果整台相机在动、被摄物不动呢?」她也找到了几个同类先例(Scannoramic 项目、John Hikerbiker 的实验、Daniel Lawrence Lu 把静止相机反过来用、Martin Liebscher 的胶片作品),但觉得结果还能改进——「把运动速度考虑进去、拿到更干净的结果,应该不难吧?」

第一次试验:扫沙发。 想到这个「大扫描仪」概念的当晚太晚了不便出门坐火车,她就把手机放在办公椅上边推边录视频,然后写了段极其潦草的代码(「为保护读者我选择不公开」)取每帧最左边一列(一条 slit)拼起来。结果「有点像我的沙发,但被压扁了,墙上的画完全看不出来」。她把每列复制一份让画面不那么压扁,但因为推椅子速度不匀,仍然一团糟。她一开始就知道需要测速度,但天真地希望不用测得太准、能糊过去;这张图说明即使很小的速度变化也会有影响,这是她第一次窥见「处理速度」会有多痛苦。下一步她坐 MBTA 橙线,把旧手机用胶带粘在座椅上采加速度计数据,新手机贴着车窗以 60 fps 录像。加速度计数据不太有用,积分成速度后更没用——「如果我没记错,y 是列车运动的轴,但数据噪声大到列车在末尾居然在倒着走。」结果看起来有意思,但要拿到清楚可读的图像还需要多得多的线。准备 EMFcamp 时她发现 Tim Jacobs(网名 mitxela)也有一场关于 slit scan 相机的演讲,一度担心撞题(对方演讲后跑来说他也担心过同一件事);mitxela 的起点相同,但最终是遍历一段视频里所有可能的 slit 位置,做出很酷很迷幻的动画。

工业线阵相机。 更多线数的来源是 Basler ruL2048-19gm,本是用来对准高速传送带的:这个古怪大小写的型号名来自它能把 1×2048 像素的传感器每秒读出接近 19,000 次。代价一是钱——厂商现售最低配的新机约 700 美元,她在 ebay 上以十分之一价格买到;二是光——因为采集太快(最慢曝光时间 1/100 秒),非常吃光,只能白天拍,除最亮的车站和隧道外都拍不了。让她意外的是 Basler 不需要支持合同或购买凭证就允许下载 SDK,而且最新版仍支持这台 2013 年的相机。相机通过千兆以太网通信,只要网卡配置为 APIPA 地址(169.254.0.0/16)软件就能自动发现它。除了对厂商用 shutter time 而不是 shutter speed、以及这台相机上「一帧」到底指什么略有抱怨,她「出乎意料地很少骂 SDK」,很快写出抓像素缓冲并写盘的程序。

机械与硬件搭建。 为了能带上火车而不需要三只手,她设计了一个相当实用主义的外壳、底部装热熔螺母以便上三脚架,由朋友 Brooke 3D 打印(第一版没成,因为她「完全忘了有种东西叫制造公差」);买零件让她终于有理由在 McMaster-Carr 下单,「感觉像个真正的工程师」。传感器板用蓝色美纹胶带贴着(「事后想想我大概该用别的东西粘,但它挺牢的」),顺时针分别是:六自由度加速度计/陀螺仪(配合数学计算得到速度)、GPS(最终不太好用,因为波士顿的列车太善于屏蔽 GPS 信号)、SAMD21 微控制器(把数据转发给笔记本)。镜头是她原本给普通相机用的 Vivitar 28mm f/2.8,通过 Pentax K 转 C 口的转接环装上;由于要拍的一些东西挺高,这个视角刚好合适。整套由 USB-C 充电宝供电,还有以太网线和 USB 线连到笔记本,工作时是一团「线材意面怪物」。加了传感器后的对比图显示:同一次「把相机伸出窗外来回挥」的采集,原始图和考虑加速度计运动后的图相差极大,后者接近正常、不再被拉伸。

波士顿的尝试。 先上 MBTA 橙线(离家最近),结果不好——预览相机输出很麻烦,只能猜曝光,而且猜错了,后处理代码也不太行,画面被诡异地拉伸和压缩。天气好的一天再去,曝光运气好些,但对焦大概搞砸了;不过与手机版本不同,站牌上的文字已经清晰可读,说明线数确实够了。她对其中一张从波士顿到剑桥的 Longfellow Bridge 特别满意。

采集(细节过多的一节)。 早期在波士顿拍时她用厂商的 Pylon 工具预览,能看到的只有一条黑色横带、而且相对她想看的方向旋转了 90°;在里面调好曝光、释放相机占用、再在列车开动前把自己的代码启回来,是件真正的苦差事。她第一版 GUI 用 OpenCV 的 highgui,不合用:它要求每帧后有 1 毫秒延迟,对慢相机没问题,但对她意味着每显示一帧(256 线)就丢掉整整 4 条线(在她常用的快门速度下每条 250 微秒)。改用 Dear ImGUI 就与已有的帧采集循环配合良好;在该库支持的约二十几个后端里她选了 GLFW(引用当时朋友的消息,戏称其为「girl love for workgroups」)和 OpenGL3,「大概就是因为那个 girl love 的梗」。GUI 大部分是在多伦多一个不眠之夜写的,当时「把图像旋转过来感觉像是计算机科学里最难的问题」。加速度计数据也很折腾:第一版把读数以文本经串口发送,结果在微控制器上计算开销极大(把浮点数转成字符串再拼字符串非常贵,即使在「有整整三十二个比特」的 SAMD21 上也如此);她把转换挪到笔记本上,却带来新问题——加速度数据以原始浮点数发送,而 GPS 数据仍是 NMEA 语句,两者切换要靠发送固定字节序列然后祈祷别被误判(NMEA 语句里不该出现 0x11 0x11 0x11 0x11,也就是她的加速度数据起始序列;但加速度数据里出现 0x22 0x22 0x22 0x22,也就是 NMEA 起始序列,并非完全不可能)。她还遇到过没在正确时机 flush 串口而毁掉一整天素材的情况——图中那道「缝」就是丢失了约半秒串口数据的地方,「在线阵相机的时间尺度上这是一个永恒」,软件一直在等一个永远不会到来的加速度采样,因为串口缓冲已满。

「See It, Say It, Sorted」(安全与围观)。 组装好的相机看起来是一团可疑的乱麻,用它的「女巫」看起来也不比它正常。尽管波士顿历史上有对无害电子项目反应过度的警方记录,她在 MBTA 上最不担心被抓:这里的人习惯各管各事,从没有人报过警;警察也不常坐车,更喜欢在车站骚扰人。在别的城市她只在有朋友同行时才带相机出门(有时还听调度电台)。「到目前为止我只被看见(seen),还没被说(said)或被处理(sorted)。」在蒙特利尔的 Gare Centrale 她被保安拦下,被告知不许用三脚架,并被以「franglais」问是在录像还是在拍照——「与其用我不会的语言回答这个哲学问题,我就说了几声 désolé 并收起三脚架,看起来这就够了。」

后处理地狱。 采集图像和加速度数据反而是简单的部分。相机每秒约采 4,000 线,每次拍摄的线都比需要的多,必须挑出真正要用的。取线太少会出问题;线之间跳得太快会显得人工、不对劲(在奥克兰渡轮早期版本做的专辑封面里,水线处能明显看出来)。她用加速度计测的速度来决定取哪些线,但这带来几个问题:第一,加速度计并不测速度,它测加速度,积分得到的速度是相对于某个初始值的;她通常可以假设起点在车站因此初速为零,但无法确定,如果不为零就只能猜到「闻起来对」的值。第二,加速度计的采样率有限,相机取线大约比它快 4 倍,因此每几条线得共享一个速度值;而且由于她的微控制器代码不够快,采样也不够规整,会在最终图像里造成不规则;加速度采样的多少还会改变积分的准确度,制造更多问题。GPS 基本没用:大多数列车上收不到信号,收到时也只有每秒 10 次,而这覆盖了相机整整 400 条线;如果它更稳定,本可以用卡尔曼滤波来校正积分误差,「但那是等我有更好的 GPS 数据再说的问题」。第三,即使速度测得完美,还有视差问题——离相机近的东西看起来移动更快,这与光学对焦无关(她通常把焦点设在无穷远)。应对办法是调整「每个像素代表多少距离」:值越小越强调近处,越大则背景越清晰。文中做了个滑块交互演示,每张图都是 10,000 × 2,048 像素、单位是任意的(「我可以换成真实的米每像素,但看不出有什么意义」)。相机和软件不知道她想「聚焦」什么,所以这是她为每一段画面手工做的艺术决定,然后把各段拼起来;她测试不同的每像素距离和每段起始速度,再在 GNU IMP 里拼成最终图。拼好的图往往超过 JPEG 的 65,535 × 65,535 上限,于是用老牌的 TIFF 格式(PNG 规范理论上也允许类似大小,但手边软件更吃大 TIFF)。把加速度数据应用到每一行的程序叫 grindstone(磨石),因为它把数 GB 的原始采集「磨」成可用的小图;第一版基于她早期那段很糟的 slit scan 代码,极慢,处理几分钟的采集要几小时,而且常常跑几小时后因为图太大存不成 JPEG 而失败,调试也极痛苦。她「把朋友 Maddie 狙击进来」用符合 NumPy 惯用法的方式重写了 grindstone,让其中的数学运算更清晰(Maddie 坚称所有运算原本就在,她的改动更像是「把它变身成魔法少女」);后来 Maddie 又把它拆成一条「也许略微过度工程」的多阶段流水线,便于试验、替换不同的运算和输出策略。

色彩地狱。 4 月她和朋友 Ari 在树叶初生时坐 Mattapan 线,照片因采集软件的 bug 没成,但让他们想到彩色线阵照片会很好看,秋天尤其。深夜刷 ebay 时她捡到同代彩色线阵相机的好价——Basler ruL2098-10gc,3×2098 像素、每秒约 10,000 线;拆掉工厂产线上用的外壳、换过镜头座就机械就绪了。她给三台相机贴了红绿蓝条纹以便不摘镜头、不去眯着看铭牌上的小字就能分辨。采集软件侧不算太难,但要修一堆关于每条线大小的假设、重做 GUI 的旋转。得益于 Maddie 那次高度模块化的重写,加彩色支持也不太难。随后新问题冒出来:最明显的是树叶远比应有的亮。原因是彩色相机的三个通道都对红外敏感(如果只有红通道敏感,叶子会偏红;三通道红外叠加强烈的可见绿,就变成偏绿的白);单色相机同样对红外敏感,但只有一个通道、可见光完全压过它所以无所谓。她承认在合适光线下这效果挺好看——在波士顿以北 Manchester-by-the-Sea 拍的一张「既是灰度又同时有色彩」。她用只透过 400–700 nm 的紫外/红外截止滤镜解决了它,接近人眼可见光谱,一切看起来就对了,后期只需极小调色。她还试了只透过 720 nm 以上的滤镜(配红外敏感胶片曾有很有趣的结果),打算以后多拍。另一个问题是某些物体出现奇怪的红绿蓝边缘,尤其是离相机更远或移动更快的主体。这源于传感器本身的工作方式:红绿蓝是三条各自独立的竖线(而不是拜耳滤镜),因此无法在同一时刻看到完全相同的东西;当只有一条线看到某物时就出现色边,明亮主体尤其明显;边缘是斜的而非完全竖直,因为相机本身不完全垂直(她尽量摆正,但在行驶的列车上能做的有限)。厂商在手册里给了可用镜头光学放大倍率和精确速度来抵消它的公式,但她没有主体的(相对)速度估计。她的做法是针对某个主体,把红蓝通道平移去对齐绿通道;因为三条线等距,只需在相反方向平移相同量而不必逐通道测量。理论上可以靠通道间亮度变化的相关性自动决定平移量,目前还是手工。示例中帆船桅杆上的通道分离通过红通道右移 10 像素、蓝通道左移 10 像素修正;背景仍有色边,因为它远得多、角速度更快,可以为它单独校正但那样帆船会难看得多。

展示。 显示和分享这些照片一路都是麻烦:电脑上多数软件都受不了这个尺寸,她找到最可靠的查看工具是 GNU IMP(「感觉有点大炮打蚊子」);发给朋友的聊天软件也受不了超宽图,有时会压成一小团垃圾。她担心浏览器里也一样痛苦,好在 OpenSeadragon 项目已经把重活干完了,提供了缩放浏览大图的简便方式。她用 vips 工具把巨大的 TIFF 切成小 JPEG 瓦片来提供,并自己写了点 JavaScript 支持深链到图库(主要是为了让这篇博文好写)。「Web 开发不是我擅长的事,所以为它最后长得这么丑道歉。」

后续计划。 最大一项是让相机不再依赖笔记本来采集,这会让它看起来不那么可疑、也更方便带出门,为此要修掉一些困扰她的加速度数据采集和采集 UI 问题。后处理工具也要改进:想做成读一份「行号」表格再拼装的形式;如果有雄心,还想做个能标出段落并以不同每像素距离预览的 GUI;也想真的用上 GPS 并实现卡尔曼滤波(「不过我估计会一直往后拖」)。此外想拍更多奇怪的红外照片,也许做 ærochrome 式的换色。还想标定相机增益设置与 ISO 的对应关系,这样就能只带测光表去踩点(此前试过,但室外光照条件变化太快拿不到好结果)。采集端代码和后处理器 grindstone 都已开源。文末她感谢了 Meadow、Brooke、Ari、nyanotech、cat、Maddie、kim 在坐车/坐船、机械设计、3D 打印、代码、演讲准备和校对上的帮助。

HN 评论精华

这条帖子 450 分、71 条评论。讨论主线不是质疑而是「共鸣与考古」:大量人拿出自己或别人做过的同类项目(从 1990 年代把平板扫描仪装在电机上,到 2008 年 Ward Cunningham 的午后 hack,到卫星成像和终点线摄影),并指出线阵扫描其实是铁路检测、工业质检和照相判定的成熟工艺。也有少数技术性异议和改进建议。