Android astro-photography app
  • Kotlin 98%
  • Python 2%
Find a file
a.sveshnikov 9f35dce65a Init
2026-10-24 05:14:08 +03:00
.idea Init 2026-10-24 05:14:08 +03:00
app Init 2026-10-24 05:14:08 +03:00
gradle Init 2026-10-24 05:14:08 +03:00
tools Init 2026-10-24 05:14:08 +03:00
.gitignore Init 2026-10-24 05:14:08 +03:00
build.gradle.kts Init 2026-10-24 05:14:08 +03:00
gradle.properties Init 2026-10-24 05:14:08 +03:00
gradlew Init 2026-10-24 05:14:08 +03:00
gradlew.bat Init 2026-10-24 05:14:08 +03:00
README.md Init 2026-10-24 05:14:08 +03:00
settings.gradle.kts Init 2026-10-24 05:14:08 +03:00

VibeAstro

Android-приложение для астрофотографии со штатива. Складывает серию снимков неба в один кадр, компенсируя вращение Земли, извлечённое из самих снимков, и отбраковывая артефакты (блики окна, самолёты, спутники, горячие пиксели). Полностью pure Kotlin, без нативных зависимостей.

Сборка

Проект — стандартный Gradle-проект (AGP 9.2.1, Kotlin 2.2.10, minSdk 35, wrapper в репозитории):

./gradlew assembleDebug        # или открыть в Android Studio и Run

Разрешения не нужны: выбор кадров через SAF (сохраняет оригинальный EXIF, включая GPS), сохранение результата через MediaStore в Pictures/VibeAstro.

Каталог звёзд (для отождествления поля, опционально): один раз выполнить

python tools/make_catalog.py 6.0    # Hipparcos mag<=6 -> app/src/main/assets/star_catalog.csv (~190 КБ)

Без каталога всё работает — пропускается только plate solving (строка в отчёте).

Использование

  1. Снять серию: телефон на штативе/упоре, 10–50 кадров, фиксированные выдержка и ISO, ручной фокус. Чем больше кадров — тем чище результат.
  2. В приложении: «Выбрать кадры из галереи» → проверить сводку EXIF → «Сложить N кадров».
  3. Результат и отчёт обработки — на экране; PNG сохраняется в галерею.

Пайплайн

