Последние несколько месяцев ушли на эксперименты с промышленной линейной сканирующей камерой, снимающей очень широкие фотографии из окон поездов и паромов. Добиться рабочего результата оказалось непросто, но итоговые снимки того стоили.
Снимок сделан на пароме Сан-Франциско — Окленд в феврале 2026 года (изображение в градациях серого, 56 894×2 048 пикселей).
Больше фотографий представлено в галерее проекта.
Доклад об этом проекте был представлен на EMFcamp 2026.
Что вообще на этих снимках

Процесс съёмки изображения вроде того, что показано выше — с контейнерным портом.
Камера направлена из движущегося транспорта и постоянно фиксирует одну вертикальную линию — что-то похожее на серые полоски на диаграмме, только гораздо тоньше. Пока камера движется, содержимое этой линии меняется. Если захватывать линии достаточно быстро и сшивать их вместе, получается цельное изображение. На практике всё немного сложнее, и добиться качественного результата было непросто, но идея именно такая.
Предыстория и предшественники
В 1990-х годах цифровые сенсоры ещё не догнали по размеру и эффективному разрешению средне- и крупноформатную плёнку, поэтому появились цифровые сканирующие задники. Они получают изображение высокого разрешения без гигантской матрицы пикселей — за счёт перемещения одной линии пикселей (или трёх линий для цвета) по кадру. С тех пор сенсоры сильно выросли (сегодня существует даже модель, покрывающая формат 4×5 дюймов), но для больших форматов такой подход всё ещё дешевле, чем гигантский сенсор.
Идея собрать собственный сканирующий задник для крупноформатной камеры вынашивалась давно, но дело до неё не доходило — казалось слишком сложным сделать конструкцию, которая корректно крепится к камере. (Купить готовый вариант тоже было можно, но экземпляры из 1990-х до сих пор стоят на eBay тысячи долларов и требуют воссоздания компьютерного окружения той же эпохи.) В конце прошлого года, посмотрев видео о среднеформатной сканирующей камере Gigawipf, внезапно пришла мысль: «А что если движется вся камера целиком, а объект стоит на месте?» Решено было попробовать.

Зарядка плёнки в крупноформатную камеру на вершине горы в Вермонте — потому что заниматься фотографией обычным способом скучно.
Похожие проекты уже существовали (Scannoramic, эксперимент Джона Хикербайкера, реверс стационарной камеры Дэниела Лоуренса Лу и очень интересные плёночные снимки Мартина Либшера), но результаты казались улучшаемыми. Учесть скорость движения и получить более чистую картинку не должно было составить труда — по крайней мере, так казалось изначально.
Щелевое сканирование дивана
В ту же ночь, когда родилась идея «большого сканера», удержаться от эксперимента не получилось. Ехать снимать поезд было уже поздно, поэтому в роли объекта выступил собственный диван.
Телефон установили на офисное кресло и медленно катили его по комнате, снимая видео. Затем был написан довольно неряшливый код (публиковать который ради спокойствия читателей не стоит), извлекающий крайний левый столбец («щель») каждого кадра и объединяющий их в одно изображение.
В комментариях к коду затесались строчки из песни «Future Me Hates Me» группы The Beths — пророчество сбылось, когда пришлось писать постпроцессор для следующей версии камеры, отталкиваясь от того же кода, и проклинать прошлые решения.

Отдалённо похоже на диван, но изображение сплюснуто, а картина на стене превратилась в неразборчивое пятно.

Постобработка с дублированием каждого столбца убрала сплющенность, но результат всё равно получился неряшливым — кресло катилось не с постоянной скоростью.
С самого начала было понятно, что скорость придётся как-то измерять, но теплилась наивная надежда обойтись приблизительной оценкой. Однако это изображение показало: даже небольшие колебания скорости имеют значение. Так пришло первое понимание, насколько муторной окажется работа со скоростью.
Следующим шагом стала поездка на оранжевой линии MBTA. Старый телефон был приклеен скотчем к сиденью — для использования акселерометра, а текущий телефон прижат к окну с частотой кадров, выкрученной на максимум — 60 кадров в секунду.
Данные акселерометра оказались практически бесполезны, а после взятия интеграла для получения скорости — ещё менее полезны.

