Склеиваем несколько фотографий в одну длинную с помощью компьютерного зрения

    В предыдущих статьях был описан шеститочечный метод разворачивания этикеток и как мы тренировали нейронную сеть. В этой статье описано, как склеить фрагменты, сделанные из разных ракурсов, в одну длинную картинку.


    Этикетки заранее сегментированы и развернуты нейронной сетью, описанной в предыдущей статье.



    Как вообще работает склеивание? Нужно взять две картинки с нахлестом, посчитать взаимный сдвиг и наложить одну на другую. Звучит довольно просто, но давайте рассмотрим каждый из шагов.

    Чтобы посчитать взаимный сдвиг, нужно найти какие-то объекты, которые присутствуют на обоих изображениях и вычислить каким-то образом преобразование точек с одной картинки на другую. Этот сдвиг может быть представлен матрицей преобразования, где элементы матрицы кодируют сразу несколько трансформаций — масштабирование, перенос и вращение.

    Есть отличная таблица в википедии, где показано, как и какие элементы влияют на трансформацию.

    Как видно на картинке ниже, общих объектов вполне хватает:



    Но выбранными объектами есть проблема — их сложно детектировать алгоритмически. Вместо этого, принято искать более простые объекты — так называемые “уголки” (“corners”), они же дескрипторы (“descriptors”, “features”).

    Есть отличная статья в документации OpenCV, почему именно уголки — если вкратце, то определить линию легко, но она дает только одну координату. Поэтому нужно детектировать еще и вторую (не параллельную) линию. Если они сходятся в точке, то это место и есть идеальное для поиска дескриптора, он же является уголком (хотя реальные дескрипторы не являются уголками в геометрическом смысле этого слова).

    Одним из алгоритмов по поиску дескрипторов, является SIFT (Scale-Invariant Feature Transform). Несмотря на то, что его изобрели в 1999, он довольно популярен из-за простоты и надежности. Этот алгоритм был запатентован, но патент истёк этой весной (2020). Тем не менее, его не успели перенести в основную сборку OpenCV, так что нужно использовать специальный non-free билд.

    Так давайте же найдем похожие уголки на обоих изображениях:

    sift = cv2.xfeatures2d.SIFT_create()
    features_left = sift.detectAndCompute(left_image, None)
    



    features_right = sift.detectAndCompute(left_image, None)
    



    Воспользуемся сопоставителем дескрипторов Flann (Flann matcher) — у него хорошая производительность даже, если количество дескрипторов велико.

    KNN = 2
    LOWE = 0.7
    TREES = 5
    CHECKS = 50
    
    matcher = cv2.FlannBasedMatcher({'algorithm': 0, 'trees': TREES}, {'checks': CHECKS})
    matches = matcher.knnMatch(left_descriptors, right_descriptors, k=KNN)
    
    logging.debug("filtering matches with lowe test")
    
    positive = []
    for left_match, right_match in matches:
        if left_match.distance < LOWE * right_match.distance:
            positive.append(left_match)
    

    Желтые линии показывают, как сопоставитель нашёл совпадения.



    Как хорошо видно — правильных совпадений примерно только половина. Однако, если правильные совпадения всегда дают одно и то же преобразование, то неправильные показывают хаотично новое направление. Т.е. теоретически, их можно как-то отделить друг от друга:



    Одним из алгоритмов, чтобы найти правильное преобразование, является RANSAC. Этот алгоритм отлично работает, если нужно отделить хорошие значения от шумов — как раз наш случай.

    К счастью, в OpenCV уже есть функции, которые найдут матрицу преобразования по совпадениям, используя RANSAC, т.е. фактически, ничего писать не придется.

    Воспользуемся функцией estimateAffinePartial2D которая ищет следующие преобразования: поворот, масштабирование и перенос (4 степени свободы).

    H, _ = cv2.estimateAffinePartial2D(right_matches, left_matches, False)
    

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

    Левый фрагмент:


    Правый фрагмент:



    Для начала давайте воспользуемся простейшим способом склейки фрагментов, когда каждый пиксель их пересечения вычисляется, как среднее. К сожалению, результат получается так себе — изображение заметно двоится, особенно возле линии склейки.



    На анимации различие между двумя кадрами видны более наглядно:



    Это не удивительно — фотографии были сделаны под разными углами, нейронная сеть также развернула их слегка по-разному, и в итоге получились небольшие расхождения.

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

    Вообще, есть разные методики вычисления оптического потока — некоторые из них встроены прямо в OpenCV, а есть и специальные нейронные сети.

    В нашем же случае, конкретную методику я опущу, но опубликую результат:



    Но компенсацию нужно осуществлять пропорционально обоих фрагментов. Для этого разделим его на две матрицы:



    Левый фрагмент будет компенсироваться слева направо по нарастающей, в то время, как правый — наоборот.

    Теперь оба фрагмента накладываются один на другой практически идеально:



    Теперь наложение геометрически корректно, но мы наблюдаем весьма заметный скачок яркости на швах:



    Эту проблему легко исправить, если вместо средних значений, накладывать их с градиентом:



    С таким подходом, шва вообще не видно:



    В принципе, есть еще и другие методики склейки, например, multiband blending, которые используют для склейки панорам, но они плохо работают с текстом — только компенсация оптического потока способно полностью убрать двоения на тексте.

    Теперь склеиваем полное изображение:



    Финальный вариант:



    Дальнейшими улучшениями могут быть компенсация эффекта тени (правая сторона изображения), либо еще большая пост-обработка цвета и контрастности. Также видно, что слегка пострадала глобальная геометрия — линии справа чуть уползли вверх. Это проблему теоретически тоже можно исправить добавлением глобальной коррекцией масштабирования, но это тоже не совсем тривиальная задача.

    Мы рассмотрели, как работает склейка, готовое решение доступно здесь в виде REST API, также рекомендую посмотреть следующие ссылки:

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 30

      +4
      Забыл упомянуть в статье, что в общем случае склейка панорам — более сложный процесс, если речь идет об обычных панорамных фото, т.к. там нужно компенсировать сферические искажения, делать глобальное выравнивае, плюс фрагменты могут сходится под разными углами.
      В случае с этикетками, задача проще, т.к. склеивать нужно всегда слева направо, и стык всегда вертиакальный (и не бывает мест, где сходятся три фрагмента в одной точке). По сравнению с обычными панорамами есть только одна сложность — двоение на тексте, но, как это уже упоминалось, решается оптическим потоком.
        +8
        градиент для скрытия мест склейки — изящное решение.
          +1

          Спасибо! Для неподвижного объекта действенно получается очень хорошо. Для панорамных фото, ситуация усложняется тем, что вода колеблется, трава и листья — тоже. Бывает вообще посторонний объект появляется. Поэтому там склейку проводят по разным контурам, и линию склейки приходится делать более узкой (из-за чего она становится более заметной).

        0
        Сколько времени требуется алгоритму? А как долго будут обрабатываться файлы, например, для изборажений 4000x4000px?
          0
          Алгоритм работает где-то 4 секунды на 1000x1000px. Рост сложности можно сделать линейным, но не факт, что наша конкретная реализация не отвалится, если я ей скормлю картинки в таком разрешении. Но там надо еще и много оперативной памяти, т.к. много манипуляций с битмапами.
            0
            спасибо. А как себя будет вести opticalflow при обьемных изображениях? Не 2D? будут ли так же хорошо совмещаться изображения?
              0
              Если честно, я не очень понял вопроса. Речь идет именно о 3D моделях, или двух фотографиях из немного разного ракурса?
                0
                Две фотографии с разными ракурсами. У них есть глубина в отличие от этикетки.

                И еще один вопрос созрел, вы получаете этикетку из видео ряда? Или серии фотографий? Тут вопрос наверное пользовательского удобства, покрутить на видео пользователю проще, а вот разбивать видео потом на фреймы, выкидывая лишнее и не иметь гарантий резких кадров для склейки — сложнее. Или же вы просите сделать серию фотографий?
                  0
                  Мы просим делать серию фотографий, хотя записывать видео — выглядит довольно заманчиво.
                  Что касается фотографий из разных ракурсов, то мне кажется, что оптический поток склеит довольно неплохо, другое дело, что полученная фотография может выглядеть довольно странно (хотя и без стыков).
                  Для таких задач лучше использовать другие методики, которые по кадрам восстанавливают 3D habr.com/ru/post/516116/#comment_21985568
                    0
                    Видео изначально с низкой резкостью, там же не полное разрешение используется, но для анализа может быть и пойдет. Ведь такая резкость на фото, чтобы разглядеть даже линии гильоша вам не нужна. И, вероятно, нужно же еще выбирать не мыльные кадры для сшивания. На фото резкость контролирует сам пользователь.Хотя я не знаю, какие точные возможности у camera api, вероятно, можно забирать видео поток с 5-10fps тогда можно и не делить видео на фреймы, обрабатывать сразу. В идеале, конечно, самостоятельно забирать из видеопотока нужные кадры, но как это по мощностям с телефона получится — не знаю. Но для удобства, наверное, это лучшй вариант, нажал и провел камерой по обьекту.
                    спасибо.
                      0
                      Была идея запустить нейронку сразу на телефоне, и выбирать хорошие кадры прямо ей. На практике, она работала слишком медленно (пару секунд на детектирование), так что эту идею пока отложили. Специально ничего не затачивали под мобильные, если не считать стандартных методик компрессии моделей, которые предлагает pytorch из коробки.
          0

          Интересно было бы сравнить с решением на основе NeRF-W — судя по всему, это как раз задача, которую NeRF решает легко.

            0
            Для таких алгоритмов нужно большее количество фоток, плюс обычно приходится убирать артефакты. А вообще да, это все работает, плюс даже есть специальные девайсы для более удобного сканирования.
              +1

              да, артефакты были у NeRF, а в NeRF-W как раз провели огромную работу по подавлению этих артефактов. Тем более, что артефакты как раз обычно возникают, когда есть вариабельность (пальцы на этикетке), а в вашем случае пальцев нет. Потенциально возможны артефакты на фоне, т.к. фон остается одинаковым, тогда как бутылка вращается, но мне кажется, что т.к. фон вас все равно не интересует, это не будет проблемой.


              Насчет бОльшего количества фоток — похоже, что нет, не нужно, если, опять же, у вас нет заслонений ключевых элементов. В примерах с реконструкцией машин, кораблей и т.д. использовались очень маленькие выборки фотографий, чтобы это стоило попробовать.

                0

                Ну, здесь склеивание — это скорее дополнительный продукт. Основное назначение — это сегментация этикетки и разворачивание для дальнейшей обработки (к примеру, для поиска продукта по фотографии, как в tineye). Но обязательно посмотрю, как оно работает, спасибо!

            +10
            с помощью машинного обучения

            Но причём тут машинное обучение? И RANSAK и OpticalFlow — достаточно классические алгоритмы (Ок, OF можно сделать через нейронки, но тут это не рассмотренно). Плюс набор эмпирик чтобы всё попристойне выглядело.
            По заголовку я надеялся будет какой-нибудь очередной подход без RANSAK, типо этого.

              +3
              ZlodeiBaal, ну извините, что не оправдал надежды :)
              Что касается машинного обучения — это более широкая область, она не обязательно включает в себя нейронные сети.
                +10

                Но машинное обучение предполагает, что функция отображения пространства признаков в пространство решений подбирается компьютером на основе предъявленных образцов (наборов признаков или пар из наборов признаков и решений), в данной статье, кажется, этого нет. Разве что в шаге фильтрации пар соответствующих точек, где по сути делается кластеризация.
                Возможно, имелось в виду машинное зрение? Либо, возможно, что-то упустил, тогда извините.
                В целом же статья понравилась, задача интересная, не тривиальная и в меру сложная, хорошо показан набор методов и шаги решения. Спасибо. Постараюсь добраться до предыдущих статей цикла, которые раньше не видел.

                  +6

                  Сдаюсь! "Компьютерное зрение" здесь действительно лучше подходит.

                  +5
                  Я не спорю, что включает:)
                  Но всё же и RANSAK — это чисто логический алгоритм перебора гипотез и поиска оптимальной, а OpticalFlow, где нет нейронок — либо алгоритмы дифференциального анализа, либо опять же, какая-то логика, либо матан.
                  В матчинге вы сами говорите что KNN, который сложно назвать ML.

                  Так что в базовом пайплайне ни SVM ни Boost'инговых алгоритмов не вижу.

                  Но я только к заголовку придираюсь, всё остальное — ок:)
                +1
                del
                  0

                  А можно вопрос немного не по теме: если есть фотографии человека с младенчества до подросткового возраста — как-то можно автоматически обработать в гифку, чтобы показать изменения лица во времени?

                    +2
                    Ну я бы решал задачу так — сначало детектирование лица, и его сегментация (есть куча натренированых библиотек). Затем совмещение лиц с разных фотографий так, чтобы keypoints лица совпали (глаза, брови, линия рта и т.д.). Теоретически, гифка уже будет показывать изменение, но можно еще отдельным шагом сделать промежуточные кадры, где один кадр плавно переходит в вдругой — для более красивой анимации (просто накладывать один кадр на другой с полупрозрачностью).
                    После генерации всех кадров, их можно склеить в гифку каким-нибудь imagemagick.
                      0

                      Спасибо! А не знаете, может быть есть какие-то уже готовы подобные скрипты и решения?

                        +1
                        Прямо готовых я не знаю, но знаю, что есть целая куча готовых библиотек по распознаванию лиц. Тут главное найти такие, которые ищут не просто bounding box, а выделяют keypoints (глаза, и т.д.), чтбы их можно было точно совмещать в автоматическом режиме.
                        0
                        Вы имеете ввиду что-то вроде www.fmwconcepts.com/imagemagick/shapemorph2/index.php?

                        Как только определять точные координаты контрольных точек?
                          +1
                          Для этого нужно брать специальную нейронную сеть — таких архтитектур (даже уже обученых) хватает.
                          Попробовал поискать — есть готовая библиотека для Python github.com/ageitgey/face_recognition.
                        +1
                        Сделайте обычный морфинг. Софта множество за 20 лет.
                          +2

                          Возьмите StyleGAN2, используйте проектор, чтобы получить координаты лица в latent space, затем используйте интерполяцию между полученными координатами.


                          https://colab.research.google.com/drive/1ShgW6wohEFQtqs_znMna3dzrcVoABKIH#scrollTo=rYdsgv4i6YPl вот тут пример работы проектора.

                            0

                            Спасибо! Крутая штука похоже.

                        Only users with full accounts can post comments. Log in, please.