Практическое применение фильтра Байера

Введение

Недавно я обнаружил, что спутник RoseyCubesat-1 отправляет изображения из космоса и, конечно же, мне захотелось их декодировать. К сожалению, официального описания формата я не нашёл. Зато нашёл вполне работающий декодер на GitHub. Пакеты с камеры имеют тип 0xA40C и отлично встраиваются в уже существующий декодер: RoseyCubesatBeacon.

Каждый пакет со спутника содержит:

Название Размер (байт) Описание
sequenceId 2 Всегда 0. Можно игнорировать
isPreview 1 Позволяет понять изображение - это небольшое превью размером 48х36 или полноценное - 480х360
elementId 2 Порядковый номер пакета в изображении
data 80 Массив байт с данными изображения. Каждый байт - это пиксель

Конечно, протокол не такой совершенный, как ssdv, но он и не настолько примитивный, как в 1kuns-pf. Благодаря передаче пикселей “как есть”, возможно восстановление изображения, даже если потеряно один или несколько пакетов. Это особенно важно для кубсатов со слабым сигналом.

Если получить несколько пакетов, отсортировать по порядковому номеру, то получится вполне неплохой результат:

Однако, если присмотреться поближе, то можно увидеть странные артефакты:

Так выглядит результат работы фильтра Байера.

Фильтр Байера

Фильтр Байера используется для того, чтобы уменьшить размер изображения, не сильно ухудшая его восприятие. Идея заключается в том, что для каждого пикселя можно передавать не 3 байта (RGB), а только один определённого цвета. Какой именно цвет передавать зависит от специального шаблона. Изображение разделяется на небольшие области 2х2 пикселя. Далее в зависимости от положения пикселя и выбранного шаблона выбирается нужный цвет. Бывает 4 шаблона:

B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
Text is not SVG - cannot display

Вопрос со звёздочкой: Почему зелёного в два раза больше, чем красного и синего?

Если восстановить цвет согласно положению пикселя в шаблоне, то получится следующее:

Значительно лучше! Можно отчётливо видеть разные цвета и переходы. Изображение явно не чёрно-белое. При увеличении видно, что пиксели просто раскрашены согласно шаблону с разной интенсивностью:

Следующим этапом будет интерполяция цветов. Вместо того, чтобы брать только зелёный согласно шаблону, можно посчитать значение красного и синего для этого же пикселя как среднее между соседними пикселями этого же цвета. Например, для шаблона BGGR:

B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
G
G
G
G
G
G
G
G
B
B
B
B
B
B
B
B
Gx
Gx
Bx
Bx
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
B
B
G
G
G
G
R
R
R
R
R
R
B
B
B
B
Rx
Rx
Bx
Bx
Text is not SVG - cannot display

Если применить интерполяцию, то получится отличный результат:

Кстати, если попробовать другой шаблон, то всё изображение приобретёт другой оттенок.

Высококачественная интерполяция

Группа исследователей предположила, что интенсивность каждого пикселя может зависить не только от соседних пикселей того же цвета, но и от цвета других. И в качестве региона для интерполяции предложила использовать область 5х5 пикселей. Помимо этого каждый пиксель нужно брать с определённым коэффициентом.

2
2
2
2
2
2
2
2
4
4
-1
-1
-1
-1
-1
-1
-1
-1
2
2
2
2
2
2
2
2
4
4
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
4
4
-1
-1
-1
-1
5
5
+1/2
+1/2
-1
-1
+1/2
+1/2
-1
-1
4
4
-1
-1
-1
-1
4
4
-1
-1
-1
-1
5
5
+1/2
+1/2
-1
-1
+1/2
+1/2
-1
-1
4
4
-1
-1
-1
-1
4
4
-1
-1
-1
-1
5
5
-1
-1
+1/2
+1/2
-1
-1
+1/2
+1/2
4
4
-1
-1
-1
-1
4
4
-1
-1
-1
-1
5
5
-1
-1
+1/2
+1/2
-1
-1
+1/2
+1/2
4
4
-1
-1
2
2
2
2
2
2
6
6
-3/2
-3/2
-3/2
-3/2
-3/2
-3/2
-3/2
-3/2
2
2
2
2
2
2
2
2
6
6
-3/2
-3/2
-3/2
-3/2
-3/2
-3/2
-3/2
-3/2
2
2
Text is not SVG - cannot display

То ли лыжи не едут, то ли я неправильно что-то закодил, но разницы особо не видно.

BEFORE AFTER

В итоге я решил остановится на простой интерполяции, так как она даёт более гладкий градиент между соседними пикселями.

Результат

Декодер изображения получился достаточно простой, без лишних зависимостей вроде OpenCV и делает то, что надо. Из того, что можно улучшить и добавить:

  • альфа-канал. Пропущенные пакеты неплохо было бы делать прозрачными
  • протестировать на превью-изображениях. Я долго искал подобные изображения в данных из Satnogs, но ничего не нашёл