Если память не подводит, ось y соответствовала направлению движения поезда, но данные настолько зашумлены, что к концу записи поезд якобы поехал назад.
Результат получился по-своему интересным, хотя линий явно не хватало для по-настоящему читаемого изображения. 
Готовясь к EMFcamp, в расписании обнаружился ещё один доклад — Тима Джейкобса (более известного как mitxela), тоже про щелевое сканирование, что вызвало опасение по поводу совпадения тем. (После выступления он сам подошёл рассказать, что переживал о том же.) Его доклад начинался похоже — с извлечения щели из видео, — но в итоге он делал по-настоящему завораживающие анимации, перебирая все возможные позиции щели в кадре.
Промышленная линейная камера
Источником большего числа линий в секунду стала Basler ruL2048-19gm, спроектированная для наблюдения за быстро движущимися конвейерными лентами. Странное написание названия отражает способность камеры считывать сенсор 1×2048 пикселей почти 19 000 раз в секунду.
За эти возможности приходится платить: новые модели младшей линейки производителя стоят около 700 долларов США. К счастью для бюджета, экземпляр нашёлся на eBay за десятую часть этой суммы.
Цена измеряется и в количестве света: съёмка идёт настолько быстро (минимальная выдержка — 1/100 с), что камере требуется очень много освещения. Снимать получается только днём, а самые тёмные станции и тоннели вовсе недоступны.
Неожиданно приятным сюрпризом стало то, что Basler позволяет скачать SDK без договора поддержки и подтверждения покупки. Последняя версия SDK всё ещё поддерживает эту камеру 2013 года выпуска, что удобно, хоть и не удивительно.
Камера общается с компьютером по гигабитному Ethernet, и программное обеспечение находит её автоматически, если на соответствующем интерфейсе настроены адреса APIPA (169.254.0.0/16). Статические адреса можно было бы задать на обеих сторонах, но поскольку камера используется только одна за раз, менять настройки не было нужды.
SDK не вызвал особого раздражения, если не считать пары претензий к использованию времени выдержки вместо скорости затвора и путаницы с понятием «кадра» у этой камеры. В итоге получилась программа, забирающая буферы пикселей и записывающая их на диск.

Первое изображение, полученное с камеры при помощи собственного кода — неплохо для съёмки с рук.
Установка и механическая конструкция
Чтобы брать камеру в поезд без необходимости в трёх руках, требовалось крепление к штативу. В итоге был спроектирован довольно утилитарный корпус с термовставкой снизу, который напечатал на 3D-принтере друг Брук. Покупка деталей стала поводом наконец сделать заказ у McMaster-Carr и почувствовать себя настоящим инженером.
Первая попытка не удалась — оказывается, существуют такие вещи, как «производственные допуски», о которых напрочь забыли.

Ой, немного маловато получилось.

Задним числом стоило бы приклеить сенсоры чем-то понадёжнее синего малярного скотча, но и так держится неплохо.
По часовой стрелке платы следующие:
- 6-осевой акселерометр/гироскоп, из показаний которого с помощью математики можно получить скорость
- GPS-модуль, который сработал хуже, чем хотелось — бостонские поезда неплохо экранируют сигнал GPS
- микроконтроллер SAMD21 для передачи данных на ноутбук
Объектив спереди — Vivitar 28mm f/2.8, уже имевшийся для обычной камеры, с переходником с байонета Pentax K на резьбовое крепление C-mount. Поскольку часть снимаемых объектов довольно высокая, угол обзора этого объектива подошёл хорошо.
Питается вся конструкция от USB-C повербанка, плюс тянутся Ethernet- и USB-кабели к ноутбуку — в действии это настоящий кабельный монстр-спагетти.
С подключёнными сенсорами наконец можно было испытать систему.

Оба изображения — один и тот же кадр покачивания камерой из окна, но верхнее — необработанное изображение, а нижнее учитывает данные акселерометра. Как видно, использование акселерометра делает картинку куда ближе к норме и менее растянутой. (Подробнее о механизме — в разделе про постобработку.)
Попытки в Бостоне
Когда всё было собрано, пришло время взять камеру в поезд.

