Source code for giant.image_processing.feature_matchers.orb_matcher
from dataclasses import dataclass, field, asdict
from typing import Sequence
import numpy as np
from numpy.typing import NDArray, DTypeLike
import cv2
from giant.image_processing.feature_matchers.keypoint_matcher import KeypointMatcher, KeypointMatcherOptions
from giant.image_processing.feature_matchers.flann_configuration import FLANNIndexAlgorithmType, FLANNIndexLSHParams, \
FLANN_INDEX_PARAM_TYPES
from giant.utilities.options import UserOptions
from giant.utilities.mixin_classes.attribute_equality_comparison import AttributeEqualityComparison
from giant.utilities.mixin_classes.attribute_printing import AttributePrinting
from giant.utilities.mixin_classes.user_option_configured import UserOptionConfigured
@dataclass
class ORBOptions:
"""
Options for creating the ORB instance
"""
n_features: int = 500
"""
The maximum number of features to retain.
"""
scale_factor: float = 1.2
"""
Pyramid decimation ratio, greater than 1.
scaleFactor==2 means the classical pyramid, where each next level has 4x less pixels than the previous,
but such a big scale factor will degrade feature matching scores dramatically. On the other hand, too
close to 1 scale factor will mean that to cover certain scale range you will need more pyramid levels
and so the speed will suffer.
"""
n_levels: int = 8
"""
The number of pyramid levels.
The smallest level will have linear size equal to input_image_linear_size/pow(scaleFactor, n_levels - first_level).
"""
edge_threshold: int = 31
"""
This is size of the border where the features are not detected.
It should roughly match the patchSize parameter.
"""
first_level: int = 0
"""
The level of pyramid to put source image to.
Previous layers are filled with upscaled source image.
"""
wta_k: int = 2
"""
The number of points that produce each element of the oriented BRIEF descriptor.
The default value 2 means the BRIEF where we take a random point pair and compare their brightnesses, so we get 0/1
response. Other possible values are 3 and 4. For example, 3 means that we take 3 random points (of course, those
point coordinates are random, but they are generated from the pre-defined seed, so each element of BRIEF descriptor
is computed deterministically from the pixel rectangle), find point of maximum brightness and output index of the
winner (0, 1 or 2). Such output will occupy 2 bits, and therefore it will need a special variant of Hamming distance,
denoted as NORM_HAMMING2 (2 bits per bin). When WTA_K=4, we take 4 random points to compute each bin (that will also
occupy 2 bits with possible values 0, 1, 2 or 3).
"""
score_type: int = cv2.ORB_HARRIS_SCORE
"""
The default ORB_HARRIS_SCORE means that Harris algorithm is used to rank features (the score is written to
KeyPoint::score and is used to retain best n_features features); FAST_SCORE is alternative value of the parameter that
produces slightly less stable keypoints, but it is a little faster to compute.
"""
patch_size: int = 31
"""
Size of the patch used by the oriented BRIEF descriptor.
Of course, on smaller pyramid layers the perceived image area covered by a feature will be larger
"""
fast_threshold: int = 20
"""
The fast threshold.
"""
[docs]
@dataclass
class ORBKeypointMatcherOptions(KeypointMatcherOptions):
"""
Options for configuring the ORB keypoint matcher
"""
orb_options: ORBOptions = field(default_factory=ORBOptions)
"""
Options used to configure cv2.ORB.create() which configures how keypoints are detected and described
"""
flann_index_algorithm_type: FLANNIndexAlgorithmType = FLANNIndexAlgorithmType.FLANN_INDEX_LSH
"""
What FLANN algorithm to use.
For ORB, FLANN_INDEX_LSH is recommended
"""
flann_algorithm_parameters: FLANN_INDEX_PARAM_TYPES = field(default_factory=FLANNIndexLSHParams)
"""
The parameters to configure the index in FLANN.
This should match the flann_index_algorithm_type although OpenCV will not complain if it doesn't.
"""
[docs]
class OrbKeypointMatcher(KeypointMatcher, ORBKeypointMatcherOptions):
"""
Implementation of KeypointMatcher using ORB for detection and FLANN for matching.
"""
allowed_dtypes: list[DTypeLike] = [np.uint8]
def __init__(self, options: ORBKeypointMatcherOptions | None = None):
"""
Initialize the OrbKeypointMatcher.
:param ratio_threshold: Value used to filter keypoint matches (Lowe's ratio test)
:param detect_kwargs: Dictionary of kwargs for cv2.ORB_create
:param index_params: Dictionary of kwargs for FlannBasedMatcher
:param search_params: Dictionary of kwargs for FlannBasedMatcher.knnMatch
"""
super().__init__(ORBKeypointMatcherOptions, options=options)
self.orb = cv2.ORB.create(nfeatures=self.orb_options.n_features, scaleFactor=self.orb_options.scale_factor,
nlevels=self.orb_options.n_levels, edgeThreshold=self.orb_options.edge_threshold,
firstLevel=self.orb_options.first_level, WTA_K=self.orb_options.wta_k,
scoreType=self.orb_options.score_type, patchSize=self.orb_options.patch_size,
fastThreshold=self.orb_options.fast_threshold)
[docs]
def detect_keypoints(self, image: NDArray) -> tuple[Sequence[cv2.KeyPoint], NDArray]:
"""
Detect keypoint descriptors using ORB.
:param image: The image to search for keypoints
:returns: Tuple of (keypoints, descriptors)
"""
# make sure the image is the right dtype (though it already should be by this point)
image = self._validate_and_convert_image(image)
keypoints, descriptors = self.orb.detectAndCompute(image, None) # type: ignore
return keypoints, descriptors