or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-processing.mdface-analysis.mdfeature-detection.mdindex.mdutilities.mdvideo-processing.md

feature-detection.mddocs/

0

# Feature Detection

1

2

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.

3

4

## Capabilities

5

6

### Factory Functions

7

8

Factory functions that provide unified interfaces for creating feature detectors, extractors, and matchers with cross-version compatibility.

9

10

```python { .api }

11

def FeatureDetector_create(detector, *args, **kwargs):

12

"""

13

Create feature detector with cross-version compatibility.

14

15

Args:

16

detector (str): Type of detector to create

17

*args: Positional arguments for detector

18

**kwargs: Keyword arguments for detector

19

20

Returns:

21

Detector object with detect() method

22

23

Supported detectors:

24

BRISK, DENSE, FAST, GFTT, HARRIS, MSER, ORB, SIFT, SURF, STAR

25

"""

26

27

def DescriptorExtractor_create(extractor, *args, **kwargs):

28

"""

29

Create descriptor extractor with cross-version compatibility.

30

31

Args:

32

extractor (str): Type of extractor to create

33

*args: Positional arguments for extractor

34

**kwargs: Keyword arguments for extractor

35

36

Returns:

37

Extractor object with compute() method

38

39

Supported extractors:

40

SIFT, ROOTSIFT, SURF, BRIEF, ORB, BRISK, FREAK

41

"""

42

43

def DescriptorMatcher_create(matcher):

44

"""

45

Create descriptor matcher with cross-version compatibility.

46

47

Args:

48

matcher (str): Type of matcher to create

49

50

Returns:

51

Matcher object

52

53

Supported matchers:

54

BruteForce, BruteForce-SL2, BruteForce-L1, BruteForce-Hamming, FlannBased

55

"""

56

```

57

58

**Usage Example:**

59

```python

60

import cv2

61

from imutils.feature import FeatureDetector_create, DescriptorExtractor_create, DescriptorMatcher_create

62

63

# Load images

64

image1 = cv2.imread("image1.jpg", cv2.IMREAD_GRAYSCALE)

65

image2 = cv2.imread("image2.jpg", cv2.IMREAD_GRAYSCALE)

66

67

# Create detector and extractor

68

detector = FeatureDetector_create("SIFT")

69

extractor = DescriptorExtractor_create("SIFT")

70

71

# Detect keypoints

72

kp1 = detector.detect(image1)

73

kp2 = detector.detect(image2)

74

75

# Compute descriptors

76

kp1, desc1 = extractor.compute(image1, kp1)

77

kp2, desc2 = extractor.compute(image2, kp2)

78

79

# Create matcher and find matches

80

matcher = DescriptorMatcher_create("BruteForce")

81

matches = matcher.match(desc1, desc2)

82

83

# Draw matches

84

output = cv2.drawMatches(image1, kp1, image2, kp2, matches, None)

85

cv2.imshow("Matches", output)

86

cv2.waitKey(0)

87

cv2.destroyAllWindows()

88

```

89

90

### Custom Feature Detectors

91

92

Custom implementations of feature detectors that maintain consistent APIs across OpenCV versions.

93

94

#### Dense Feature Detector

95

96

```python { .api }

97

class DENSE:

98

def __init__(self, step=6, radius=0.5):

99

"""

100

Dense keypoint detector that samples keypoints on a grid.

101

102

Args:

103

step (int): Step size for grid sampling (default: 6)

104

radius (float): Keypoint radius (default: 0.5)

105

"""

106

107

def detect(self, img):

108

"""

109

Detect dense keypoints on a grid.

110

111

Args:

112

img (np.ndarray): Input image

113

114

Returns:

115

list: List of cv2.KeyPoint objects

116

"""

117

118

def setInt(self, var, val):

119

"""

120

Set integer parameters.

121

122

Args:

123

var (str): Parameter name ("initXyStep")

124

val (int): Parameter value

125

"""

126

```

127

128

#### Good Features To Track Detector

129

130