Первая поездка состоялась на оранжевой линии MBTA — она ближе всего, — но результаты, как видно, оказались так себе. Предпросмотр изображения с камеры был мучением, поэтому экспозицию пришлось выбирать почти вслепую, и выбор оказался неудачным. Написанный код постобработки работал плохо, и всё изображение оказалось странно растянутым и сжатым местами.

В следующий выход при более благоприятной погоде экспозиция удалась лучше, хотя с фокусировкой вышла небольшая промашка. В отличие от попытки со смартфоном, текст на станционных указателях вполне читаем — значит, линий захватывается достаточно.

Особенно удачным получился снимок моста Лонгфелло, соединяющего Бостон и Кембридж.
Захват изображения (в мельчайших подробностях)

Во время первых съёмок в Бостоне для предпросмотра использовался фирменный инструмент Pylon от производителя камеры. Чёрная горизонтальная полоса была всем, что можно было увидеть от изображения в конкретный момент, да ещё и повёрнута на 90° относительно желаемой ориентации. Настройка экспозиции в Pylon, освобождение камеры от захвата этой программой и запуск собственного кода до того, как поезд снова тронется — сущее мучение, которое требовало решения.
Первая попытка собственного GUI использовала OpenCV highgui, что не сработало. Библиотека требует задержку в 1 мс после каждого кадра — приемлемо для медленных камер, но приводило к потере целых 4 линий (по 250 мкс каждая на используемых скоростях затвора) за каждый кадр отображения (256 линий).
В итоге выбор пал на Dear ImGUI, хорошо вписавшийся в уже существующий цикл захвата кадров. Из примерно двух десятков поддерживаемых бэкендов был выбран GLFW («girl love for workgroups», как пошутил однажды друг) и OpenGL3 — вероятно, из-за этой шутки, хотя точно сказать сложно.

Большая часть GUI была написана за одну бессонную ночь в Торонто, где поворот изображения ощущался как сложнейшая задача в информатике. (Реализацию точно можно улучшить, но пока она работает достаточно сносно.) К сожалению, снимки, сделанные в Торонто, не получились, зато хотя бы экспозиция была верной.

При добавлении гистограммы для изображения обнаружились странные баги.
Получение данных с акселерометра оказалось отдельной головной болью. Первая версия отправляла показания текстом по последовательному порту, что оказалось чрезвычайно затратным по вычислениям для микроконтроллера. (Преобразование чисел с плавающей точкой в строки и последующая сборка строк — дорогая операция даже для довольно мощного микроконтроллера SAMD21 с целыми тридцатью двумя битами.) Преобразования были перенесены на ноутбук, обладающий достаточной вычислительной мощностью, но это принесло свои проблемы.

Очень утомительная сессия отладки.
Показания акселерометра отправлялись как «сырые» числа с плавающей точкой, но данные GPS всё ещё приходили в виде NMEA-строк, и переключение между ними требовало отправки фиксированных последовательностей байт с надеждой, что ничто не будет ошибочно принято за них. (В NMEA-строке не должно встречаться 0x11 0x11 0x11 0x11 — стартовая последовательность данных акселерометра, но данные акселерометра теоретически могут содержать 0x22 0x22 0x22 0x22 — стартовую последовательность NMEA-строк.)
Также встречались случаи, когда несвоевременная очистка буфера последовательного порта портила съёмку целого дня. К счастью, съёмка велась на линии Маттапан в Бостоне, куда легко вернуться и попробовать снова. 
Тот «шов» на изображении — место, где данные последовательного порта были полностью потеряны примерно на полсекунды, что для линейной камеры равносильно вечности. Программа продолжала ждать очередной показатель акселерометра, который так и не пришёл, потому что буфер порта переполнился.
Замечен, но не задержан
Полностью собранная камера выглядит как подозрительная мешанина проводов, а человек с ней в руках — не намного благонадёжнее.

