По профессии я режиссер монтажа, а прикладное программирование как увлечение в свободное время.
В какой то момент пришла идея совместить работу с хобби, прочитал статью на хабре о распознавании объектов на картинках с помощью Core ML, с этого собственно все и началось. Поделюсь скромным опытом и проблемами с которыми можно столкнуться при разработке приложений работающих с Core ML.
Дело в том что почти треть работы видеомонтажера заключается в рутинном поиске видеоряда из исходников, которые надо каждый раз шерстить в поиске контекстного плана под закадровый текст, по моему это не несет никакой творческой составляющей, особенно когда ты занимаешься этим 15 лет). Ну и подумал я, а что если написать софтину, которая будет проходится по папке с исходниками, распознавать объекты, аккуратненько «складывать» их в БД. Далее, в момент поиска видео фрагментов для так называемой «джинсы», вводится поисковое слово, например «Солнце», и все что находится каким то образом передается в монтажную систему.
Идея зрела, собирался стёк, писать решил на Swift, обученные модели собственно Core ML, база данных SQLite. На первый взгляд идея казалась легко реализуемой, вроде ничего сложного.
Очень быстро накидал основной код, который вытаскивает кадры из видео, распознает обьекты с помощью модели Resnet50, которую рекомендовали яблочники у себя на сайте, она очень шустро работала и позволяла настраивать процент при котором считать объект распознанным. Сам код спокойно раздается на том же apple.com для всех желающих. Подключил библиотеку SQLite.swift, обернул ее функции в свои методы, все работает!
Потом еще пришлось неплохо повозиться с алгоритмами создания очереди обработки списка файлов и в этот момент я обратил внимание что программа то разрослась! Уже после 1000-й строчки кода вдруг пришло понимание что mvc-паттерн уже совсем не подходит для этого проекта, а именно он обычно и предлагается на всех туториалах и подсказках из Stackoverflow. Как же затягивает процесс когда все получается и даже не обращаешь внимания что у тебя весь код навален в одном файле. Стал раскидывать все по классам, синглтонам и прочим сущностям. Вроде стало полегче, но это не надолго, ибо впереди еще нужно распаралелить процессы на потоки, что бы программа не замирала пока идет процесс распознавания в большом количестве файлов.
Почитал статьи о многопоточных приложениях, о Grand Central Dispatch (GCD) - технологии Apple, предназначенная для многоядерных процессоров, вроде бы тоже все просто - кидаешь фоновую работу в основной поток а обновление интерфейса в главный поток и опять все работает! Но что то подсказывало что так легко и быстро не бывает! Начался процесс тестирования.
Первый серьезный глюк дал о себе знать когда запустил сканировать большой архив семейных видеофайлов, 70 гигов, видео снятые в разное время на разные телефоны и поэтому и разные форматы - идеально! Как раз то что надо для тестирования! Сканирование останавливалось на 420-ом файле, снятом на какой-то старый Самсунг под windows mobile, ну да ладно, может битый файл, подумал я и удалил его, запустил снова…. опять 420 файл! Совершенно в другом формате, с яблофона, не битый! Что за магия такая? Ну давайте и его удалим…. еще раз… опять 420 файл… пора лезть в дебаггер.
Две недели, две недели жизни (в свободное от работы время) я посвятил поиску этой ошибки! Виновником оказался объект VNCoreMLRequest, работающий с запросами к ML-модели и который не любит когда его используют в нескольких потоках, при этом он никак не проявляет себя в логах дебаггера а просто выдает ошибку времени выполнения, проще говоря кладет один из потоков. Так же «порадовал» метод обработки изображений copyCGImage, который отказывался работать стабильно, правда яблочники предупредили об этом на своем ресурсе для разработчиков и предлагали использовать вместо него другой асинхронный метод generateCGImagesAsynchronously, который как ни странно работал еще хуже, в итоге я вернулся к первому методу «окружив» его блоком try catch.
Следующим этапом проектирования стала идея распознавать обьекты сразу несколькими обученными моделями для более эффективного результата. То есть если одна модель в одном кадре ничего не нашла, то вторая тоже имея другой набор данных в своих нейронах может чего и найдет. Я создал три слота для подгрузки моделей: первые два для поставляемых с приложением моделей YOLOv3 и Resnet50, и третий слот для любой другой модели, которая может быть подгружена из файла. Я посчитал что кто то из пользователей заведет свои собственные модели для специфических задач и тогда третий слот будет очень кстати.
К тому же Apple в поставке с Xcode теперь предлагает отдельный инструмент Create ML для создания своих моделей из набора картинок, там все очень просто, никаких командных строк, обычный пользовательский интерфейс для практически любого юсера.
Программа сформировывалась в завершенный продукт, не хватало одного - как пользователю выводить найденные видео фрагменты в программу видеомонтажа. Я наметил два варианта - это форматы EDL и XML. Реализовать первый формат не составляло особого труда, это старый известный с «ленточных» времен формат, используемый киношниками для переноса намеченных фрагментов в системы монтажа. Но проблема состояла в том, что EDL не содержит информацию о передаваемых файлах, а только о таймкодах, точках входа и выхода фрагмента, то есть в итоге пользователь получит набор фрагментов в секвенции, но они все будут оффлайн, потому что не известно из каких файлов брать эти куски, а ведь их, этих файлов может быть много, для каждого фрагмента свой файл. Другое дело XML! Он содержит всю информацию которую ты только можешь в него запихнуть: и путь, и формат файлов, и настройки звука, и даже применяемые маркеры, все что нужно, современный формат! Но вот реализовать всю эту крутизну это дело далеко не простое, и прочитать надо литературу которой нигде нет, ибо нужна информация именно по XML, используемом для экспорта секвенции именно с видео данными, а не какого нибудь там каталога для инет-магазина. Эту задачу я стал решать с изучения выведенного из Adobe Premiere шаблонной секвенции с парой файлов на таймлайне в XML. Полученный файл я открыл в текстовом редакторе и стал изучать. Постепенно стали вырисовываться блоки кода, для каждого плана на секвенции три блока - один для видео и два для звука, в общем теле кода сначала идут видео блоки а потом привязанные к ним аудио блоки, так же есть начальные и завершающее блоки файла с тегами описывающими, видимо, формат секвенции. Я разделил все эти блоки в отдельные файлы, которые обозначил как многострочные String ресурсы в Xcode. Создал отдельный класс, который оперирует этими блоками в цикле, собирая их в нужной последовательности в один код и подставляя в нужные места строковые данные с именем файла и информацией о таймкоде. Та еще работка! Хотя может быть абсолютно привычно для html-верстальщика. На первый взгляд сложная задача, но решена была довольно быстро, хотя это можно назвать хакерским методом) Но формат то по сути открытый! Другое дело что мы используем версию XML , сгенерированную Аdobe Premiere, с его тэгами, но насколько эти теги имеют проприетарный формат я рассуждать не берусь, знаю только что все работает, и в Final Cut Pro (в полной версии), и вдругих монтажках
Вот так я получил работающий инструмент, позволяющий найти необходимые фрагменты видео по поисковому слову, содержащие искомый объект, и весь список найденных файлов с нужных таймкодов импортировать в программу видеомонтажа, и все это найденное добро окажется на таймлайне.
Сейчас я думаю, чего еще можно добавить в приложение, есть уже некоторые идеи, и собственные и присланные пользователями, которые уже пользуются приложением. Например сейчас с появлением новых процессоров Apple Silicon, которые имеют аппаратное ускорение ML процессов до 16x, нужно обязательно сделать поддержку этой платформы в новых версиях. Ну а пока программа уже доступна в Mac App Store, называется Videoindex.
Надеюсь кому то пригодится мой опыт и этот инструмент, позволяющихся сэкономить до трети времени видеомонтажа