```python { .api }

131

class GFTT:

132

def __init__(self, maxCorners=0, qualityLevel=0.01, minDistance=1, mask=None,

133

blockSize=3, useHarrisDetector=False, k=0.04):

134

"""

135

Good Features To Track detector.

136

137

Args:

138

maxCorners (int): Maximum number of corners (0 = no limit) (default: 0)

139

qualityLevel (float): Quality level parameter (default: 0.01)

140

minDistance (int): Minimum distance between corners (default: 1)

141

mask (np.ndarray, optional): Mask image

142

blockSize (int): Size of averaging block (default: 3)

143

useHarrisDetector (bool): Use Harris detector (default: False)

144

k (float): Harris detector free parameter (default: 0.04)

145

"""

146

147

def detect(self, img):

148

"""

149

Detect good features to track.

150

151

Args:

152

img (np.ndarray): Input image

153

154

Returns:

155

list: List of cv2.KeyPoint objects

156

"""

157

```

158

159

#### Harris Corner Detector

160

161

```python { .api }

162

class HARRIS:

163

def __init__(self, blockSize=2, apertureSize=3, k=0.1, T=0.02):

164

"""

165

Harris corner detector.

166

167

Args:

168

blockSize (int): Size of neighborhood (default: 2)

169

apertureSize (int): Aperture parameter for Sobel operator (default: 3)

170

k (float): Harris detector free parameter (default: 0.1)

171

T (float): Threshold for corner detection (default: 0.02)

172

"""

173

174

def detect(self, img):

175

"""

176

Detect Harris corners.

177

178

Args:

179

img (np.ndarray): Input image

180

181

Returns:

182

list: List of cv2.KeyPoint objects with 3-pixel radius

183

"""

184

```

185

186

**Usage Example:**

187

```python

188

import cv2

189

from imutils.feature import DENSE, GFTT, HARRIS

190

191

image = cv2.imread("example.jpg", cv2.IMREAD_GRAYSCALE)

192

193

# Dense detector

194

dense = DENSE(step=10, radius=3)

195

dense_kp = dense.detect(image)

196

197

# GFTT detector

198

gftt = GFTT(maxCorners=100, qualityLevel=0.01, minDistance=10)

199

gftt_kp = gftt.detect(image)

200

201

# Harris detector

202

harris = HARRIS(blockSize=2, k=0.04, T=0.01)

203

harris_kp = harris.detect(image)

204

205

# Visualize keypoints

206

dense_img = cv2.drawKeypoints(image, dense_kp, None, color=(0, 255, 0))

207

gftt_img = cv2.drawKeypoints(image, gftt_kp, None, color=(255, 0, 0))

208

harris_img = cv2.drawKeypoints(image, harris_kp, None, color=(0, 0, 255))

209

210

cv2.imshow("Dense", dense_img)

211

cv2.imshow("GFTT", gftt_img)

212

cv2.imshow("Harris", harris_img)

213

cv2.waitKey(0)

214

cv2.destroyAllWindows()

215

```

216

217

### Enhanced Descriptors

218

219

#### RootSIFT Descriptor

220

221

Enhanced SIFT descriptor with L1 normalization and square root operation for improved matching performance.

222

223

```python { .api }

224

class RootSIFT:

225

def __init__(self):

226

"""

227

RootSIFT descriptor extractor.

228

229

Automatically initializes SIFT extractor based on OpenCV version.

230

Applies L1 normalization followed by square root operation.

231

"""

232

233

def compute(self, image, kps, eps=1e-7):

234

"""

235

Compute RootSIFT descriptors.

236

237

Args:

238

image (np.ndarray): Input image

239

kps (list): List of keypoints

240

eps (float): Small epsilon value for numerical stability (default: 1e-7)

241

242

Returns:

243

tuple: (keypoints, descriptors) where descriptors are RootSIFT-normalized

244

245

Note:

246

RootSIFT normalization: L1 normalize -> square root -> L2 normalize

247

This provides better matching performance than standard SIFT.

248

"""

249

```

250

251

**Usage Example:**

252

