Завантажуючи панорами в Google Photo вони інколи "оживали" і вмикався вбудований переглядач для кругових панорам. Це було доволі класно, але, як я не старався, виявити якусь залежність між налаштуваннями склейки панорам і активацією переглядача знайти мені не вдавалося.
Потім в однієї людини на FB побачив "живу" панораму в якості обкладинки профіля - це підштовхнуло мене погуглити як же правильно робити панорами, щоб вони "оживали". Знайшов готову програму, яка усе робить сама: Exif Fixer. Завантажив для Linux x64 - усе працює, показує теги, які будуть інтегровані в Exif, але результат не зберігається. Програма поширюється бінарником, тому покопатися в ній не вийде.
Ок, в Linux є чудова утиліта ExifTool, пробуємо інтегрувати теги за її допомогою, тестове завантаження панорами в FB - усе працює. Але це граблі конкретні, запускати програму, копіювати з неї теги й в консолі інтегрувати їх.
Та яжпрограміст: зроблю свій велосипед :)
Треба розібратися як обчислювати необхідні значення тегів. Після кількох годин читання мануалів на facebook360.fb.com та developers.facebook.com більш менш розібрався. Тримайте вільний виклад тих постулатів:
- Представлення панорами завжди має вигляд прямокутного зображення зі співвідношенням 2:1 (360° * 180°), що при згортці дає нам повну сферичну поверхню.
- Наша панорама може займати або усе поле повної панорами 360° * 180°, або ж якусь частину (наприклад, 180° * 86°);
- В Exif обов'язково прописати такі параметри: ProjectionType, CroppedAreaImageWidthPixels, CroppedAreaImageHeightPixels, FullPanoWidthPixels, FullPanoHeightPixels.
- Для панорам, які не займають усе поле сфери бажано ще додатково прописати параметри: CroppedAreaLeftPixels, CroppedAreaTopPixels.
Спробуємо тепер розібратися що куди й до чого. В цьому нам допоможе наступне зображення (параметри обізвані по перших літерах слів в їхній назві):
Нічого не зрозуміло? Зараз поясню.
На вході ми маємо нашу панораму (внутрішній прямокутник). Її фізичні розміри CAI_WP * CAI_HP отримуємо з неї самої (ширина та висота в пікселях). FP_WP та FP_HP - розміри повного поля сферичної панорами (FP_HP завжди дорівнює FP_WP / 2), а CA_LP та CA_TP - відступи від країв повного поля (зазвичай панораму розміщують по центру повного поля).
Єдиний параметр, який не показано вище - ProjectionType. Він може приймати 3 значення: equirectangular, cylindrical, cubestrip. equirectangular використовується, коли в нас на вході є повна сферична панорама, тоді FP_WP = CAI_WP, FP_HP = CAI_HP, CA_LP = 0, CA_HP = 0. cylindrical використовується для панорам, які не займають усе сферичне поле зору. Всі параметри зображення в такому разі нам необхідно порахувати. І, врешті-решт, cubestrip - це така "вундервафля", що використовує проекції сфери на внутрішню поверхню куба. Страшна і не зрозуміла штука, яку можна використовувати, якщо ми маємо 6 камер для створення повної сферичної панорами, але то вже тема для абсолютно іншої статті.
Отже, реалізуємо це все діло в коді.
На вході нам треба мати саме зображення та один із кутових розмірів (найпростіше запам'ятати, огляд панорами по горизонталі):
filename = '~/panorama.jpg' width_degree = 120Далі, отримуємо розміри зображення:
from PIL import Image im = Image.open(filename) width, height = im.sizeВирахуємо кут огляду по вертикалі:
height_degree = int(width_degree * height / width)
Визначимося з типом проекції:if width_degree == 360 and height_degree == 180: projection = "equirectangular"else: projection = "cylindrical"
full_width = int(width * 360 / width_degree) full_height = int(full_width / 2)І, відступи від країв повного поля:
offset_left = int((full_width - width) / 2) offset_up = int((full_height - height) / 2)Усі дані отримані, тепер їх якось треба записати в Exif зображення. Є кілька бібліотек для роботи із Exif-ами, але такої, яка могла б записувати в xmp нестандартні теги я не знайшов (якщо хто знає, підкажіть). Тому, довелося викручуватися із ExifTool:
command = f"exiftool \ -ProjectionType={projection} \ -CroppedAreaImageWidthPixels={width} \ -CroppedAreaImageHeightPixels={height} \ -FullPanoWidthPixels={full_width} \ -FullPanoHeightPixels={full_height} \ -CroppedAreaLeftPixels={offset_left} \ -CroppedAreaTopPixels={offset_up}" process = subprocess.Popen(command.split(' ') + [filename], stdout=subprocess.PIPE) output, error = process.communicate()
Повний код можна знайти на GitLab.
Коментарі
Дописати коментар