Документация Engee
Notebook

Поиск ключевых точек с помощью детектора ORB

Детектор ORB используется для стабилизации видео, создания панорам, обнаружения объектов, 3D-реконструкции, оценки движения камеры, выравнивания изображений и локализации для дополненной реальности. Он также применяется для распознавания местоположений, таких как здания или достопримечательности, через базу данных ключевых точек.

Теоритическая часть

ORB (Oriented FAST and Rotated BRIEF) — это быстрый детектор и дескриптор ключевых точек. Он объединяет алгоритмы FAST (для детекции ключевых точек) и BRIEF (для их описания).

Алгоритм FAST (Features from Accelerated Segment Test) используется для поиска углов на изображении. Для отбора наиболее значимых точек применяется мера Харриса (Harris Corner Measure), что позволяет уменьшить количество слабых ключевых точек. Каждой ключевой точке вычисляется ориентация. Это делает ORB инвариантным к поворотам изображения.

Алгоритм BRIEF (Binary Robust Independent Elementary Features) создает бинарные дескрипторы. Для сравнения точек между изображениями ORB использует попарное сравнение бинарных дескрипторов с метрикой Хэмминга.

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

Подключаем необходимые пакеты для работы с изображениями

In [ ]:
import Pkg; 
Pkg.add("Images")
Pkg.add("ImageFeatures")
Pkg.add("ImageDraw")
Pkg.add("ImageProjectiveGeometry")
Pkg.add("LinearAlgebra")
Pkg.add("OffsetArrays")
Pkg.add("ImageTransformations")
Pkg.add("StaticArrays");
In [ ]:
using Pkg, Images, ImageFeatures, ImageDraw
using ImageProjectiveGeometry
using LinearAlgebra, OffsetArrays
using ImageTransformations, StaticArrays

Загрузка изображений

Указываем путь к изображениям, загружаем и отображаем их:

In [ ]:
path_to_img_1 = "$(@__DIR__)/1.jpg"   
path_to_img_2 = "$(@__DIR__)/2.jpg"   

Image_1 = load(path_to_img_1)
Image_2 = load(path_to_img_2)
Out[0]:
No description has been provided for this image

Применение ORB

Инициалдизируем дискриптор

In [ ]:
orb_params = ORB(num_keypoints = 1000);

Определим функцию, которая использует дискриптор для поиска ключевых точек на изображении

In [ ]:
function find_features(img::AbstractArray, orb_params)
    desc, ret_features = create_descriptor(Gray.(img), orb_params)
end;

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

In [ ]:
function match_points(img1::AbstractArray, img2::AbstractArray, orb_params, threshold::Float64=0.1)
    desc_1, ret_features_1 = find_features(img1, orb_params)
    desc_2, ret_features_2 = find_features(img2, orb_params)
    matches = match_keypoints(ret_features_1, ret_features_2, desc_1, desc_2, threshold);
end;

Найдем совпадающие ключевые точки

In [ ]:
matches = match_points(Image_1, Image_2, orb_params, 0.35);

Определим функцию, которая визуализирует связь ключевых точек. Функция pad_display размещает изображения рядом, горизонтально на одном холсте. draw_matches отрисовывает линии, которые соединяет общие ключевые точки

In [ ]:
function pad_display(img1, img2)
    img1h = length(axes(img1, 1))
    img2h = length(axes(img2, 1))
    mx = max(img1h, img2h);

    hcat(vcat(img1, zeros(RGB{Float64},
                max(0, mx - img1h), length(axes(img1, 2)))),
        vcat(img2, zeros(RGB{Float64},
                max(0, mx - img2h), length(axes(img2, 2)))))
end

function draw_matches(img1, img2, matches)
    grid = pad_display(parent(img1), parent(img2));
    offset = CartesianIndex(0, size(img1, 2));
    for m in matches
        draw!(grid, LineSegment(m[1], m[2] + offset))
    end
    grid