```python

253

import cv2

254

from imutils.feature import RootSIFT, FeatureDetector_create

255

256

# Load images

257

image1 = cv2.imread("image1.jpg", cv2.IMREAD_GRAYSCALE)

258

image2 = cv2.imread("image2.jpg", cv2.IMREAD_GRAYSCALE)

259

260

# Create detector and RootSIFT extractor

261

detector = FeatureDetector_create("SIFT")

262

rootsift = RootSIFT()

263

264

# Detect keypoints

265

kp1 = detector.detect(image1)

266

kp2 = detector.detect(image2)

267

268

# Compute RootSIFT descriptors

269

kp1, desc1 = rootsift.compute(image1, kp1)

270

kp2, desc2 = rootsift.compute(image2, kp2)

271

272

# Match descriptors

273

bf = cv2.BFMatcher()

274

matches = bf.knnMatch(desc1, desc2, k=2)

275

276

# Apply ratio test

277

good_matches = []

278

for m, n in matches:

279

if m.distance < 0.75 * n.distance:

280

good_matches.append([m])

281

282

# Draw matches

283

output = cv2.drawMatchesKnn(image1, kp1, image2, kp2, good_matches, None, flags=2)

284

cv2.imshow("RootSIFT Matches", output)

285

cv2.waitKey(0)

286

cv2.destroyAllWindows()

287

```

288

289

### Helper Utilities

290

291

```python { .api }

292

def corners_to_keypoints(corners):

293

"""

294

Convert corners from cv2.goodFeaturesToTrack to cv2.KeyPoint objects.

295

296

Args:

297

corners (np.ndarray): Corners array from cv2.goodFeaturesToTrack

298

299

Returns:

300

list: List of cv2.KeyPoint objects

301

"""

302

```

303

304

**Usage Example:**

305

```python

306

import cv2

307

from imutils.feature import corners_to_keypoints

308

309

image = cv2.imread("example.jpg", cv2.IMREAD_GRAYSCALE)

310

311

# Use OpenCV's goodFeaturesToTrack

312

corners = cv2.goodFeaturesToTrack(image, maxCorners=100, qualityLevel=0.01, minDistance=10)

313

314

# Convert to KeyPoint objects

315

keypoints = corners_to_keypoints(corners)

316

317

# Draw keypoints

318

output = cv2.drawKeypoints(image, keypoints, None, color=(0, 255, 0))

319

cv2.imshow("Keypoints from Corners", output)

320

cv2.waitKey(0)

321

cv2.destroyAllWindows()

322

```

323

324

### Complete Feature Matching Pipeline

325

326

Here's a comprehensive example demonstrating feature detection, description, and matching:

327

328

```python

329

import cv2

330

import numpy as np

331

from imutils.feature import (FeatureDetector_create, DescriptorExtractor_create,

332

DescriptorMatcher_create, RootSIFT)

333

334

def match_features(image1_path, image2_path, detector_type="SIFT", extractor_type="ROOTSIFT"):

335

# Load images

336

img1 = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE)

337

img2 = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE)

338

339

# Create detector

340

detector = FeatureDetector_create(detector_type)

341

342

# Create extractor

343

if extractor_type == "ROOTSIFT":

344

extractor = RootSIFT()

345

else:

346

extractor = DescriptorExtractor_create(extractor_type)

347

348

# Detect keypoints

349

kp1 = detector.detect(img1)

350

kp2 = detector.detect(img2)

351

352

print(f"Image 1: {len(kp1)} keypoints")

353

print(f"Image 2: {len(kp2)} keypoints")

354

355

# Compute descriptors

356

if extractor_type == "ROOTSIFT":

357

kp1, desc1 = extractor.compute(img1, kp1)

358

kp2, desc2 = extractor.compute(img2, kp2)

359

else:

360

desc1 = extractor.compute(img1, kp1)[1]

361

desc2 = extractor.compute(img2, kp2)[1]

362

363

# Skip if no descriptors found

364

if desc1 is None or desc2 is None:

365

print("No descriptors found")

366

return

367

368

# Create matcher

369

if extractor_type in ["ORB", "BRISK"]:

370

matcher = DescriptorMatcher_create("BruteForce-Hamming")

371

else:

372

matcher = DescriptorMatcher_create("BruteForce")

373

374

# Match descriptors

375

matches = matcher.knnMatch(desc1, desc2, k=2)

376

377

# Apply ratio test (Lowe's ratio test)

378

good_matches = []

379

for match_pair in matches:

380

if len(match_pair) == 2:

381

m, n = match_pair

382

if m.distance < 0.75 * n.distance:

383

good_matches.append(m)

384

385

print(f"Good matches: {len(good_matches)}")

386

387

# Draw matches

388

output = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None,

389

flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

390

391

# Resize for display if too large

392

if output.shape[1] > 1200:

393

scale = 1200 / output.shape[1]

394

new_width = int(output.shape[1] * scale)

395

new_height = int(output.shape[0] * scale)

396

output = cv2.resize(output, (new_width, new_height))

397

398

cv2.imshow(f"{detector_type}-{extractor_type} Matches", output)

399

cv2.waitKey(0)

400

cv2.destroyAllWindows()

401

402

return good_matches

403

404

def compare_detectors(image1_path, image2_path):

405

"""Compare different detector/extractor combinations."""

406

combinations = [

407

("SIFT", "SIFT"),

408

("SIFT", "ROOTSIFT"),

409

("ORB", "ORB"),

410

("GFTT", "SIFT"),

411

("HARRIS", "SIFT")

412

]

413

414

results = {}

415

416

for detector, extractor in combinations:

417

try:

418

print(f"\nTesting {detector}-{extractor}...")

419

matches = match_features(image1_path, image2_path, detector, extractor)

420

results[f"{detector}-{extractor}"] = len(matches) if matches else 0

421

except Exception as e:

422

print(f"Error with {detector}-{extractor}: {e}")

423

results[f"{detector}-{extractor}"] = 0

424

425

# Print comparison results

426

print("\nComparison Results:")

427

print("-" * 30)

428

for combo, count in sorted(results.items(), key=lambda x: x[1], reverse=True):

429

print(f"{combo:15}: {count:3} matches")

430

431

return results

432

433

# Usage

434

if __name__ == "__main__":

435

# Match features between two images

436

matches = match_features("image1.jpg", "image2.jpg", "SIFT", "ROOTSIFT")

437

438

# Compare different detector/extractor combinations

439

comparison_results = compare_detectors("image1.jpg", "image2.jpg")

440

```