Обычно камера скреплена не таким количеством скотча, но во время поездки в Монреаль пластина крепления к штативу была забыта дома. 
Доверили бы вы такому человеку пронести странное оборудование в поезд?
Несмотря на бостонскую историю чрезмерной реакции полиции на безобидные электронные проекты, меньше всего опасений возникает именно на MBTA. Местные жители обычно не лезут не в своё дело и никогда не вызывали полицию. Сама полиция тоже редко ездит в поездах, предпочитая приставать к людям на станциях.
Порядки в других городах не так хорошо знакомы, поэтому камера берётся с собой только в компании друга — на случай неприятностей (а иногда и чтобы послушать диспетчерское радио).

Пока дело ограничивалось лишь тем, что «заметили», но не «сказали» или «разобрались». Остаётся надеяться, что с расширением географии съёмок ситуация не изменится.
В Монреале, на вокзале Гар-Сентраль, охрана остановила съёмку и сообщила, что штативы запрещены, а также спросила на смеси французского с английским, ведётся ли видеозапись или делается фото. Вместо попытки ответить на этот философский вопрос на языке, которым не владеешь, было проще несколько раз сказать «désolé» и убрать штатив — этого оказалось достаточно.
Ад постобработки
Захват изображения и данных акселерометра оказался лёгкой частью по сравнению с постобработкой и приведением снимков к приличному виду.
Камера захватывала около 4 000 линий в секунду, так что линий в каждой съёмке было гораздо больше необходимого, и требовалось выбрать, какие из них действительно важны.

Что происходит при выборе слишком малого числа линий (автомагистраль 10 в Броссаре, Квебек, из окна REM A). 
Слишком резкие переходы между линиями выглядят искусственно и неправильно, как видно у ватерлинии на этой версии обложки для ранней версии снимка парома Окленда.
Для выбора нужных линий в итоге использовалась скорость, измеренная акселерометром, но здесь возникло несколько проблем.
Во-первых, акселерометры на самом деле не измеряют скорость. Они измеряют ускорение — скорость изменения скорости. Взяв интеграл, можно получить скорость, но она будет относительной от начального значения. Обычно можно предположить, что начальная скорость на станции равна нулю, но полной уверенности в этом нет. Если это не так, точное значение узнать неоткуда — приходится подбирать наугад, пока результат не «запахнет» правильно.
Во-вторых, как показано на диаграмме, используемый акселерометр измеряет данные не так уж быстро. Камера захватывает линии примерно в 4 раза быстрее, поэтому нескольким линиям подряд приходится делить одно значение скорости. К тому же частота показаний не слишком стабильна — код микроконтроллера не настолько быстр, насколько мог бы быть, что вызывает неровности в итоговом изображении. Количество замеров ускорения также влияет на точность интеграла, создавая дополнительные проблемы. 
Ранее упоминалось добавление GPS-приёмника к камере, но пользы от него оказалось немного. Сигнал не ловился в большинстве поездов, а когда всё же удавалось поймать, частота обновления составляла лишь 10 раз в секунду — это покрывает целых 400 линий с камеры. Работай он стабильнее, GPS мог бы пригодиться для коррекции ошибки интегрирования с помощью фильтра Калмана, но это задача на будущее, когда данные GPS станут надёжнее.
Даже при идеальном измерении скорости остаётся проблема параллакса: объекты ближе к камере кажутся движущимися быстрее объектов на заднем плане. Это не связано с оптической фокусировкой, обычно установленной на бесконечность.
Проблема решается изменением того, сколько реального расстояния соответствует одному пикселю. Меньшие значения подчёркивают объекты ближе к камере, большие — делают более заметным задний план.
Каждое из таких изображений имеет одинаковый размер (10 000 пикселей в ширину на 2048 в высоту, масштабировано под браузер) и включает всё содержимое предыдущего, охватывая большую область съёмки. Единицы измерения условны и не отражают реального расстояния (теоретически можно было бы выразить их в метрах на пиксель, но особого смысла в этом нет).
Камера и программное обеспечение понятия не имеют, на чём именно нужно «сфокусироваться», поэтому творческое решение принимается вручную для каждого сегмента изображения, а сегменты затем сшиваются вместе для итоговых снимков в галерее. Были опробованы разные значения расстояния на пиксель и начальной скорости для каждого сегмента, после чего сегменты склеивались в GNU IMP для получения финальных изображений. Собранные изображения часто превышали максимальный размер JPEG в 65 535×65 535 пикселей, поэтому пришлось прибегнуть к старому доброму формату TIFF. (Спецификация PNG теоретически допускает столь же большие изображения, но имеющееся под рукой программное обеспечение явно предпочитает большие TIFF большим PNG.)

