Сшивание изображений

Эффект панорамы! Да, это то, что мы раскроем в этом посте.

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

Все мы использовали режим «Панорама» на мобильной камере. В этом режиме алгоритм мозаики изображений запускает захват и объединение изображений. Но мы можем использовать тот же алгоритм и в автономном режиме. См. Изображение ниже, где у нас есть предварительно захваченные изображения, и мы хотим их объединить. Обратите внимание, холм частично виден на обоих входах.

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

Предполагается, что каждая пара рассматриваемых изображений имеет определенные общие черты.

Давайте посмотрим на пошаговую процедуру создания панорамного изображения:

Шаг 1] Чтение входных изображений

Возьмите изображения с некоторой перекрывающейся структурой / объектом. Если вы используете собственную камеру, убедитесь, что вы не меняете свойства камеры во время съемки. Тем более, что не стоит брать в кадр много подобных конструкций. Мы не хотим путать наш крохотный алгоритм.

Теперь прочтите оба первых изображения в OpenCV.

def createPanorama(input_img_list):
    images = []
    dims = []
    for index, path in enumerate(input_img_list):
        print (path)
        images.append(cv2.imread(path))
        dims.append(images[index].shape)
    return images, dims

Шаг 2] Вычислить функции SIFT

Определите особенности / интересные места для обоих изображений. Эти точки являются уникальными идентификаторами, которые используются как маркеры . Мы будем использовать возможности SIFT. Это популярный алгоритм обнаружения и описания локальных особенностей. Он используется во многих задачах сопоставления объектов компьютерного зрения. Некоторые другие примеры дескрипторов функций: SURF, HOG.

SIFT использует пирамидальный подход с использованием DOG (разность гауссиана). Полученные таким образом элементы будут неизменными по отношению к масштабу. Это хорошо для панорамных приложений, в которых изображения могут иметь особенности поворота, масштаба, освещения и т. Д.

## Define feature type
feature_typye = cv2.xfeatures2d.SIFT_create()
​
points1, des1 = features.detectAndCompute(image1,  None)
points2, des2 = features.detectAndCompute(image2,  None)

Здесь points1 - это список ключевых точек, тогда как des1 - это список дескрипторов, выраженных в пространстве функций SIFT. Каждый дескриптор будет вектором 1x128.

Шаг 3] Сопоставьте сильные точки интереса

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

Для этой цели OpenCV имеет встроенный Matcher на основе FLANN. Это метод сопоставления на основе гистограммы, который вычисляет расстояние для 2 точек, описанных в пространстве признаков SIFT.

## Define flann based matcher
matcher = cv2.FlannBasedMatcher()
matches = matcher.knnMatch(des1,des2,k=2)
# important features
imp = []
for i, (one, two) in enumerate(matches):
    if one.distance < dist_threshold*two.distance:
    imp.append((one.trainIdx, one.queryIdx))

Шаг 4] Рассчитайте гомографию.

Теперь, когда у нас есть совпадающие точки, идентифицированные на обоих изображениях, мы будем использовать их для получения общей связи между изображениями. Эта взаимосвязь определяется в литературе как гомография. Это взаимосвязь между image1 и image2, описанная как матрица.

Нам нужно преобразовать одно изображение в пространство другого изображения, используя матрицу гомографии. Мы можем сделать любой путь от изображения 1 к 2 или изображения 2 к 1, поскольку матрица гомографии (в данном случае 3x3) будет квадратной и неособой. Единственное, что нам нужно быть последовательными при передаче баллов на омографию.

Я использовал свой собственный подход, основанный на RANSAC, чтобы получить матрицу гомографии. Но вы также можете использовать встроенную функцию OpenCV cv2.findHomography ()

### RANSAC
def ransac_calibrate(real_points , image_points, total_points, image_path, iterations):
index_list = list(range(total_points))
    iterations = min(total_points - 1, iterations)
    errors = list(np.zeros(iterations))
    combinations = []
    p_estimations=[]
for i in range(iterations):
        selected = random.sample(index_list,4)
        combinations.append(selected)
        real_selected =[]
        image_selected =[]
for x in selected:
            real_selected.append(real_points[x])
            image_selected.append(image_points[x])
p_estimated = dlt_calibrate(real_selected, image_selected, 4)
not_selected = list(set(index_list) - set(selected))
        error = 0
        for num in tqdm(not_selected):
# get points from the estimation
            test_point = list(real_points[num])
            test_point = [int(x) for x in test_point]
            test_point = test_point + [1]
try:
                xest, yest = calculate_image_point(p_estimated, np.array(test_point), image_path)
            except ValueError:
                
                continue
error = error + np.square(abs(np.array(image_points[num])-np.asarray([xest,yest])))
#            print("estimated  :",np.array([xest, yest])  )
#            print("actual :",image_points[0])
#            print("error :",error)
        errors.append(np.mean(error))
        p_estimations.append(p_estimated)
p_final = p_estimations[errors.index(min(errors))]
    return p_final ,errors, p_estimations

Шаг 5] Преобразуйте изображения в то же пространство

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

image2_transformed = H * image2

Шаг 6] Теперь займемся строчкой ...

После вычисления преобразованных изображений мы получаем два изображения, каждое из которых содержит некоторую отдельную информацию, а некоторые - общие w.r.t. Другие. Когда оно отделено, другое изображение будет иметь нулевое значение интенсивности в соответствующем месте.

Нам нужно объединить эту информацию в каждом месте, сохраняя при этом перекрывающуюся информацию нетронутой.

Это операция на уровне пикселей, которая технически может быть оптимизирована путем добавления, вычитания, побитового_и и т. Д. На уровне изображения, но я обнаружил, что при выполнении этой манипуляции результат был скомпрометирован. Результат, сгенерированный с помощью старого подхода, основанного на циклах, для выбора максимального пикселя из соответствующих входных пикселей, работал лучше.

## get maximum of 2 images
for ch in tqdm(range(3)):
    for x in range(0, h):
        for y in range(0, w):
            final_out[x, y, ch] = max(out[x,y, ch], i1_mask[x,y, ch])

См. Вывод ниже:

Хотя этот пост был всего лишь небольшим руководством, вы можете сослаться на весь код, доступный здесь.