Pull to refresh

Снимаем звёздное небо с Emgu CV

Reading time4 min
Views12K
image

Доброго времени суток, Хабр.

Так уж получилось, что довольно давно интересуюсь фотографией и астрономией. Звёздное небо снимать люблю. Так как света ночью мало, то для того, чтобы получить по-настоящему что-то красивое приходится делать довольно долгие экспозиции. Но тут всплывает другая проблема — из-за того что Земля вращается, звёзды на небе движутся. Соответственно при относительно долгих экспозициях звёзды перестают быть точками, и начинают прочерчивать дуги. Чтобы компенсировать это движение при фотографии/наблюдении deep-sky объектов существуют устройства — монтировки. К сожалению, на данный момент купить монтировку нет возможности, поэтому я решил задаться вопросом – а можно ли реализовать подобный эффект программно и что из этого получится?

Под катом много фотографий. Все фотографии в посте мои, (почти все) кликабельны и free-to-download.

Введение


Проблема в съёмке звёзд заключается в следующем – они двигаются. На первый взгляд может показаться, что это движение совсем незаметно, но даже на относительно коротких выдержках (20”+) звёзды уже перестают быть точками – начинают виднеться короткие дуги, которые они прочерчивают двигаясь по небу.

image
Выдержка ~15"

image
Выдержка ~20'

Теория


Земля вращается вокруг собственной оси. Относительно далёких звёзд данный период составляет 86164,090530833 секунд.
Соответственно, зная выдержку кадра можно просчитать, на сколько градусов относительно центра повернулись все звёзды в кадре. Идея такова, что если компенсировать это вращение, повернув весь кадр в обратную сторону на эту величину – то все звёзды должны остаться на своём месте.

Проблема нахождения центра, вокруг которого всё вращается – не проблема. Достаточно найти полярную звезду — ось вращения Земли проходит совсем близко с ней.

image
Celestial Pole — та самая ось.

Реализация



Для реализации этой идеи я решил использовать Emgu CV – wrapper библиотеки OpenCV для .NET.

Я не буду расписывать всю программу — расскажу только о основных методах.

Если фотографии накладывать одну на другую, то в Photoshop я обычно использовал Blending Mode: Lighten. Суть его такова, что из двух изображений он составляет одно, выбирая пиксели с наибольшей яркостью. Тоесть, если есть два пикселя в двух исходных изображениях — то в результирующем изображении будет тот пиксель, который ярче.

В Emgu CV этот метод уже реализован.

public Image<TColor, TDepth> Max(
	Image<TColor, TDepth> img2
)


Mode 1: Сложение изображений без поворота

Здесь ничего сложного нет — просто поочерёдно накладываем все фотографии из списка одну на другую.


List<string> fileNames = (List<string>)filesList; // input 
Image<Bgr, Byte> image = new Image<Bgr, Byte>(fileNames.First()); // resulting 
foreach (string filename in fileNames)
{
       Image<Bgr, Byte> nextImage = new Image<Bgr, byte>(filename);
       image = image.Max(nextImage);
       nextImage.Dispose();
       pictureProcessed(this, new PictureProcessedEventArgs(image)); // updating image in imagebox
}


Зачем нужен этот режим? Затем, что после сложения фотографий получаются дуги из-за движения звёзд. Можно очень легко найти центр вращения (полярную звезду). Да и иногда получается довольно интересные фотографии со следами.

Mode 2: Сложение изображений с компенсацией вращения

В данном режиме всё становится немного сложнее. Изначально нам нужно рассчитывать угол смещения звезды за время съёмки. Была идея это рассчитывать просто на основе выдержки, но понял, что это может быть не самым точным. Поэтому мы будем вытаскивать время съёмки из EXIF данных фотографии.


Bitmap referencePic = new Bitmap(fileNames.First()); //loading first image
byte[] timeTakenRaw = referencePic.GetPropertyItem(36867).Value; // EXIF DateTime taken
string timeTaken = System.Text.Encoding.ASCII.GetString(timeTakenRaw, 0, timeTakenRaw.Length - 1); // array to string without last char (newline)
DateTime referenceTime = DateTime.ParseExact(timeTaken, "yyyy:MM:d H:m:s", System.Globalization.CultureInfo.InvariantCulture);


Теперь по порядку.
Нам нужно определить момент начала съёмки чтобы от него начать отсчитывать смещение остальных кадров.
Загружаем фотографию и получаем PropertyItem ID: 36867 — дату и время получения кадра.
Преобразуем массив символов в строку (последний символ — \n, поэтому его исключаем).
Получили время, когда началась съёмка.

Для каждой последующей фотографии мы будем находить тоже самое и использовать разницу во времени для вычисления угла поворота.
Считается всё просто, зная, сколько секунд занимает полный оборот Земли.


double secondsShift = (dateTimeTaken - referenceTime).TotalSeconds;
double rotationAngle = secondsShift / stellarDay * 360;


Поворачивать изображения будем аффинным преобразованием, но для этого нам нужно рассчитать матрицу поворота.
К счастью, в Emgu CV это всё уже сделано за нас.


RotationMatrix2D<double> rotationMatrix = new RotationMatrix2D<double>(rotationCenter, -rotationAngle, 1); //change rotationAngle sign for CW/CCW


Имея матрицу можно применить аффинное преобразование для текущей фотографии, а затем сложить их как и в первом режиме.

using (Image<Bgr, Byte> imageToRotate = new Image<Bgr, Byte>(currentPic))
{
       referenceImage = referenceImage.Max(imageToRotate.WarpAffine<double>(rotationMatrix, Emgu.CV.CvEnum.INTER.CV_INTER_CUBIC, Emgu.CV.CvEnum.WARP.CV_WARP_FILL_OUTLIERS, background));
}


Собственно, вот и всё. Для каждой фотографии выполняем эти два метода, и смотрим что получилось.

Тесты


Исходные изображения:

27 штук

Тест 1: Stack

Результаты наложения всех фотографий в одну.
image

Тест 2: Rotate & Stack

Отметили центр вращения, и вперёд.


Во втором тесте получился довольно-таки странный эффект.
1. Земля размазана — это нормально. Звёзды должны быть фиксированы.
2. Звёзды в центре остались на своих местах, тоесть всё сработало как надо.
3. Звёзды по краям «поплыли»

Почему так произошло? Понятия не имею! Предполагаю, что из-за дисторсии объектива траектория звёзд перестала быть кругом — стала эллипсом.

image
Выдержка ~43'

На этом фото хорошо видно, что траектория отличается от идеального круга — а под это всё рассчитывалось ведь.

Аппаратура

Снималось всё на Canon 7D с Canon EF-S 10-22mm. У этого объектива не самые лучшие оптические параметры — а именно дисторсия. Поэтому именно его я и виню в том, что всё получилось не так гладко. В следующий раз попробую подкорректировать дисторсию и протестирую вновь.

Ясного неба!

Репозиторий на github
Tags:
Hubs:
+53
Comments41

Articles

Change theme settings