Стадия Метод
1. EXIF выдержка, ISO, время (+субсекунды), фокусное, GPS, ориентация
2. Декод JPEG/HEIC → линейный свет (обратная sRGB-гамма), биннинг до сетки обработки, дисковый кэш u16
3. Звёзды фон = 25-й перцентиль ячеек 32px; порог k·σ (σ через MAD); связные компоненты; субпиксельные центроиды; фильтры треков и облаков. Затем кросс-кадровый фильтр статичных точек: пыль/капли/блики на стекле (позиция повторяется в ≥50% кадров) исключаются из совмещения, а их координаты превращаются в битовую маску (r=6 px), зануляющую сэмплы пыли в стеке — иначе пыль оставляет «бусины» вдоль трека неба, которые sigma-clip не добивает
4. Совмещение треугольные инварианты (отношения сторон) + голосование вершин + RANSAC + комплексный МНК: `m = Σ b̃·conj(ã) / Σ
5. Модель вращения ω̂ = Σθᵢτᵢ / Στᵢ² (τ — из EXIF-времени); центр c = Σ(I−R)ᵀt / Σ(2−2cosθ) — фит по ВСЕМ кадрам; сверка с звёздной скоростью 15.04″/с; fallback на модель переноса, когда полюс далеко за кадром. Модель предсказывает преобразование каждого кадра и возвращает в стек кадры, не совмещённые иначе (итеративный рефит)
5b. Сферическая модель гномоника + 3-DOF вращение сферы (фокус в px из 35мм-эквивалента EXIF): каждая 2D-регистрация поднимается до вращения (кватернион Хорна), пересопоставление по сфере возвращает краевые звёзды — подобие у краёв широкоугольника систематически врёт. Общая 3D-ось вращения + ω с MAD-отсечением; остаточная дисторсия k1 само-калибруется по невязкам и принимается только при снижении RMS ≥5%
5c. Каталог plate solving по встроенному каталогу (Hipparcos до mag 6, assets/star_catalog.csv): треугольники в угловой мере со сканом масштаба ±6% (EXIF-фокус врёт из-за кропа/округления), стороны — между якорями (mag≤4.6), третья вершина и верификация — по всему каталогу; Хорн по тройке + уточнение f по совпадениям. Внимание на хиральность: кадровая система левая, экваториальная правая — ось y отражается. Даёт RA/Dec поля, имена звёзд, уточнённый фокус и независимую валидацию оси вращения (времени не требует)
6. Стек потоковый двухпроходный sigma-clip: проход 1 — среднее/дисперсия, проход 2 — клипованное среднее. Статичные блики и пролёты после выравнивания по звёздам становятся выбросами и отсекаются
7. Деблюр смаз за выдержку: трек len = ω·t_exp·R(пикс. до центра), поле скоростей v = ωJ(p−c); Richardson-Lucy по тайлам 160px с PSF-отрезком вдоль трека (кадры выровнены и выдержка одна — деконволюция один раз по стеку)
8. Засветка фон = 20-й перцентиль ячеек 64px + медиана 3×3 по сетке, билинейное растяжение, вычитание с пьедесталом
9. Рендер кроп до общей области кадров, цветосохраняющий asinh-автостретч, поворот по EXIF, PNG

Архитектура

app/src/main/java/com/xenial/vibeastro/
  MainActivity.kt        Compose-экран (пикер, параметры, прогресс, результат)
  AstroViewModel.kt      состояние, отмена, сохранение
  astro/
    FrameMeta.kt         EXIF-ридер
    Planes.kt            линейные float-планы RGB, sRGB LUT, дисковый кэш
    Decoding.kt          декод + биннинг в линейный свет
    StarDetector.kt      фон, MAD, компоненты, центроиды
    StaticFilter.kt      кросс-кадровый фильтр пыли/бликов на стекле
    Registration.kt      треугольники, RANSAC, similarity, 2D-модель вращения
    Sphere.kt            гномоника, 3-DOF вращение (Хорн), 3D-ось, k1, FrameMap
    PlateSolver.kt       каталог, прецессия, отождествление поля, валидация оси
    Stacker.kt           потоковый sigma-clip стек (2 прохода)
    Deblur.kt            Richardson-Lucy с PSF вдоль трека звезды
    Gradient.kt          вычитание засветки
    TiledMap.kt          тайловая аппроксимация варпа (быстрый CPU-путь)
    GpuEngine.kt         OpenGL ES 3.1 compute: варп+стек, RL (fallback на CPU)
    Render.kt            стретч, Bitmap, MediaStore
    Pipeline.kt          оркестратор + отчёт
    Parallel.kt          параллельный цикл с отменой

Constraints: пик памяти ~12 планов сетки обработки (при 2048px ≈ 150 МБ, largeHeap); кадры читаются потоково — в памяти не больше одного. Время: 20 кадров 12 МП при 2048px — порядка минуты на среднем телефоне.

GPU: варп+стек и RL-деблюр исполняются на OpenGL ES 3.1 compute (GpuEngine.kt): шейдеры компилируются в рантайме, выделенный GL-поток, SSBO-аккумуляторы, поток=пиксель (без атомиков). Любой сбой GL — автоматический откат на CPU с причиной в отчёте. CPU-путь тоже ускорен: тайловая аппроксимация сферического отображения (TiledMap, решётка 32 px, ошибка <0.05 px). При разрешении >2048 GPU-путь отключается (гарантированный спекой минимум размера SSBO-блока — 128 МБ). JPEG-декод остаётся на CPU — это нижняя граница времени обработки.

Ограничения v1 / roadmap

  • RAW/DNG не декодируется (BitmapFactory) — вход JPEG/HEIC. RAW дал бы линейный сигнал без 8-битного квантования.
  • Вывод 8-бит PNG; линейный 16-бит TIFF — кандидат на v2.
  • Глобальное similarity-преобразование: на сверхшироких полях (>90°) проекционные искажения у краёв не моделируются (нужна локальная сетка/гномоника).
  • Если в EXIF нет времени съёмки, межкадровое выравнивание работает, но ω и деблюр недоступны (нечем масштабировать угол на секунды выдержки).
  • Деконволюция по стеку с усреднённым ядром: при сильно разных выдержках в серии — приближение (предупреждение в отчёте).
  • Фит ω робастный (MAD-отсечение кадров, защёлкнутых на статике); кадры, чьё движение противоречит модели, выбрасываются из стека. Деблюр автоматически отключается, если |ω̂| расходится со звёздной нормой более чем на 25% — ложное ядро вреднее смаза.
  • Звёзды у самой проекции полюса (сдвиг <2 px за серию) могут отфильтроваться как статичные — допустимая потеря.
  • Статичный передний план (стена, рама окна) при выравнивании по небу размазывается дугами — это физически корректно; кадрируйте или маскируйте в v2.