Заметки по значениям расстояния на пиксель (u) и начальной скорости (v) для разных частей нескольких изображений.
Программа, учитывающая данные акселерометра для каждой линии изображения, называется grindstone («точильный камень») — она перемалывает многогигабайтные необработанные захваты в компактные готовые изображения. Первая версия была слабо основана на очень плохом коде щелевого сканирования из ранних экспериментов и работала крайне медленно, обрабатывая многоминутную съёмку часами. Программа часто не могла сохранить результат после многочасовой работы, потому что итоговое изображение превышало ограничения формата JPEG, а отладка была настоящим мучением.
В итоге удалось уговорить подругу Мэдди переписать grindstone в идиоматичном стиле NumPy, чтобы прояснить происходящие математические операции (сама она настаивает, что все операции уже присутствовали в оригинале, а её правки скорее были «превращением кода в девочку-волшебницу»). Позже Мэдди разбила версию на «возможно, слегка переусложнённый» конвейер из нескольких стадий, что упростило эксперименты и замену отдельных операций, стратегий вывода и прочего. Благодаря этой помощи стало гораздо проще пробовать разные комбинации параметров и получать более удовлетворительные результаты.
Цветовой ад
В апреле, во время поездки на линии Маттапан с другом Ари, когда на деревьях распускались листья, снимки не получились из-за бага в программном обеспечении захвата — но эта поездка навела на мысль, что цветные снимки с линейной камеры могут выглядеть особенно эффектно, особенно осенью.
Однажды поздно ночью, просматривая eBay, удалось найти очень выгодное предложение на цветную линейную камеру того же поколения, что и уже имевшаяся монохромная (Basler ruL2098-10gc, 3×2098 пикселей на скорости около 10 000 линий в секунду). После небольшой разборки (камера пришла в заводском корпусе с производственной линии) и замены крепления объектива, механическая часть была готова.
На камеру были нанесены красная, зелёная и синяя полоски, чтобы различать камеры без снятия объектива или разглядывания мелкого текста на этикетке.
Программная часть захвата оказалась не намного сложнее, хотя пришлось исправить ряд допущений о размере каждой линии и переделать поворот изображения в GUI.

Прогресс настройки цветного захвата.
Благодаря очень модульному переписанному варианту grindstone от Мэдди добавление поддержки цветных изображений далось без особого труда, хотя пришлось исправить несколько странных на вид багов.

Поезд двигался настолько медленно и неравномерно, что ошибка интегрирования накопилась настолько, что grindstone посчитал, будто камера движется назад, и перескакивал к разным предыдущим точкам съёмки.
С захватом и обработкой изображений, работающими в целом нормально, проявились новые проблемы. Самая заметная — листья на снимках оказываются гораздо ярче, чем должны быть. 
Причина в том, что цветная камера чувствительна к инфракрасному свету на всех трёх каналах. (Будь чувствительность только на красном канале, листья выглядели бы красноватыми, но сочетание ИК на всех трёх каналах с сильным видимым зелёным даёт зеленовато-белый оттенок на этом снимке.) Монохромная камера тоже чувствительна к ИК, но это неважно — там всего один канал, и видимый свет полностью его перебивает.

(Диаграмма взята из руководства к камере)
Стоит признать, что при правильном освещении этот эффект выглядит довольно красиво. Один из снимков, сделанный в Манчестер-бай-зэ-Си к северу от Бостона, получился одновременно и монохромным, и цветным.

Проблема решилась установкой УФ- и ИК-отсекающего фильтра, пропускающего свет только в диапазоне 400–700 нм — достаточно близко к видимому человеком спектру, чтобы всё выглядело правильно. Это первый крупный снимок с этим фильтром, потребовавший лишь минимальной цветокоррекции.

