Добрый день Хабрчане!
В этом посте я хочу поделиться с вами своим скромным опытом в написании софта для удаленного управления БЗК Никон Z6. Точнее правильнее будет сказать не «написании», а скорее «допиливании», однако более подробно об этом я напишу ниже. Ну а сначала я расскажу немного о себе и о том, что именно - натолкнуло меня на такую, на первый взгляд, бредовую идею.
Итак, меня зовут Вадим, мне сорок с хвостиком, и я то, что называется Jack of all trades. Однако в первую очередь – я коммерческий фотограф, специализирующийся на технической, индустриальной и предметной фотографии. Чуть больше двадцати лет назад, когда вопрос определения специальности и выбора соответствующего ВУЗа, встал, так сказать, ребром – я понял что сфера IT и написание кода руками – явно не удовлетворяют мои творческие амбиции. Поэтому я решил остановить свой выбор на творческой профессии, и, несмотря на протесты родителей, поступил в академию искусств на факультет фотографии. В ретроспективе я не берусь утверждать, что мой выбор профессии – был наиболее удачным с финансовой точки зрения. Однако - я изо дня в день занимаюсь тем, что приносит мне огромное удовольствие и чувство глубокого морального удовлетворения. На сегодняшний день я признанный и востребованный специалист в вопросах сложных технических съемок и интеграции различного рода фотооборудования в различных отраслях. Ах да, вдобавок к этому - я жуткий зануда, и технократ. Наверное, именно эти черты моего характера, и привели меня к мысли о написании своей софтинки…
Но все это присказка. А вот сказка, собственно, начинается отсюда.
Как я уже написал выше, львиная доля фото-проектов, за которые я берусь – это комплексные и подчас весьма технические съемки. Съемки, в которых приходится решать интересные задачи для того чтобы «на выходе» получить «невозможный» кадр. Это может быть замедленная (скажем 20,000 к/с) фото или видеосъемка различного рода процессов. Или микрофотография с коэффициентом увеличения 10:1. Или, например, съемка, требующая нетривиальную расстановку световой схемы. Также, подчас от меня требуется завести камеру под «невозможным» углом к объекту. Короче говоря: как правило, это проекты, требующие навыков Mc-Guyver-а или Самоделкина. Именно в процессе работы над проектами такого типа я и «уперся» в т.н. стеклянный потолок возможностей камеры. Точнее выражаясь, скорее не самой камеры, а особенностей ее использования. Все дело в том, что нередко мне приходится крепить камеру вне прямой досягаемости. То есть – таким образом, что дотянуться до ее органов управления, таких как экран и кнопки – просто не предоставляется возможным. Например, в тех случаях, когда камера закреплена на «Си» или «Бум» стэнде на высоте 3 метров. Или тогда когда доступ к камере просто закрыт «флагами» или рассеивателями. На самом деле, таких примеров можно привести как минимум с десяток. Но я думаю, что вы поняли, о чем именно идет речь. Зачем мне требуется доступ ко всем органам управления камеры, и почему нельзя ограничиться дистанционным пультом? - Потому что помимо управления экспозиционным треугольником (ISO/Выдержка/Диафрагма), я пользуюсь еще и такими функциями как Focus Peaking, зуммирование в режиме LiveView и передвижение увеличенной (зуммированной) области по площади кадра. Весь этот функционал необходим мне для того чтобы можно было:
-
Определить распределение ГРИП.
-
Выбрать или подогнать точное месторасположение плоскости фокуса в режиме ручной наводки на резкость.
-
Рассчитать и проверить угол наклона и распределение зоны резко изображаемого пространства в процессе работы с наклонами и подвижками Tilt/Shift оптики (т.н. Scheimpflug principle).
-
Оценить композицию или проверить отдельно взятые участки кадра.
-
Оценить светотеневой рисунок, обрисовываемый «пилотным» светом или постоянными источниками света, непосредственно в процессе расстановки световой схемы.
Конечно, можно было бы просто снимать десятки или даже сотни тестовых кадров, и априори рассматривать их на большом экране при помощи внешнего HDMI монитора, или скажем на экране Лэптопа с помощью П.О. обеспечивающего Tethering. Однако весь этот функционал необходим мне непосредственно В ПРОЦЕССЕ работы над кадром. Т.Е. в режиме «real-time». Поэтому этот вариант отпадает сразу. Еще один вариант – это подключить камеру ко внешнему монитору по интерфейсу HDMI, и работать в режиме LiveView. Однако интерфейс HDMI не предусматривает двустороннюю связь, и посему не предоставляет возможности управлять камерой. Поэтому здесь тоже «тупик». Также, ввиду того, что у компании Nikon, камерами которой я пользуюсь уже более девяти лет – пульты Д.У. с расширенным функционалом, отсутствуют как класс (в отличие от, скажем, Remote Commander unit для беззеркалок Sony) – единственное более менее удобоваримое решение которое применимо в таких ситуациях, это работа с П.О, обеспечивающим LiveView в режиме tethering. Таким, например, как Capture one pro, Adobe Lightroom CC, или Helicon Remote. Что, в принципе, я и делал вплоть до воплощения своей идеи в жизнь.
Кстати, в процессе работы с программами, обеспечивающими LiveView в режиме Tethering -выясняется весьма любопытная «фишка», о наличии которой не подозревают 85% фотографов. Оказывается что во всех БЗК камерах Никон серии Z, а также в «зеркалках» D850 и D780 – предусмотрен режим работы, позволяющий работать с HDMI монитором (подключенным к камере) и с подключением камеры к ПК – ОДНОВРЕМЕННО. В остальных камерах это либо HDMI монитор, либо LiveView tethering. Однако, использование tethering программ, несмотря на поддержу LiveView, и наличие (почти) полноценного управления функциями камеры – несет с собой кучу ограничений и неудобств. Рассмотрим их детально:
-
Качество «стрима» LiveView картинки, залетающей в П.О по интерфейсу USB – оставляет желать лучшего. Данный «стрим», как правило, является весьма «сжатым», и не поступает на экран компьютера в «полноценной» резолюции (имеет место быть UP/Down Sampling). Особенно хорошо это заметно при увеличении (зуммировании) отдельно взятых областей кадра. Что попросту сводит на «нет» возможность точно навестись на резкость. То же самое можно сказать и о возможности оценить мелкие детали в кадре. Их попросту «глушат» либо шум, либо слишком агрессивный «шумодав» (в каждом П.О. реализована своя алгоритмика обработки «стрима»).
-
Задержки в «стриме», поступающем на компьютер через интерфейс USB. Безусловно, что эти задержки отчасти зависят от вычислительной мощности компьютера, скорости интерфейса USB шины, а также качества и длины USB кабеля. Однако даже мощные системы с камнями i7 и полноценным USB 3.1 – начинают «заикаться» в режиме LiveView. Особенно при «зуммировании», когда скорость обновления картинки может вполне упасть до 7fps, а то и ниже. Для общей работы – эти задержки некритичны. Однако в случаях ручной наводки на резкость, или настройки наклонов оптики по принципу Шаймпфлюга – они начисто лишают меня возможности увидеть изменения в режиме real-time, превращая работу в игру «попал/не попал».
-
П.О. для Tethering в режиме LiveView – как правило, предъявляет достаточно высокие требования к «железу» . Безусловно – можно работать с «Капчей»(Capture One Pro) даже на маломощных системах. Однако это только усугубит задержки в «стриме». Поэтому, для того чтобы более менее полноценно работать в таком режиме – приходится брать на съемочную площадку достаточно мощный Лэптоп. А это еще 2.5 кг не особо компактного оборудования.
-
На данный момент – ни одна из используемых мною программ для tethering, не предоставляет возможности сделать полноценные Keyboard Shortcuts для управления функционалом в режиме LiveView. Например, для передвижения зуммированного окошка по площади кадра. Или для контроля увеличения картинки в режиме LiveView Zoom. Поэтому приходится работать непосредственно с GUI программы с помощью мышки. Что, в общем, не особо удобно в случаях, когда я строю кадр и попросту не могу (да и не хочу) дотянуться до компьютера или мышки, поскольку у меня заняты руки. Например, в процессе работы над «стайлингом», или работы с источниками освещения. Именно эти ограничения и подтолкнули меня к мысли о разработки своей собственной, альтернативной методики контроля и управления функционалом камеры.
Итак. Для того чтобы разработать что либо – необходимо составить два документа, которые разложат по полочкам всю информацию, на основе которой мы будем создавать наше решение, а именно: Requirements (требования) и Limitations (ограничения).
По требованиям у нас получается следующее:
Необходимо разработать портативную систему управления конкретно взятыми функциями камеры (LiveView Zoom-IN, LiveView Zoom-OUT, LiveView zoomed area panning, Focus peaking ON/OF, Exposure Preview ON/OFF, активация функции спуска(съемка)) посредством беспроводного контроллера с автономным питанием (ИК пульт? / Джойстик? / Беспроводная мини-клавиатура?) . Желательно чтобы система была сверхпортативной и не обосновывалась на мощных энергоемких и громоздких системах, таких как переносные компьютеры. Также, желательно чтобы система управляла камерой посредством отдельно взятых клавиш/кнопок на беспроводном контроллере, а не посредством GUI или манипулятора/мышки. В идеале – систему желательно разделить на два блока: ПК модуль, подключенный к камере, и беспроводной модуль-контроллер, который будет передавать на ПК модуль, нажатия клавиш/кнопок. В таком случае ПК модуль будет получать сигнал о нажатии той, или иной кнопки на контроллере, инициировать функцию, закрепленную за этой клавишей, и пересылать соответствующую команду камере. Также, желательно чтобы ПК модуль был маленьким и имел возможность работать от аккумуляторной батареи Power-bank. Как варианты можно рассмотреть модули Raspberry Pi, Arduino или Intel Computestick / PC on a Stick. Как я уже упоминал выше, у всех БЗК камер серии Z пр-ва Никон, присутствует возможность выводить картинку LiveView на внешний монитор посредством разъема HDMI на камере, не мешая при этом процессу tethering-а или дистанционному управлению камерой через USB порт. Поэтому ПК модуль, по сути, вообще не обязан быть привязан к экрану.
По ограничениям у нас получается такая картина:
У камеры есть три интерфейса подключения: беспроводной модуль WiFi/BT, разъем HDMI и разъем USB. Беспроводной модуль подключения отпадает сразу - поскольку для него нет полноценного открытого кода, и работает он исключительно с родной аппликацией SnapBridge. Разъем HDMI – предоставляет нам почти нулевую задержку (во всяком случае, в сравнении с USB “стримом») и идеальную картинку в полной резолюции HDMI интерфейса (FHD/4K) без сжатия или UP/DOWN-sampling, но НЕ ПРЕДОСТАВЛЯЕТ двусторонней связи. Поэтому управление камерой через интерфейс HDMI отпадает. Что, впрочем, не мешает нам задействовать этот интерфейс в качестве внешнего монитора для отображения LiveView, при его подключении к камере. Разъем USB – предоставляет нам возможность управления камерой (двусторонняя связь) посредством специализированного П.О, установленного на устройстве, подключенном к камере, однако не позволяет нам работать с изображением LiveView в требуемой детализации и с нулевой задержкой. Также, имеющиеся на данный момент решения для Tethering, не имеют возможности вынести управление требуемыми функциями, на отдельно взятый контроллер.
В итоге, все вышеописанное выливается в следующую блок схему:
Почему в блок-схеме указано именно х86/х64 устройство с Win10, а не, скажем, «Малинка» или Arduino, упомянутые в Requirements? На это есть вполне обоснованный ответ: Как оказалось Исходные коды и библиотеки вроде libGphoto2, заточенные под подобные микрокомпьютеры – не обладают достаточным функционалом и внятной документацией, чтобы написать полноценную программу для управления камерой следуя тому, что указано в требованиях. Поэтому единственным верным вариантом – оказалось использование «родного» SDK предоставляемого компанией Nikon. В свою очередь Nikon SDK – предоставляется в виде исходников для систем Macintosh и Windows. Что ограничивает выбор нашего модуля управления в пользу PC on a stick, отчасти потому что компания Apple не выпускает устройств в таком форм-факторе. Вдобавок к этому, PC On a stick можно подсоединить к камере прямиком, наподобие USB Dongle. Что вполне соответствует требованиям к габаритам и мобильности устройства.
Nikon SDK – краткая справка.
С 2008 года - производитель предоставляет сторонним разработчикам возможность использовать «родной» SDK для управления устройствам Nikon. Этот SDK детально документирован, охватывает почти весь возможный функционал устройств, и периодически обновляется, чтобы обеспечить более обширную совместимость. При этом он включает в себя не только описание, документацию и необходимые библиотеки, но и уже написанную и готовую к компиляции Sample Program. Стоит заметить, что библиотеки и модули, необходимые для работы – рознятся друг от друга, а посему, при заполнении заявки для получения доступа к SDK – необходимо отметить именно те камеры, с которыми разработчиками придется работать. Без соответствующего «родного» модуля – SDK просто не сможет распознать камеру.
Итак, выводы сделаны, план действий намечен. Направляемся скачивать Nikon SDK. После заполнения анкеты с заявкой, на электронную почту «прилетает» ссылка на желаемый ресурс с которого можно «скачать» SDK. Сохраняем SDK, открываем папку – и видим код, написанный под С++. Что, в принципе, замечательно. Вот только одна незадача. Последний раз, когда я писал код, длиной более чем двадцать строчек на чем-то кроме Python, PHP или RUST – был приблизительно двадцать пять лет назад. Не говоря уже о том, что на самом деле, я ни разу не программист руками, и даже в ту пору я предпочитал «крестам» VB. В этой точке – мой проект застопорился. Пришлось искать программиста фрилансера. Причем не просто программиста, а человека, который хотя бы пару раз работал с SDK производителей фототехники. Поскольку документация – вещь конечно хорошая, но не имея конкретного опыта работы, освоить 318 страниц описания это долгий процесс.
Спустя пару месяцев подходящая кандидатура нашлась! Меня вывели на человека, который уже несколько лет занимается разработкой разного рода софта, работающего на основе SDK для камер пр-ва Canon. Мы договорились о встрече, я передал ему документацию, описывающую требования и ограничения, SDK для камеры Z6, и стал ждать результатов. Однако последние не последовали. Программист меня просто «продинамил». Может быть ввиду ограниченного бюджета который мы оговорили. Может быть из-за того что этот проект показался ему более энергоемким чем на первый взгляд. Может быть из-за того что и ему, как и мне, не хотелось разбираться с C++. Так или иначе я был вынужден искать альтернативные варианты.
И тут произошло два поворотных события: во первых, я неделю провел дома в изоляции, подхватив пресловутый COVID-19, а во вторых, после усиленного штудирования просторов Интернета, я нашел любопытный проект, портировавший «родной» SDK c «Крестов» на C#. А поскольку «дурная голова – ногам покоя не дает», я решил провести время проведенное в изоляции с пользой и «тряхнуть стариной» попытавшись написать требуемое П.О своими силами. Благо C#, с точки зрения синтаксиса гораздо более благосклонен к «нубам».
Nikon SDK C# Wrapper – краткая справка.
Итак, вышеупомянутый проект – является «портом» родного Nikon SDK с С++ на C#. Проект лицензируется согласно условиям Creative Commons Attribution 3.0 Unported License, и являет собой открытый исходный код с хорошим набором Sample programs, демонстрирующих функционал и синтаксис и обильно приправленных комментариями к коду. Единственные оговорки проекта это отсутствие документации (пользователю стоит работать с документацией «родного» Nikon SDK чтобы освоить или понять общую логику или конкретные eNums стоящие за имплементацией той или иной функции), и полное отсутствие обновлений проекта с 2016 года. Несмотря на то, что автор этого проекта явно забросил его поддержку и не появлялся на странице проекта уже более чем три года – Discussion board вполне обширен и все еще содержит в себе признаки жизни.
Как я уже упомянул выше – последнее обновление C# Wrapper вышло в 2016 году. Отсюда следует сделать вывод, что, несмотря на то, что «родной» Nikon SDK получал регулярные обновления, включающие в себя модули для камер, вышедших после 2016-го – функционал и поддержка модулей в C# Wrapper явно не предусматривали работу с БЗК камерами серии Z. Поэтому, для начала, ради того чтобы программа смогла распознать «неведому зверушку» Z6, под которую я собрался разрабатывать эту программу – мне пришлось перелопатить недра форума и видоизменить код. После нескольких попыток это удалось, и программа смогла определить модуль камеры и установить с ней двустороннюю связь. Решив не мучиться с написанием кода «с нуля», и убедившись, что при определенных изменениях имеющейся Sample Program – программа вполне жизнеспособна, я принялся перерабатывать уже готовый код, переписывая его под свои нужды, и подгоняя его на соответствие с моей БЗК. Отчасти благодаря мудрым подсказкам IntelliSense в Visual Studio 2019.
Процесс «написания» растянулся на месяц, на протяжении которого я освоил много интересных нюансов и проштудировал более 90% оригинальной документации, сопровождающей Nikon SDK. Также, я смог вникнуть в то, что на самом деле происходит «Under the hood» в программах для tethering во время того как они запускают режим Liveview, получая от камеры «стрим» посредством USB.
А происходит там примерно следующее: «стрим», поступающий в компьютер из камеры, на самом деле, состоит из чередующихся картинок JPEG, не являясь «полноценным» видео, в полном смысле этого слова. Именно этим обуславливается отсутствие «внутрикамерного» звука в родных программах Nikon Webcam utility и EOS Webcam utility, «превращающих» зеркальные и беззеркальные камеры в интернет камеру. Более того, каждый кадр этого «стрима», вполне может быть подвергнуть сжатию как на уровне «железа» (обуславливается соответствующей функцией SDK), так и на уровне программы, в процессе отрисовки кадра в окне ImageBox. Работает это примерно так: программа выделяет буфер, в который захватываются и сохраняются JPEG картинки из «стрима», а потом отображает их в окне вывода с определенной частотой обновления, дабы сохранить видимость консистентного видеопотока. Как только объем информации в этих кадрах меняется – или соседние кадры содержат в себе существенные различия (движение в кадре) – «плавность» этого псевдо-видеопотока нарушается, и мы видим скачки и задержку. Однако гораздо более интересным фактом – является то, что на самом деле программа задействует два буфера. Один – выделенный под JPEG картинки из «стрима», тогда как второй содержит в себе сопровождающую каждый «кадр» (из этого «стрима») информацию. Т.Н. Header.
Именно этот Header наиболее интересен с точки зрения имплементации моей программы. Постольку поскольку он содержит в себе всевозможную информацию, описывающую статус камеры на момент «захвата» каждого кадра в «стриме». Такую, например, как координаты точки или зоны фокуса, состояние привода АФ камеры и индикацию подтверждения фокуса, коэффициент зуммирования в режиме LiveView, Размер картинки LiveView, размер полноценного кадра, уровень сжатия картинки Liveview, статус гироскопов камеры (наклон камеры), и.т.д. И именно эта, сопровождающая каждый кадр из «стрима», информация – является для меня наиболее ценной. Ведь с ее помощью можно управлять такими функциями как LiveView Zoom и передвигать зуммированую область по площади кадра. Точнее сказать – управление происходит посредством других функций SDK, однако «опрос» камеры касательно координат области (зоны) фокуса или статуса LiveView Zoom - происходит именно с помощью этой информации. Однако, несмотря на то, что все это звучит довольно просто – на самом деле, для того чтобы «выудить» необходимую информацию из этого «заголовка» мне пришлось немного повозиться. Компания Никон заполняет содержимое header buffer, применяя Big endian порядок байтов. Поэтому, для того чтобы разложить индексацию значений в буфере «по полочкам», мне пришлось написать небольшой parser, чтобы не обращаться к искомым значениям наугад. После того как посредством написанного parser-а была найдена корреляция между индексом и значениями – написание функций, выхватывающих из «заголовка» координаты области (зоны) фокусировки и статус зуммирования в LiveView – стало делом пары часов.
На тот момент, моя софтинка могла уже не только отсылать камере команды, но и обрабатывать статус того, что камера отображает на экране/мониторе на данный момент. Далее имплементируем обработку этих данных в отдельные функции, изменяющие координаты области (зоны) фокуса инкрементально (с шагом в 200 пикселей), и отвечающие за изменение коэффициента увеличения LiveView Zoom, при нажатии соответствующих клавиш на NumPad. Всё! Необходимый функционал готов. Теперь желаемыми функциями камеры можно управлять удаленно, с помощью Microsoft Wireless Numeric Pad, подключенным к ПК посредством Bluetooth.
Однако я решил не останавливаться на достигнутом. Как я уже написал выше, в требованиях к системе – указана маломощная машинка в форм-факторе PC on a Stick. А это значит, что как правило, такая система имеет «на борту» процессор Atom или любой другой ULV пр-ва Интел, и мизерное количество памяти, которое «съедается» ОС. Что из этого следует? А следует из этого - то, что софт должен загружать ресурсы CPU и RAM с большой экономией и в «щадящем» режиме. Поэтому, из кода необходимо было «вырезать» куски, особо прожорливые к ресурсам. GUI (я решил довольствоваться Windows Forms) – систему не «напрягает». Периодические обращения к камере, для того чтобы отослать ей ту, или иную, команду через USB порт – тоже не особо. Поэтому главным «злодеем» в нашем случае – являются выделение и обновление буфера, в который «залетают» JPEG картинки из «стрима», и их последующая отрисовка в ImageBox при включении LiveView. Однако наличие ImageBox в GUI моей программы – вовсе необязательно. Как я уже написал выше – LiveView картинка из камеры выводится непосредственно на монитор HDMI, подключенный к камере. Поэтому отображение LiveView (Да и вообще, наличие экрана, как такового) в ПК модуле – это явно не прерогатива. Однако отключением отрисовки картинки или выключением ImageBox здесь явно не ограничиться. Картинка, конечно не отображается, однако обработка «стрима» все равно продолжается, и буфер продолжает обновляться, не выводя свое содержимое в GUI. Значит надо отключить или видоизменить функцию заполнения самого буфера, выделенного под JPEG «стрим». А как вы помните, буферов, на самом деле – два! Один для JPEG картинок, и второй для «заголовка» с сопроводительной информацией. Поэтому быстренько переписываем исходник таким образом, чтобы при желании (при помощи заданного флага) можно было выделять и задействовать либо оба буфера одновременно, либо только один из них. А именно – только тот, который содержит в себе сопроводительную информацию из Header. Таким образом, «стрим» JPEG картинок – полностью игнорируется П.О., тогда как в режиме реального времени, при включении LiveView, программа «мониторит» только буфер с «заголовками». Что мы имеет в сухом остатке? Как показало последующее тестирование - мы имеем экономию 20% процентов ресурсов CPU и более чем 200Мб RAM. Что уже вполне соответствует требованиям к маломощной системе. Протестировав «на коленке» пробную сборку в Нетбуке с Atom, 2GB памяти и Win10 - я был доволен. Система работала на ура без видимых задержек.
Дальше осталось лишь прописать возможность изменения флага включения и отображения JPEG «стрима» по желанию пользователя, сделать ревизию логики и прогнать программу через Sanity Checks (как пример: изменение координат или зуммирование при выключенной камере – должно быть заблокировано вплоть до того момента когда П.О не «увидит» камеру и не убедится что режим LiveView активирован), и написание полноценного LOG модуля для диагностики.
С момента начала работы прошло около месяца. За это время программа успела обрасти дополнительными функциями и дошла до стадии стабильного бильда. На сегодняшний день – помимо функционала определенного в требованиях, задействованы также возможности удаленного управления экспозицией, возможность выбора сохранения отснятых картинок (либо на карту памяти в камере, либо в указанную папку на ПК), и возможность интеграции программы с полноценными tethering программами, отображающими отснятые посредством программы картинки, с помощью функций Monitor folder (Adobe Lightroom CC) или Show Hot folder (Capture One Pro). К слову: одновременная работа программы с модулями tethering в программах Capture One Pro или Lightroom CC - невозможна. Отчасти потому что они начинают "перехватывать" друг у друга бразды управления камерой посредством USB порта. Поэтому Tethering модули стоит либо отменить, либо не запускать одновременно с программой, а довольствоваться лишь вышеуказанными функциями отслеживания папок, в которые программа сохраняет отснятый материал.
Общим счетом, на переиначивание Sample Program и «допиливания» кода до желаемого функционала – у меня ушло порядка 60-ти с хвостиком часов. Отдельно стоит отметить тот факт, что не имея на руках Nikon SDK C# Wrapper, и не обращаясь за помощью к Discussion board этого замечательного проекта – я не смог бы провернуть и малой толики того, что было проделано за эти два месяца. Конечно, будь этот код написан программистом, а не «диванным теоретиком» - он выглядел бы гораздо более элегантно и не пестрил бы комментариями к коду через каждые пять строчек. Тем не менее – поставленная цель была успешно достигнута, а программа имплементирована в мой Workflow, успев доказать себя «в деле». Так что, в общем и в целом, я доволен. Это был действительно захватывающий experience. Надеюсь что этот пост будет интересен не только технократам и разработчикам, но и продвинутым фотолюбителям не чурающимся написания кода, и желающим попробовать свои силы в работе с Nikon SDK!
Вадим К.Риз.
Автор:
VKRiez