Поиск ключевых точек с помощью детектора 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 для поиска ключевых точек на двух изображениях и их сопоставления. На основе вычисленной гомографии изображения будут выровнены и объединены, в результате чего будет построена панорама, демонстрирующая процесс совмещения двух снимков в единое изображение.
Подключаем необходимые пакеты для работы с изображениями¶
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");
using Pkg, Images, ImageFeatures, ImageDraw
using ImageProjectiveGeometry
using LinearAlgebra, OffsetArrays
using ImageTransformations, StaticArrays
Загрузка изображений¶
Указываем путь к изображениям, загружаем и отображаем их:
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)
Применение ORB¶
Инициалдизируем дискриптор
orb_params = ORB(num_keypoints = 1000);
Определим функцию, которая использует дискриптор для поиска ключевых точек на изображении
function find_features(img::AbstractArray, orb_params)
desc, ret_features = create_descriptor(Gray.(img), orb_params)
end;
Определим функцию, которая выполняет поиск общих ключевых точек на двух изображениях. Сначала вычисляются ключевые точки и дескрипторы для каждого изображения, затем выполняется сопоставление дескрипторов с использованием заданного порога.
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;
Найдем совпадающие ключевые точки
matches = match_points(Image_1, Image_2, orb_params, 0.35);
Определим функцию, которая визуализирует связь ключевых точек. Функция pad_display
размещает изображения рядом, горизонтально на одном холсте. draw_matches
отрисовывает линии, которые соединяет общие ключевые точки
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;
Посмторим, что имеем на выходе
draw_matches(Image_1, Image_2, matches)
Построение панорамы¶
Для вычисления гомографии (геометрической трансформации) требуются координаты точек, которые соответствуют друг другу на двух изображениях.
Здесь из списка сопоставленных ключевых точек matches
извлекаются координаты точек для двух изображений. x1
— это координаты из первого изображения, x2
— из второго.
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
.
t = 0.01
# Вычисление гомографии с помощью RANSAC
H, inliers = ransacfithomography(x1, x2, t);
t
- это порог, который определяет, насколько точки могут отличаться после применения гомографии, чтобы их считать "правильными" (инлайерами).
Структура Homography
для представления матрицы гомографии. Она содержит матрицу размера 3x3, которая определяет преобразование между двумя изображениями.
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
.
new_img = warp(Image_1, Homography(H))
Функция объединяет два изображения — исходное и трансформированное — в один общий холст, где новое изображение добавляется поверх старого.
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
panorama = merge_images(Image_1, new_img)
Выводы¶
В примере было продемонстрировано использование детектора ORB для автоматического нахождения ключевых точек и их сопоставления на двух изображениях. Построенная гомография позволила выровнять изображения, а их объединение дало возможность создать панораму.