Также был опробован фильтр, пропускающий только свет длиной волны более 720 нм — с ним и ИК-чувствительной плёнкой уже получались весьма интересные результаты, и эксперименты в этом направлении определённо продолжатся.
Следующая проблема — странные красные, зелёные и синие окантовки у некоторых объектов, особенно удалённых или быстро движущихся.

Как выяснилось, это неотъемлемая особенность работы данного сенсора. Красный, зелёный и синий представляют собой отдельные вертикальные линии (а не фильтр Байера), и потому не могут видеть ровно одно и то же в один момент времени. Окантовки появляются там, где что-то попадает в поле зрения лишь одной линии, особенно заметно на ярких объектах. Они диагональные, а не строго вертикальные, потому что сама камера установлена не идеально вертикально. (Добиться точности на движущемся поезде можно лишь до определённого предела.)

Из руководства к камере: производитель приводит формулы, позволяющие с помощью оптического коэффициента увеличения объектива и точной скорости компенсировать этот эффект, но точной (относительной) скорости объекта съёмки в распоряжении нет.
Компенсация выполняется для конкретного объекта смещением красного и синего каналов относительно зелёного. Поскольку линии расположены равномерно, можно сдвигать оба канала на одинаковую величину в противоположных направлениях, вместо измерения отдельных смещений для каждого канала. Теоретически величину сдвига можно было бы определять корреляцией изменений яркости между каналами, но пока это делается вручную.

Разделение каналов заметно на мачтах парусника, скорректированное сдвигом красного канала на 10 пикселей вправо и синего на 10 пикселей влево. Цветные окантовки всё ещё видны на заднем плане, поскольку он находится намного дальше парусника и имеет более высокую угловую скорость; коррекция для него испортила бы вид самого парусника.
Отображение
Показ и обмен снимками на протяжении всего проекта был сущим мучением. Большинство программ на компьютере плохо справляются с настолько большими файлами, а самым надёжным инструментом просмотра оказался GNU IMP, что кажется избыточным решением. Мессенджеры для переписки с друзьями тоже не любят широкие изображения и порой сжимают их в нечитаемый мусор. Опасения по поводу подобных проблем в браузере развеял проект OpenSeadragon, уже проделавший всю тяжёлую работу и предоставивший удобный способ приближения изображений.
Утилита vips позволила разбить гигантские TIFF-файлы на небольшие JPEG-тайлы для раздачи, а немного собственного JavaScript обеспечило прямые ссылки на элементы галереи (в основном для удобства этой самой публикации). Веб-разработка — не самая сильная сторона, так что за не самый привлекательный внешний вид результата стоит извиниться.
Планы на будущее
Для этой камеры есть ещё много идей на будущее. Главная — избавиться от зависимости от ноутбука при съёмке, что сделает конструкцию менее подозрительной и удобнее для переноски. Для этого придётся решить ряд накопившихся проблем со сбором данных акселерометра и интерфейсом захвата.
Планируется также улучшить инструменты постобработки — реализовать нечто, принимающее таблицу с номерами линий и автоматически собирающее из них изображение. При достаточной амбициозности возможен GUI, позволяющий отмечать сегменты и просматривать их при разных значениях расстояния на пиксель. Есть также соблазн наконец задействовать GPS и реализовать фильтр Калмана, но, вероятно, это отложится ещё надолго.
Ещё одна идея — больше странных инфракрасных снимков, возможно, с цветовой инверсией в стиле æрохром, как это делает RYE.
Также хотелось бы охарактеризовать зависимость между настройкой усиления камеры и ISO, что позволило бы подбирать локации с помощью простого экспонометра. Попытка уже предпринималась, но условия освещения на улице менялись слишком быстро для получения качественных результатов.
Код
Код для захвата доступен здесь, а постпроцессор (grindstone) — здесь.
Благодарности
Отдельная благодарность:
- Мэдоу (поездки на пароме/поезде, подготовка презентации, вычитка)
- Брук (помощь с механической конструкцией, 3D-печать)
- Ари (поездки на поезде, код, подготовка презентации, вычитка)
- nyanotech (поездки на пароме/поезде)
- cat (поездки на поезде)
- Мэдди (код, поездки на пароме, вычитка)
- kim (вычитка)
Без их помощи результат вышел бы далеко не таким удачным.









