1 /** @file covdet.c
2 ** @brief Covariant feature detectors - Definition
3 ** @author Karel Lenc
4 ** @author Andrea Vedaldi
5 ** @author Michal Perdoch
6 **/
7
8 /*
9 Copyright (C) 2013-14 Andrea Vedaldi.
10 Copyright (C) 2012 Karel Lenc, Andrea Vedaldi and Michal Perdoch.
11 All rights reserved.
12
13 This file is part of the VLFeat library and is made available under
14 the terms of the BSD license (see the COPYING file).
15 */
16
17 /**
18 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
19 @page covdet Covariant feature detectors
20 @author Karel Lenc
21 @author Andrea Vedaldi
22 @author Michal Perdoch
23 @tableofcontents
24 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
25
26 @ref covdet.h implements a number of covariant feature detectors, based
27 on three cornerness measures (determinant of the Hessian, trace of the Hessian
28 (aka Difference of Gaussians, and Harris). It supprots affine adaptation,
29 orientation estimation, as well as Laplacian scale detection.
30
31 - @subpage covdet-fundamentals
32 - @subpage covdet-principles
33 - @subpage covdet-differential
34 - @subpage covdet-corner-types
35
36 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
37 @section covdet-starting Getting started
38 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
39
40 The ::VlCovDet object implements a number of covariant feature
41 detectors: Difference of Gaussian, Harris, determinant of Hessian.
42 Variant of the basic detectors support scale selection by maximizing
43 the Laplacian measure as well as affine normalization.
44
45 @code
46 // create a detector object
47 VlCovDet * covdet = vl_covdet_new(method) ;
48
49 // set various parameters (optional)
50 vl_covdet_set_first_octave(covdet, -1) ; // start by doubling the image resolution
51 vl_covdet_set_octave_resolution(covdet, octaveResolution) ;
52 vl_covdet_set_peak_threshold(covdet, peakThreshold) ;
53 vl_covdet_set_edge_threshold(covdet, edgeThreshold) ;
54
55 // process the image and run the detector
56 vl_covdet_put_image(covdet, image, numRows, numCols) ;
57 vl_covdet_detect(covdet) ;
58
59 // drop features on the margin (optional)
60 vl_covdet_drop_features_outside (covdet, boundaryMargin) ;
61
62 // compute the affine shape of the features (optional)
63 vl_covdet_extract_affine_shape(covdet) ;
64
65 // compute the orientation of the features (optional)
66 vl_covdet_extract_orientations(covdet) ;
67
68 // get feature frames back
69 vl_size numFeatures = vl_covdet_get_num_features(covdet) ;
70 VlCovDetFeature const * feature = vl_covdet_get_features(covdet) ;
71
72 // get normalized feature appearance patches (optional)
73 vl_size w = 2*patchResolution + 1 ;
74 for (i = 0 ; i < numFeatures ; ++i) {
75 float * patch = malloc(w*w*sizeof(*desc)) ;
76 vl_covdet_extract_patch_for_frame(covdet,
77 patch,
78 patchResolution,
79 patchRelativeExtent,
80 patchRelativeSmoothing,
81 feature[i].frame) ;
82 // do something with patch
83 }
84 @endcode
85
86 This example code:
87
88 - Calls ::vl_covdet_new constructs a new detector object. @ref
89 covdet.h supports a variety of different detectors (see
90 ::VlCovDetMethod).
91 - Optionally calls various functions to set the detector parameters if
92 needed (e.g. ::vl_covdet_set_peak_threshold).
93 - Calls ::vl_covdet_put_image to start processing a new image. It
94 causes the detector to compute the scale space representation of the
95 image, but does not compute the features yet.
96 - Calls ::vl_covdet_detect runs the detector. At this point features are
97 ready to be extracted. However, one or all of the following steps
98 may be executed in order to process the features further.
99 - Optionally calls ::vl_covdet_drop_features_outside to drop features
100 outside the image boundary.
101 - Optionally calls ::vl_covdet_extract_affine_shape to compute the
102 affine shape of features using affine adaptation.
103 - Optionally calls ::vl_covdet_extract_orientations to compute the
104 dominant orientation of features looking for the dominant gradient
105 orientation in patches.
106 - Optionally calls ::vl_covdet_extract_patch_for_frame to extract a
107 normalized feature patch, for example to compute an invariant
108 feature descriptor.
109
110 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
111 @page covdet-fundamentals Covariant detectors fundamentals
112 @tableofcontents
113 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
114
115 This page describes the fundamental concepts required to understand a
116 covariant feature detector, the geometry of covariant features, and
117 the process of feature normalization.
118
119 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
120 @section covdet-covariance Covariant detection
121 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
122
123 The purpose of a *covariant detector* is to extract from an image a
124 set of local features in a manner which is consistent with spatial
125 transformations of the image itself. For instance, a covariant
126 detector that extracts interest points $\bx_1,\dots,\bx_n$ from image
127 $\ell$ extracts the translated points $\bx_1+T,\dots,\bx_n+T$ from the
128 translated image $\ell'(\bx) = \ell(\bx-T)$.
129
130 More in general, consider a image $\ell$ and a transformed version
131 $\ell' = \ell \circ w^{-1}$ of it, as in the following figure:
132
133 @image html covdet.png "Covariant detection of local features."
134
135 The transformation or <em>warp</em> $w : \real^2 \mapsto \real^2$ is a
136 deformation of the image domain which may capture a change of camera
137 viewpoint or similar imaging factor. Examples of warps typically
138 considered are translations, scaling, rotations, and general affine
139 transformations; however, in $w$ could be another type of continuous
140 and invertible transformation.
141
142 Given an image $\ell$, a **detector** selects features $R_1,\dots,R_n$
143 (one such features is shown in the example as a green circle). The
144 detector is said to be **covariant** with the warps $w$ if it extracts
145 the transformed features $w[R_1],\dots, w[R_n]$ from the transformed
146 image $w[\ell]$. Intuitively, this means that the “same
147 features” are extracted in both cases up to the transformation
148 $w$. This property is described more formally in @ref
149 covdet-principles.
150
151 Covariance is a key property of local feature detectors as it allows
152 extracting corresponding features from two or more images, making it
153 possible to match them in a meaningful way.
154
155 The @ref covdet.h module in VLFeat implements an array of feature
156 detection algorithm that have are covariant to different classes of
157 transformations.
158
159 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
160 @section covdet-frame Feature geometry and feature frames
161 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
162
163 As we have seen, local features are subject to image transformations,
164 and they apply a fundamental role in matching and normalizing
165 images. To operates effectively with local features is therefore
166 necessary to understand their geometry.
167
168 The geometry of a local feature is captured by a <b>feature frame</b>
169 $R$. In VLFeat, depending on the specific detector, the frame can be
170 either a point, a disc, an ellipse, an oriented disc, or an oriented
171 ellipse.
172
173 A frame captures both the extent of the local features, useful to know
174 which portions of two images are put in correspondence, as well their
175 shape. The latter can be used to associate to diagnose the
176 transformation that affects a feature and remove it through the
177 process of **normalization**.
178
179 More precisely, in covariant detection feature frames are constructed
180 to be compatible with a certain class of transformations. For example,
181 circles are compatible with similarity transformations as they are
182 closed under them. Likewise, ellipses are compatible with affine
183 transformations.
184
185 Beyond this closure property, the key idea here is that all feature
186 occurrences can be seen as transformed versions of a base or
187 <b>canonical</b> feature. For example, all discs $R$ can be obtained
188 by applying a similarity transformation to the unit disc $\bar R$
189 centered at the origin. $\bar R$ is an example of canonical frame
190 as any other disc can be written as $R = w[\bar R]$ for a suitable
191 similarity $w$.
192
193 @image html frame-canonical.png "The idea of canonical frame and normalization"
194
195 The equation $R = w[\bar R_0]$ matching the canonical and detected
196 feature frames establishes a constraint on the warp $w$, very similar
197 to the way two reference frames in geometry establish a transformation
198 between spaces. The transformation $w$ can be thought as a the
199 **pose** of the detected feature, a generalization of its location.
200
201 In the case of discs and similarity transformations, the equation $R =
202 w[\bar R_0]$ fixes $w$ up to a residual rotation. This can be
203 addressed by considering oriented discs instead. An **oriented disc**
204 is a disc with a radius highlighted to represent the feature
205 orientation.
206
207 While discs are appropriate for similarity transformations, they are
208 not closed under general affine transformations. In this case, one
209 should consider the more general class of (oriented) ellipses. The
210 following image illustrates the five types of feature frames used in
211 VLFeat:
212
213 @image html frame-types.png "Types of local feature frames: points, discs, oriented discs, ellipses, oriented ellipses."
214
215 Note that these frames are described respectively by 2, 3, 4, 5 and 6
216 parameters. The most general type are the oriented ellipses, which can
217 be used to represent all the other frame types as well.
218
219 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
220 @section covdet-frame-transformation Transforming feature frames
221 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
222
223 Consider a warp $w$ mapping image $\ell$ into image $\ell'$ as in the
224 figure below. A feature $R$ in the first image co-variantly transform
225 into a feature $R'=w[R]$ in the second image:
226
227 @image html covdet-normalization.png "Normalization removes the effect of an image deformation."
228
229 The poses $u,u'$ of $R=u[R_0]$ and $R' = u'[R_0]$ are then related by
230 the simple expression:
231
232 \[
233 u' = w \circ u.
234 \]
235
236 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
237 @section covdet-frame-normalization Normalizing feature frames
238 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
239
240 In the example above, the poses $u$ and $u'$ relate the two
241 occurrences $R$ and $R'$ of the feature to its canonical version
242 $R_0$. If the pose $u$ of the feature in image $\ell$ is known, the
243 canonical feature appearance can be computed by un-warping it:
244
245 \[
246 \ell_0 = u^{-1}[\ell] = \ell \circ u.
247 \]
248
249 This process is known as **normalization** and is the key in the
250 computation of invariant feature descriptors as well as in the
251 construction of most co-variant detectors.
252
253 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
254 @page covdet-principles Principles of covariant detection
255 @tableofcontents
256 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
257
258 The goals of a co-variant detector were discussed in @ref
259 covdet-fundamentals. This page introduces a few general principles
260 that are at the basis of most covariant detection algorithms. Consider
261 an input image $\ell$ and a two dimensional continuous and invertible
262 warp $w$. The *warped image* $w[\ell]$ is defined to be
263
264 \[
265 w[\ell] = \ell \circ w^{-1},
266 \]
267
268 or, equivalently,
269
270 \[
271 w[\ell](x,y) = \ell(w^{-1}(x,y)), \qquad \forall (x,y)\in\real^2.
272 \]
273
274 Note that, while $w$ pushes pixels forward, from the original to the
275 transformed image domain, defining the transformed image $\ell'$
276 requires inverting the warp and composing $\ell$ with $w^{-1}$.
277
278 The goal a covariant detector is to extract the same local features
279 irregardless of image transformations. The detector is said to be
280 <b>covariant</b> or <b>equivariant</b> with a class of warps
281 $w\in\mathcal{W}$ if, when the feature $R$ is detected in image
282 $\ell$, then the transformed feature $w[R]$ is detected in the
283 transformed image $w[\ell]$.
284
285 The net effect is that a covariant feature detector appears to
286 “track” image transformations; however, it is important to
287 note that a detector *is not a tracker* because it processes images
288 individually rather than jointly as part of a sequence.
289
290 An intuitive way to construct a covariant feature detector is to
291 extract features in correspondence of images structures that are
292 easily identifiable even after a transformation. Example of specific
293 structures include dots, corners, and blobs. These will be generically
294 indicated as **corners** in the followup.
295
296 A covariant detector faces two challenges. First, corners have, in
297 practice, an infinite variety of individual appearances and the
298 detector must be able to capture them to be of general applicability.
299 Second, the way corners are identified and detected must remain stable
300 under transformations of the image. These two problems are addressed
301 in @ref covdet-cornerness-localmax and @ref
302 covdet-cornerness-normalization respectively.
303
304 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
305 @section covdet-cornerness Detection using a cornerness measure
306 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
307
308 One way to decide whether an image region $R$ contains a corner is to
309 compare the local appearance to a model or template of the corner; the
310 result of this comparisons produces a *cornerness score* at that
311 location. This page describe general theoretical properties of the
312 cornerness and the detection process. Concrete examples of cornerness
313 are given in @ref covdet-corner-types.
314
315 A **cornerness measure** associate a score to all possible feature
316 locations in an image $\ell$. As described in @ref covdet-frame, the
317 location or, more in general, pose $u$ of a feature $R$ is the warp
318 $w$ that maps the canonical feature frame $R_0$ to $R$:
319
320 \[
321 R = u[R_0].
322 \]
323
324 The goal of a cornerness measure is to associate a score $F(u;\ell)$
325 to all possible feature poses $u$ and use this score to extract a
326 finite number of co-variant features from any image.
327
328 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
329 @subsection covdet-cornerness-localmax Local maxima of a cornerness measure
330 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
331
332 Given the cornerness of each candidate feature, the detector must
333 extract a finite number of them. However, the cornerness of features
334 with nearly identical pose must be similar (otherwise the cornerness
335 measure would be unstable). As such, simply thresholding $F(w;\ell)$
336 would detect an infinite number of nearly identical features rather
337 than a finite number.
338
339 The solution is to detect features in correspondence of the local
340 maxima of the score measure:
341
342 \[
343 \{w_1,\dots,w_n\} = \operatorname{localmax}_{w\in\mathcal{W}} F(w;\ell).
344 \]
345
346 This also means that features are never detected in isolation, but by
347 comparing neighborhoods of them.
348
349 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
350 @subsection covdet-cornerness-normalization Covariant detection by normalization
351 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
352
353 The next difficulty is to guarantee that detection is co-variant with
354 image transformations. Hence, if $u$ is the pose of a feature
355 extracted from image $\ell$, then the transformed pose $u' = w[u]$
356 must be detected in the transformed image $\ell' = w[\ell]$.
357
358 Since features are extracted in correspondence of the local maxima of
359 the cornerness score, a sufficient condition is that corresponding
360 features attain the same score in the two images:
361
362 \[
363 \forall u\in\mathcal{W}: \quad F(u;\ell) = F(w[u];w[\ell]),
364 \qquad\text{or}\qquad
365 F(u;\ell) = F(w \circ u ;\ell \circ w^{-1}).
366 \]
367
368 One simple way to satisfy this equation is to compute a cornerness
369 score *after normalizing the image* by the inverse of the candidate
370 feature pose warp $u$, as follows:
371
372 \[
373 F(u;\ell) = F(1;u^{-1}[\ell]) = F(1;\ell \circ u) = \mathcal{F}(\ell \circ u),
374 \]
375
376 where $1 = u^{-1} \circ u$ is the identity transformation and
377 $\mathcal{F}$ is an arbitrary functional. Intuitively, co-variant
378 detection is obtained by looking if the appearance of the feature
379 resembles a corner only *after normalization*. Formally:
380
381 @f{align*}
382 F(w[u];w[\ell])
383 &=
384 F(w \circ u ;\ell \circ w^{-1})
385 \\
386 &=
387 F(1; \ell \circ w^{-1} \circ w \circ u)
388 \\
389 &=
390 \mathcal{F}(\ell\circ u)
391 \\
392 &=
393 F(u;\ell).
394 @f}
395
396 Concrete examples of the functional $\mathcal{F}$ are given in @ref
397 covdet-corner-types.
398
399 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
400 @subsection covdet-locality Locality of the detected features
401 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
402
403 In the definition above, the cornenress functional $\mathcal{F}$ is an
404 arbitrary functional of the entire normalized image $u^{-1}[\ell]$.
405 In practice, one is always interested in detecting **local features**
406 (at the very least because the image extent is finite).
407
408 This is easily obtained by considering a cornerness $\mathcal{F}$
409 which only looks in a small region of the normalized image, usually
410 corresponding to the extent of the canonical feature $R_0$ (e.g. a
411 unit disc centered at the origin).
412
413 In this case the extent of the local feature in the original image is
414 simply given by $R = u[R_0]$.
415
416 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
417 @section covdet-partial Partial and iterated normalization
418 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
419
420 Practical detectors implement variants of the ideas above. Very often,
421 for instance, detection is an iterative process, in which successive
422 parameters of the pose of a feature are determined. For instance, it
423 is typical to first detect the location and scale of a feature using a
424 rotation-invariant cornerness score $\mathcal{F}$. Once these two
425 parameters are known, the rotation can be determined using a different
426 score, sensitive to the orientation of the local image structures.
427
428 Certain detectors (such as Harris-Laplace and Hessian-Laplace) use
429 even more sophisticated schemes, in which different scores are used to
430 jointly (rather than in succession) different parameters of the pose
431 of a feature, such as its translation and scale. While a formal
432 treatment of these cases is possible as well, we point to the original
433 papers.
434
435 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
436 @page covdet-differential Differential and integral image operations
437 @tableofcontents
438 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
439
440 Dealing with covariant interest point detector requires working a good
441 deal with derivatives, convolutions, and transformations of images.
442 The notation and fundamental properties of interest here are discussed
443 next.
444
445 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
446 @section covdet-derivatives Derivative operations: gradients
447 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
448
449 For the derivatives, we borrow the notation of
450 @cite{kinghorn96integrals}. Let $f: \mathbb{R}^m \rightarrow
451 \mathbb{R}^n, \bx \mapsto f(\bx)$ be a vector function. The derivative
452 of the function with respect to $\bx$ is given by its *Jacobian
453 matrix* denoted by the symbol:
454
455 \[
456 \frac{\partial f}{\partial \bx^\top}
457 =
458 \begin{bmatrix}
459 \frac{\partial f_1}{x_1} & \frac{\partial f_1}{x_2} & \dots \\
460 \frac{\partial f_2}{x_1} & \frac{\partial f_2}{x_2} & \dots \\
461 \vdots & \vdots & \ddots \\
462 \end{bmatrix}.
463 \]
464
465 When the function $ f $ is scalar ($n=1$), the Jacobian is the same as
466 the gradient of the function (or, in fact, its transpose). More
467 precisely, the <b>gradient</b> $\nabla f $ of $ f $ denotes the column
468 vector of partial derivatives:
469
470 \[
471 \nabla f
472 = \frac{\partial f}{\partial \bx}
473 =
474 \begin{bmatrix}
475 \frac{\partial f}{\partial x_1} \\
476 \frac{\partial f}{\partial x_2} \\
477 \vdots
478 \end{bmatrix}.
479 \]
480
481 The second derivative $H_f $ of a scalar function $ f $, or
482 <b>Hessian</b>, is denoted as
483
484 \[
485 H_f
486 = \frac{\partial f}{\partial \bx \partial \bx^\top}
487 = \frac{\partial \nabla f}{\partial \bx^\top}
488 =
489 \begin{bmatrix}
490 \frac{\partial f}{\partial x_1 \partial x_1} & \frac{\partial f}{\partial x_1 \partial x_2} & \dots \\
491 \frac{\partial f}{\partial x_2 \partial x_1} & \frac{\partial f}{\partial x_2 \partial x_2} & \dots \\
492 \vdots & \vdots & \ddots \\
493 \end{bmatrix}.
494 \]
495
496 The determinant of the Hessian is also known as <b>Laplacian</b> and denoted as
497
498 \[
499 \Delta f = \operatorname{det} H_f =
500 \frac{\partial f}{\partial x_1^2} +
501 \frac{\partial f}{\partial x_2^2} +
502 \dots
503 \]
504
505 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
506 @subsection covdet-derivative-transformations Derivative and image warps
507 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
508
509 In the following, we will often been interested in domain warpings $u:
510 \mathbb{R}^m \rightarrow \mathbb{R}^n, \bx \mapsto u(\bx)$ of a
511 function $f(\bar\bx) $ and its effect on the derivatives of the
512 function. The key transformation is the chain rule:
513
514 \[
515 \frac{\partial f \circ u}{\partial \bx^\top}
516 =
517 \left(\frac{\partial f}{\partial \bar\bx^\top} \circ u\right)
518 \frac{\partial u}{\partial \bx^\top}
519 \]
520
521 In particular, for an affine transformation $u = (A,T) : \bx \mapsto
522 A\bx + T$, one obtains the transformation rules:
523
524 \[
525 \begin{align*}
526 \frac{\partial f \circ (A,T)}{\partial \bx^\top}
527 &=
528 \left(\frac{\partial f}{\partial \bar\bx^\top} \circ (A,T)\right)A,
529 \\
530 \nabla (f \circ (A,T))
531 &= A^\top (\nabla f) \circ (A,T),
532 \\
533 H_{f \circ(A,T)}
534 &= A^\top (H_f \circ (A,T)) A,
535 \\
536 \Delta (f \circ(A,T))
537 &= \det(A)^2\, (\Delta f) \circ (A,T).
538 \end{align*}
539 \]
540
541 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
542 @section covdet-smoothing Integral operations: smoothing
543 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
544
545 In practice, given an image $\ell$ expressed in digital format, good
546 derivative approximations can be computed only if the bandwidth of the
547 image is limited and, in particular, compatible with the sampling
548 density. Since it is unreasonable to expect real images to be
549 band-limited, the bandwidth is artificially constrained by suitably
550 smoothing the image prior to computing its derivatives. This is also
551 interpreted as a form of regularization or as a way of focusing on the
552 image content at a particular scale.
553
554 Formally, we will focus on Gaussian smoothing kernels. For the 2D case
555 $\bx\in\real^2$, the Gaussian kernel of covariance $\Sigma$ is given
556 by
557
558 \[
559 g_{\Sigma}(\bx) = \frac{1}{2\pi \sqrt{\det\Sigma}}
560 \exp\left(
561 - \frac{1}{2} \bx^\top \Sigma^{-1} \bx
562 \right).
563 \]
564
565 The symbol $g_{\sigma^2}$ will be used to denote a Gaussian kernel
566 with isotropic standard deviation $\sigma$, i.e. $\Sigma = \sigma^2
567 I$. Given an image $\ell$, the symbol $\ell_\Sigma$ will be used to
568 denote the image smoothed by the Gaussian kernel of parameter
569 $\Sigma$:
570
571 \[
572 \ell_\Sigma(\bx) = (g_\Sigma * \ell)(\bx)
573 =
574 \int_{\real^m}
575 g_\Sigma(\bx - \by) \ell(\by)\,d\by.
576 \]
577
578 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
579 @subsection covdet-smoothing-transformations Smoothing and image warps
580 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
581
582 One advantage of Gaussian kernels is that they are (up to
583 renormalization) closed under a linear warp:
584
585 \[
586 |A|\, g_\Sigma \circ A = g_{A^{-1} \Sigma A^{-\top}}
587 \]
588
589 This also means that smoothing a warped image is the same as warping
590 the result of smoothing the original image by a suitably adjusted
591 Gaussian kernel:
592
593 \[
594 g_{\Sigma} * (\ell \circ (A,T))
595 =
596 (g_{A\Sigma A^\top} * \ell) \circ (A,T).
597 \]
598
599 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
600 @page covdet-corner-types Cornerness measures
601 @tableofcontents
602 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
603
604 The goal of a cornerness measure (@ref covdet-cornerness) is to
605 associate to an image patch a score proportional to how strongly the
606 patch contain a certain strucuture, for example a corner or a
607 blob. This page reviews the most important cornerness measures as
608 implemented in VLFeat:
609
610 - @ref covdet-harris
611 - @ref covdet-laplacian
612 - @ref covdet-hessian
613
614 This page makes use of notation introduced in @ref
615 covdet-differential.
616
617 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
618 @section covdet-harris Harris corners
619 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
620
621 This section introduces the fist of the cornerness measure
622 $\mathcal{F}[\ell]$. Recall (@ref covdet-cornerness) that the goal of
623 this functional is to respond strongly to images $\ell$ of corner-like
624 structure.
625
626 Rather than explicitly encoding the appearance of corners, the idea of
627 the Harris measure is to label as corner *any* image patch whose
628 appearance is sufficiently distinctive to allow accurate
629 localization. In particular, consider an image patch $\ell(\bx),
630 \bx\in\Omega$, where $\Omega$ is a smooth circular window of radius
631 approximately $\sigma_i$; at necessary condition for the patch to
632 allow accurate localization is that even a small translation
633 $\ell(\bx+\delta)$ causes the appearance to vary significantly (if not
634 the origin and location $\delta$ would not be distinguishable from the
635 image alone). This variation is measured by the sum of squared
636 differences
637
638 \[
639 E(\delta) = \int g_{\sigma_i^2}(\bx)
640 (\ell_{\sigma_d^2}(\bx+\delta) -
641 \ell_{\sigma_d^2}(\bx))^2 \,d\bx
642 \]
643
644 Note that images are compared at scale $\sigma_d$, known as
645 *differentiation scale* for reasons that will be clear in a moment,
646 and that the squared differences are summed over a window softly
647 defined by $\sigma_i$, also known as *integration scale*. This
648 function can be approximated as $E(\delta)\approx \delta^\top
649 M[\ell;\sigma_i^2,\sigma_d^2] \delta$ where
650
651 \[
652 M[\ell;\sigma_i^2,\sigma_d^2]
653 = \int g_{\sigma_i^2}(\bx)
654 (\nabla \ell_{\sigma_d^2}(\bx))
655 (\nabla \ell_{\sigma_d^2}(\bx))^\top \, d\bx.
656 \]
657
658 is the so called **structure tensor**.
659
660 A corner is identified when the sum of squared differences $E(\delta)$
661 is large for displacements $\delta$ in all directions. This condition
662 is obtained when both the eignenvalues $\lambda_1,\lambda_2$ of the
663 structure tensor $M$ are large. The **Harris cornerness measure**
664 captures this fact:
665
666 \[
667 \operatorname{Harris}[\ell;\sigma_i^2,\sigma_d^2] =
668 \det M - \kappa \operatorname{trace}^2 M =
669 \lambda_1\lambda_2 - \kappa (\lambda_1+\lambda_2)^2
670 \]
671
672 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
673 @subsection covdet-harris-warped Harris in the warped domain
674 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
675
676 The cornerness measure of a feature a location $u$ (recall that
677 locations $u$ are in general defined as image warps) should be
678 computed after normalizing the image (by applying to it the warp
679 $u^{-1}$). This section shows that, for affine warps, the Harris
680 cornerness measure can be computed directly in the Gaussian affine
681 scale space of the image. In particular, for similarities, it can be
682 computed in the standard Gaussian scale space.
683
684 To this end, let $u=(A,T)$ be an affine warp identifying a feature
685 location in image $\ell(\bx)$. Let $\bar\ell(\bar\bx) =
686 \ell(A\bar\bx+T)$ be the normalized image and rewrite the structure
687 tensor of the normalized image as follows:
688
689 \[
690 M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d]
691 =
692 M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d](\mathbf{0})
693 =
694 \left[
695 g_{\bar\Sigma_i} *
696 (\nabla\bar\ell_{\bar\Sigma_d})
697 (\nabla\bar\ell_{\bar\Sigma_d})^\top
698 \right](\mathbf{0})
699 \]
700
701 This notation emphasizes that the structure tensor is obtained by
702 taking derivatives and convolutions of the image. Using the fact that
703 $\nabla g_{\bar\Sigma_d} * \bar\ell = A^\top (\nabla g_{A\bar\Sigma
704 A^\top} * \ell) \circ (A,T)$ and that $g_{\bar\Sigma} * \bar \ell =
705 (g_{A\bar\Sigma A^\top} * \ell) \circ (A,T)$, we get the equivalent
706 expression:
707
708 \[
709 M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d](\mathbf{0})
710 =
711 A^\top
712 \left[
713 g_{A\bar\Sigma_i A^\top} *
714 (\nabla\ell_{A\bar\Sigma_dA^\top})(\nabla\ell_{A\bar\Sigma_d A^\top})^\top
715 \right](A\mathbf{0}+T)
716 A.
717 \]
718
719 In other words, the structure tensor of the normalized image can be
720 computed as:
721
722 \[
723 M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d](\mathbf{0})
724 =
725 A^\top M[\ell; \Sigma_i, \Sigma_d](T) A,
726 \quad
727 \Sigma_{i} = A\bar\Sigma_{i}A^\top,
728 \quad
729 \Sigma_{d} = A\bar\Sigma_{d}A^\top.
730 \]
731
732 This equation allows to compute the structure tensor for feature at
733 all locations directly in the original image. In particular, features
734 at all translations $T$ can be evaluated efficiently by computing
735 convolutions and derivatives of the image
736 $\ell_{A\bar\Sigma_dA^\top}$.
737
738 A case of particular instance is when $\bar\Sigma_i= \bar\sigma_i^2 I$
739 and $\bar\Sigma_d = \bar\sigma_d^2$ are both isotropic covariance
740 matrices and the affine transformation is a similarity $A=sR$. Using
741 the fact that $\det\left( s^2 R^\top M R \right)= s^4 \det M$ and
742 $\operatorname{tr}\left(s^2 R^\top M R\right) = s^2 \operatorname{tr}
743 M$, one obtains the relation
744
745 \[
746 \operatorname{Harris}[\bar \ell;\bar\sigma_i^2,\bar\sigma_d^2] =
747 s^4 \operatorname{Harris}[\ell;s^2\bar\sigma_i^2,s^2\bar\sigma_d^2](T).
748 \]
749
750 This equation indicates that, for similarity transformations, not only
751 the structure tensor, but directly the Harris cornerness measure can
752 be computed on the original image and then be transferred back to the
753 normalized domain. Note, however, that this requires rescaling the
754 measure by the factor $s^4$.
755
756 Another important consequence of this relation is that the Harris
757 measure is invariant to pure image rotations. It cannot, therefore, be
758 used to associate an orientation to the detected features.
759
760 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
761 @section covdet-hessian Hessian blobs
762 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
763
764 The *(determinant of the) Hessian* cornerness measure is given
765 determinant of the Hessian of the image:
766
767 \[
768 \operatorname{DetHess}[\ell;\sigma_d^2]
769 =
770 \det H_{g_{\sigma_d^2} * \ell}(\mathbf{0})
771 \]
772
773 This number is large and positive if the image is locally curved
774 (peaked), roughly corresponding to blob-like structures in the image.
775 In particular, a large score requires the product of the eigenvalues
776 of the Hessian to be large, which requires both of them to have the
777 same sign and are large in absolute value.
778
779 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
780 @subsection covdet-hessian-warped Hessian in the warped domain
781 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
782
783 Similarly to the Harris measure, it is possible to work with the
784 Hessian measure on the original unnormalized image. As before, let
785 $\bar\ell(\bar\bx) = \ell(A\bar\bx+T)$ be the normalized image and
786 rewrite the Hessian of the normalized image as follows:
787
788 \[
789 H_{g_{\bar\Sigma_d} * \bar\ell}(\mathbf{0}) = A^\top \left(H_{g_{\Sigma_d} * \ell}(T)\right) A.
790 \]
791
792 Then
793
794 \[
795 \operatorname{DetHess}[\bar\ell;\bar\Sigma_d]
796 =
797 (\det A)^2 \operatorname{DetHess}[\ell;A\bar\Sigma_d A^\top](T).
798 \]
799
800 In particular, for isotropic covariance matrices and similarity
801 transformations $A=sR$:
802
803 \[
804 \operatorname{DetHess}[\bar\ell;\bar\sigma_d^2]
805 =
806 s^4 \operatorname{DetHess}[\ell;s^2\bar\sigma_d^2](T)
807 \]
808
809 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
810 @section covdet-laplacian Laplacian and Difference of Gaussians blobs
811 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
812
813 The **Laplacian of Gaussian (LoG)** or **trace of the Hessian**
814 cornerness measure is given by the trace of the Hessian of the image:
815
816 \[
817 \operatorname{Lap}[\ell;\sigma_d^2]
818 =
819 \operatorname{tr} H_{g_{\sigma_d}^2 * \ell}
820 \]
821
822 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
823 @subsection covdet-laplacian-warped Laplacian in the warped domain
824 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
825
826 Similarly to the Hessian measure, the Laplacian cornenress can often
827 be efficiently computed for features at all locations in the original
828 unnormalized image domain. In particular, if the derivative covariance
829 matrix $\Sigma_d$ is isotropic and one considers as warpings
830 similarity transformations $A=sR$, where $R$ is a rotatin and $s$ a
831 rescaling, one has
832
833 \[
834 \operatorname{Lap}[\bar\ell;\bar\sigma_d^2]
835 =
836 s^2 \operatorname{Lap}[\ell;s^2\bar\sigma_d^2](T)
837 \]
838
839 Note that, comparing to the Harris and determinant of Hessian
840 measures, the scaling for the Laplacian is $s^2$ rather than $s^4$.
841
842 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
843 @subsection covdet-laplacian-matched Laplacian as a matched filter
844 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
845
846 The Laplacian is given by the trace of the Hessian
847 operator. Differently from the determinant of the Hessian, this is a
848 linear operation. This means that computing the Laplacian cornerness
849 measure can be seen as applying a linear filtering operator to the
850 image. This filter can then be interpreted as a *template* of a corner
851 being matched to the image. Hence, the Laplacian cornerness measure
852 can be interpreted as matching this corner template at all possible
853 image locations.
854
855 To see this formally, compute the Laplacian score in the input image domain:
856
857 \[
858 \operatorname{Lap}[\bar\ell;\bar\sigma_d^2]
859 =
860 s^2 \operatorname{Lap}[\ell;s^2\bar\sigma_d^2](T)
861 =
862 s^2 (\Delta g_{s^2\bar\sigma_d^2} * \ell)(T)
863 \]
864
865 The Laplacian fitler is obtained by moving the Laplacian operator from
866 the image to the Gaussian smoothing kernel:
867
868 \[
869 s^2 (\Delta g_{s^2\bar\sigma_d^2} * \ell)
870 =
871 (s^2 \Delta g_{s^2\bar\sigma_d^2}) * \ell
872 \]
873
874 Note that the filter is rescaled by the $s^2$; sometimes, this factor
875 is incorporated in the Laplacian operator, yielding the so-called
876 normalized Laplacian.
877
878 The Laplacian of Gaussian is also called *top-hat function* and has
879 the expression:
880
881 \[
882 \Delta g_{\sigma^2}(x,y)
883 =
884 \frac{x^2+y^2 - 2 \sigma^2}{\sigma^4} g_{\sigma^2}(x,y).
885 \]
886
887 This filter, which acts as corner template, resembles a blob (a dark
888 disk surrounded by a bright ring).
889
890 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
891 @subsection covdet-laplacian-dog Difference of Gaussians
892 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
893
894 The **Difference of Gaussian** (DoG) cornerness measure can be
895 interpreted as an approximation of the Laplacian that is easy to
896 obtain once a scalespace of the input image has been computed.
897
898 As noted above, the Laplacian cornerness of the normalized feature can
899 be computed directly from the input image by convolving the image by
900 the normalized Laplacian of Gaussian filter $s^2 \Delta
901 g_{s^2\bar\sigma_d^2}$.
902
903 Like the other derivative operators, this filter is simpe to
904 discriteize. However, it is often approximated by computing the the
905 *Difference of Gaussians* (DoG) approximation instead. This
906 approximation is obtained from the easily-proved identity:
907
908 \[
909 \frac{\partial}{\partial \sigma} g_{\sigma^2} =
910 \sigma \Delta g_{\sigma^2}.
911 \]
912
913 This indicates that computing the normalized Laplacian of a Gaussian
914 filter is, in the limit, the same as taking the difference between
915 Gaussian filters of slightly increasing standard deviation $\sigma$
916 and $\kappa\sigma$, where $\kappa \approx 1$:
917
918 \[
919 \sigma^2 \Delta g_{\sigma^2}
920 \approx
921 \sigma \frac{g_{(\kappa\sigma)^2} - g_{\sigma^2}}{\kappa\sigma - \sigma}
922 =
923 \frac{1}{\kappa - 1}
924 (g_{(\kappa\sigma)^2} - g_{\sigma^2}).
925 \]
926
927 One nice propery of this expression is that the factor $\sigma$
928 cancels out in the right-hand side. Usually, scales $\sigma$ and
929 $\kappa\sigma$ are pre-computed in the image scale-space and
930 successive scales are sampled with uniform geometric spacing, meaning
931 that the factor $\kappa$ is the same for all scales. Then, up to a
932 overall scaling factor, the LoG cornerness measure can be obtained by
933 taking the difference of successive scale space images
934 $\ell_{(\kappa\sigma)^2}$ and $\ell_{\sigma^2}$.
935
936 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
937 @page covdet-affine-adaptation Affine adaptation
938 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
939
940 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
941 @page covdet-dominant-orientation Dominant orientation
942 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
943 **/
944
945 #include "covdet.h"
946 #include <string.h>
947
948 /** @brief Reallocate buffer
949 ** @param buffer
950 ** @param bufferSize
951 ** @param targetSize
952 ** @return error code
953 **/
954
955 static int
_vl_resize_buffer(void ** buffer,vl_size * bufferSize,vl_size targetSize)956 _vl_resize_buffer (void ** buffer, vl_size * bufferSize, vl_size targetSize) {
957 void * newBuffer ;
958 if (*buffer == NULL) {
959 *buffer = vl_malloc(targetSize) ;
960 if (*buffer) {
961 *bufferSize = targetSize ;
962 return VL_ERR_OK ;
963 } else {
964 *bufferSize = 0 ;
965 return VL_ERR_ALLOC ;
966 }
967 }
968 newBuffer = vl_realloc(*buffer, targetSize) ;
969 if (newBuffer) {
970 *buffer = newBuffer ;
971 *bufferSize = targetSize ;
972 return VL_ERR_OK ;
973 } else {
974 return VL_ERR_ALLOC ;
975 }
976 }
977
978 /** @brief Enlarge buffer
979 ** @param buffer
980 ** @param bufferSize
981 ** @param targetSize
982 ** @return error code
983 **/
984
985 static int
_vl_enlarge_buffer(void ** buffer,vl_size * bufferSize,vl_size targetSize)986 _vl_enlarge_buffer (void ** buffer, vl_size * bufferSize, vl_size targetSize) {
987 if (*bufferSize >= targetSize) return VL_ERR_OK ;
988 return _vl_resize_buffer(buffer,bufferSize,targetSize) ;
989 }
990
991 /* ---------------------------------------------------------------- */
992 /* Finding local extrema */
993 /* ---------------------------------------------------------------- */
994
995 /* Todo: make this generally available in the library */
996
997 typedef struct _VlCovDetExtremum2
998 {
999 vl_index xi ;
1000 vl_index yi ;
1001 float x ;
1002 float y ;
1003 float peakScore ;
1004 float edgeScore ;
1005 } VlCovDetExtremum2 ;
1006
1007 typedef struct _VlCovDetExtremum3
1008 {
1009 vl_index xi ;
1010 vl_index yi ;
1011 vl_index zi ;
1012 float x ;
1013 float y ;
1014 float z ;
1015 float peakScore ;
1016 float edgeScore ;
1017 } VlCovDetExtremum3 ;
1018
1019 VL_EXPORT vl_size
1020 vl_find_local_extrema_3 (vl_index ** extrema, vl_size * bufferSize,
1021 float const * map,
1022 vl_size width, vl_size height, vl_size depth,
1023 double threshold) ;
1024
1025 VL_EXPORT vl_size
1026 vl_find_local_extrema_2 (vl_index ** extrema, vl_size * bufferSize,
1027 float const * map,
1028 vl_size width, vl_size height,
1029 double threshold) ;
1030
1031 VL_EXPORT vl_bool
1032 vl_refine_local_extreum_3 (VlCovDetExtremum3 * refined,
1033 float const * map,
1034 vl_size width, vl_size height, vl_size depth,
1035 vl_index x, vl_index y, vl_index z) ;
1036
1037 VL_EXPORT vl_bool
1038 vl_refine_local_extreum_2 (VlCovDetExtremum2 * refined,
1039 float const * map,
1040 vl_size width, vl_size height,
1041 vl_index x, vl_index y) ;
1042
1043 /** @internal
1044 ** @brief Find the extrema of a 3D function
1045 ** @param extrema buffer containing the extrema found (in/out).
1046 ** @param bufferSize size of the @a extrema buffer in bytes (in/out).
1047 ** @param map a 3D array representing the map.
1048 ** @param width of the map.
1049 ** @param height of the map.
1050 ** @param depth of the map.
1051 ** @param threshold minumum extremum value.
1052 ** @return number of extrema found.
1053 ** @see @ref ::vl_refine_local_extreum_2.
1054 **/
1055
1056 vl_size
vl_find_local_extrema_3(vl_index ** extrema,vl_size * bufferSize,float const * map,vl_size width,vl_size height,vl_size depth,double threshold)1057 vl_find_local_extrema_3 (vl_index ** extrema, vl_size * bufferSize,
1058 float const * map,
1059 vl_size width, vl_size height, vl_size depth,
1060 double threshold)
1061 {
1062 vl_index x, y, z ;
1063 vl_size const xo = 1 ;
1064 vl_size const yo = width ;
1065 vl_size const zo = width * height ;
1066 float const *pt = map + xo + yo + zo ;
1067
1068 vl_size numExtrema = 0 ;
1069 vl_size requiredSize = 0 ;
1070
1071 #define CHECK_NEIGHBORS_3(v,CMP,SGN) (\
1072 v CMP ## = SGN threshold && \
1073 v CMP *(pt + xo) && \
1074 v CMP *(pt - xo) && \
1075 v CMP *(pt + zo) && \
1076 v CMP *(pt - zo) && \
1077 v CMP *(pt + yo) && \
1078 v CMP *(pt - yo) && \
1079 \
1080 v CMP *(pt + yo + xo) && \
1081 v CMP *(pt + yo - xo) && \
1082 v CMP *(pt - yo + xo) && \
1083 v CMP *(pt - yo - xo) && \
1084 \
1085 v CMP *(pt + xo + zo) && \
1086 v CMP *(pt - xo + zo) && \
1087 v CMP *(pt + yo + zo) && \
1088 v CMP *(pt - yo + zo) && \
1089 v CMP *(pt + yo + xo + zo) && \
1090 v CMP *(pt + yo - xo + zo) && \
1091 v CMP *(pt - yo + xo + zo) && \
1092 v CMP *(pt - yo - xo + zo) && \
1093 \
1094 v CMP *(pt + xo - zo) && \
1095 v CMP *(pt - xo - zo) && \
1096 v CMP *(pt + yo - zo) && \
1097 v CMP *(pt - yo - zo) && \
1098 v CMP *(pt + yo + xo - zo) && \
1099 v CMP *(pt + yo - xo - zo) && \
1100 v CMP *(pt - yo + xo - zo) && \
1101 v CMP *(pt - yo - xo - zo) )
1102
1103 for (z = 1 ; z < (signed)depth - 1 ; ++z) {
1104 for (y = 1 ; y < (signed)height - 1 ; ++y) {
1105 for (x = 1 ; x < (signed)width - 1 ; ++x) {
1106 float value = *pt ;
1107 if (CHECK_NEIGHBORS_3(value,>,+) || CHECK_NEIGHBORS_3(value,<,-)) {
1108 numExtrema ++ ;
1109 requiredSize += sizeof(vl_index) * 3 ;
1110 if (*bufferSize < requiredSize) {
1111 int err = _vl_resize_buffer((void**)extrema, bufferSize,
1112 requiredSize + 2000 * 3 * sizeof(vl_index)) ;
1113 if (err != VL_ERR_OK) abort() ;
1114 }
1115 (*extrema) [3 * (numExtrema - 1) + 0] = x ;
1116 (*extrema) [3 * (numExtrema - 1) + 1] = y ;
1117 (*extrema) [3 * (numExtrema - 1) + 2] = z ;
1118 }
1119 pt += xo ;
1120 }
1121 pt += 2*xo ;
1122 }
1123 pt += 2*yo ;
1124 }
1125 return numExtrema ;
1126 }
1127
1128 /** @internal
1129 ** @brief Find extrema in a 2D function
1130 ** @param extrema buffer containing the found extrema (in/out).
1131 ** @param bufferSize size of the @a extrema buffer in bytes (in/out).
1132 ** @param map a 3D array representing the map.
1133 ** @param width of the map.
1134 ** @param height of the map.
1135 ** @param threshold minumum extremum value.
1136 ** @return number of extrema found.
1137 **
1138 ** An extremum contains 2 ::vl_index values; they are arranged
1139 ** sequentially.
1140 **
1141 ** The function can reuse an already allocated buffer if
1142 ** @a extrema and @a bufferSize are initialized on input.
1143 ** It may have to @a realloc the memory if the buffer is too small.
1144 **/
1145
1146 vl_size
vl_find_local_extrema_2(vl_index ** extrema,vl_size * bufferSize,float const * map,vl_size width,vl_size height,double threshold)1147 vl_find_local_extrema_2 (vl_index ** extrema, vl_size * bufferSize,
1148 float const* map,
1149 vl_size width, vl_size height,
1150 double threshold)
1151 {
1152 vl_index x, y ;
1153 vl_size const xo = 1 ;
1154 vl_size const yo = width ;
1155 float const *pt = map + xo + yo ;
1156
1157 vl_size numExtrema = 0 ;
1158 vl_size requiredSize = 0 ;
1159 #define CHECK_NEIGHBORS_2(v,CMP,SGN) (\
1160 v CMP ## = SGN threshold && \
1161 v CMP *(pt + xo) && \
1162 v CMP *(pt - xo) && \
1163 v CMP *(pt + yo) && \
1164 v CMP *(pt - yo) && \
1165 \
1166 v CMP *(pt + yo + xo) && \
1167 v CMP *(pt + yo - xo) && \
1168 v CMP *(pt - yo + xo) && \
1169 v CMP *(pt - yo - xo) )
1170
1171 for (y = 1 ; y < (signed)height - 1 ; ++y) {
1172 for (x = 1 ; x < (signed)width - 1 ; ++x) {
1173 float value = *pt ;
1174 if (CHECK_NEIGHBORS_2(value,>,+) || CHECK_NEIGHBORS_2(value,<,-)) {
1175 numExtrema ++ ;
1176 requiredSize += sizeof(vl_index) * 2 ;
1177 if (*bufferSize < requiredSize) {
1178 int err = _vl_resize_buffer((void**)extrema, bufferSize,
1179 requiredSize + 2000 * 2 * sizeof(vl_index)) ;
1180 if (err != VL_ERR_OK) abort() ;
1181 }
1182 (*extrema) [2 * (numExtrema - 1) + 0] = x ;
1183 (*extrema) [2 * (numExtrema - 1) + 1] = y ;
1184 }
1185 pt += xo ;
1186 }
1187 pt += 2*xo ;
1188 }
1189 return numExtrema ;
1190 }
1191
1192 /** @internal
1193 ** @brief Refine the location of a local extremum of a 3D map
1194 ** @param refined refined extremum (out).
1195 ** @param map a 3D array representing the map.
1196 ** @param width of the map.
1197 ** @param height of the map.
1198 ** @param depth of the map.
1199 ** @param x initial x position.
1200 ** @param y initial y position.
1201 ** @param z initial z position.
1202 ** @return a flat indicating whether the extrema refinement was stable.
1203 **/
1204
1205 VL_EXPORT vl_bool
vl_refine_local_extreum_3(VlCovDetExtremum3 * refined,float const * map,vl_size width,vl_size height,vl_size depth,vl_index x,vl_index y,vl_index z)1206 vl_refine_local_extreum_3 (VlCovDetExtremum3 * refined,
1207 float const * map,
1208 vl_size width, vl_size height, vl_size depth,
1209 vl_index x, vl_index y, vl_index z)
1210 {
1211 vl_size const xo = 1 ;
1212 vl_size const yo = width ;
1213 vl_size const zo = width * height ;
1214
1215 double Dx=0,Dy=0,Dz=0,Dxx=0,Dyy=0,Dzz=0,Dxy=0,Dxz=0,Dyz=0 ;
1216 double A [3*3], b [3] ;
1217
1218 #define at(dx,dy,dz) (*(pt + (dx)*xo + (dy)*yo + (dz)*zo))
1219 #define Aat(i,j) (A[(i)+(j)*3])
1220
1221 float const * pt ;
1222 vl_index dx = 0 ;
1223 vl_index dy = 0 ;
1224 /*vl_index dz = 0 ;*/
1225 vl_index iter ;
1226 int err ;
1227
1228 assert (map) ;
1229 assert (1 <= x && x <= (signed)width - 2) ;
1230 assert (1 <= y && y <= (signed)height - 2) ;
1231 assert (1 <= z && z <= (signed)depth - 2) ;
1232
1233 for (iter = 0 ; iter < 5 ; ++iter) {
1234 x += dx ;
1235 y += dy ;
1236 pt = map + x*xo + y*yo + z*zo ;
1237
1238 /* compute the gradient */
1239 Dx = 0.5 * (at(+1,0,0) - at(-1,0,0)) ;
1240 Dy = 0.5 * (at(0,+1,0) - at(0,-1,0));
1241 Dz = 0.5 * (at(0,0,+1) - at(0,0,-1)) ;
1242
1243 /* compute the Hessian */
1244 Dxx = (at(+1,0,0) + at(-1,0,0) - 2.0 * at(0,0,0)) ;
1245 Dyy = (at(0,+1,0) + at(0,-1,0) - 2.0 * at(0,0,0)) ;
1246 Dzz = (at(0,0,+1) + at(0,0,-1) - 2.0 * at(0,0,0)) ;
1247
1248 Dxy = 0.25 * (at(+1,+1,0) + at(-1,-1,0) - at(-1,+1,0) - at(+1,-1,0)) ;
1249 Dxz = 0.25 * (at(+1,0,+1) + at(-1,0,-1) - at(-1,0,+1) - at(+1,0,-1)) ;
1250 Dyz = 0.25 * (at(0,+1,+1) + at(0,-1,-1) - at(0,-1,+1) - at(0,+1,-1)) ;
1251
1252 /* solve linear system */
1253 Aat(0,0) = Dxx ;
1254 Aat(1,1) = Dyy ;
1255 Aat(2,2) = Dzz ;
1256 Aat(0,1) = Aat(1,0) = Dxy ;
1257 Aat(0,2) = Aat(2,0) = Dxz ;
1258 Aat(1,2) = Aat(2,1) = Dyz ;
1259
1260 b[0] = - Dx ;
1261 b[1] = - Dy ;
1262 b[2] = - Dz ;
1263
1264 err = vl_solve_linear_system_3(b, A, b) ;
1265
1266 if (err != VL_ERR_OK) {
1267 b[0] = 0 ;
1268 b[1] = 0 ;
1269 b[2] = 0 ;
1270 break ;
1271 }
1272
1273 /* Keep going if there is sufficient translation */
1274
1275 dx = (b[0] > 0.6 && x < (signed)width - 2 ? 1 : 0)
1276 + (b[0] < -0.6 && x > 1 ? -1 : 0) ;
1277
1278 dy = (b[1] > 0.6 && y < (signed)height - 2 ? 1 : 0)
1279 + (b[1] < -0.6 && y > 1 ? -1 : 0) ;
1280
1281 if (dx == 0 && dy == 0) break ;
1282 }
1283
1284 /* check threshold and other conditions */
1285 {
1286 double peakScore = at(0,0,0)
1287 + 0.5 * (Dx * b[0] + Dy * b[1] + Dz * b[2]) ;
1288 double alpha = (Dxx+Dyy)*(Dxx+Dyy) / (Dxx*Dyy - Dxy*Dxy) ;
1289 double edgeScore ;
1290
1291 if (alpha < 0) {
1292 /* not an extremum */
1293 edgeScore = VL_INFINITY_D ;
1294 } else {
1295 edgeScore = (0.5*alpha - 1) + sqrt(VL_MAX(0.25*alpha - 1,0)*alpha) ;
1296 }
1297
1298 refined->xi = x ;
1299 refined->yi = y ;
1300 refined->zi = z ;
1301 refined->x = x + b[0] ;
1302 refined->y = y + b[1] ;
1303 refined->z = z + b[2] ;
1304 refined->peakScore = peakScore ;
1305 refined->edgeScore = edgeScore ;
1306
1307 return
1308 err == VL_ERR_OK &&
1309 vl_abs_d(b[0]) < 1.5 &&
1310 vl_abs_d(b[1]) < 1.5 &&
1311 vl_abs_d(b[2]) < 1.5 &&
1312 0 <= refined->x && refined->x <= (signed)width - 1 &&
1313 0 <= refined->y && refined->y <= (signed)height - 1 &&
1314 0 <= refined->z && refined->z <= (signed)depth - 1 ;
1315 }
1316 #undef Aat
1317 #undef at
1318 }
1319
1320 /** @internal
1321 ** @brief Refine the location of a local extremum of a 2D map
1322 ** @param refined refined extremum (out).
1323 ** @param map a 2D array representing the map.
1324 ** @param width of the map.
1325 ** @param height of the map.
1326 ** @param x initial x position.
1327 ** @param y initial y position.
1328 ** @return a flat indicating whether the extrema refinement was stable.
1329 **/
1330
1331 VL_EXPORT vl_bool
vl_refine_local_extreum_2(VlCovDetExtremum2 * refined,float const * map,vl_size width,vl_size height,vl_index x,vl_index y)1332 vl_refine_local_extreum_2 (VlCovDetExtremum2 * refined,
1333 float const * map,
1334 vl_size width, vl_size height,
1335 vl_index x, vl_index y)
1336 {
1337 vl_size const xo = 1 ;
1338 vl_size const yo = width ;
1339
1340 double Dx=0,Dy=0,Dxx=0,Dyy=0,Dxy=0;
1341 double A [2*2], b [2] ;
1342
1343 #define at(dx,dy) (*(pt + (dx)*xo + (dy)*yo ))
1344 #define Aat(i,j) (A[(i)+(j)*2])
1345
1346 float const * pt ;
1347 vl_index dx = 0 ;
1348 vl_index dy = 0 ;
1349 vl_index iter ;
1350 int err ;
1351
1352 assert (map) ;
1353 assert (1 <= x && x <= (signed)width - 2) ;
1354 assert (1 <= y && y <= (signed)height - 2) ;
1355
1356 for (iter = 0 ; iter < 5 ; ++iter) {
1357 x += dx ;
1358 y += dy ;
1359 pt = map + x*xo + y*yo ;
1360
1361 /* compute the gradient */
1362 Dx = 0.5 * (at(+1,0) - at(-1,0)) ;
1363 Dy = 0.5 * (at(0,+1) - at(0,-1));
1364
1365 /* compute the Hessian */
1366 Dxx = (at(+1,0) + at(-1,0) - 2.0 * at(0,0)) ;
1367 Dyy = (at(0,+1) + at(0,-1) - 2.0 * at(0,0)) ;
1368 Dxy = 0.25 * (at(+1,+1) + at(-1,-1) - at(-1,+1) - at(+1,-1)) ;
1369
1370 /* solve linear system */
1371 Aat(0,0) = Dxx ;
1372 Aat(1,1) = Dyy ;
1373 Aat(0,1) = Aat(1,0) = Dxy ;
1374
1375 b[0] = - Dx ;
1376 b[1] = - Dy ;
1377
1378 err = vl_solve_linear_system_2(b, A, b) ;
1379
1380 if (err != VL_ERR_OK) {
1381 b[0] = 0 ;
1382 b[1] = 0 ;
1383 break ;
1384 }
1385
1386 /* Keep going if there is sufficient translation */
1387
1388 dx = (b[0] > 0.6 && x < (signed)width - 2 ? 1 : 0)
1389 + (b[0] < -0.6 && x > 1 ? -1 : 0) ;
1390
1391 dy = (b[1] > 0.6 && y < (signed)height - 2 ? 1 : 0)
1392 + (b[1] < -0.6 && y > 1 ? -1 : 0) ;
1393
1394 if (dx == 0 && dy == 0) break ;
1395 }
1396
1397 /* check threshold and other conditions */
1398 {
1399 double peakScore = at(0,0) + 0.5 * (Dx * b[0] + Dy * b[1]) ;
1400 double alpha = (Dxx+Dyy)*(Dxx+Dyy) / (Dxx*Dyy - Dxy*Dxy) ;
1401 double edgeScore ;
1402
1403 if (alpha < 0) {
1404 /* not an extremum */
1405 edgeScore = VL_INFINITY_D ;
1406 } else {
1407 edgeScore = (0.5*alpha - 1) + sqrt(VL_MAX(0.25*alpha - 1,0)*alpha) ;
1408 }
1409
1410 refined->xi = x ;
1411 refined->yi = y ;
1412 refined->x = x + b[0] ;
1413 refined->y = y + b[1] ;
1414 refined->peakScore = peakScore ;
1415 refined->edgeScore = edgeScore ;
1416
1417 return
1418 err == VL_ERR_OK &&
1419 vl_abs_d(b[0]) < 1.5 &&
1420 vl_abs_d(b[1]) < 1.5 &&
1421 0 <= refined->x && refined->x <= (signed)width - 1 &&
1422 0 <= refined->y && refined->y <= (signed)height - 1 ;
1423 }
1424 #undef Aat
1425 #undef at
1426 }
1427
1428 /* ---------------------------------------------------------------- */
1429 /* Covarant detector */
1430 /* ---------------------------------------------------------------- */
1431
1432 #define VL_COVDET_MAX_NUM_ORIENTATIONS 4
1433 #define VL_COVDET_MAX_NUM_LAPLACIAN_SCALES 4
1434 #define VL_COVDET_AA_PATCH_RESOLUTION 20
1435 #define VL_COVDET_AA_MAX_NUM_ITERATIONS 15
1436 #define VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS 36
1437 #define VL_COVDET_AA_RELATIVE_INTEGRATION_SIGMA 3
1438 #define VL_COVDET_AA_RELATIVE_DERIVATIVE_SIGMA 1
1439 #define VL_COVDET_AA_MAX_ANISOTROPY 5
1440 #define VL_COVDET_AA_CONVERGENCE_THRESHOLD 1.001
1441 #define VL_COVDET_AA_ACCURATE_SMOOTHING VL_FALSE
1442 #define VL_COVDET_AA_PATCH_EXTENT (3*VL_COVDET_AA_RELATIVE_INTEGRATION_SIGMA)
1443 #define VL_COVDET_OR_ADDITIONAL_PEAKS_RELATIVE_SIZE 0.8
1444 #define VL_COVDET_LAP_NUM_LEVELS 10
1445 #define VL_COVDET_LAP_PATCH_RESOLUTION 16
1446 #define VL_COVDET_LAP_DEF_PEAK_THRESHOLD 0.01
1447 #define VL_COVDET_DOG_DEF_PEAK_THRESHOLD VL_COVDET_LAP_DEF_PEAK_THRESHOLD
1448 #define VL_COVDET_DOG_DEF_EDGE_THRESHOLD 10.0
1449 #define VL_COVDET_HARRIS_DEF_PEAK_THRESHOLD 0.000002
1450 #define VL_COVDET_HARRIS_DEF_EDGE_THRESHOLD 10.0
1451 #define VL_COVDET_HESSIAN_DEF_PEAK_THRESHOLD 0.003
1452 #define VL_COVDET_HESSIAN_DEF_EDGE_THRESHOLD 10.0
1453
1454 /** @brief Covariant feature detector */
1455 struct _VlCovDet
1456 {
1457 VlScaleSpace *gss ; /**< Gaussian scale space. */
1458 VlScaleSpace *css ; /**< Cornerness scale space. */
1459 VlCovDetMethod method ; /**< feature extraction method. */
1460 double peakThreshold ; /**< peak threshold. */
1461 double edgeThreshold ; /**< edge threshold. */
1462 double lapPeakThreshold; /**< peak threshold for Laplacian scale selection. */
1463 vl_size octaveResolution ; /**< resolution of each octave. */
1464 vl_index firstOctave ; /**< index of the first octave. */
1465
1466 double nonExtremaSuppression ;
1467 vl_size numNonExtremaSuppressed ;
1468
1469 VlCovDetFeature *features ;
1470 vl_size numFeatures ;
1471 vl_size numFeatureBufferSize ;
1472
1473 float * patch ;
1474 vl_size patchBufferSize ;
1475
1476 vl_bool transposed ;
1477 VlCovDetFeatureOrientation orientations [VL_COVDET_MAX_NUM_ORIENTATIONS] ;
1478 VlCovDetFeatureLaplacianScale scales [VL_COVDET_MAX_NUM_LAPLACIAN_SCALES] ;
1479
1480 vl_bool aaAccurateSmoothing ;
1481 float aaPatch [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
1482 float aaPatchX [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
1483 float aaPatchY [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
1484 float aaMask [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
1485
1486 float lapPatch [(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)*(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)] ;
1487 float laplacians [(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)*(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)*VL_COVDET_LAP_NUM_LEVELS] ;
1488 vl_size numFeaturesWithNumScales [VL_COVDET_MAX_NUM_LAPLACIAN_SCALES + 1] ;
1489 } ;
1490
1491 VlEnumerator vlCovdetMethods [VL_COVDET_METHOD_NUM] = {
1492 {"DoG" , (vl_index)VL_COVDET_METHOD_DOG },
1493 {"Hessian", (vl_index)VL_COVDET_METHOD_HESSIAN },
1494 {"HessianLaplace", (vl_index)VL_COVDET_METHOD_HESSIAN_LAPLACE },
1495 {"HarrisLaplace", (vl_index)VL_COVDET_METHOD_HARRIS_LAPLACE },
1496 {"MultiscaleHessian", (vl_index)VL_COVDET_METHOD_MULTISCALE_HESSIAN},
1497 {"MultiscaleHarris", (vl_index)VL_COVDET_METHOD_MULTISCALE_HARRIS },
1498 {0, 0 }
1499 } ;
1500
1501 /** @brief Create a new object instance
1502 ** @param method method for covariant feature detection.
1503 ** @return new covariant detector.
1504 **/
1505
1506 VlCovDet *
vl_covdet_new(VlCovDetMethod method)1507 vl_covdet_new (VlCovDetMethod method)
1508 {
1509 VlCovDet * self = vl_calloc(sizeof(VlCovDet),1) ;
1510 self->method = method ;
1511 self->octaveResolution = 3 ;
1512 self->firstOctave = -1 ;
1513 switch (self->method) {
1514 case VL_COVDET_METHOD_DOG :
1515 self->peakThreshold = VL_COVDET_DOG_DEF_PEAK_THRESHOLD ;
1516 self->edgeThreshold = VL_COVDET_DOG_DEF_EDGE_THRESHOLD ;
1517 self->lapPeakThreshold = 0 ; /* not used */
1518 break ;
1519 case VL_COVDET_METHOD_HARRIS_LAPLACE:
1520 case VL_COVDET_METHOD_MULTISCALE_HARRIS:
1521 self->peakThreshold = VL_COVDET_HARRIS_DEF_PEAK_THRESHOLD ;
1522 self->edgeThreshold = VL_COVDET_HARRIS_DEF_EDGE_THRESHOLD ;
1523 self->lapPeakThreshold = VL_COVDET_LAP_DEF_PEAK_THRESHOLD ;
1524 break ;
1525 case VL_COVDET_METHOD_HESSIAN :
1526 case VL_COVDET_METHOD_HESSIAN_LAPLACE:
1527 case VL_COVDET_METHOD_MULTISCALE_HESSIAN:
1528 self->peakThreshold = VL_COVDET_HESSIAN_DEF_PEAK_THRESHOLD ;
1529 self->edgeThreshold = VL_COVDET_HESSIAN_DEF_EDGE_THRESHOLD ;
1530 self->lapPeakThreshold = VL_COVDET_LAP_DEF_PEAK_THRESHOLD ;
1531 break;
1532 default:
1533 assert(0) ;
1534 }
1535
1536 self->nonExtremaSuppression = 0.5 ;
1537 self->features = NULL ;
1538 self->numFeatures = 0 ;
1539 self->numFeatureBufferSize = 0 ;
1540 self->patch = NULL ;
1541 self->patchBufferSize = 0 ;
1542 self->transposed = VL_FALSE ;
1543 self->aaAccurateSmoothing = VL_COVDET_AA_ACCURATE_SMOOTHING ;
1544
1545 {
1546 vl_index const w = VL_COVDET_AA_PATCH_RESOLUTION ;
1547 vl_index i,j ;
1548 double step = (2.0 * VL_COVDET_AA_PATCH_EXTENT) / (2*w+1) ;
1549 double sigma = VL_COVDET_AA_RELATIVE_INTEGRATION_SIGMA ;
1550 for (j = -w ; j <= w ; ++j) {
1551 for (i = -w ; i <= w ; ++i) {
1552 double dx = i*step/sigma ;
1553 double dy = j*step/sigma ;
1554 self->aaMask[(i+w) + (2*w+1)*(j+w)] = exp(-0.5*(dx*dx+dy*dy)) ;
1555 }
1556 }
1557 }
1558
1559 {
1560 /*
1561 Covers one octave of Laplacian filters, from sigma=1 to sigma=2.
1562 The spatial sampling step is 0.5.
1563 */
1564 vl_index s ;
1565 for (s = 0 ; s < VL_COVDET_LAP_NUM_LEVELS ; ++s) {
1566 double sigmaLap = pow(2.0, -0.5 +
1567 (double)s / (VL_COVDET_LAP_NUM_LEVELS - 1)) ;
1568 double const sigmaImage = 1.0 / sqrt(2.0) ;
1569 double const step = 0.5 * sigmaImage ;
1570 double const sigmaDelta = sqrt(sigmaLap*sigmaLap - sigmaImage*sigmaImage) ;
1571 vl_size const w = VL_COVDET_LAP_PATCH_RESOLUTION ;
1572 vl_size const num = 2 * w + 1 ;
1573 float * pt = self->laplacians + s * (num * num) ;
1574
1575 memset(pt, 0, num * num * sizeof(float)) ;
1576
1577 #define at(x,y) pt[(x+w)+(y+w)*(2*w+1)]
1578 at(0,0) = - 4.0 ;
1579 at(-1,0) = 1.0 ;
1580 at(+1,0) = 1.0 ;
1581 at(0,1) = 1.0 ;
1582 at(0,-1) = 1.0 ;
1583 #undef at
1584
1585 vl_imsmooth_f(pt, num,
1586 pt, num, num, num,
1587 sigmaDelta / step, sigmaDelta / step) ;
1588
1589 #if 0
1590 {
1591 char name [200] ;
1592 snprintf(name, 200, "/tmp/%f-lap.pgm", sigmaDelta) ;
1593 vl_pgm_write_f(name, pt, num, num) ;
1594 }
1595 #endif
1596
1597 }
1598 }
1599 return self ;
1600 }
1601
1602 /** @brief Reset object
1603 ** @param self object.
1604 **
1605 ** This function removes any buffered features and frees other
1606 ** internal buffers.
1607 **/
1608
1609 void
vl_covdet_reset(VlCovDet * self)1610 vl_covdet_reset (VlCovDet * self)
1611 {
1612 if (self->features) {
1613 vl_free(self->features) ;
1614 self->features = NULL ;
1615 }
1616 if (self->css) {
1617 vl_scalespace_delete(self->css) ;
1618 self->css = NULL ;
1619 }
1620 if (self->gss) {
1621 vl_scalespace_delete(self->gss) ;
1622 self->gss = NULL ;
1623 }
1624 }
1625
1626 /** @brief Delete object instance
1627 ** @param self object.
1628 **/
1629
1630 void
vl_covdet_delete(VlCovDet * self)1631 vl_covdet_delete (VlCovDet * self)
1632 {
1633 vl_covdet_reset(self) ;
1634 if (self->patch) vl_free (self->patch) ;
1635 vl_free(self) ;
1636 }
1637
1638 /** @brief Append a feature to the internal buffer.
1639 ** @param self object.
1640 ** @param feature a pointer to the feature to append.
1641 ** @return status.
1642 **
1643 ** The feature is copied. The function may fail with @c status
1644 ** equal to ::VL_ERR_ALLOC if there is insufficient memory.
1645 **/
1646
1647 int
vl_covdet_append_feature(VlCovDet * self,VlCovDetFeature const * feature)1648 vl_covdet_append_feature (VlCovDet * self, VlCovDetFeature const * feature)
1649 {
1650 vl_size requiredSize ;
1651 assert(self) ;
1652 assert(feature) ;
1653 self->numFeatures ++ ;
1654 requiredSize = self->numFeatures * sizeof(VlCovDetFeature) ;
1655 if (requiredSize > self->numFeatureBufferSize) {
1656 int err = _vl_resize_buffer((void**)&self->features, &self->numFeatureBufferSize,
1657 (self->numFeatures + 1000) * sizeof(VlCovDetFeature)) ;
1658 if (err) {
1659 self->numFeatures -- ;
1660 return err ;
1661 }
1662 }
1663 self->features[self->numFeatures - 1] = *feature ;
1664 return VL_ERR_OK ;
1665 }
1666
1667 /* ---------------------------------------------------------------- */
1668 /* Process a new image */
1669 /* ---------------------------------------------------------------- */
1670
1671 /** @brief Detect features in an image
1672 ** @param self object.
1673 ** @param image image to process.
1674 ** @param width image width.
1675 ** @param height image height.
1676 ** @return status.
1677 **
1678 ** @a width and @a height must be at least one pixel. The function
1679 ** fails by returing ::VL_ERR_ALLOC if the memory is insufficient.
1680 **/
1681
1682 int
vl_covdet_put_image(VlCovDet * self,float const * image,vl_size width,vl_size height)1683 vl_covdet_put_image (VlCovDet * self,
1684 float const * image,
1685 vl_size width, vl_size height)
1686 {
1687 vl_size const minOctaveSize = 16 ;
1688 vl_index lastOctave ;
1689 vl_index octaveFirstSubdivision ;
1690 vl_index octaveLastSubdivision ;
1691 VlScaleSpaceGeometry geom = vl_scalespace_get_default_geometry(width,height) ;
1692
1693 assert (self) ;
1694 assert (image) ;
1695 assert (width >= 1) ;
1696 assert (height >= 1) ;
1697
1698 /* (minOctaveSize - 1) 2^lastOctave <= min(width,height) - 1 */
1699 lastOctave = vl_floor_d(vl_log2_d(VL_MIN((double)width-1,(double)height-1) / (minOctaveSize - 1))) ;
1700
1701 if (self->method == VL_COVDET_METHOD_DOG) {
1702 octaveFirstSubdivision = -1 ;
1703 octaveLastSubdivision = self->octaveResolution + 1 ;
1704 } else if (self->method == VL_COVDET_METHOD_HESSIAN) {
1705 octaveFirstSubdivision = -1 ;
1706 octaveLastSubdivision = self->octaveResolution ;
1707 } else {
1708 octaveFirstSubdivision = 0 ;
1709 octaveLastSubdivision = self->octaveResolution - 1 ;
1710 }
1711
1712 geom.width = width ;
1713 geom.height = height ;
1714 geom.firstOctave = self->firstOctave ;
1715 geom.lastOctave = lastOctave ;
1716 geom.octaveResolution = self->octaveResolution ;
1717 geom.octaveFirstSubdivision = octaveFirstSubdivision ;
1718 geom.octaveLastSubdivision = octaveLastSubdivision ;
1719
1720 if (self->gss == NULL ||
1721 ! vl_scalespacegeometry_is_equal (geom,
1722 vl_scalespace_get_geometry(self->gss)))
1723 {
1724 if (self->gss) vl_scalespace_delete(self->gss) ;
1725 self->gss = vl_scalespace_new_with_geometry(geom) ;
1726 if (self->gss == NULL) return VL_ERR_ALLOC ;
1727 }
1728 vl_scalespace_put_image(self->gss, image) ;
1729 return VL_ERR_OK ;
1730 }
1731
1732 /* ---------------------------------------------------------------- */
1733 /* Cornerness measures */
1734 /* ---------------------------------------------------------------- */
1735
1736 /** @brief Scaled derminant of the Hessian filter
1737 ** @param hessian output image.
1738 ** @param image input image.
1739 ** @param width image width.
1740 ** @param height image height.
1741 ** @param step image sampling step (pixel size).
1742 ** @param sigma Gaussian smoothing of the input image.
1743 **/
1744
1745 static void
_vl_det_hessian_response(float * hessian,float const * image,vl_size width,vl_size height,double step,double sigma)1746 _vl_det_hessian_response (float * hessian,
1747 float const * image,
1748 vl_size width, vl_size height,
1749 double step, double sigma)
1750 {
1751 float factor = (float) pow(sigma/step, 4.0) ;
1752 vl_index const xo = 1 ; /* x-stride */
1753 vl_index const yo = width; /* y-stride */
1754 vl_size r, c;
1755
1756 float p11, p12, p13, p21, p22, p23, p31, p32, p33;
1757
1758 /* setup input pointer to be centered at 0,1 */
1759 float const *in = image + yo ;
1760
1761 /* setup output pointer to be centered at 1,1 */
1762 float *out = hessian + xo + yo;
1763
1764 /* move 3x3 window and convolve */
1765 for (r = 1; r < height - 1; ++r)
1766 {
1767 /* fill in shift registers at the beginning of the row */
1768 p11 = in[-yo]; p12 = in[xo - yo];
1769 p21 = in[ 0]; p22 = in[xo ];
1770 p31 = in[+yo]; p32 = in[xo + yo];
1771 /* setup input pointer to (2,1) of the 3x3 square */
1772 in += 2;
1773 for (c = 1; c < width - 1; ++c)
1774 {
1775 float Lxx, Lyy, Lxy;
1776 /* fetch remaining values (last column) */
1777 p13 = in[-yo]; p23 = *in; p33 = in[+yo];
1778
1779 /* Compute 3x3 Hessian values from pixel differences. */
1780 Lxx = (-p21 + 2*p22 - p23);
1781 Lyy = (-p12 + 2*p22 - p32);
1782 Lxy = ((p11 - p31 - p13 + p33)/4.0f);
1783
1784 /* normalize and write out */
1785 *out = (Lxx * Lyy - Lxy * Lxy) * factor ;
1786
1787 /* move window */
1788 p11=p12; p12=p13;
1789 p21=p22; p22=p23;
1790 p31=p32; p32=p33;
1791
1792 /* move input/output pointers */
1793 in++; out++;
1794 }
1795 out += 2;
1796 }
1797
1798 /* Copy the computed values to borders */
1799 in = hessian + yo + xo ;
1800 out = hessian + xo ;
1801
1802 /* Top row without corners */
1803 memcpy(out, in, (width - 2)*sizeof(float));
1804 out--;
1805 in -= yo;
1806
1807 /* Left border columns without last row */
1808 for (r = 0; r < height - 1; r++){
1809 *out = *in;
1810 *(out + yo - 1) = *(in + yo - 3);
1811 in += yo;
1812 out += yo;
1813 }
1814
1815 /* Bottom corners */
1816 in -= yo;
1817 *out = *in;
1818 *(out + yo - 1) = *(in + yo - 3);
1819
1820 /* Bottom row without corners */
1821 out++;
1822 memcpy(out, in, (width - 2)*sizeof(float));
1823 }
1824
1825 /** @brief Scale-normalised Harris response
1826 ** @param harris output image.
1827 ** @param image input image.
1828 ** @param width image width.
1829 ** @param height image height.
1830 ** @param step image sampling step (pixel size).
1831 ** @param sigma Gaussian smoothing of the input image.
1832 ** @param sigmaI integration scale.
1833 ** @param alpha factor in the definition of the Harris score.
1834 **/
1835
1836 static void
_vl_harris_response(float * harris,float const * image,vl_size width,vl_size height,double step,double sigma,double sigmaI,double alpha)1837 _vl_harris_response (float * harris,
1838 float const * image,
1839 vl_size width, vl_size height,
1840 double step, double sigma,
1841 double sigmaI, double alpha)
1842 {
1843 float factor = (float) pow(sigma/step, 4.0) ;
1844 vl_index k ;
1845
1846 float * LxLx ;
1847 float * LyLy ;
1848 float * LxLy ;
1849
1850 LxLx = vl_malloc(sizeof(float) * width * height) ;
1851 LyLy = vl_malloc(sizeof(float) * width * height) ;
1852 LxLy = vl_malloc(sizeof(float) * width * height) ;
1853
1854 vl_imgradient_f (LxLx, LyLy, 1, width, image, width, height, width) ;
1855
1856 for (k = 0 ; k < (signed)(width * height) ; ++k) {
1857 float dx = LxLx[k] ;
1858 float dy = LyLy[k] ;
1859 LxLx[k] = dx*dx ;
1860 LyLy[k] = dy*dy ;
1861 LxLy[k] = dx*dy ;
1862 }
1863
1864 vl_imsmooth_f(LxLx, width, LxLx, width, height, width,
1865 sigmaI / step, sigmaI / step) ;
1866
1867 vl_imsmooth_f(LyLy, width, LyLy, width, height, width,
1868 sigmaI / step, sigmaI / step) ;
1869
1870 vl_imsmooth_f(LxLy, width, LxLy, width, height, width,
1871 sigmaI / step, sigmaI / step) ;
1872
1873 for (k = 0 ; k < (signed)(width * height) ; ++k) {
1874 float a = LxLx[k] ;
1875 float b = LyLy[k] ;
1876 float c = LxLy[k] ;
1877
1878 float determinant = a * b - c * c ;
1879 float trace = a + b ;
1880
1881 harris[k] = factor * (determinant - alpha * (trace * trace)) ;
1882 }
1883
1884 vl_free(LxLy) ;
1885 vl_free(LyLy) ;
1886 vl_free(LxLx) ;
1887 }
1888
1889 /** @brief Difference of Gaussian
1890 ** @param dog output image.
1891 ** @param level1 input image at the smaller Gaussian scale.
1892 ** @param level2 input image at the larger Gaussian scale.
1893 ** @param width image width.
1894 ** @param height image height.
1895 **/
1896
1897 static void
_vl_dog_response(float * dog,float const * level1,float const * level2,vl_size width,vl_size height)1898 _vl_dog_response (float * dog,
1899 float const * level1,
1900 float const * level2,
1901 vl_size width, vl_size height)
1902 {
1903 vl_index k ;
1904 for (k = 0 ; k < (signed)(width*height) ; ++k) {
1905 dog[k] = level2[k] - level1[k] ;
1906 }
1907 }
1908
1909 /* ---------------------------------------------------------------- */
1910 /* Detect features */
1911 /* ---------------------------------------------------------------- */
1912
1913 /** @brief Detect scale-space features
1914 ** @param self object.
1915 **
1916 ** This function runs the configured feature detector on the image
1917 ** that was passed by using ::vl_covdet_put_image.
1918 **/
1919
1920 void
vl_covdet_detect(VlCovDet * self,vl_size max_num_features)1921 vl_covdet_detect (VlCovDet * self, vl_size max_num_features)
1922 {
1923 VlScaleSpaceGeometry geom = vl_scalespace_get_geometry(self->gss) ;
1924 VlScaleSpaceGeometry cgeom ;
1925 float * levelxx = NULL ;
1926 float * levelyy = NULL ;
1927 float * levelxy = NULL ;
1928 vl_index o, s ;
1929
1930 assert (self) ;
1931 assert (self->gss) ;
1932
1933 /* clear previous detections if any */
1934 self->numFeatures = 0 ;
1935
1936 /* prepare buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
1937 cgeom = geom ;
1938 if (self->method == VL_COVDET_METHOD_DOG) {
1939 cgeom.octaveLastSubdivision -= 1 ;
1940 }
1941 if (!self->css ||
1942 !vl_scalespacegeometry_is_equal(cgeom,
1943 vl_scalespace_get_geometry(self->css)))
1944 {
1945 if (self->css) vl_scalespace_delete(self->css) ;
1946 self->css = vl_scalespace_new_with_geometry(cgeom) ;
1947 }
1948 if (self->method == VL_COVDET_METHOD_HARRIS_LAPLACE ||
1949 self->method == VL_COVDET_METHOD_MULTISCALE_HARRIS) {
1950 VlScaleSpaceOctaveGeometry oct = vl_scalespace_get_octave_geometry(self->gss, geom.firstOctave) ;
1951 levelxx = vl_malloc(oct.width * oct.height * sizeof(float)) ;
1952 levelyy = vl_malloc(oct.width * oct.height * sizeof(float)) ;
1953 levelxy = vl_malloc(oct.width * oct.height * sizeof(float)) ;
1954 }
1955
1956 /* compute cornerness ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
1957 for (o = cgeom.firstOctave ; o <= cgeom.lastOctave ; ++o) {
1958 VlScaleSpaceOctaveGeometry oct = vl_scalespace_get_octave_geometry(self->css, o) ;
1959
1960 for (s = cgeom.octaveFirstSubdivision ; s <= cgeom.octaveLastSubdivision ; ++s) {
1961 float * level = vl_scalespace_get_level(self->gss, o, s) ;
1962 float * clevel = vl_scalespace_get_level(self->css, o, s) ;
1963 double sigma = vl_scalespace_get_level_sigma(self->css, o, s) ;
1964 switch (self->method) {
1965 case VL_COVDET_METHOD_DOG:
1966 _vl_dog_response(clevel,
1967 vl_scalespace_get_level(self->gss, o, s + 1),
1968 level,
1969 oct.width, oct.height) ;
1970 break ;
1971
1972 case VL_COVDET_METHOD_HARRIS_LAPLACE:
1973 case VL_COVDET_METHOD_MULTISCALE_HARRIS:
1974 _vl_harris_response(clevel,
1975 level, oct.width, oct.height, oct.step,
1976 sigma, 1.4 * sigma, 0.05) ;
1977 break ;
1978
1979 case VL_COVDET_METHOD_HESSIAN:
1980 case VL_COVDET_METHOD_HESSIAN_LAPLACE:
1981 case VL_COVDET_METHOD_MULTISCALE_HESSIAN:
1982 _vl_det_hessian_response(clevel, level, oct.width, oct.height, oct.step, sigma) ;
1983 break ;
1984
1985 default:
1986 assert(0) ;
1987 }
1988 }
1989 }
1990
1991 /* find and refine local maxima ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
1992 {
1993 vl_index * extrema = NULL ;
1994 vl_size extremaBufferSize = 0 ;
1995 vl_size numExtrema ;
1996 vl_size index ;
1997 for (o = cgeom.lastOctave; o >= cgeom.firstOctave; --o) {
1998 VlScaleSpaceOctaveGeometry octgeom = vl_scalespace_get_octave_geometry(self->css, o) ;
1999 double step = octgeom.step ;
2000 vl_size width = octgeom.width ;
2001 vl_size height = octgeom.height ;
2002 vl_size depth = cgeom.octaveLastSubdivision - cgeom.octaveFirstSubdivision + 1 ;
2003
2004 switch (self->method) {
2005 case VL_COVDET_METHOD_DOG:
2006 case VL_COVDET_METHOD_HESSIAN:
2007 {
2008 /* scale-space extrema */
2009 float const * octave =
2010 vl_scalespace_get_level(self->css, o, cgeom.octaveFirstSubdivision) ;
2011 numExtrema = vl_find_local_extrema_3(&extrema, &extremaBufferSize,
2012 octave, width, height, depth,
2013 0.8 * self->peakThreshold);
2014 for (index = 0 ; index < numExtrema ; ++index) {
2015 VlCovDetExtremum3 refined ;
2016 VlCovDetFeature feature ;
2017 vl_bool ok ;
2018 memset(&feature, 0, sizeof(feature)) ;
2019 ok = vl_refine_local_extreum_3(&refined,
2020 octave, width, height, depth,
2021 extrema[3*index+0],
2022 extrema[3*index+1],
2023 extrema[3*index+2]) ;
2024 ok &= fabs(refined.peakScore) > self->peakThreshold ;
2025 ok &= refined.edgeScore < self->edgeThreshold ;
2026 if (ok) {
2027 double sigma = cgeom.baseScale *
2028 pow(2.0, o + (refined.z + cgeom.octaveFirstSubdivision)
2029 / cgeom.octaveResolution) ;
2030 feature.frame.x = refined.x * step ;
2031 feature.frame.y = refined.y * step ;
2032 feature.frame.a11 = sigma ;
2033 feature.frame.a12 = 0.0 ;
2034 feature.frame.a21 = 0.0 ;
2035 feature.frame.a22 = sigma ;
2036 feature.o = o ;
2037 feature.s = round(refined.z) ;
2038 feature.peakScore = refined.peakScore ;
2039 feature.edgeScore = refined.edgeScore ;
2040 vl_covdet_append_feature(self, &feature) ;
2041 }
2042 }
2043 break ;
2044 }
2045
2046 default:
2047 {
2048 for (s = cgeom.octaveFirstSubdivision ; s < cgeom.octaveLastSubdivision ; ++s) {
2049 /* space extrema */
2050 float const * level = vl_scalespace_get_level(self->css,o,s) ;
2051 numExtrema = vl_find_local_extrema_2(&extrema, &extremaBufferSize,
2052 level,
2053 width, height,
2054 0.8 * self->peakThreshold);
2055 for (index = 0 ; index < numExtrema ; ++index) {
2056 VlCovDetExtremum2 refined ;
2057 VlCovDetFeature feature ;
2058 vl_bool ok ;
2059 memset(&feature, 0, sizeof(feature)) ;
2060 ok = vl_refine_local_extreum_2(&refined,
2061 level, width, height,
2062 extrema[2*index+0],
2063 extrema[2*index+1]);
2064 ok &= fabs(refined.peakScore) > self->peakThreshold ;
2065 ok &= refined.edgeScore < self->edgeThreshold ;
2066 if (ok) {
2067 double sigma = cgeom.baseScale *
2068 pow(2.0, o + (double)s / cgeom.octaveResolution) ;
2069 feature.frame.x = refined.x * step ;
2070 feature.frame.y = refined.y * step ;
2071 feature.frame.a11 = sigma ;
2072 feature.frame.a12 = 0.0 ;
2073 feature.frame.a21 = 0.0 ;
2074 feature.frame.a22 = sigma ;
2075 feature.o = o ;
2076 feature.s = s ;
2077 feature.peakScore = refined.peakScore ;
2078 feature.edgeScore = refined.edgeScore ;
2079 vl_covdet_append_feature(self, &feature) ;
2080 }
2081 }
2082 }
2083 break ;
2084 }
2085 }
2086 if (self->numFeatures >= max_num_features) {
2087 break;
2088 }
2089 } /* next octave */
2090
2091 if (extrema) { vl_free(extrema) ; extrema = 0 ; }
2092 }
2093
2094 /* Laplacian scale selection for certain methods */
2095 switch (self->method) {
2096 case VL_COVDET_METHOD_HARRIS_LAPLACE :
2097 case VL_COVDET_METHOD_HESSIAN_LAPLACE :
2098 vl_covdet_extract_laplacian_scales (self) ;
2099 break ;
2100 default:
2101 break ;
2102 }
2103
2104 if (self->nonExtremaSuppression) {
2105 vl_index i, j ;
2106 double tol = self->nonExtremaSuppression ;
2107 self->numNonExtremaSuppressed = 0 ;
2108 for (i = 0 ; i < (signed)self->numFeatures ; ++i) {
2109 double x = self->features[i].frame.x ;
2110 double y = self->features[i].frame.y ;
2111 double sigma = self->features[i].frame.a11 ;
2112 double score = self->features[i].peakScore ;
2113 if (score == 0) continue ;
2114
2115 for (j = 0 ; j < (signed)self->numFeatures ; ++j) {
2116 double dx_ = self->features[j].frame.x - x ;
2117 double dy_ = self->features[j].frame.y - y ;
2118 double sigma_ = self->features[j].frame.a11 ;
2119 double score_ = self->features[j].peakScore ;
2120 if (score_ == 0) continue ;
2121 if (sigma < (1+tol) * sigma_ &&
2122 sigma_ < (1+tol) * sigma &&
2123 vl_abs_d(dx_) < tol * sigma &&
2124 vl_abs_d(dy_) < tol * sigma &&
2125 vl_abs_d(score) > vl_abs_d(score_)) {
2126 self->features[j].peakScore = 0 ;
2127 self->numNonExtremaSuppressed ++ ;
2128 }
2129 }
2130 }
2131 j = 0 ;
2132 for (i = 0 ; i < (signed)self->numFeatures ; ++i) {
2133 VlCovDetFeature feature = self->features[i] ;
2134 if (self->features[i].peakScore != 0) {
2135 self->features[j++] = feature ;
2136 }
2137 }
2138 self->numFeatures = j ;
2139 }
2140
2141 if (levelxx) vl_free(levelxx) ;
2142 if (levelyy) vl_free(levelyy) ;
2143 if (levelxy) vl_free(levelxy) ;
2144 }
2145
2146 /* ---------------------------------------------------------------- */
2147 /* Extract patches */
2148 /* ---------------------------------------------------------------- */
2149
2150 /** @internal
2151 ** @brief Helper for extracting patches
2152 ** @param self object.
2153 ** @param[out] sigma1 actual patch smoothing along the first axis.
2154 ** @param[out] sigma2 actual patch smoothing along the second axis.
2155 ** @param patch buffer.
2156 ** @param resolution patch resolution.
2157 ** @param extent patch extent.
2158 ** @param sigma desired smoothing in the patch frame.
2159 ** @param A_ linear transfomration from patch to image.
2160 ** @param T_ translation from patch to image.
2161 ** @param d1 first singular value @a A.
2162 ** @param d2 second singular value of @a A.
2163 **/
2164
2165 vl_bool
vl_covdet_extract_patch_helper(VlCovDet * self,double * sigma1,double * sigma2,float * patch,vl_size resolution,double extent,double sigma,double A_[4],double T_[2],double d1,double d2)2166 vl_covdet_extract_patch_helper (VlCovDet * self,
2167 double * sigma1,
2168 double * sigma2,
2169 float * patch,
2170 vl_size resolution,
2171 double extent,
2172 double sigma,
2173 double A_ [4],
2174 double T_ [2],
2175 double d1, double d2)
2176 {
2177 vl_index o, s ;
2178 double factor ;
2179 double sigma_ ;
2180 float const * level ;
2181 vl_size width, height ;
2182 double step ;
2183
2184 double A [4] = {A_[0], A_[1], A_[2], A_[3]} ;
2185 double T [2] = {T_[0], T_[1]} ;
2186
2187 VlScaleSpaceGeometry geom = vl_scalespace_get_geometry(self->gss) ;
2188 VlScaleSpaceOctaveGeometry oct ;
2189
2190 /* Starting from a pre-smoothed image at scale sigma_
2191 because of the mapping A the resulting smoothing in
2192 the warped patch is S, where
2193
2194 sigma_^2 I = A S A',
2195
2196 S = sigma_^2 inv(A) inv(A)' = sigma_^2 V D^-2 V',
2197
2198 A = U D V'.
2199
2200 Thus we rotate A by V to obtain an axis-aligned smoothing:
2201
2202 A = U*D,
2203
2204 S = sigma_^2 D^-2.
2205
2206 Then we search the scale-space for the best sigma_ such
2207 that the target smoothing is approximated from below:
2208
2209 max sigma_(o,s) : simga_(o,s) factor <= sigma,
2210 factor = max{abs(D11), abs(D22)}.
2211 */
2212
2213
2214 /*
2215 Determine the best level (o,s) such that sigma_(o,s) factor <= sigma.
2216 This can be obtained by scanning octaves from smallest to largest
2217 and stopping when no level in the octave satisfies the relation.
2218
2219 Given the range of octave availables, do the best you can.
2220 */
2221
2222 factor = 1.0 / VL_MIN(d1, d2) ;
2223
2224 for (o = geom.firstOctave + 1 ; o <= geom.lastOctave ; ++o) {
2225 s = vl_floor_d(vl_log2_d(sigma / (factor * geom.baseScale)) - o) ;
2226 s = VL_MAX(s, geom.octaveFirstSubdivision) ;
2227 s = VL_MIN(s, geom.octaveLastSubdivision) ;
2228 sigma_ = geom.baseScale * pow(2.0, o + (double)s / geom.octaveResolution) ;
2229 /*VL_PRINTF(".. %d D=%g %g; sigma_=%g factor*sigma_=%g\n", o, d1, d2, sigma_, factor* sigma_) ;*/
2230 if (factor * sigma_ > sigma) {
2231 o -- ;
2232 break ;
2233 }
2234 }
2235 o = VL_MIN(o, geom.lastOctave) ;
2236 s = vl_floor_d(vl_log2_d(sigma / (factor * geom.baseScale)) - o) ;
2237 s = VL_MAX(s, geom.octaveFirstSubdivision) ;
2238 s = VL_MIN(s, geom.octaveLastSubdivision) ;
2239 sigma_ = geom.baseScale * pow(2.0, o + (double)s / geom.octaveResolution) ;
2240 if (sigma1) *sigma1 = sigma_ / d1 ;
2241 if (sigma2) *sigma2 = sigma_ / d2 ;
2242
2243 /*VL_PRINTF("%d %d %g %g %g %g\n", o, s, factor, sigma_, factor * sigma_, sigma) ;*/
2244
2245 /*
2246 Now the scale space level to be used for this warping has been
2247 determined.
2248
2249 If the patch is partially or completely out of the image boundary,
2250 create a padded copy of the required region first.
2251 */
2252
2253 level = vl_scalespace_get_level(self->gss, o, s) ;
2254 oct = vl_scalespace_get_octave_geometry(self->gss, o) ;
2255 width = oct.width ;
2256 height = oct.height ;
2257 step = oct.step ;
2258
2259 A[0] /= step ;
2260 A[1] /= step ;
2261 A[2] /= step ;
2262 A[3] /= step ;
2263 T[0] /= step ;
2264 T[1] /= step ;
2265
2266 {
2267 /*
2268 Warp the patch domain [x0hat,y0hat,x1hat,y1hat] to the image domain/
2269 Obtain a box [x0,y0,x1,y1] enclosing that wrapped box, and then
2270 an integer vertexes version [x0i, y0i, x1i, y1i], making room
2271 for one pixel at the boundary to simplify bilinear interpolation
2272 later on.
2273 */
2274 vl_index x0i, y0i, x1i, y1i ;
2275 double x0 = +VL_INFINITY_D ;
2276 double x1 = -VL_INFINITY_D ;
2277 double y0 = +VL_INFINITY_D ;
2278 double y1 = -VL_INFINITY_D ;
2279 double boxx [4] = {extent, extent, -extent, -extent} ;
2280 double boxy [4] = {-extent, extent, extent, -extent} ;
2281 int i ;
2282 for (i = 0 ; i < 4 ; ++i) {
2283 double x = A[0] * boxx[i] + A[2] * boxy[i] + T[0] ;
2284 double y = A[1] * boxx[i] + A[3] * boxy[i] + T[1] ;
2285 x0 = VL_MIN(x0, x) ;
2286 x1 = VL_MAX(x1, x) ;
2287 y0 = VL_MIN(y0, y) ;
2288 y1 = VL_MAX(y1, y) ;
2289 }
2290
2291 /* Leave one pixel border for bilinear interpolation. */
2292 x0i = floor(x0) - 1 ;
2293 y0i = floor(y0) - 1 ;
2294 x1i = ceil(x1) + 1 ;
2295 y1i = ceil(y1) + 1 ;
2296
2297 /*
2298 If the box [x0i,y0i,x1i,y1i] is not fully contained in the
2299 image domain, then create a copy of this region by padding
2300 the image. The image is extended by continuity.
2301 */
2302
2303 if (x0i < 0 || x1i > (signed)width-1 ||
2304 y0i < 0 || y1i > (signed)height-1) {
2305 vl_index xi, yi ;
2306
2307 /* compute the amount of l,r,t,b padding needed to complete the patch */
2308 vl_index padx0 = VL_MAX(0, - x0i) ;
2309 vl_index pady0 = VL_MAX(0, - y0i) ;
2310 vl_index padx1 = VL_MAX(0, x1i - ((signed)width - 1)) ;
2311 vl_index pady1 = VL_MAX(0, y1i - ((signed)height - 1)) ;
2312
2313 /* make enough room for the patch */
2314 vl_index patchWidth = x1i - x0i + 1 ;
2315 vl_index patchHeight = y1i - y0i + 1 ;
2316 vl_size patchBufferSize = patchWidth * patchHeight * sizeof(float) ;
2317 if (patchBufferSize > self->patchBufferSize) {
2318 int err = _vl_resize_buffer((void**)&self->patch, &self->patchBufferSize, patchBufferSize) ;
2319 if (err) return vl_set_last_error(VL_ERR_ALLOC, NULL) ;
2320 }
2321
2322 if (pady0 < patchHeight - pady1) {
2323 /* start by filling the central horizontal band */
2324 for (yi = y0i + pady0 ; yi < y0i + patchHeight - pady1 ; ++ yi) {
2325 float *dst = self->patch + (yi - y0i) * patchWidth ;
2326 float const *src = level + yi * width + VL_MIN(VL_MAX(0, x0i),(signed)width-1) ;
2327 for (xi = x0i ; xi < x0i + padx0 ; ++xi) *dst++ = *src ;
2328 for ( ; xi < x0i + patchWidth - padx1 - 2 ; ++xi) *dst++ = *src++ ;
2329 for ( ; xi < x0i + patchWidth ; ++xi) *dst++ = *src ;
2330 }
2331 /* now extend the central band up and down */
2332 for (yi = 0 ; yi < pady0 ; ++yi) {
2333 memcpy(self->patch + yi * patchWidth,
2334 self->patch + pady0 * patchWidth,
2335 patchWidth * sizeof(float)) ;
2336 }
2337 for (yi = patchHeight - pady1 ; yi < patchHeight ; ++yi) {
2338 memcpy(self->patch + yi * patchWidth,
2339 self->patch + (patchHeight - pady1 - 1) * patchWidth,
2340 patchWidth * sizeof(float)) ;
2341 }
2342 } else {
2343 /* should be handled better! */
2344 memset(self->patch, 0, self->patchBufferSize) ;
2345 }
2346 #if 0
2347 {
2348 char name [200] ;
2349 snprintf(name, 200, "/tmp/%20.0f-ext.pgm", 1e10*vl_get_cpu_time()) ;
2350 vl_pgm_write_f(name, patch, patchWidth, patchWidth) ;
2351 }
2352 #endif
2353
2354 level = self->patch ;
2355 width = patchWidth ;
2356 height = patchHeight ;
2357 T[0] -= x0i ;
2358 T[1] -= y0i ;
2359 }
2360 }
2361
2362 /*
2363 Resample by using bilinear interpolation.
2364 */
2365 {
2366 float * pt = patch ;
2367 double yhat = -extent ;
2368 vl_index xxi ;
2369 vl_index yyi ;
2370 double stephat = extent / resolution ;
2371
2372 for (yyi = 0 ; yyi < 2 * (signed)resolution + 1 ; ++yyi) {
2373 double xhat = -extent ;
2374 double rx = A[2] * yhat + T[0] ;
2375 double ry = A[3] * yhat + T[1] ;
2376 for (xxi = 0 ; xxi < 2 * (signed)resolution + 1 ; ++xxi) {
2377 double x = A[0] * xhat + rx ;
2378 double y = A[1] * xhat + ry ;
2379 vl_index xi = vl_floor_d(x) ;
2380 vl_index yi = vl_floor_d(y) ;
2381 double i00 = level[yi * width + xi] ;
2382 double i10 = level[yi * width + xi + 1] ;
2383 double i01 = level[(yi + 1) * width + xi] ;
2384 double i11 = level[(yi + 1) * width + xi + 1] ;
2385 double wx = x - xi ;
2386 double wy = y - yi ;
2387
2388 assert(xi >= 0 && xi <= (signed)width - 1) ;
2389 assert(yi >= 0 && yi <= (signed)height - 1) ;
2390
2391 *pt++ =
2392 (1.0 - wy) * ((1.0 - wx) * i00 + wx * i10) +
2393 wy * ((1.0 - wx) * i01 + wx * i11) ;
2394
2395 xhat += stephat ;
2396 }
2397 yhat += stephat ;
2398 }
2399 }
2400 #if 0
2401 {
2402 char name [200] ;
2403 snprintf(name, 200, "/tmp/%20.0f.pgm", 1e10*vl_get_cpu_time()) ;
2404 vl_pgm_write_f(name, patch, 2*resolution+1, 2*resolution+1) ;
2405 }
2406 #endif
2407 return VL_ERR_OK ;
2408 }
2409
2410 /** @brief Helper for extracting patches
2411 ** @param self object.
2412 ** @param patch buffer.
2413 ** @param resolution patch resolution.
2414 ** @param extent patch extent.
2415 ** @param sigma desired smoothing in the patch frame.
2416 ** @param frame feature frame.
2417 **
2418 ** The function considers a patch of extent <code>[-extent,extent]</code>
2419 ** on each side, with a side counting <code>2*resolution+1</code> pixels.
2420 ** In attempts to extract from the scale space a patch
2421 ** based on the affine warping specified by @a frame in such a way
2422 ** that the resulting smoothing of the image is @a sigma (in the
2423 ** patch frame).
2424 **
2425 ** The transformation is specified by the matrices @c A and @c T
2426 ** embedded in the feature @a frame. Note that this transformation maps
2427 ** pixels from the patch frame to the image frame.
2428 **/
2429
2430 vl_bool
vl_covdet_extract_patch_for_frame(VlCovDet * self,float * patch,vl_size resolution,double extent,double sigma,VlFrameOrientedEllipse frame)2431 vl_covdet_extract_patch_for_frame (VlCovDet * self,
2432 float * patch,
2433 vl_size resolution,
2434 double extent,
2435 double sigma,
2436 VlFrameOrientedEllipse frame)
2437 {
2438 double A[2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
2439 double T[2] = {frame.x, frame.y} ;
2440 double D[4], U[4], V[4] ;
2441
2442 vl_svd2(D, U, V, A) ;
2443
2444 return vl_covdet_extract_patch_helper
2445 (self, NULL, NULL, patch, resolution, extent, sigma, A, T, D[0], D[3]) ;
2446 }
2447
2448 /* ---------------------------------------------------------------- */
2449 /* Affine shape */
2450 /* ---------------------------------------------------------------- */
2451
2452 /** @brief Extract the affine shape for a feature frame
2453 ** @param self object.
2454 ** @param adapted the shape-adapted frame.
2455 ** @param frame the input frame.
2456 ** @return ::VL_ERR_OK if affine adaptation is successful.
2457 **
2458 ** This function may fail if adaptation is unsuccessful or if
2459 ** memory is insufficient.
2460 **/
2461
2462 int
vl_covdet_extract_affine_shape_for_frame(VlCovDet * self,VlFrameOrientedEllipse * adapted,VlFrameOrientedEllipse frame)2463 vl_covdet_extract_affine_shape_for_frame (VlCovDet * self,
2464 VlFrameOrientedEllipse * adapted,
2465 VlFrameOrientedEllipse frame)
2466 {
2467 vl_index iter = 0 ;
2468
2469 double A [2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
2470 double T [2] = {frame.x, frame.y} ;
2471 double U [2*2] ;
2472 double V [2*2] ;
2473 double D [2*2] ;
2474 double M [2*2] ;
2475 double P [2*2] ;
2476 double P_ [2*2] ;
2477 double Q [2*2] ;
2478 double sigma1, sigma2 ;
2479 double sigmaD = VL_COVDET_AA_RELATIVE_DERIVATIVE_SIGMA ;
2480 double factor ;
2481 double anisotropy ;
2482 double referenceScale ;
2483 vl_size const resolution = VL_COVDET_AA_PATCH_RESOLUTION ;
2484 vl_size const side = 2*VL_COVDET_AA_PATCH_RESOLUTION + 1 ;
2485 double const extent = VL_COVDET_AA_PATCH_EXTENT ;
2486
2487
2488 *adapted = frame ;
2489
2490 while (1) {
2491 double lxx = 0, lxy = 0, lyy = 0 ;
2492 vl_index k ;
2493 int err ;
2494
2495 /* A = U D V' */
2496 vl_svd2(D, U, V, A) ;
2497 anisotropy = VL_MAX(D[0]/D[3], D[3]/D[0]) ;
2498
2499 /* VL_PRINTF("anisot: %g\n", anisotropy); */
2500
2501 if (anisotropy > VL_COVDET_AA_MAX_ANISOTROPY) {
2502 /* diverged, give up with current solution */
2503 break ;
2504 }
2505
2506 /* make sure that the smallest singluar value stays fixed
2507 after the first iteration */
2508 if (iter == 0) {
2509 referenceScale = VL_MIN(D[0], D[3]) ;
2510 factor = 1.0 ;
2511 } else {
2512 factor = referenceScale / VL_MIN(D[0],D[3]) ;
2513 }
2514
2515 D[0] *= factor ;
2516 D[3] *= factor ;
2517
2518 A[0] = U[0] * D[0] ;
2519 A[1] = U[1] * D[0] ;
2520 A[2] = U[2] * D[3] ;
2521 A[3] = U[3] * D[3] ;
2522
2523 adapted->a11 = A[0] ;
2524 adapted->a21 = A[1] ;
2525 adapted->a12 = A[2] ;
2526 adapted->a22 = A[3] ;
2527
2528 if (++iter >= VL_COVDET_AA_MAX_NUM_ITERATIONS) break ;
2529
2530 err = vl_covdet_extract_patch_helper(self,
2531 &sigma1, &sigma2,
2532 self->aaPatch,
2533 resolution,
2534 extent,
2535 sigmaD,
2536 A, T, D[0], D[3]) ;
2537 if (err) return err ;
2538
2539 if (self->aaAccurateSmoothing ) {
2540 double deltaSigma1 = sqrt(VL_MAX(sigmaD*sigmaD - sigma1*sigma1,0)) ;
2541 double deltaSigma2 = sqrt(VL_MAX(sigmaD*sigmaD - sigma2*sigma2,0)) ;
2542 double stephat = extent / resolution ;
2543 vl_imsmooth_f(self->aaPatch, side,
2544 self->aaPatch, side, side, side,
2545 deltaSigma1 / stephat, deltaSigma2 / stephat) ;
2546 }
2547
2548 /* compute second moment matrix */
2549 vl_imgradient_f (self->aaPatchX, self->aaPatchY, 1, side,
2550 self->aaPatch, side, side, side) ;
2551
2552 for (k = 0 ; k < (signed)(side*side) ; ++k) {
2553 double lx = self->aaPatchX[k] ;
2554 double ly = self->aaPatchY[k] ;
2555 lxx += lx * lx * self->aaMask[k] ;
2556 lyy += ly * ly * self->aaMask[k] ;
2557 lxy += lx * ly * self->aaMask[k] ;
2558 }
2559 M[0] = lxx ;
2560 M[1] = lxy ;
2561 M[2] = lxy ;
2562 M[3] = lyy ;
2563
2564 if (lxx == 0 || lyy == 0) {
2565 *adapted = frame ;
2566 break ;
2567 }
2568
2569 /* decompose M = P * Q * P' */
2570 vl_svd2 (Q, P, P_, M) ;
2571
2572 /*
2573 Setting A <- A * dA results in M to change approximatively as
2574
2575 M --> dA' M dA = dA' P Q P dA
2576
2577 To make this proportional to the identity, we set
2578
2579 dA ~= P Q^1/2
2580
2581 we also make it so the smallest singular value of A is unchanged.
2582 */
2583
2584 if (Q[3]/Q[0] < VL_COVDET_AA_CONVERGENCE_THRESHOLD &&
2585 Q[0]/Q[3] < VL_COVDET_AA_CONVERGENCE_THRESHOLD) {
2586 break ;
2587 }
2588
2589 {
2590 double Ap [4] ;
2591 double q0 = sqrt(Q[0]) ;
2592 double q1 = sqrt(Q[3]) ;
2593 Ap[0] = (A[0] * P[0] + A[2] * P[1]) / q0 ;
2594 Ap[1] = (A[1] * P[0] + A[3] * P[1]) / q0 ;
2595 Ap[2] = (A[0] * P[2] + A[2] * P[3]) / q1 ;
2596 Ap[3] = (A[1] * P[2] + A[3] * P[3]) / q1 ;
2597 memcpy(A,Ap,4*sizeof(double)) ;
2598 }
2599
2600 } /* next iteration */
2601
2602 /*
2603 Make upright.
2604
2605 Shape adaptation does not estimate rotation. This is fixed by default
2606 so that a selected axis is not rotated at all (usually this is the
2607 vertical axis for upright features). To do so, the frame is rotated
2608 as follows.
2609 */
2610 {
2611 double A [2*2] = {adapted->a11, adapted->a21, adapted->a12, adapted->a22} ;
2612 double ref [2] ;
2613 double ref_ [2] ;
2614 double angle ;
2615 double angle_ ;
2616 double dangle ;
2617 double r1, r2 ;
2618
2619 if (self->transposed) {
2620 /* up is the x axis */
2621 ref[0] = 1 ;
2622 ref[1] = 0 ;
2623 } else {
2624 /* up is the y axis */
2625 ref[0] = 0 ;
2626 ref[1] = 1 ;
2627 }
2628
2629 vl_solve_linear_system_2 (ref_, A, ref) ;
2630 angle = atan2(ref[1], ref[0]) ;
2631 angle_ = atan2(ref_[1], ref_[0]) ;
2632 dangle = angle_ - angle ;
2633 r1 = cos(dangle) ;
2634 r2 = sin(dangle) ;
2635 adapted->a11 = + A[0] * r1 + A[2] * r2 ;
2636 adapted->a21 = + A[1] * r1 + A[3] * r2 ;
2637 adapted->a12 = - A[0] * r2 + A[2] * r1 ;
2638 adapted->a22 = - A[1] * r2 + A[3] * r1 ;
2639 }
2640
2641 return VL_ERR_OK ;
2642 }
2643
2644 /** @brief Extract the affine shape for the stored features
2645 ** @param self object.
2646 **
2647 ** This function may discard features for which no affine
2648 ** shape can reliably be detected.
2649 **/
2650
2651 void
vl_covdet_extract_affine_shape(VlCovDet * self)2652 vl_covdet_extract_affine_shape (VlCovDet * self)
2653 {
2654 vl_index i, j = 0 ;
2655 vl_size numFeatures = vl_covdet_get_num_features(self) ;
2656 VlCovDetFeature * feature = vl_covdet_get_features(self);
2657 for (i = 0 ; i < (signed)numFeatures ; ++i) {
2658 int status ;
2659 VlFrameOrientedEllipse adapted ;
2660 status = vl_covdet_extract_affine_shape_for_frame(self, &adapted, feature[i].frame) ;
2661 if (status == VL_ERR_OK) {
2662 feature[j] = feature[i] ;
2663 feature[j].frame = adapted ;
2664 ++ j ;
2665 }
2666 }
2667 self->numFeatures = j ;
2668 }
2669
2670 /* ---------------------------------------------------------------- */
2671 /* Orientation */
2672 /* ---------------------------------------------------------------- */
2673
2674 static int
_vl_covdet_compare_orientations_descending(void const * a_,void const * b_)2675 _vl_covdet_compare_orientations_descending (void const * a_,
2676 void const * b_)
2677 {
2678 VlCovDetFeatureOrientation const * a = a_ ;
2679 VlCovDetFeatureOrientation const * b = b_ ;
2680 if (a->score > b->score) return -1 ;
2681 if (a->score < b->score) return +1 ;
2682 return 0 ;
2683 }
2684
2685 /** @brief Extract the orientation(s) for a feature
2686 ** @param self object.
2687 ** @param numOrientations the number of detected orientations.
2688 ** @param frame pose of the feature.
2689 ** @return an array of detected orientations with their scores.
2690 **
2691 ** The returned array is a matrix of size @f$ 2 \times n @f$
2692 ** where <em>n</em> is the number of detected orientations.
2693 **
2694 ** The function returns @c NULL if memory is insufficient.
2695 **/
2696
2697 VlCovDetFeatureOrientation *
vl_covdet_extract_orientations_for_frame(VlCovDet * self,vl_size * numOrientations,VlFrameOrientedEllipse frame)2698 vl_covdet_extract_orientations_for_frame (VlCovDet * self,
2699 vl_size * numOrientations,
2700 VlFrameOrientedEllipse frame)
2701 {
2702 int err ;
2703 vl_index k, i ;
2704 vl_index iter ;
2705
2706 double extent = VL_COVDET_AA_PATCH_EXTENT ;
2707 vl_size resolution = VL_COVDET_AA_PATCH_RESOLUTION ;
2708 vl_size side = 2 * resolution + 1 ;
2709
2710 vl_size const numBins = VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS ;
2711 double hist [VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS] ;
2712 double const binExtent = 2 * VL_PI / VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS ;
2713 double const peakRelativeSize = VL_COVDET_OR_ADDITIONAL_PEAKS_RELATIVE_SIZE ;
2714 double maxPeakValue ;
2715
2716 double A [2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
2717 double T [2] = {frame.x, frame.y} ;
2718 double U [2*2] ;
2719 double V [2*2] ;
2720 double D [2*2] ;
2721 double sigma1, sigma2 ;
2722 double sigmaD = 1.0 ;
2723 double theta0 ;
2724
2725 assert(self);
2726 assert(numOrientations) ;
2727
2728 /*
2729 The goal is to estimate a rotation R(theta) such that the patch given
2730 by the transformation A R(theta) has the strongest average
2731 gradient pointing right (or down for transposed conventions).
2732
2733 To compensate for tha anisotropic smoothing due to warping,
2734 A is decomposed as A = U D V' and the patch is warped by
2735 U D only, meaning that the matrix R_(theta) will be estimated instead,
2736 where:
2737
2738 A R(theta) = U D V' R(theta) = U D R_(theta)
2739
2740 such that R(theta) = V R(theta). That is an extra rotation of
2741 theta0 = atan2(V(2,1),V(1,1)).
2742 */
2743
2744 /* axis aligned anisotropic smoothing for easier compensation */
2745 vl_svd2(D, U, V, A) ;
2746
2747 A[0] = U[0] * D[0] ;
2748 A[1] = U[1] * D[0] ;
2749 A[2] = U[2] * D[3] ;
2750 A[3] = U[3] * D[3] ;
2751
2752 theta0 = atan2(V[1],V[0]) ;
2753
2754 err = vl_covdet_extract_patch_helper(self,
2755 &sigma1, &sigma2,
2756 self->aaPatch,
2757 resolution,
2758 extent,
2759 sigmaD,
2760 A, T, D[0], D[3]) ;
2761
2762 if (err) {
2763 *numOrientations = 0 ;
2764 return NULL ;
2765 }
2766
2767 if (1) {
2768 double deltaSigma1 = sqrt(VL_MAX(sigmaD*sigmaD - sigma1*sigma1,0)) ;
2769 double deltaSigma2 = sqrt(VL_MAX(sigmaD*sigmaD - sigma2*sigma2,0)) ;
2770 double stephat = extent / resolution ;
2771 vl_imsmooth_f(self->aaPatch, side,
2772 self->aaPatch, side, side, side,
2773 deltaSigma1 / stephat, deltaSigma2 / stephat) ;
2774 }
2775
2776 /* histogram of oriented gradients */
2777 vl_imgradient_polar_f (self->aaPatchX, self->aaPatchY, 1, side,
2778 self->aaPatch, side, side, side) ;
2779
2780 memset (hist, 0, sizeof(double) * numBins) ;
2781
2782 for (k = 0 ; k < (signed)(side*side) ; ++k) {
2783 double modulus = self->aaPatchX[k] ;
2784 double angle = self->aaPatchY[k] ;
2785 double weight = self->aaMask[k] ;
2786
2787 double x = angle / binExtent ;
2788 vl_index bin = vl_floor_d(x) ;
2789 double w2 = x - bin ;
2790 double w1 = 1.0 - w2 ;
2791
2792 hist[(bin + numBins) % numBins] += w1 * (modulus * weight) ;
2793 hist[(bin + numBins + 1) % numBins] += w2 * (modulus * weight) ;
2794 }
2795
2796 /* smooth histogram */
2797 for (iter = 0; iter < 6; iter ++) {
2798 double prev = hist [numBins - 1] ;
2799 double first = hist [0] ;
2800 vl_index i ;
2801 for (i = 0; i < (signed)numBins - 1; ++i) {
2802 double curr = (prev + hist[i] + hist[(i + 1) % numBins]) / 3.0 ;
2803 prev = hist[i] ;
2804 hist[i] = curr ;
2805 }
2806 hist[i] = (prev + hist[i] + first) / 3.0 ;
2807 }
2808
2809 /* find the histogram maximum */
2810 maxPeakValue = 0 ;
2811 for (i = 0 ; i < (signed)numBins ; ++i) {
2812 maxPeakValue = VL_MAX (maxPeakValue, hist[i]) ;
2813 }
2814
2815 /* find peaks within 80% from max */
2816 *numOrientations = 0 ;
2817 for(i = 0 ; i < (signed)numBins ; ++i) {
2818 double h0 = hist [i] ;
2819 double hm = hist [(i - 1 + numBins) % numBins] ;
2820 double hp = hist [(i + 1 + numBins) % numBins] ;
2821
2822 /* is this a peak? */
2823 if (h0 > peakRelativeSize * maxPeakValue && h0 > hm && h0 > hp) {
2824 /* quadratic interpolation */
2825 double di = - 0.5 * (hp - hm) / (hp + hm - 2 * h0) ;
2826 double th = binExtent * (i + di) + theta0 ;
2827 if (self->transposed) {
2828 /* the axis to the right is y, measure orientations from this */
2829 th = th - VL_PI/2 ;
2830 }
2831 self->orientations[*numOrientations].angle = th ;
2832 self->orientations[*numOrientations].score = h0 ;
2833 *numOrientations += 1 ;
2834 //VL_PRINTF("%d %g\n", *numOrientations, th) ;
2835
2836 if (*numOrientations >= VL_COVDET_MAX_NUM_ORIENTATIONS) break ;
2837 }
2838 }
2839
2840 /* sort the orientations by decreasing scores */
2841 qsort(self->orientations,
2842 *numOrientations,
2843 sizeof(VlCovDetFeatureOrientation),
2844 _vl_covdet_compare_orientations_descending) ;
2845
2846 return self->orientations ;
2847 }
2848
2849 /** @brief Extract the orientation(s) for the stored features.
2850 ** @param self object.
2851 **
2852 ** Note that, since more than one orientation can be detected
2853 ** for each feature, this function may create copies of them,
2854 ** one for each orientation.
2855 **/
2856
2857 void
vl_covdet_extract_orientations(VlCovDet * self)2858 vl_covdet_extract_orientations (VlCovDet * self)
2859 {
2860 vl_index i, j ;
2861 vl_size numFeatures = vl_covdet_get_num_features(self) ;
2862 for (i = 0 ; i < (signed)numFeatures ; ++i) {
2863 vl_size numOrientations ;
2864 VlCovDetFeature feature = self->features[i] ;
2865 VlCovDetFeatureOrientation* orientations =
2866 vl_covdet_extract_orientations_for_frame(self, &numOrientations, feature.frame) ;
2867
2868 for (j = 0 ; j < (signed)numOrientations ; ++j) {
2869 double A [2*2] = {
2870 feature.frame.a11,
2871 feature.frame.a21,
2872 feature.frame.a12,
2873 feature.frame.a22} ;
2874 double r1 = cos(orientations[j].angle) ;
2875 double r2 = sin(orientations[j].angle) ;
2876 VlCovDetFeature * oriented ;
2877
2878 if (j == 0) {
2879 oriented = & self->features[i] ;
2880 } else {
2881 vl_covdet_append_feature(self, &feature) ;
2882 oriented = & self->features[self->numFeatures -1] ;
2883 }
2884
2885 oriented->orientationScore = orientations[j].score ;
2886 oriented->frame.a11 = + A[0] * r1 + A[2] * r2 ;
2887 oriented->frame.a21 = + A[1] * r1 + A[3] * r2 ;
2888 oriented->frame.a12 = - A[0] * r2 + A[2] * r1 ;
2889 oriented->frame.a22 = - A[1] * r2 + A[3] * r1 ;
2890 }
2891 }
2892 }
2893
2894 /* ---------------------------------------------------------------- */
2895 /* Laplacian scales */
2896 /* ---------------------------------------------------------------- */
2897
2898 /** @brief Extract the Laplacian scale(s) for a feature frame.
2899 ** @param self object.
2900 ** @param numScales the number of detected scales.
2901 ** @param frame pose of the feature.
2902 ** @return an array of detected scales.
2903 **
2904 ** The function returns @c NULL if memory is insufficient.
2905 **/
2906
2907 VlCovDetFeatureLaplacianScale *
vl_covdet_extract_laplacian_scales_for_frame(VlCovDet * self,vl_size * numScales,VlFrameOrientedEllipse frame)2908 vl_covdet_extract_laplacian_scales_for_frame (VlCovDet * self,
2909 vl_size * numScales,
2910 VlFrameOrientedEllipse frame)
2911 {
2912 /*
2913 We try to explore one octave, with the nominal detection scale 1.0
2914 (in the patch reference frame) in the middle. Thus the goal is to sample
2915 the response of the tr-Laplacian operator at logarithmically
2916 spaced scales in 1/sqrt(2), sqrt(2).
2917
2918 To this end, the patch is warped with a smoothing of at most
2919 sigmaImage = 1 / sqrt(2) (beginning of the scale), sampled at
2920 roughly twice the Nyquist frequency (so step = 1 / (2*sqrt(2))).
2921 This maes it possible to approximate the Laplacian operator at
2922 that scale by simple finite differences.
2923
2924 */
2925 int err ;
2926 double const sigmaImage = 1.0 / sqrt(2.0) ;
2927 double const step = 0.5 * sigmaImage ;
2928 double actualSigmaImage ;
2929 vl_size const resolution = VL_COVDET_LAP_PATCH_RESOLUTION ;
2930 vl_size const num = 2 * resolution + 1 ;
2931 double extent = step * resolution ;
2932 double scores [VL_COVDET_LAP_NUM_LEVELS] ;
2933 double factor = 1.0 ;
2934 float const * pt ;
2935 vl_index k ;
2936
2937 double A[2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
2938 double T[2] = {frame.x, frame.y} ;
2939 double D[4], U[4], V[4] ;
2940 double sigma1, sigma2 ;
2941
2942 assert(self) ;
2943 assert(numScales) ;
2944
2945 *numScales = 0 ;
2946
2947 vl_svd2(D, U, V, A) ;
2948
2949 err = vl_covdet_extract_patch_helper
2950 (self, &sigma1, &sigma2, self->lapPatch, resolution, extent, sigmaImage, A, T, D[0], D[3]) ;
2951 if (err) return NULL ;
2952
2953 /* the actual smoothing after warping is never the target one */
2954 if (sigma1 == sigma2) {
2955 actualSigmaImage = sigma1 ;
2956 } else {
2957 /* here we could compensate */
2958 actualSigmaImage = sqrt(sigma1*sigma2) ;
2959 }
2960
2961 /* now multiply by the bank of Laplacians */
2962 pt = self->laplacians ;
2963 for (k = 0 ; k < VL_COVDET_LAP_NUM_LEVELS ; ++k) {
2964 vl_index q ;
2965 double score = 0 ;
2966 double sigmaLap = pow(2.0, -0.5 + (double)k / (VL_COVDET_LAP_NUM_LEVELS - 1)) ;
2967 /* note that the sqrt argument cannot be negative since by construction
2968 sigmaLap >= sigmaImage */
2969 sigmaLap = sqrt(sigmaLap*sigmaLap
2970 - sigmaImage*sigmaImage
2971 + actualSigmaImage*actualSigmaImage) ;
2972
2973 for (q = 0 ; q < (signed)(num * num) ; ++q) {
2974 score += (*pt++) * self->lapPatch[q] ;
2975 }
2976 scores[k] = score * sigmaLap * sigmaLap ;
2977 }
2978
2979 /* find and interpolate maxima */
2980 for (k = 1 ; k < VL_COVDET_LAP_NUM_LEVELS - 1 ; ++k) {
2981 double a = scores[k-1] ;
2982 double b = scores[k] ;
2983 double c = scores[k+1] ;
2984 double t = self->lapPeakThreshold ;
2985
2986 if ((((b > a) && (b > c)) || ((b < a) && (b < c))) && (vl_abs_d(b) >= t)) {
2987 double dk = - 0.5 * (c - a) / (c + a - 2 * b) ;
2988 double s = k + dk ;
2989 double sigmaLap = pow(2.0, -0.5 + s / (VL_COVDET_LAP_NUM_LEVELS - 1)) ;
2990 double scale ;
2991 sigmaLap = sqrt(sigmaLap*sigmaLap
2992 - sigmaImage*sigmaImage
2993 + actualSigmaImage*actualSigmaImage) ;
2994 scale = sigmaLap / 1.0 ;
2995 /*
2996 VL_PRINTF("** k:%d, s:%f, sigmaLapFilter:%f, sigmaLap%f, scale:%f (%f %f %f)\n",
2997 k,s,sigmaLapFilter,sigmaLap,scale,a,b,c) ;
2998 */
2999 if (*numScales < VL_COVDET_MAX_NUM_LAPLACIAN_SCALES) {
3000 self->scales[*numScales].scale = scale * factor ;
3001 self->scales[*numScales].score = b + 0.5 * (c - a) * dk ;
3002 *numScales += 1 ;
3003 }
3004 }
3005 }
3006 return self->scales ;
3007 }
3008
3009 /** @brief Extract the Laplacian scales for the stored features
3010 ** @param self object.
3011 **
3012 ** Note that, since more than one orientation can be detected
3013 ** for each feature, this function may create copies of them,
3014 ** one for each orientation.
3015 **/
3016 void
vl_covdet_extract_laplacian_scales(VlCovDet * self)3017 vl_covdet_extract_laplacian_scales (VlCovDet * self)
3018 {
3019 vl_index i, j ;
3020 vl_bool dropFeaturesWithoutScale = VL_TRUE ;
3021 vl_size numFeatures = vl_covdet_get_num_features(self) ;
3022 memset(self->numFeaturesWithNumScales, 0,
3023 sizeof(self->numFeaturesWithNumScales)) ;
3024
3025 for (i = 0 ; i < (signed)numFeatures ; ++i) {
3026 vl_size numScales ;
3027 VlCovDetFeature feature = self->features[i] ;
3028 VlCovDetFeatureLaplacianScale const * scales =
3029 vl_covdet_extract_laplacian_scales_for_frame(self, &numScales, feature.frame) ;
3030
3031 self->numFeaturesWithNumScales[numScales] ++ ;
3032
3033 if (numScales == 0 && dropFeaturesWithoutScale) {
3034 self->features[i].peakScore = 0 ;
3035 }
3036
3037 for (j = 0 ; j < (signed)numScales ; ++j) {
3038 VlCovDetFeature * scaled ;
3039
3040 if (j == 0) {
3041 scaled = & self->features[i] ;
3042 } else {
3043 vl_covdet_append_feature(self, &feature) ;
3044 scaled = & self->features[self->numFeatures -1] ;
3045 }
3046
3047 scaled->laplacianScaleScore = scales[j].score ;
3048 scaled->frame.a11 *= scales[j].scale ;
3049 scaled->frame.a21 *= scales[j].scale ;
3050 scaled->frame.a12 *= scales[j].scale ;
3051 scaled->frame.a22 *= scales[j].scale ;
3052 }
3053 }
3054 if (dropFeaturesWithoutScale) {
3055 j = 0 ;
3056 for (i = 0 ; i < (signed)self->numFeatures ; ++i) {
3057 VlCovDetFeature feature = self->features[i] ;
3058 if (feature.peakScore) {
3059 self->features[j++] = feature ;
3060 }
3061 }
3062 self->numFeatures = j ;
3063 }
3064
3065 }
3066
3067 /* ---------------------------------------------------------------- */
3068 /* Checking that features are inside an image */
3069 /* ---------------------------------------------------------------- */
3070
3071 vl_bool
_vl_covdet_check_frame_inside(VlCovDet * self,VlFrameOrientedEllipse frame,double margin)3072 _vl_covdet_check_frame_inside (VlCovDet * self, VlFrameOrientedEllipse frame, double margin)
3073 {
3074 double extent = margin ;
3075 double A [2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
3076 double T[2] = {frame.x, frame.y} ;
3077 double x0 = +VL_INFINITY_D ;
3078 double x1 = -VL_INFINITY_D ;
3079 double y0 = +VL_INFINITY_D ;
3080 double y1 = -VL_INFINITY_D ;
3081 double boxx [4] = {extent, extent, -extent, -extent} ;
3082 double boxy [4] = {-extent, extent, extent, -extent} ;
3083 VlScaleSpaceGeometry geom = vl_scalespace_get_geometry(self->gss) ;
3084 int i ;
3085 for (i = 0 ; i < 4 ; ++i) {
3086 double x = A[0] * boxx[i] + A[2] * boxy[i] + T[0] ;
3087 double y = A[1] * boxx[i] + A[3] * boxy[i] + T[1] ;
3088 x0 = VL_MIN(x0, x) ;
3089 x1 = VL_MAX(x1, x) ;
3090 y0 = VL_MIN(y0, y) ;
3091 y1 = VL_MAX(y1, y) ;
3092 }
3093
3094 return
3095 0 <= x0 && x1 <= geom.width-1 &&
3096 0 <= y0 && y1 <= geom.height-1 ;
3097 }
3098
3099 /** @brief Drop features (partially) outside the image
3100 ** @param self object.
3101 ** @param margin geometric marging.
3102 **
3103 ** The feature extent is defined by @c maring. A bounding box
3104 ** in the normalised feature frame containin a circle of radius
3105 ** @a maring is created and mapped to the image by
3106 ** the feature frame transformation. Then the feature
3107 ** is dropped if the bounding box is not contained in the image.
3108 **
3109 ** For example, setting @c margin to zero drops a feature only
3110 ** if its center is not contained.
3111 **
3112 ** Typically a valua of @c margin equal to 1 or 2 is used.
3113 **/
3114
3115 void
vl_covdet_drop_features_outside(VlCovDet * self,double margin)3116 vl_covdet_drop_features_outside (VlCovDet * self, double margin)
3117 {
3118 vl_index i, j = 0 ;
3119 vl_size numFeatures = vl_covdet_get_num_features(self) ;
3120 for (i = 0 ; i < (signed)numFeatures ; ++i) {
3121 vl_bool inside =
3122 _vl_covdet_check_frame_inside (self, self->features[i].frame, margin) ;
3123 if (inside) {
3124 self->features[j] = self->features[i] ;
3125 ++j ;
3126 }
3127 }
3128 self->numFeatures = j ;
3129 }
3130
3131 /* ---------------------------------------------------------------- */
3132 /* Setters and getters */
3133 /* ---------------------------------------------------------------- */
3134
3135 /* ---------------------------------------------------------------- */
3136 /** @brief Get wether images are passed in transposed
3137 ** @param self object.
3138 ** @return whether images are transposed.
3139 **/
3140 vl_bool
vl_covdet_get_transposed(VlCovDet const * self)3141 vl_covdet_get_transposed (VlCovDet const * self)
3142 {
3143 return self->transposed ;
3144 }
3145
3146 /** @brief Set the index of the first octave
3147 ** @param self object.
3148 ** @param t whether images are transposed.
3149 **/
3150 void
vl_covdet_set_transposed(VlCovDet * self,vl_bool t)3151 vl_covdet_set_transposed (VlCovDet * self, vl_bool t)
3152 {
3153 self->transposed = t ;
3154 }
3155
3156 /* ---------------------------------------------------------------- */
3157 /** @brief Get the edge threshold
3158 ** @param self object.
3159 ** @return the edge threshold.
3160 **/
3161 double
vl_covdet_get_edge_threshold(VlCovDet const * self)3162 vl_covdet_get_edge_threshold (VlCovDet const * self)
3163 {
3164 return self->edgeThreshold ;
3165 }
3166
3167 /** @brief Set the edge threshold
3168 ** @param self object.
3169 ** @param edgeThreshold the edge threshold.
3170 **
3171 ** The edge threshold must be non-negative.
3172 **/
3173 void
vl_covdet_set_edge_threshold(VlCovDet * self,double edgeThreshold)3174 vl_covdet_set_edge_threshold (VlCovDet * self, double edgeThreshold)
3175 {
3176 assert(edgeThreshold >= 0) ;
3177 self->edgeThreshold = edgeThreshold ;
3178 }
3179
3180 /* ---------------------------------------------------------------- */
3181 /** @brief Get the peak threshold
3182 ** @param self object.
3183 ** @return the peak threshold.
3184 **/
3185 double
vl_covdet_get_peak_threshold(VlCovDet const * self)3186 vl_covdet_get_peak_threshold (VlCovDet const * self)
3187 {
3188 return self->peakThreshold ;
3189 }
3190
3191 /** @brief Set the peak threshold
3192 ** @param self object.
3193 ** @param peakThreshold the peak threshold.
3194 **
3195 ** The peak threshold must be non-negative.
3196 **/
3197 void
vl_covdet_set_peak_threshold(VlCovDet * self,double peakThreshold)3198 vl_covdet_set_peak_threshold (VlCovDet * self, double peakThreshold)
3199 {
3200 assert(peakThreshold >= 0) ;
3201 self->peakThreshold = peakThreshold ;
3202 }
3203
3204 /* ---------------------------------------------------------------- */
3205 /** @brief Get the Laplacian peak threshold
3206 ** @param self object.
3207 ** @return the Laplacian peak threshold.
3208 **
3209 ** This parameter affects only the detecors using the Laplacian
3210 ** scale selectino method such as Harris-Laplace.
3211 **/
3212 double
vl_covdet_get_laplacian_peak_threshold(VlCovDet const * self)3213 vl_covdet_get_laplacian_peak_threshold (VlCovDet const * self)
3214 {
3215 return self->lapPeakThreshold ;
3216 }
3217
3218 /** @brief Set the Laplacian peak threshold
3219 ** @param self object.
3220 ** @param peakThreshold the Laplacian peak threshold.
3221 **
3222 ** The peak threshold must be non-negative.
3223 **/
3224 void
vl_covdet_set_laplacian_peak_threshold(VlCovDet * self,double peakThreshold)3225 vl_covdet_set_laplacian_peak_threshold (VlCovDet * self, double peakThreshold)
3226 {
3227 assert(peakThreshold >= 0) ;
3228 self->lapPeakThreshold = peakThreshold ;
3229 }
3230
3231 /* ---------------------------------------------------------------- */
3232 /** @brief Get the index of the first octave
3233 ** @param self object.
3234 ** @return index of the first octave.
3235 **/
3236 vl_index
vl_covdet_get_first_octave(VlCovDet const * self)3237 vl_covdet_get_first_octave (VlCovDet const * self)
3238 {
3239 return self->firstOctave ;
3240 }
3241
3242 /** @brief Set the index of the first octave
3243 ** @param self object.
3244 ** @param o index of the first octave.
3245 **
3246 ** Calling this function resets the detector.
3247 **/
3248 void
vl_covdet_set_first_octave(VlCovDet * self,vl_index o)3249 vl_covdet_set_first_octave (VlCovDet * self, vl_index o)
3250 {
3251 self->firstOctave = o ;
3252 vl_covdet_reset(self) ;
3253 }
3254
3255 /* ---------------------------------------------------------------- */
3256 /** @brief Get the octave resolution.
3257 ** @param self object.
3258 ** @return octave resolution.
3259 **/
3260
3261 vl_size
vl_covdet_get_octave_resolution(VlCovDet const * self)3262 vl_covdet_get_octave_resolution (VlCovDet const * self)
3263 {
3264 return self->octaveResolution ;
3265 }
3266
3267 /** @brief Set the octave resolutuon.
3268 ** @param self object.
3269 ** @param r octave resoltuion.
3270 **
3271 ** Calling this function resets the detector.
3272 **/
3273
3274 void
vl_covdet_set_octave_resolution(VlCovDet * self,vl_size r)3275 vl_covdet_set_octave_resolution (VlCovDet * self, vl_size r)
3276 {
3277 self->octaveResolution = r ;
3278 vl_covdet_reset(self) ;
3279 }
3280
3281 /* ---------------------------------------------------------------- */
3282 /** @brief Get whether affine adaptation uses accurate smoothing.
3283 ** @param self object.
3284 ** @return @c true if accurate smoothing is used.
3285 **/
3286
3287 vl_bool
vl_covdet_get_aa_accurate_smoothing(VlCovDet const * self)3288 vl_covdet_get_aa_accurate_smoothing (VlCovDet const * self)
3289 {
3290 return self->aaAccurateSmoothing ;
3291 }
3292
3293 /** @brief Set whether affine adaptation uses accurate smoothing.
3294 ** @param self object.
3295 ** @param x whether accurate smoothing should be usd.
3296 **/
3297
3298 void
vl_covdet_set_aa_accurate_smoothing(VlCovDet * self,vl_bool x)3299 vl_covdet_set_aa_accurate_smoothing (VlCovDet * self, vl_bool x)
3300 {
3301 self->aaAccurateSmoothing = x ;
3302 }
3303
3304 /* ---------------------------------------------------------------- */
3305 /** @brief Get the non-extrema suppression threshold
3306 ** @param self object.
3307 ** @return threshold.
3308 **/
3309
3310 double
vl_covdet_get_non_extrema_suppression_threshold(VlCovDet const * self)3311 vl_covdet_get_non_extrema_suppression_threshold (VlCovDet const * self)
3312 {
3313 return self->nonExtremaSuppression ;
3314 }
3315
3316 /** @brief Set the non-extrema suppression threshod
3317 ** @param self object.
3318 ** @param x threshold.
3319 **/
3320
3321 void
vl_covdet_set_non_extrema_suppression_threshold(VlCovDet * self,double x)3322 vl_covdet_set_non_extrema_suppression_threshold (VlCovDet * self, double x)
3323 {
3324 self->nonExtremaSuppression = x ;
3325 }
3326
3327 /** @brief Get the number of non-extrema suppressed
3328 ** @param self object.
3329 ** @return number.
3330 **/
3331
3332 vl_size
vl_covdet_get_num_non_extrema_suppressed(VlCovDet const * self)3333 vl_covdet_get_num_non_extrema_suppressed (VlCovDet const * self)
3334 {
3335 return self->numNonExtremaSuppressed ;
3336 }
3337
3338
3339 /* ---------------------------------------------------------------- */
3340 /** @brief Get number of stored frames
3341 ** @return number of frames stored in the detector.
3342 **/
3343 vl_size
vl_covdet_get_num_features(VlCovDet const * self)3344 vl_covdet_get_num_features (VlCovDet const * self)
3345 {
3346 return self->numFeatures ;
3347 }
3348
3349 /** @brief Get the stored frames
3350 ** @return frames stored in the detector.
3351 **/
3352 VlCovDetFeature *
vl_covdet_get_features(VlCovDet * self)3353 vl_covdet_get_features (VlCovDet * self)
3354 {
3355 return self->features ;
3356 }
3357
3358 /** @brief Get the Gaussian scale space
3359 ** @return Gaussian scale space.
3360 **
3361 ** A Gaussian scale space exists only after calling ::vl_covdet_put_image.
3362 ** Otherwise the function returns @c NULL.
3363 **/
3364
3365 VlScaleSpace *
vl_covdet_get_gss(VlCovDet const * self)3366 vl_covdet_get_gss (VlCovDet const * self)
3367 {
3368 return self->gss ;
3369 }
3370
3371 /** @brief Get the cornerness measure scale space
3372 ** @return cornerness measure scale space.
3373 **
3374 ** A cornerness measure scale space exists only after calling
3375 ** ::vl_covdet_detect. Otherwise the function returns @c NULL.
3376 **/
3377
3378 VlScaleSpace *
vl_covdet_get_css(VlCovDet const * self)3379 vl_covdet_get_css (VlCovDet const * self)
3380 {
3381 return self->css ;
3382 }
3383
3384 /** @brief Get the number of features found with a certain number of scales
3385 ** @param self object.
3386 ** @param numScales length of the histogram (out).
3387 ** @return histogram.
3388 **
3389 ** Calling this function makes sense only after running a detector
3390 ** that uses the Laplacian as a secondary measure for scale
3391 ** detection
3392 **/
3393
3394 vl_size const *
vl_covdet_get_laplacian_scales_statistics(VlCovDet const * self,vl_size * numScales)3395 vl_covdet_get_laplacian_scales_statistics (VlCovDet const * self,
3396 vl_size * numScales)
3397 {
3398 *numScales = VL_COVDET_MAX_NUM_LAPLACIAN_SCALES ;
3399 return self->numFeaturesWithNumScales ;
3400 }
3401