441

442

### Advanced Feature Matching with Homography

443

444

Example of robust feature matching with RANSAC homography estimation:

445

446

```python

447

import cv2

448

import numpy as np

449

from imutils.feature import RootSIFT, FeatureDetector_create

450

451

def robust_feature_matching(image1_path, image2_path, min_matches=10):

452

# Load images

453

img1 = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE)

454

img2 = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE)

455

456

# Initialize detector and RootSIFT

457

detector = FeatureDetector_create("SIFT")

458

rootsift = RootSIFT()

459

460

# Detect and compute

461

kp1 = detector.detect(img1)

462

kp2 = detector.detect(img2)

463

kp1, desc1 = rootsift.compute(img1, kp1)

464

kp2, desc2 = rootsift.compute(img2, kp2)

465

466

# Match descriptors

467

bf = cv2.BFMatcher()

468

matches = bf.knnMatch(desc1, desc2, k=2)

469

470

# Apply ratio test

471

good_matches = []

472

for m, n in matches:

473

if m.distance < 0.75 * n.distance:

474

good_matches.append(m)

475

476

if len(good_matches) < min_matches:

477

print(f"Not enough matches found: {len(good_matches)}/{min_matches}")

478

return None

479

480

# Extract matched points

481

src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)

482

dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)

483

484

# Find homography with RANSAC

485

homography, mask = cv2.findHomography(src_pts, dst_pts,

486

cv2.RANSAC, 5.0)

487

488

# Count inliers

489

inliers = mask.ravel().tolist()

490

inlier_matches = [good_matches[i] for i in range(len(good_matches)) if inliers[i]]

491

492

print(f"Total matches: {len(good_matches)}")

493

print(f"Inlier matches: {len(inlier_matches)}")

494

495

# Draw matches

496

draw_params = dict(matchColor=(0, 255, 0),

497

singlePointColor=None,

498

matchesMask=inliers,

499

flags=2)

500

501

output = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, **draw_params)

502

503

# Draw bounding box of detected object in second image

504

if homography is not None:

505

h, w = img1.shape

506

corners = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2)

507

transformed_corners = cv2.perspectiveTransform(corners, homography)

508

509

# Draw the bounding box

510

img2_color = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)

511

cv2.polylines(img2_color, [np.int32(transformed_corners)], True, (0, 255, 0), 3)

512

513

cv2.imshow("Detected Object", img2_color)

514

515

cv2.imshow("Robust Feature Matching", output)

516

cv2.waitKey(0)

517

cv2.destroyAllWindows()

518

519

return homography

520

521

# Usage

522

homography = robust_feature_matching("template.jpg", "scene.jpg", min_matches=15)

523

```