end;

Посмторим, что имеем на выходе

In [ ]:
draw_matches(Image_1, Image_2, matches)
Out[0]:
No description has been provided for this image

Построение панорамы

Для вычисления гомографии (геометрической трансформации) требуются координаты точек, которые соответствуют друг другу на двух изображениях.

Здесь из списка сопоставленных ключевых точек matches извлекаются координаты точек для двух изображений. x1 — это координаты из первого изображения, x2 — из второго.

In [ ]:
x1 = hcat([Float64[m[1].I[1], m[1].I[2]] for m in matches]...)  # 2xN
x2 = hcat([Float64[m[2].I[1], m[2].I[2]] for m in matches]...);  # 2xN

Здесь используется метод RANSAC для поиска матрицы гомографии H, которая максимально точно выравнивает две группы точек x1 и x2.

In [ ]:
t = 0.01 

# Вычисление гомографии с помощью RANSAC
H, inliers = ransacfithomography(x1, x2, t);

t - это порог, который определяет, насколько точки могут отличаться после применения гомографии, чтобы их считать "правильными" (инлайерами).

Структура Homography для представления матрицы гомографии. Она содержит матрицу размера 3x3, которая определяет преобразование между двумя изображениями.

In [ ]:
struct Homography{T}
    m::SMatrix{3, 3, T, 9}
end

Homography(m::AbstractMatrix{T}) where {T} = Homography{T}(SMatrix{3, 3, T, 9}(m))
function (trans::Homography{M})(x::SVector{3}) where M
    out = trans.m * x
    out = out / out[end]
    SVector{2}(out[1:2])
end
function (trans::Homography{M})(x::SVector{2}) where M
    trans(SVector{3}([x[1], x[2], 1.0]))
end
function (trans::Homography{M})(x::CartesianIndex{2}) where M
    trans(SVector{3}([collect(x.I)..., 1]))
end
function (trans::Homography{M})(x::Tuple{Int, Int}) where M
    trans(CartesianIndex(x))
end
function (trans::Homography{M})(x::Array{CartesianIndex{2}, 1}) where M
    CartesianIndex{2}.([tuple(y...) for y in trunc.(Int, collect.(trans.(x)))])
end
function Base.inv(trans::Homography{M}) where M
    i = inv(trans.m)
    Homography(i ./ i[end])
end

Используется функция warp, чтобы применить матрицу гомографии H к изображению Image_1, создавая трансформированное изображение new_img.

In [ ]:
new_img = warp(Image_1, Homography(H))
Out[0]:
No description has been provided for this image

Функция объединяет два изображения — исходное и трансформированное — в один общий холст, где новое изображение добавляется поверх старого.

In [ ]:
function merge_images(img1, new_img)
    # Вычисляем размеры холста
    axis1_size = max(last(axes(new_img, 1)), size(img1, 1)) - min(first(axes(new_img, 1)), 1) + 1
    axis2_size = max(last(axes(new_img, 2)), size(img1, 2)) - min(first(axes(new_img, 2)), 1) + 1

    # Создаем OffsetArray для объединённого изображения
    combined_image = OffsetArray(
        zeros(RGB{N0f8}, axis1_size, axis2_size), (
            min(0, first(axes(new_img, 1))),
            min(0, first(axes(new_img, 2)))))

    # Копируем первое изображение в общий холст
    combined_image[1:size(img1, 1), 1:size(img1, 2)] .= img1

    # Копируем второе изображение в общий холст
    for i in axes(new_img, 1)
        for j in axes(new_img, 2)
            if new_img[i, j] != colorant"black"  # Пропускаем чёрные пиксели
                combined_image[i, j] = new_img[i, j]
            end
        end
    end

    combined_image
end
Out[0]:
merge_images (generic function with 1 method)
In [ ]:
panorama = merge_images(Image_1, new_img)
Out[0]:
No description has been provided for this image

Выводы

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