- Kotlin 98%
- Python 2%
| .idea | ||
| app | ||
| gradle | ||
| tools | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
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 (строка в отчёте).
Использование
- Снять серию: телефон на штативе/упоре, 10–50 кадров, фиксированные выдержка и ISO, ручной фокус. Чем больше кадров — тем чище результат.
- В приложении: «Выбрать кадры из галереи» → проверить сводку EXIF → «Сложить N кадров».
- Результат и отчёт обработки — на экране; 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.