A series of convenience functions to make basic image processing functions such as translation, rotation, resizing, skeletonization, displaying Matplotlib images, sorting contours, detecting edges, and much more easier with OpenCV and both Python 2.7 and Python 3.
91
OpenCV-compatible feature detectors and descriptors with cross-version compatibility. These utilities provide unified interfaces for creating feature detectors, extractors, and matchers that work across different OpenCV versions.
Factory functions that provide unified interfaces for creating feature detectors, extractors, and matchers with cross-version compatibility.
def FeatureDetector_create(detector, *args, **kwargs):
"""
Create feature detector with cross-version compatibility.
Args:
detector (str): Type of detector to create
*args: Positional arguments for detector
**kwargs: Keyword arguments for detector
Returns:
Detector object with detect() method
Supported detectors:
BRISK, DENSE, FAST, GFTT, HARRIS, MSER, ORB, SIFT, SURF, STAR
"""
def DescriptorExtractor_create(extractor, *args, **kwargs):
"""
Create descriptor extractor with cross-version compatibility.
Args:
extractor (str): Type of extractor to create
*args: Positional arguments for extractor
**kwargs: Keyword arguments for extractor
Returns:
Extractor object with compute() method
Supported extractors:
SIFT, ROOTSIFT, SURF, BRIEF, ORB, BRISK, FREAK
"""
def DescriptorMatcher_create(matcher):
"""
Create descriptor matcher with cross-version compatibility.
Args:
matcher (str): Type of matcher to create
Returns:
Matcher object
Supported matchers:
BruteForce, BruteForce-SL2, BruteForce-L1, BruteForce-Hamming, FlannBased
"""Usage Example:
import cv2
from imutils.feature import FeatureDetector_create, DescriptorExtractor_create, DescriptorMatcher_create
# Load images
image1 = cv2.imread("image1.jpg", cv2.IMREAD_GRAYSCALE)
image2 = cv2.imread("image2.jpg", cv2.IMREAD_GRAYSCALE)
# Create detector and extractor
detector = FeatureDetector_create("SIFT")
extractor = DescriptorExtractor_create("SIFT")
# Detect keypoints
kp1 = detector.detect(image1)
kp2 = detector.detect(image2)
# Compute descriptors
kp1, desc1 = extractor.compute(image1, kp1)
kp2, desc2 = extractor.compute(image2, kp2)
# Create matcher and find matches
matcher = DescriptorMatcher_create("BruteForce")
matches = matcher.match(desc1, desc2)
# Draw matches
output = cv2.drawMatches(image1, kp1, image2, kp2, matches, None)
cv2.imshow("Matches", output)
cv2.waitKey(0)
cv2.destroyAllWindows()Custom implementations of feature detectors that maintain consistent APIs across OpenCV versions.
class DENSE:
def __init__(self, step=6, radius=0.5):
"""
Dense keypoint detector that samples keypoints on a grid.
Args:
step (int): Step size for grid sampling (default: 6)
radius (float): Keypoint radius (default: 0.5)
"""
def detect(self, img):
"""
Detect dense keypoints on a grid.
Args:
img (np.ndarray): Input image
Returns:
list: List of cv2.KeyPoint objects
"""
def setInt(self, var, val):
"""
Set integer parameters.
Args:
var (str): Parameter name ("initXyStep")
val (int): Parameter value
"""class GFTT:
def __init__(self, maxCorners=0, qualityLevel=0.01, minDistance=1, mask=None,
blockSize=3, useHarrisDetector=False, k=0.04):
"""
Good Features To Track detector.
Args:
maxCorners (int): Maximum number of corners (0 = no limit) (default: 0)
qualityLevel (float): Quality level parameter (default: 0.01)
minDistance (int): Minimum distance between corners (default: 1)
mask (np.ndarray, optional): Mask image
blockSize (int): Size of averaging block (default: 3)
useHarrisDetector (bool): Use Harris detector (default: False)
k (float): Harris detector free parameter (default: 0.04)
"""
def detect(self, img):
"""
Detect good features to track.
Args:
img (np.ndarray): Input image
Returns:
list: List of cv2.KeyPoint objects
"""class HARRIS:
def __init__(self, blockSize=2, apertureSize=3, k=0.1, T=0.02):
"""
Harris corner detector.
Args:
blockSize (int): Size of neighborhood (default: 2)
apertureSize (int): Aperture parameter for Sobel operator (default: 3)
k (float): Harris detector free parameter (default: 0.1)
T (float): Threshold for corner detection (default: 0.02)
"""
def detect(self, img):
"""
Detect Harris corners.
Args:
img (np.ndarray): Input image
Returns:
list: List of cv2.KeyPoint objects with 3-pixel radius
"""Usage Example:
import cv2
from imutils.feature import DENSE, GFTT, HARRIS
image = cv2.imread("example.jpg", cv2.IMREAD_GRAYSCALE)
# Dense detector
dense = DENSE(step=10, radius=3)
dense_kp = dense.detect(image)
# GFTT detector
gftt = GFTT(maxCorners=100, qualityLevel=0.01, minDistance=10)
gftt_kp = gftt.detect(image)
# Harris detector
harris = HARRIS(blockSize=2, k=0.04, T=0.01)
harris_kp = harris.detect(image)
# Visualize keypoints
dense_img = cv2.drawKeypoints(image, dense_kp, None, color=(0, 255, 0))
gftt_img = cv2.drawKeypoints(image, gftt_kp, None, color=(255, 0, 0))
harris_img = cv2.drawKeypoints(image, harris_kp, None, color=(0, 0, 255))
cv2.imshow("Dense", dense_img)
cv2.imshow("GFTT", gftt_img)
cv2.imshow("Harris", harris_img)
cv2.waitKey(0)
cv2.destroyAllWindows()Enhanced SIFT descriptor with L1 normalization and square root operation for improved matching performance.
class RootSIFT:
def __init__(self):
"""
RootSIFT descriptor extractor.
Automatically initializes SIFT extractor based on OpenCV version.
Applies L1 normalization followed by square root operation.
"""
def compute(self, image, kps, eps=1e-7):
"""
Compute RootSIFT descriptors.
Args:
image (np.ndarray): Input image
kps (list): List of keypoints
eps (float): Small epsilon value for numerical stability (default: 1e-7)
Returns:
tuple: (keypoints, descriptors) where descriptors are RootSIFT-normalized
Note:
RootSIFT normalization: L1 normalize -> square root -> L2 normalize
This provides better matching performance than standard SIFT.
"""Usage Example:
import cv2
from imutils.feature import RootSIFT, FeatureDetector_create
# Load images
image1 = cv2.imread("image1.jpg", cv2.IMREAD_GRAYSCALE)
image2 = cv2.imread("image2.jpg", cv2.IMREAD_GRAYSCALE)
# Create detector and RootSIFT extractor
detector = FeatureDetector_create("SIFT")
rootsift = RootSIFT()
# Detect keypoints
kp1 = detector.detect(image1)
kp2 = detector.detect(image2)
# Compute RootSIFT descriptors
kp1, desc1 = rootsift.compute(image1, kp1)
kp2, desc2 = rootsift.compute(image2, kp2)
# Match descriptors
bf = cv2.BFMatcher()
matches = bf.knnMatch(desc1, desc2, k=2)
# Apply ratio test
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append([m])
# Draw matches
output = cv2.drawMatchesKnn(image1, kp1, image2, kp2, good_matches, None, flags=2)
cv2.imshow("RootSIFT Matches", output)
cv2.waitKey(0)
cv2.destroyAllWindows()def corners_to_keypoints(corners):
"""
Convert corners from cv2.goodFeaturesToTrack to cv2.KeyPoint objects.
Args:
corners (np.ndarray): Corners array from cv2.goodFeaturesToTrack
Returns:
list: List of cv2.KeyPoint objects
"""Usage Example:
import cv2
from imutils.feature import corners_to_keypoints
image = cv2.imread("example.jpg", cv2.IMREAD_GRAYSCALE)
# Use OpenCV's goodFeaturesToTrack
corners = cv2.goodFeaturesToTrack(image, maxCorners=100, qualityLevel=0.01, minDistance=10)
# Convert to KeyPoint objects
keypoints = corners_to_keypoints(corners)
# Draw keypoints
output = cv2.drawKeypoints(image, keypoints, None, color=(0, 255, 0))
cv2.imshow("Keypoints from Corners", output)
cv2.waitKey(0)
cv2.destroyAllWindows()Here's a comprehensive example demonstrating feature detection, description, and matching:
import cv2
import numpy as np
from imutils.feature import (FeatureDetector_create, DescriptorExtractor_create,
DescriptorMatcher_create, RootSIFT)
def match_features(image1_path, image2_path, detector_type="SIFT", extractor_type="ROOTSIFT"):
# Load images
img1 = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE)
# Create detector
detector = FeatureDetector_create(detector_type)
# Create extractor
if extractor_type == "ROOTSIFT":
extractor = RootSIFT()
else:
extractor = DescriptorExtractor_create(extractor_type)
# Detect keypoints
kp1 = detector.detect(img1)
kp2 = detector.detect(img2)
print(f"Image 1: {len(kp1)} keypoints")
print(f"Image 2: {len(kp2)} keypoints")
# Compute descriptors
if extractor_type == "ROOTSIFT":
kp1, desc1 = extractor.compute(img1, kp1)
kp2, desc2 = extractor.compute(img2, kp2)
else:
desc1 = extractor.compute(img1, kp1)[1]
desc2 = extractor.compute(img2, kp2)[1]
# Skip if no descriptors found
if desc1 is None or desc2 is None:
print("No descriptors found")
return
# Create matcher
if extractor_type in ["ORB", "BRISK"]:
matcher = DescriptorMatcher_create("BruteForce-Hamming")
else:
matcher = DescriptorMatcher_create("BruteForce")
# Match descriptors
matches = matcher.knnMatch(desc1, desc2, k=2)
# Apply ratio test (Lowe's ratio test)
good_matches = []
for match_pair in matches:
if len(match_pair) == 2:
m, n = match_pair
if m.distance < 0.75 * n.distance:
good_matches.append(m)
print(f"Good matches: {len(good_matches)}")
# Draw matches
output = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# Resize for display if too large
if output.shape[1] > 1200:
scale = 1200 / output.shape[1]
new_width = int(output.shape[1] * scale)
new_height = int(output.shape[0] * scale)
output = cv2.resize(output, (new_width, new_height))
cv2.imshow(f"{detector_type}-{extractor_type} Matches", output)
cv2.waitKey(0)
cv2.destroyAllWindows()
return good_matches
def compare_detectors(image1_path, image2_path):
"""Compare different detector/extractor combinations."""
combinations = [
("SIFT", "SIFT"),
("SIFT", "ROOTSIFT"),
("ORB", "ORB"),
("GFTT", "SIFT"),
("HARRIS", "SIFT")
]
results = {}
for detector, extractor in combinations:
try:
print(f"\nTesting {detector}-{extractor}...")
matches = match_features(image1_path, image2_path, detector, extractor)
results[f"{detector}-{extractor}"] = len(matches) if matches else 0
except Exception as e:
print(f"Error with {detector}-{extractor}: {e}")
results[f"{detector}-{extractor}"] = 0
# Print comparison results
print("\nComparison Results:")
print("-" * 30)
for combo, count in sorted(results.items(), key=lambda x: x[1], reverse=True):
print(f"{combo:15}: {count:3} matches")
return results
# Usage
if __name__ == "__main__":
# Match features between two images
matches = match_features("image1.jpg", "image2.jpg", "SIFT", "ROOTSIFT")
# Compare different detector/extractor combinations
comparison_results = compare_detectors("image1.jpg", "image2.jpg")Example of robust feature matching with RANSAC homography estimation:
import cv2
import numpy as np
from imutils.feature import RootSIFT, FeatureDetector_create
def robust_feature_matching(image1_path, image2_path, min_matches=10):
# Load images
img1 = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE)
# Initialize detector and RootSIFT
detector = FeatureDetector_create("SIFT")
rootsift = RootSIFT()
# Detect and compute
kp1 = detector.detect(img1)
kp2 = detector.detect(img2)
kp1, desc1 = rootsift.compute(img1, kp1)
kp2, desc2 = rootsift.compute(img2, kp2)
# Match descriptors
bf = cv2.BFMatcher()
matches = bf.knnMatch(desc1, desc2, k=2)
# Apply ratio test
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
if len(good_matches) < min_matches:
print(f"Not enough matches found: {len(good_matches)}/{min_matches}")
return None
# Extract matched points
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
# Find homography with RANSAC
homography, mask = cv2.findHomography(src_pts, dst_pts,
cv2.RANSAC, 5.0)
# Count inliers
inliers = mask.ravel().tolist()
inlier_matches = [good_matches[i] for i in range(len(good_matches)) if inliers[i]]
print(f"Total matches: {len(good_matches)}")
print(f"Inlier matches: {len(inlier_matches)}")
# Draw matches
draw_params = dict(matchColor=(0, 255, 0),
singlePointColor=None,
matchesMask=inliers,
flags=2)
output = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, **draw_params)
# Draw bounding box of detected object in second image
if homography is not None:
h, w = img1.shape
corners = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2)
transformed_corners = cv2.perspectiveTransform(corners, homography)
# Draw the bounding box
img2_color = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
cv2.polylines(img2_color, [np.int32(transformed_corners)], True, (0, 255, 0), 3)
cv2.imshow("Detected Object", img2_color)
cv2.imshow("Robust Feature Matching", output)
cv2.waitKey(0)
cv2.destroyAllWindows()
return homography
# Usage
homography = robust_feature_matching("template.jpg", "scene.jpg", min_matches=15)Install with Tessl CLI
npx tessl i tessl/pypi-imutilsevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10