You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3401 lines
110 KiB
3401 lines
110 KiB
/** @file covdet.c
|
|
** @brief Covariant feature detectors - Definition
|
|
** @author Karel Lenc
|
|
** @author Andrea Vedaldi
|
|
** @author Michal Perdoch
|
|
**/
|
|
|
|
/*
|
|
Copyright (C) 2013-14 Andrea Vedaldi.
|
|
Copyright (C) 2012 Karel Lenc, Andrea Vedaldi and Michal Perdoch.
|
|
All rights reserved.
|
|
|
|
This file is part of the VLFeat library and is made available under
|
|
the terms of the BSD license (see the COPYING file).
|
|
*/
|
|
|
|
/**
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet Covariant feature detectors
|
|
@author Karel Lenc
|
|
@author Andrea Vedaldi
|
|
@author Michal Perdoch
|
|
@tableofcontents
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
@ref covdet.h implements a number of covariant feature detectors, based
|
|
on three cornerness measures (determinant of the Hessian, trace of the Hessian
|
|
(aka Difference of Gaussians, and Harris). It supprots affine adaptation,
|
|
orientation estimation, as well as Laplacian scale detection.
|
|
|
|
- @subpage covdet-fundamentals
|
|
- @subpage covdet-principles
|
|
- @subpage covdet-differential
|
|
- @subpage covdet-corner-types
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-starting Getting started
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The ::VlCovDet object implements a number of covariant feature
|
|
detectors: Difference of Gaussian, Harris, determinant of Hessian.
|
|
Variant of the basic detectors support scale selection by maximizing
|
|
the Laplacian measure as well as affine normalization.
|
|
|
|
@code
|
|
// create a detector object
|
|
VlCovDet * covdet = vl_covdet_new(method) ;
|
|
|
|
// set various parameters (optional)
|
|
vl_covdet_set_first_octave(covdet, -1) ; // start by doubling the image resolution
|
|
vl_covdet_set_octave_resolution(covdet, octaveResolution) ;
|
|
vl_covdet_set_peak_threshold(covdet, peakThreshold) ;
|
|
vl_covdet_set_edge_threshold(covdet, edgeThreshold) ;
|
|
|
|
// process the image and run the detector
|
|
vl_covdet_put_image(covdet, image, numRows, numCols) ;
|
|
vl_covdet_detect(covdet) ;
|
|
|
|
// drop features on the margin (optional)
|
|
vl_covdet_drop_features_outside (covdet, boundaryMargin) ;
|
|
|
|
// compute the affine shape of the features (optional)
|
|
vl_covdet_extract_affine_shape(covdet) ;
|
|
|
|
// compute the orientation of the features (optional)
|
|
vl_covdet_extract_orientations(covdet) ;
|
|
|
|
// get feature frames back
|
|
vl_size numFeatures = vl_covdet_get_num_features(covdet) ;
|
|
VlCovDetFeature const * feature = vl_covdet_get_features(covdet) ;
|
|
|
|
// get normalized feature appearance patches (optional)
|
|
vl_size w = 2*patchResolution + 1 ;
|
|
for (i = 0 ; i < numFeatures ; ++i) {
|
|
float * patch = malloc(w*w*sizeof(*desc)) ;
|
|
vl_covdet_extract_patch_for_frame(covdet,
|
|
patch,
|
|
patchResolution,
|
|
patchRelativeExtent,
|
|
patchRelativeSmoothing,
|
|
feature[i].frame) ;
|
|
// do something with patch
|
|
}
|
|
@endcode
|
|
|
|
This example code:
|
|
|
|
- Calls ::vl_covdet_new constructs a new detector object. @ref
|
|
covdet.h supports a variety of different detectors (see
|
|
::VlCovDetMethod).
|
|
- Optionally calls various functions to set the detector parameters if
|
|
needed (e.g. ::vl_covdet_set_peak_threshold).
|
|
- Calls ::vl_covdet_put_image to start processing a new image. It
|
|
causes the detector to compute the scale space representation of the
|
|
image, but does not compute the features yet.
|
|
- Calls ::vl_covdet_detect runs the detector. At this point features are
|
|
ready to be extracted. However, one or all of the following steps
|
|
may be executed in order to process the features further.
|
|
- Optionally calls ::vl_covdet_drop_features_outside to drop features
|
|
outside the image boundary.
|
|
- Optionally calls ::vl_covdet_extract_affine_shape to compute the
|
|
affine shape of features using affine adaptation.
|
|
- Optionally calls ::vl_covdet_extract_orientations to compute the
|
|
dominant orientation of features looking for the dominant gradient
|
|
orientation in patches.
|
|
- Optionally calls ::vl_covdet_extract_patch_for_frame to extract a
|
|
normalized feature patch, for example to compute an invariant
|
|
feature descriptor.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet-fundamentals Covariant detectors fundamentals
|
|
@tableofcontents
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
This page describes the fundamental concepts required to understand a
|
|
covariant feature detector, the geometry of covariant features, and
|
|
the process of feature normalization.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-covariance Covariant detection
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The purpose of a *covariant detector* is to extract from an image a
|
|
set of local features in a manner which is consistent with spatial
|
|
transformations of the image itself. For instance, a covariant
|
|
detector that extracts interest points $\bx_1,\dots,\bx_n$ from image
|
|
$\ell$ extracts the translated points $\bx_1+T,\dots,\bx_n+T$ from the
|
|
translated image $\ell'(\bx) = \ell(\bx-T)$.
|
|
|
|
More in general, consider a image $\ell$ and a transformed version
|
|
$\ell' = \ell \circ w^{-1}$ of it, as in the following figure:
|
|
|
|
@image html covdet.png "Covariant detection of local features."
|
|
|
|
The transformation or <em>warp</em> $w : \real^2 \mapsto \real^2$ is a
|
|
deformation of the image domain which may capture a change of camera
|
|
viewpoint or similar imaging factor. Examples of warps typically
|
|
considered are translations, scaling, rotations, and general affine
|
|
transformations; however, in $w$ could be another type of continuous
|
|
and invertible transformation.
|
|
|
|
Given an image $\ell$, a **detector** selects features $R_1,\dots,R_n$
|
|
(one such features is shown in the example as a green circle). The
|
|
detector is said to be **covariant** with the warps $w$ if it extracts
|
|
the transformed features $w[R_1],\dots, w[R_n]$ from the transformed
|
|
image $w[\ell]$. Intuitively, this means that the “same
|
|
features” are extracted in both cases up to the transformation
|
|
$w$. This property is described more formally in @ref
|
|
covdet-principles.
|
|
|
|
Covariance is a key property of local feature detectors as it allows
|
|
extracting corresponding features from two or more images, making it
|
|
possible to match them in a meaningful way.
|
|
|
|
The @ref covdet.h module in VLFeat implements an array of feature
|
|
detection algorithm that have are covariant to different classes of
|
|
transformations.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-frame Feature geometry and feature frames
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
As we have seen, local features are subject to image transformations,
|
|
and they apply a fundamental role in matching and normalizing
|
|
images. To operates effectively with local features is therefore
|
|
necessary to understand their geometry.
|
|
|
|
The geometry of a local feature is captured by a <b>feature frame</b>
|
|
$R$. In VLFeat, depending on the specific detector, the frame can be
|
|
either a point, a disc, an ellipse, an oriented disc, or an oriented
|
|
ellipse.
|
|
|
|
A frame captures both the extent of the local features, useful to know
|
|
which portions of two images are put in correspondence, as well their
|
|
shape. The latter can be used to associate to diagnose the
|
|
transformation that affects a feature and remove it through the
|
|
process of **normalization**.
|
|
|
|
More precisely, in covariant detection feature frames are constructed
|
|
to be compatible with a certain class of transformations. For example,
|
|
circles are compatible with similarity transformations as they are
|
|
closed under them. Likewise, ellipses are compatible with affine
|
|
transformations.
|
|
|
|
Beyond this closure property, the key idea here is that all feature
|
|
occurrences can be seen as transformed versions of a base or
|
|
<b>canonical</b> feature. For example, all discs $R$ can be obtained
|
|
by applying a similarity transformation to the unit disc $\bar R$
|
|
centered at the origin. $\bar R$ is an example of canonical frame
|
|
as any other disc can be written as $R = w[\bar R]$ for a suitable
|
|
similarity $w$.
|
|
|
|
@image html frame-canonical.png "The idea of canonical frame and normalization"
|
|
|
|
The equation $R = w[\bar R_0]$ matching the canonical and detected
|
|
feature frames establishes a constraint on the warp $w$, very similar
|
|
to the way two reference frames in geometry establish a transformation
|
|
between spaces. The transformation $w$ can be thought as a the
|
|
**pose** of the detected feature, a generalization of its location.
|
|
|
|
In the case of discs and similarity transformations, the equation $R =
|
|
w[\bar R_0]$ fixes $w$ up to a residual rotation. This can be
|
|
addressed by considering oriented discs instead. An **oriented disc**
|
|
is a disc with a radius highlighted to represent the feature
|
|
orientation.
|
|
|
|
While discs are appropriate for similarity transformations, they are
|
|
not closed under general affine transformations. In this case, one
|
|
should consider the more general class of (oriented) ellipses. The
|
|
following image illustrates the five types of feature frames used in
|
|
VLFeat:
|
|
|
|
@image html frame-types.png "Types of local feature frames: points, discs, oriented discs, ellipses, oriented ellipses."
|
|
|
|
Note that these frames are described respectively by 2, 3, 4, 5 and 6
|
|
parameters. The most general type are the oriented ellipses, which can
|
|
be used to represent all the other frame types as well.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-frame-transformation Transforming feature frames
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
Consider a warp $w$ mapping image $\ell$ into image $\ell'$ as in the
|
|
figure below. A feature $R$ in the first image co-variantly transform
|
|
into a feature $R'=w[R]$ in the second image:
|
|
|
|
@image html covdet-normalization.png "Normalization removes the effect of an image deformation."
|
|
|
|
The poses $u,u'$ of $R=u[R_0]$ and $R' = u'[R_0]$ are then related by
|
|
the simple expression:
|
|
|
|
\[
|
|
u' = w \circ u.
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-frame-normalization Normalizing feature frames
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
In the example above, the poses $u$ and $u'$ relate the two
|
|
occurrences $R$ and $R'$ of the feature to its canonical version
|
|
$R_0$. If the pose $u$ of the feature in image $\ell$ is known, the
|
|
canonical feature appearance can be computed by un-warping it:
|
|
|
|
\[
|
|
\ell_0 = u^{-1}[\ell] = \ell \circ u.
|
|
\]
|
|
|
|
This process is known as **normalization** and is the key in the
|
|
computation of invariant feature descriptors as well as in the
|
|
construction of most co-variant detectors.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet-principles Principles of covariant detection
|
|
@tableofcontents
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The goals of a co-variant detector were discussed in @ref
|
|
covdet-fundamentals. This page introduces a few general principles
|
|
that are at the basis of most covariant detection algorithms. Consider
|
|
an input image $\ell$ and a two dimensional continuous and invertible
|
|
warp $w$. The *warped image* $w[\ell]$ is defined to be
|
|
|
|
\[
|
|
w[\ell] = \ell \circ w^{-1},
|
|
\]
|
|
|
|
or, equivalently,
|
|
|
|
\[
|
|
w[\ell](x,y) = \ell(w^{-1}(x,y)), \qquad \forall (x,y)\in\real^2.
|
|
\]
|
|
|
|
Note that, while $w$ pushes pixels forward, from the original to the
|
|
transformed image domain, defining the transformed image $\ell'$
|
|
requires inverting the warp and composing $\ell$ with $w^{-1}$.
|
|
|
|
The goal a covariant detector is to extract the same local features
|
|
irregardless of image transformations. The detector is said to be
|
|
<b>covariant</b> or <b>equivariant</b> with a class of warps
|
|
$w\in\mathcal{W}$ if, when the feature $R$ is detected in image
|
|
$\ell$, then the transformed feature $w[R]$ is detected in the
|
|
transformed image $w[\ell]$.
|
|
|
|
The net effect is that a covariant feature detector appears to
|
|
“track” image transformations; however, it is important to
|
|
note that a detector *is not a tracker* because it processes images
|
|
individually rather than jointly as part of a sequence.
|
|
|
|
An intuitive way to construct a covariant feature detector is to
|
|
extract features in correspondence of images structures that are
|
|
easily identifiable even after a transformation. Example of specific
|
|
structures include dots, corners, and blobs. These will be generically
|
|
indicated as **corners** in the followup.
|
|
|
|
A covariant detector faces two challenges. First, corners have, in
|
|
practice, an infinite variety of individual appearances and the
|
|
detector must be able to capture them to be of general applicability.
|
|
Second, the way corners are identified and detected must remain stable
|
|
under transformations of the image. These two problems are addressed
|
|
in @ref covdet-cornerness-localmax and @ref
|
|
covdet-cornerness-normalization respectively.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-cornerness Detection using a cornerness measure
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
One way to decide whether an image region $R$ contains a corner is to
|
|
compare the local appearance to a model or template of the corner; the
|
|
result of this comparisons produces a *cornerness score* at that
|
|
location. This page describe general theoretical properties of the
|
|
cornerness and the detection process. Concrete examples of cornerness
|
|
are given in @ref covdet-corner-types.
|
|
|
|
A **cornerness measure** associate a score to all possible feature
|
|
locations in an image $\ell$. As described in @ref covdet-frame, the
|
|
location or, more in general, pose $u$ of a feature $R$ is the warp
|
|
$w$ that maps the canonical feature frame $R_0$ to $R$:
|
|
|
|
\[
|
|
R = u[R_0].
|
|
\]
|
|
|
|
The goal of a cornerness measure is to associate a score $F(u;\ell)$
|
|
to all possible feature poses $u$ and use this score to extract a
|
|
finite number of co-variant features from any image.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-cornerness-localmax Local maxima of a cornerness measure
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
Given the cornerness of each candidate feature, the detector must
|
|
extract a finite number of them. However, the cornerness of features
|
|
with nearly identical pose must be similar (otherwise the cornerness
|
|
measure would be unstable). As such, simply thresholding $F(w;\ell)$
|
|
would detect an infinite number of nearly identical features rather
|
|
than a finite number.
|
|
|
|
The solution is to detect features in correspondence of the local
|
|
maxima of the score measure:
|
|
|
|
\[
|
|
\{w_1,\dots,w_n\} = \operatorname{localmax}_{w\in\mathcal{W}} F(w;\ell).
|
|
\]
|
|
|
|
This also means that features are never detected in isolation, but by
|
|
comparing neighborhoods of them.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-cornerness-normalization Covariant detection by normalization
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The next difficulty is to guarantee that detection is co-variant with
|
|
image transformations. Hence, if $u$ is the pose of a feature
|
|
extracted from image $\ell$, then the transformed pose $u' = w[u]$
|
|
must be detected in the transformed image $\ell' = w[\ell]$.
|
|
|
|
Since features are extracted in correspondence of the local maxima of
|
|
the cornerness score, a sufficient condition is that corresponding
|
|
features attain the same score in the two images:
|
|
|
|
\[
|
|
\forall u\in\mathcal{W}: \quad F(u;\ell) = F(w[u];w[\ell]),
|
|
\qquad\text{or}\qquad
|
|
F(u;\ell) = F(w \circ u ;\ell \circ w^{-1}).
|
|
\]
|
|
|
|
One simple way to satisfy this equation is to compute a cornerness
|
|
score *after normalizing the image* by the inverse of the candidate
|
|
feature pose warp $u$, as follows:
|
|
|
|
\[
|
|
F(u;\ell) = F(1;u^{-1}[\ell]) = F(1;\ell \circ u) = \mathcal{F}(\ell \circ u),
|
|
\]
|
|
|
|
where $1 = u^{-1} \circ u$ is the identity transformation and
|
|
$\mathcal{F}$ is an arbitrary functional. Intuitively, co-variant
|
|
detection is obtained by looking if the appearance of the feature
|
|
resembles a corner only *after normalization*. Formally:
|
|
|
|
@f{align*}
|
|
F(w[u];w[\ell])
|
|
&=
|
|
F(w \circ u ;\ell \circ w^{-1})
|
|
\\
|
|
&=
|
|
F(1; \ell \circ w^{-1} \circ w \circ u)
|
|
\\
|
|
&=
|
|
\mathcal{F}(\ell\circ u)
|
|
\\
|
|
&=
|
|
F(u;\ell).
|
|
@f}
|
|
|
|
Concrete examples of the functional $\mathcal{F}$ are given in @ref
|
|
covdet-corner-types.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-locality Locality of the detected features
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
In the definition above, the cornenress functional $\mathcal{F}$ is an
|
|
arbitrary functional of the entire normalized image $u^{-1}[\ell]$.
|
|
In practice, one is always interested in detecting **local features**
|
|
(at the very least because the image extent is finite).
|
|
|
|
This is easily obtained by considering a cornerness $\mathcal{F}$
|
|
which only looks in a small region of the normalized image, usually
|
|
corresponding to the extent of the canonical feature $R_0$ (e.g. a
|
|
unit disc centered at the origin).
|
|
|
|
In this case the extent of the local feature in the original image is
|
|
simply given by $R = u[R_0]$.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-partial Partial and iterated normalization
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
Practical detectors implement variants of the ideas above. Very often,
|
|
for instance, detection is an iterative process, in which successive
|
|
parameters of the pose of a feature are determined. For instance, it
|
|
is typical to first detect the location and scale of a feature using a
|
|
rotation-invariant cornerness score $\mathcal{F}$. Once these two
|
|
parameters are known, the rotation can be determined using a different
|
|
score, sensitive to the orientation of the local image structures.
|
|
|
|
Certain detectors (such as Harris-Laplace and Hessian-Laplace) use
|
|
even more sophisticated schemes, in which different scores are used to
|
|
jointly (rather than in succession) different parameters of the pose
|
|
of a feature, such as its translation and scale. While a formal
|
|
treatment of these cases is possible as well, we point to the original
|
|
papers.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet-differential Differential and integral image operations
|
|
@tableofcontents
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
Dealing with covariant interest point detector requires working a good
|
|
deal with derivatives, convolutions, and transformations of images.
|
|
The notation and fundamental properties of interest here are discussed
|
|
next.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-derivatives Derivative operations: gradients
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
For the derivatives, we borrow the notation of
|
|
@cite{kinghorn96integrals}. Let $f: \mathbb{R}^m \rightarrow
|
|
\mathbb{R}^n, \bx \mapsto f(\bx)$ be a vector function. The derivative
|
|
of the function with respect to $\bx$ is given by its *Jacobian
|
|
matrix* denoted by the symbol:
|
|
|
|
\[
|
|
\frac{\partial f}{\partial \bx^\top}
|
|
=
|
|
\begin{bmatrix}
|
|
\frac{\partial f_1}{x_1} & \frac{\partial f_1}{x_2} & \dots \\
|
|
\frac{\partial f_2}{x_1} & \frac{\partial f_2}{x_2} & \dots \\
|
|
\vdots & \vdots & \ddots \\
|
|
\end{bmatrix}.
|
|
\]
|
|
|
|
When the function $ f $ is scalar ($n=1$), the Jacobian is the same as
|
|
the gradient of the function (or, in fact, its transpose). More
|
|
precisely, the <b>gradient</b> $\nabla f $ of $ f $ denotes the column
|
|
vector of partial derivatives:
|
|
|
|
\[
|
|
\nabla f
|
|
= \frac{\partial f}{\partial \bx}
|
|
=
|
|
\begin{bmatrix}
|
|
\frac{\partial f}{\partial x_1} \\
|
|
\frac{\partial f}{\partial x_2} \\
|
|
\vdots
|
|
\end{bmatrix}.
|
|
\]
|
|
|
|
The second derivative $H_f $ of a scalar function $ f $, or
|
|
<b>Hessian</b>, is denoted as
|
|
|
|
\[
|
|
H_f
|
|
= \frac{\partial f}{\partial \bx \partial \bx^\top}
|
|
= \frac{\partial \nabla f}{\partial \bx^\top}
|
|
=
|
|
\begin{bmatrix}
|
|
\frac{\partial f}{\partial x_1 \partial x_1} & \frac{\partial f}{\partial x_1 \partial x_2} & \dots \\
|
|
\frac{\partial f}{\partial x_2 \partial x_1} & \frac{\partial f}{\partial x_2 \partial x_2} & \dots \\
|
|
\vdots & \vdots & \ddots \\
|
|
\end{bmatrix}.
|
|
\]
|
|
|
|
The determinant of the Hessian is also known as <b>Laplacian</b> and denoted as
|
|
|
|
\[
|
|
\Delta f = \operatorname{det} H_f =
|
|
\frac{\partial f}{\partial x_1^2} +
|
|
\frac{\partial f}{\partial x_2^2} +
|
|
\dots
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-derivative-transformations Derivative and image warps
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
In the following, we will often been interested in domain warpings $u:
|
|
\mathbb{R}^m \rightarrow \mathbb{R}^n, \bx \mapsto u(\bx)$ of a
|
|
function $f(\bar\bx) $ and its effect on the derivatives of the
|
|
function. The key transformation is the chain rule:
|
|
|
|
\[
|
|
\frac{\partial f \circ u}{\partial \bx^\top}
|
|
=
|
|
\left(\frac{\partial f}{\partial \bar\bx^\top} \circ u\right)
|
|
\frac{\partial u}{\partial \bx^\top}
|
|
\]
|
|
|
|
In particular, for an affine transformation $u = (A,T) : \bx \mapsto
|
|
A\bx + T$, one obtains the transformation rules:
|
|
|
|
\[
|
|
\begin{align*}
|
|
\frac{\partial f \circ (A,T)}{\partial \bx^\top}
|
|
&=
|
|
\left(\frac{\partial f}{\partial \bar\bx^\top} \circ (A,T)\right)A,
|
|
\\
|
|
\nabla (f \circ (A,T))
|
|
&= A^\top (\nabla f) \circ (A,T),
|
|
\\
|
|
H_{f \circ(A,T)}
|
|
&= A^\top (H_f \circ (A,T)) A,
|
|
\\
|
|
\Delta (f \circ(A,T))
|
|
&= \det(A)^2\, (\Delta f) \circ (A,T).
|
|
\end{align*}
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-smoothing Integral operations: smoothing
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
In practice, given an image $\ell$ expressed in digital format, good
|
|
derivative approximations can be computed only if the bandwidth of the
|
|
image is limited and, in particular, compatible with the sampling
|
|
density. Since it is unreasonable to expect real images to be
|
|
band-limited, the bandwidth is artificially constrained by suitably
|
|
smoothing the image prior to computing its derivatives. This is also
|
|
interpreted as a form of regularization or as a way of focusing on the
|
|
image content at a particular scale.
|
|
|
|
Formally, we will focus on Gaussian smoothing kernels. For the 2D case
|
|
$\bx\in\real^2$, the Gaussian kernel of covariance $\Sigma$ is given
|
|
by
|
|
|
|
\[
|
|
g_{\Sigma}(\bx) = \frac{1}{2\pi \sqrt{\det\Sigma}}
|
|
\exp\left(
|
|
- \frac{1}{2} \bx^\top \Sigma^{-1} \bx
|
|
\right).
|
|
\]
|
|
|
|
The symbol $g_{\sigma^2}$ will be used to denote a Gaussian kernel
|
|
with isotropic standard deviation $\sigma$, i.e. $\Sigma = \sigma^2
|
|
I$. Given an image $\ell$, the symbol $\ell_\Sigma$ will be used to
|
|
denote the image smoothed by the Gaussian kernel of parameter
|
|
$\Sigma$:
|
|
|
|
\[
|
|
\ell_\Sigma(\bx) = (g_\Sigma * \ell)(\bx)
|
|
=
|
|
\int_{\real^m}
|
|
g_\Sigma(\bx - \by) \ell(\by)\,d\by.
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-smoothing-transformations Smoothing and image warps
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
One advantage of Gaussian kernels is that they are (up to
|
|
renormalization) closed under a linear warp:
|
|
|
|
\[
|
|
|A|\, g_\Sigma \circ A = g_{A^{-1} \Sigma A^{-\top}}
|
|
\]
|
|
|
|
This also means that smoothing a warped image is the same as warping
|
|
the result of smoothing the original image by a suitably adjusted
|
|
Gaussian kernel:
|
|
|
|
\[
|
|
g_{\Sigma} * (\ell \circ (A,T))
|
|
=
|
|
(g_{A\Sigma A^\top} * \ell) \circ (A,T).
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet-corner-types Cornerness measures
|
|
@tableofcontents
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The goal of a cornerness measure (@ref covdet-cornerness) is to
|
|
associate to an image patch a score proportional to how strongly the
|
|
patch contain a certain strucuture, for example a corner or a
|
|
blob. This page reviews the most important cornerness measures as
|
|
implemented in VLFeat:
|
|
|
|
- @ref covdet-harris
|
|
- @ref covdet-laplacian
|
|
- @ref covdet-hessian
|
|
|
|
This page makes use of notation introduced in @ref
|
|
covdet-differential.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-harris Harris corners
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
This section introduces the fist of the cornerness measure
|
|
$\mathcal{F}[\ell]$. Recall (@ref covdet-cornerness) that the goal of
|
|
this functional is to respond strongly to images $\ell$ of corner-like
|
|
structure.
|
|
|
|
Rather than explicitly encoding the appearance of corners, the idea of
|
|
the Harris measure is to label as corner *any* image patch whose
|
|
appearance is sufficiently distinctive to allow accurate
|
|
localization. In particular, consider an image patch $\ell(\bx),
|
|
\bx\in\Omega$, where $\Omega$ is a smooth circular window of radius
|
|
approximately $\sigma_i$; at necessary condition for the patch to
|
|
allow accurate localization is that even a small translation
|
|
$\ell(\bx+\delta)$ causes the appearance to vary significantly (if not
|
|
the origin and location $\delta$ would not be distinguishable from the
|
|
image alone). This variation is measured by the sum of squared
|
|
differences
|
|
|
|
\[
|
|
E(\delta) = \int g_{\sigma_i^2}(\bx)
|
|
(\ell_{\sigma_d^2}(\bx+\delta) -
|
|
\ell_{\sigma_d^2}(\bx))^2 \,d\bx
|
|
\]
|
|
|
|
Note that images are compared at scale $\sigma_d$, known as
|
|
*differentiation scale* for reasons that will be clear in a moment,
|
|
and that the squared differences are summed over a window softly
|
|
defined by $\sigma_i$, also known as *integration scale*. This
|
|
function can be approximated as $E(\delta)\approx \delta^\top
|
|
M[\ell;\sigma_i^2,\sigma_d^2] \delta$ where
|
|
|
|
\[
|
|
M[\ell;\sigma_i^2,\sigma_d^2]
|
|
= \int g_{\sigma_i^2}(\bx)
|
|
(\nabla \ell_{\sigma_d^2}(\bx))
|
|
(\nabla \ell_{\sigma_d^2}(\bx))^\top \, d\bx.
|
|
\]
|
|
|
|
is the so called **structure tensor**.
|
|
|
|
A corner is identified when the sum of squared differences $E(\delta)$
|
|
is large for displacements $\delta$ in all directions. This condition
|
|
is obtained when both the eignenvalues $\lambda_1,\lambda_2$ of the
|
|
structure tensor $M$ are large. The **Harris cornerness measure**
|
|
captures this fact:
|
|
|
|
\[
|
|
\operatorname{Harris}[\ell;\sigma_i^2,\sigma_d^2] =
|
|
\det M - \kappa \operatorname{trace}^2 M =
|
|
\lambda_1\lambda_2 - \kappa (\lambda_1+\lambda_2)^2
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-harris-warped Harris in the warped domain
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The cornerness measure of a feature a location $u$ (recall that
|
|
locations $u$ are in general defined as image warps) should be
|
|
computed after normalizing the image (by applying to it the warp
|
|
$u^{-1}$). This section shows that, for affine warps, the Harris
|
|
cornerness measure can be computed directly in the Gaussian affine
|
|
scale space of the image. In particular, for similarities, it can be
|
|
computed in the standard Gaussian scale space.
|
|
|
|
To this end, let $u=(A,T)$ be an affine warp identifying a feature
|
|
location in image $\ell(\bx)$. Let $\bar\ell(\bar\bx) =
|
|
\ell(A\bar\bx+T)$ be the normalized image and rewrite the structure
|
|
tensor of the normalized image as follows:
|
|
|
|
\[
|
|
M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d]
|
|
=
|
|
M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d](\mathbf{0})
|
|
=
|
|
\left[
|
|
g_{\bar\Sigma_i} *
|
|
(\nabla\bar\ell_{\bar\Sigma_d})
|
|
(\nabla\bar\ell_{\bar\Sigma_d})^\top
|
|
\right](\mathbf{0})
|
|
\]
|
|
|
|
This notation emphasizes that the structure tensor is obtained by
|
|
taking derivatives and convolutions of the image. Using the fact that
|
|
$\nabla g_{\bar\Sigma_d} * \bar\ell = A^\top (\nabla g_{A\bar\Sigma
|
|
A^\top} * \ell) \circ (A,T)$ and that $g_{\bar\Sigma} * \bar \ell =
|
|
(g_{A\bar\Sigma A^\top} * \ell) \circ (A,T)$, we get the equivalent
|
|
expression:
|
|
|
|
\[
|
|
M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d](\mathbf{0})
|
|
=
|
|
A^\top
|
|
\left[
|
|
g_{A\bar\Sigma_i A^\top} *
|
|
(\nabla\ell_{A\bar\Sigma_dA^\top})(\nabla\ell_{A\bar\Sigma_d A^\top})^\top
|
|
\right](A\mathbf{0}+T)
|
|
A.
|
|
\]
|
|
|
|
In other words, the structure tensor of the normalized image can be
|
|
computed as:
|
|
|
|
\[
|
|
M[\bar\ell; \bar\Sigma_i, \bar\Sigma_d](\mathbf{0})
|
|
=
|
|
A^\top M[\ell; \Sigma_i, \Sigma_d](T) A,
|
|
\quad
|
|
\Sigma_{i} = A\bar\Sigma_{i}A^\top,
|
|
\quad
|
|
\Sigma_{d} = A\bar\Sigma_{d}A^\top.
|
|
\]
|
|
|
|
This equation allows to compute the structure tensor for feature at
|
|
all locations directly in the original image. In particular, features
|
|
at all translations $T$ can be evaluated efficiently by computing
|
|
convolutions and derivatives of the image
|
|
$\ell_{A\bar\Sigma_dA^\top}$.
|
|
|
|
A case of particular instance is when $\bar\Sigma_i= \bar\sigma_i^2 I$
|
|
and $\bar\Sigma_d = \bar\sigma_d^2$ are both isotropic covariance
|
|
matrices and the affine transformation is a similarity $A=sR$. Using
|
|
the fact that $\det\left( s^2 R^\top M R \right)= s^4 \det M$ and
|
|
$\operatorname{tr}\left(s^2 R^\top M R\right) = s^2 \operatorname{tr}
|
|
M$, one obtains the relation
|
|
|
|
\[
|
|
\operatorname{Harris}[\bar \ell;\bar\sigma_i^2,\bar\sigma_d^2] =
|
|
s^4 \operatorname{Harris}[\ell;s^2\bar\sigma_i^2,s^2\bar\sigma_d^2](T).
|
|
\]
|
|
|
|
This equation indicates that, for similarity transformations, not only
|
|
the structure tensor, but directly the Harris cornerness measure can
|
|
be computed on the original image and then be transferred back to the
|
|
normalized domain. Note, however, that this requires rescaling the
|
|
measure by the factor $s^4$.
|
|
|
|
Another important consequence of this relation is that the Harris
|
|
measure is invariant to pure image rotations. It cannot, therefore, be
|
|
used to associate an orientation to the detected features.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-hessian Hessian blobs
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The *(determinant of the) Hessian* cornerness measure is given
|
|
determinant of the Hessian of the image:
|
|
|
|
\[
|
|
\operatorname{DetHess}[\ell;\sigma_d^2]
|
|
=
|
|
\det H_{g_{\sigma_d^2} * \ell}(\mathbf{0})
|
|
\]
|
|
|
|
This number is large and positive if the image is locally curved
|
|
(peaked), roughly corresponding to blob-like structures in the image.
|
|
In particular, a large score requires the product of the eigenvalues
|
|
of the Hessian to be large, which requires both of them to have the
|
|
same sign and are large in absolute value.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-hessian-warped Hessian in the warped domain
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
Similarly to the Harris measure, it is possible to work with the
|
|
Hessian measure on the original unnormalized image. As before, let
|
|
$\bar\ell(\bar\bx) = \ell(A\bar\bx+T)$ be the normalized image and
|
|
rewrite the Hessian of the normalized image as follows:
|
|
|
|
\[
|
|
H_{g_{\bar\Sigma_d} * \bar\ell}(\mathbf{0}) = A^\top \left(H_{g_{\Sigma_d} * \ell}(T)\right) A.
|
|
\]
|
|
|
|
Then
|
|
|
|
\[
|
|
\operatorname{DetHess}[\bar\ell;\bar\Sigma_d]
|
|
=
|
|
(\det A)^2 \operatorname{DetHess}[\ell;A\bar\Sigma_d A^\top](T).
|
|
\]
|
|
|
|
In particular, for isotropic covariance matrices and similarity
|
|
transformations $A=sR$:
|
|
|
|
\[
|
|
\operatorname{DetHess}[\bar\ell;\bar\sigma_d^2]
|
|
=
|
|
s^4 \operatorname{DetHess}[\ell;s^2\bar\sigma_d^2](T)
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@section covdet-laplacian Laplacian and Difference of Gaussians blobs
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The **Laplacian of Gaussian (LoG)** or **trace of the Hessian**
|
|
cornerness measure is given by the trace of the Hessian of the image:
|
|
|
|
\[
|
|
\operatorname{Lap}[\ell;\sigma_d^2]
|
|
=
|
|
\operatorname{tr} H_{g_{\sigma_d}^2 * \ell}
|
|
\]
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-laplacian-warped Laplacian in the warped domain
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
Similarly to the Hessian measure, the Laplacian cornenress can often
|
|
be efficiently computed for features at all locations in the original
|
|
unnormalized image domain. In particular, if the derivative covariance
|
|
matrix $\Sigma_d$ is isotropic and one considers as warpings
|
|
similarity transformations $A=sR$, where $R$ is a rotatin and $s$ a
|
|
rescaling, one has
|
|
|
|
\[
|
|
\operatorname{Lap}[\bar\ell;\bar\sigma_d^2]
|
|
=
|
|
s^2 \operatorname{Lap}[\ell;s^2\bar\sigma_d^2](T)
|
|
\]
|
|
|
|
Note that, comparing to the Harris and determinant of Hessian
|
|
measures, the scaling for the Laplacian is $s^2$ rather than $s^4$.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-laplacian-matched Laplacian as a matched filter
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The Laplacian is given by the trace of the Hessian
|
|
operator. Differently from the determinant of the Hessian, this is a
|
|
linear operation. This means that computing the Laplacian cornerness
|
|
measure can be seen as applying a linear filtering operator to the
|
|
image. This filter can then be interpreted as a *template* of a corner
|
|
being matched to the image. Hence, the Laplacian cornerness measure
|
|
can be interpreted as matching this corner template at all possible
|
|
image locations.
|
|
|
|
To see this formally, compute the Laplacian score in the input image domain:
|
|
|
|
\[
|
|
\operatorname{Lap}[\bar\ell;\bar\sigma_d^2]
|
|
=
|
|
s^2 \operatorname{Lap}[\ell;s^2\bar\sigma_d^2](T)
|
|
=
|
|
s^2 (\Delta g_{s^2\bar\sigma_d^2} * \ell)(T)
|
|
\]
|
|
|
|
The Laplacian fitler is obtained by moving the Laplacian operator from
|
|
the image to the Gaussian smoothing kernel:
|
|
|
|
\[
|
|
s^2 (\Delta g_{s^2\bar\sigma_d^2} * \ell)
|
|
=
|
|
(s^2 \Delta g_{s^2\bar\sigma_d^2}) * \ell
|
|
\]
|
|
|
|
Note that the filter is rescaled by the $s^2$; sometimes, this factor
|
|
is incorporated in the Laplacian operator, yielding the so-called
|
|
normalized Laplacian.
|
|
|
|
The Laplacian of Gaussian is also called *top-hat function* and has
|
|
the expression:
|
|
|
|
\[
|
|
\Delta g_{\sigma^2}(x,y)
|
|
=
|
|
\frac{x^2+y^2 - 2 \sigma^2}{\sigma^4} g_{\sigma^2}(x,y).
|
|
\]
|
|
|
|
This filter, which acts as corner template, resembles a blob (a dark
|
|
disk surrounded by a bright ring).
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@subsection covdet-laplacian-dog Difference of Gaussians
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
The **Difference of Gaussian** (DoG) cornerness measure can be
|
|
interpreted as an approximation of the Laplacian that is easy to
|
|
obtain once a scalespace of the input image has been computed.
|
|
|
|
As noted above, the Laplacian cornerness of the normalized feature can
|
|
be computed directly from the input image by convolving the image by
|
|
the normalized Laplacian of Gaussian filter $s^2 \Delta
|
|
g_{s^2\bar\sigma_d^2}$.
|
|
|
|
Like the other derivative operators, this filter is simpe to
|
|
discriteize. However, it is often approximated by computing the the
|
|
*Difference of Gaussians* (DoG) approximation instead. This
|
|
approximation is obtained from the easily-proved identity:
|
|
|
|
\[
|
|
\frac{\partial}{\partial \sigma} g_{\sigma^2} =
|
|
\sigma \Delta g_{\sigma^2}.
|
|
\]
|
|
|
|
This indicates that computing the normalized Laplacian of a Gaussian
|
|
filter is, in the limit, the same as taking the difference between
|
|
Gaussian filters of slightly increasing standard deviation $\sigma$
|
|
and $\kappa\sigma$, where $\kappa \approx 1$:
|
|
|
|
\[
|
|
\sigma^2 \Delta g_{\sigma^2}
|
|
\approx
|
|
\sigma \frac{g_{(\kappa\sigma)^2} - g_{\sigma^2}}{\kappa\sigma - \sigma}
|
|
=
|
|
\frac{1}{\kappa - 1}
|
|
(g_{(\kappa\sigma)^2} - g_{\sigma^2}).
|
|
\]
|
|
|
|
One nice propery of this expression is that the factor $\sigma$
|
|
cancels out in the right-hand side. Usually, scales $\sigma$ and
|
|
$\kappa\sigma$ are pre-computed in the image scale-space and
|
|
successive scales are sampled with uniform geometric spacing, meaning
|
|
that the factor $\kappa$ is the same for all scales. Then, up to a
|
|
overall scaling factor, the LoG cornerness measure can be obtained by
|
|
taking the difference of successive scale space images
|
|
$\ell_{(\kappa\sigma)^2}$ and $\ell_{\sigma^2}$.
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet-affine-adaptation Affine adaptation
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
@page covdet-dominant-orientation Dominant orientation
|
|
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
|
|
**/
|
|
|
|
#include "covdet.h"
|
|
#include <string.h>
|
|
|
|
/** @brief Reallocate buffer
|
|
** @param buffer
|
|
** @param bufferSize
|
|
** @param targetSize
|
|
** @return error code
|
|
**/
|
|
|
|
static int
|
|
_vl_resize_buffer (void ** buffer, vl_size * bufferSize, vl_size targetSize) {
|
|
void * newBuffer ;
|
|
if (*buffer == NULL) {
|
|
*buffer = vl_malloc(targetSize) ;
|
|
if (*buffer) {
|
|
*bufferSize = targetSize ;
|
|
return VL_ERR_OK ;
|
|
} else {
|
|
*bufferSize = 0 ;
|
|
return VL_ERR_ALLOC ;
|
|
}
|
|
}
|
|
newBuffer = vl_realloc(*buffer, targetSize) ;
|
|
if (newBuffer) {
|
|
*buffer = newBuffer ;
|
|
*bufferSize = targetSize ;
|
|
return VL_ERR_OK ;
|
|
} else {
|
|
return VL_ERR_ALLOC ;
|
|
}
|
|
}
|
|
|
|
/** @brief Enlarge buffer
|
|
** @param buffer
|
|
** @param bufferSize
|
|
** @param targetSize
|
|
** @return error code
|
|
**/
|
|
|
|
static int
|
|
_vl_enlarge_buffer (void ** buffer, vl_size * bufferSize, vl_size targetSize) {
|
|
if (*bufferSize >= targetSize) return VL_ERR_OK ;
|
|
return _vl_resize_buffer(buffer,bufferSize,targetSize) ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Finding local extrema */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/* Todo: make this generally available in the library */
|
|
|
|
typedef struct _VlCovDetExtremum2
|
|
{
|
|
vl_index xi ;
|
|
vl_index yi ;
|
|
float x ;
|
|
float y ;
|
|
float peakScore ;
|
|
float edgeScore ;
|
|
} VlCovDetExtremum2 ;
|
|
|
|
typedef struct _VlCovDetExtremum3
|
|
{
|
|
vl_index xi ;
|
|
vl_index yi ;
|
|
vl_index zi ;
|
|
float x ;
|
|
float y ;
|
|
float z ;
|
|
float peakScore ;
|
|
float edgeScore ;
|
|
} VlCovDetExtremum3 ;
|
|
|
|
VL_EXPORT 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) ;
|
|
|
|
VL_EXPORT vl_size
|
|
vl_find_local_extrema_2 (vl_index ** extrema, vl_size * bufferSize,
|
|
float const * map,
|
|
vl_size width, vl_size height,
|
|
double threshold) ;
|
|
|
|
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) ;
|
|
|
|
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) ;
|
|
|
|
/** @internal
|
|
** @brief Find the extrema of a 3D function
|
|
** @param extrema buffer containing the extrema found (in/out).
|
|
** @param bufferSize size of the @a extrema buffer in bytes (in/out).
|
|
** @param map a 3D array representing the map.
|
|
** @param width of the map.
|
|
** @param height of the map.
|
|
** @param depth of the map.
|
|
** @param threshold minumum extremum value.
|
|
** @return number of extrema found.
|
|
** @see @ref ::vl_refine_local_extreum_2.
|
|
**/
|
|
|
|
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)
|
|
{
|
|
vl_index x, y, z ;
|
|
vl_size const xo = 1 ;
|
|
vl_size const yo = width ;
|
|
vl_size const zo = width * height ;
|
|
float const *pt = map + xo + yo + zo ;
|
|
|
|
vl_size numExtrema = 0 ;
|
|
vl_size requiredSize = 0 ;
|
|
|
|
#define CHECK_NEIGHBORS_3(v,CMP,SGN) (\
|
|
v CMP ## = SGN threshold && \
|
|
v CMP *(pt + xo) && \
|
|
v CMP *(pt - xo) && \
|
|
v CMP *(pt + zo) && \
|
|
v CMP *(pt - zo) && \
|
|
v CMP *(pt + yo) && \
|
|
v CMP *(pt - yo) && \
|
|
\
|
|
v CMP *(pt + yo + xo) && \
|
|
v CMP *(pt + yo - xo) && \
|
|
v CMP *(pt - yo + xo) && \
|
|
v CMP *(pt - yo - xo) && \
|
|
\
|
|
v CMP *(pt + xo + zo) && \
|
|
v CMP *(pt - xo + zo) && \
|
|
v CMP *(pt + yo + zo) && \
|
|
v CMP *(pt - yo + zo) && \
|
|
v CMP *(pt + yo + xo + zo) && \
|
|
v CMP *(pt + yo - xo + zo) && \
|
|
v CMP *(pt - yo + xo + zo) && \
|
|
v CMP *(pt - yo - xo + zo) && \
|
|
\
|
|
v CMP *(pt + xo - zo) && \
|
|
v CMP *(pt - xo - zo) && \
|
|
v CMP *(pt + yo - zo) && \
|
|
v CMP *(pt - yo - zo) && \
|
|
v CMP *(pt + yo + xo - zo) && \
|
|
v CMP *(pt + yo - xo - zo) && \
|
|
v CMP *(pt - yo + xo - zo) && \
|
|
v CMP *(pt - yo - xo - zo) )
|
|
|
|
for (z = 1 ; z < (signed)depth - 1 ; ++z) {
|
|
for (y = 1 ; y < (signed)height - 1 ; ++y) {
|
|
for (x = 1 ; x < (signed)width - 1 ; ++x) {
|
|
float value = *pt ;
|
|
if (CHECK_NEIGHBORS_3(value,>,+) || CHECK_NEIGHBORS_3(value,<,-)) {
|
|
numExtrema ++ ;
|
|
requiredSize += sizeof(vl_index) * 3 ;
|
|
if (*bufferSize < requiredSize) {
|
|
int err = _vl_resize_buffer((void**)extrema, bufferSize,
|
|
requiredSize + 2000 * 3 * sizeof(vl_index)) ;
|
|
if (err != VL_ERR_OK) abort() ;
|
|
}
|
|
(*extrema) [3 * (numExtrema - 1) + 0] = x ;
|
|
(*extrema) [3 * (numExtrema - 1) + 1] = y ;
|
|
(*extrema) [3 * (numExtrema - 1) + 2] = z ;
|
|
}
|
|
pt += xo ;
|
|
}
|
|
pt += 2*xo ;
|
|
}
|
|
pt += 2*yo ;
|
|
}
|
|
return numExtrema ;
|
|
}
|
|
|
|
/** @internal
|
|
** @brief Find extrema in a 2D function
|
|
** @param extrema buffer containing the found extrema (in/out).
|
|
** @param bufferSize size of the @a extrema buffer in bytes (in/out).
|
|
** @param map a 3D array representing the map.
|
|
** @param width of the map.
|
|
** @param height of the map.
|
|
** @param threshold minumum extremum value.
|
|
** @return number of extrema found.
|
|
**
|
|
** An extremum contains 2 ::vl_index values; they are arranged
|
|
** sequentially.
|
|
**
|
|
** The function can reuse an already allocated buffer if
|
|
** @a extrema and @a bufferSize are initialized on input.
|
|
** It may have to @a realloc the memory if the buffer is too small.
|
|
**/
|
|
|
|
vl_size
|
|
vl_find_local_extrema_2 (vl_index ** extrema, vl_size * bufferSize,
|
|
float const* map,
|
|
vl_size width, vl_size height,
|
|
double threshold)
|
|
{
|
|
vl_index x, y ;
|
|
vl_size const xo = 1 ;
|
|
vl_size const yo = width ;
|
|
float const *pt = map + xo + yo ;
|
|
|
|
vl_size numExtrema = 0 ;
|
|
vl_size requiredSize = 0 ;
|
|
#define CHECK_NEIGHBORS_2(v,CMP,SGN) (\
|
|
v CMP ## = SGN threshold && \
|
|
v CMP *(pt + xo) && \
|
|
v CMP *(pt - xo) && \
|
|
v CMP *(pt + yo) && \
|
|
v CMP *(pt - yo) && \
|
|
\
|
|
v CMP *(pt + yo + xo) && \
|
|
v CMP *(pt + yo - xo) && \
|
|
v CMP *(pt - yo + xo) && \
|
|
v CMP *(pt - yo - xo) )
|
|
|
|
for (y = 1 ; y < (signed)height - 1 ; ++y) {
|
|
for (x = 1 ; x < (signed)width - 1 ; ++x) {
|
|
float value = *pt ;
|
|
if (CHECK_NEIGHBORS_2(value,>,+) || CHECK_NEIGHBORS_2(value,<,-)) {
|
|
numExtrema ++ ;
|
|
requiredSize += sizeof(vl_index) * 2 ;
|
|
if (*bufferSize < requiredSize) {
|
|
int err = _vl_resize_buffer((void**)extrema, bufferSize,
|
|
requiredSize + 2000 * 2 * sizeof(vl_index)) ;
|
|
if (err != VL_ERR_OK) abort() ;
|
|
}
|
|
(*extrema) [2 * (numExtrema - 1) + 0] = x ;
|
|
(*extrema) [2 * (numExtrema - 1) + 1] = y ;
|
|
}
|
|
pt += xo ;
|
|
}
|
|
pt += 2*xo ;
|
|
}
|
|
return numExtrema ;
|
|
}
|
|
|
|
/** @internal
|
|
** @brief Refine the location of a local extremum of a 3D map
|
|
** @param refined refined extremum (out).
|
|
** @param map a 3D array representing the map.
|
|
** @param width of the map.
|
|
** @param height of the map.
|
|
** @param depth of the map.
|
|
** @param x initial x position.
|
|
** @param y initial y position.
|
|
** @param z initial z position.
|
|
** @return a flat indicating whether the extrema refinement was stable.
|
|
**/
|
|
|
|
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)
|
|
{
|
|
vl_size const xo = 1 ;
|
|
vl_size const yo = width ;
|
|
vl_size const zo = width * height ;
|
|
|
|
double Dx=0,Dy=0,Dz=0,Dxx=0,Dyy=0,Dzz=0,Dxy=0,Dxz=0,Dyz=0 ;
|
|
double A [3*3], b [3] ;
|
|
|
|
#define at(dx,dy,dz) (*(pt + (dx)*xo + (dy)*yo + (dz)*zo))
|
|
#define Aat(i,j) (A[(i)+(j)*3])
|
|
|
|
float const * pt ;
|
|
vl_index dx = 0 ;
|
|
vl_index dy = 0 ;
|
|
/*vl_index dz = 0 ;*/
|
|
vl_index iter ;
|
|
int err ;
|
|
|
|
assert (map) ;
|
|
assert (1 <= x && x <= (signed)width - 2) ;
|
|
assert (1 <= y && y <= (signed)height - 2) ;
|
|
assert (1 <= z && z <= (signed)depth - 2) ;
|
|
|
|
for (iter = 0 ; iter < 5 ; ++iter) {
|
|
x += dx ;
|
|
y += dy ;
|
|
pt = map + x*xo + y*yo + z*zo ;
|
|
|
|
/* compute the gradient */
|
|
Dx = 0.5 * (at(+1,0,0) - at(-1,0,0)) ;
|
|
Dy = 0.5 * (at(0,+1,0) - at(0,-1,0));
|
|
Dz = 0.5 * (at(0,0,+1) - at(0,0,-1)) ;
|
|
|
|
/* compute the Hessian */
|
|
Dxx = (at(+1,0,0) + at(-1,0,0) - 2.0 * at(0,0,0)) ;
|
|
Dyy = (at(0,+1,0) + at(0,-1,0) - 2.0 * at(0,0,0)) ;
|
|
Dzz = (at(0,0,+1) + at(0,0,-1) - 2.0 * at(0,0,0)) ;
|
|
|
|
Dxy = 0.25 * (at(+1,+1,0) + at(-1,-1,0) - at(-1,+1,0) - at(+1,-1,0)) ;
|
|
Dxz = 0.25 * (at(+1,0,+1) + at(-1,0,-1) - at(-1,0,+1) - at(+1,0,-1)) ;
|
|
Dyz = 0.25 * (at(0,+1,+1) + at(0,-1,-1) - at(0,-1,+1) - at(0,+1,-1)) ;
|
|
|
|
/* solve linear system */
|
|
Aat(0,0) = Dxx ;
|
|
Aat(1,1) = Dyy ;
|
|
Aat(2,2) = Dzz ;
|
|
Aat(0,1) = Aat(1,0) = Dxy ;
|
|
Aat(0,2) = Aat(2,0) = Dxz ;
|
|
Aat(1,2) = Aat(2,1) = Dyz ;
|
|
|
|
b[0] = - Dx ;
|
|
b[1] = - Dy ;
|
|
b[2] = - Dz ;
|
|
|
|
err = vl_solve_linear_system_3(b, A, b) ;
|
|
|
|
if (err != VL_ERR_OK) {
|
|
b[0] = 0 ;
|
|
b[1] = 0 ;
|
|
b[2] = 0 ;
|
|
break ;
|
|
}
|
|
|
|
/* Keep going if there is sufficient translation */
|
|
|
|
dx = (b[0] > 0.6 && x < (signed)width - 2 ? 1 : 0)
|
|
+ (b[0] < -0.6 && x > 1 ? -1 : 0) ;
|
|
|
|
dy = (b[1] > 0.6 && y < (signed)height - 2 ? 1 : 0)
|
|
+ (b[1] < -0.6 && y > 1 ? -1 : 0) ;
|
|
|
|
if (dx == 0 && dy == 0) break ;
|
|
}
|
|
|
|
/* check threshold and other conditions */
|
|
{
|
|
double peakScore = at(0,0,0)
|
|
+ 0.5 * (Dx * b[0] + Dy * b[1] + Dz * b[2]) ;
|
|
double alpha = (Dxx+Dyy)*(Dxx+Dyy) / (Dxx*Dyy - Dxy*Dxy) ;
|
|
double edgeScore ;
|
|
|
|
if (alpha < 0) {
|
|
/* not an extremum */
|
|
edgeScore = VL_INFINITY_D ;
|
|
} else {
|
|
edgeScore = (0.5*alpha - 1) + sqrt(VL_MAX(0.25*alpha - 1,0)*alpha) ;
|
|
}
|
|
|
|
refined->xi = x ;
|
|
refined->yi = y ;
|
|
refined->zi = z ;
|
|
refined->x = x + b[0] ;
|
|
refined->y = y + b[1] ;
|
|
refined->z = z + b[2] ;
|
|
refined->peakScore = peakScore ;
|
|
refined->edgeScore = edgeScore ;
|
|
|
|
return
|
|
err == VL_ERR_OK &&
|
|
vl_abs_d(b[0]) < 1.5 &&
|
|
vl_abs_d(b[1]) < 1.5 &&
|
|
vl_abs_d(b[2]) < 1.5 &&
|
|
0 <= refined->x && refined->x <= (signed)width - 1 &&
|
|
0 <= refined->y && refined->y <= (signed)height - 1 &&
|
|
0 <= refined->z && refined->z <= (signed)depth - 1 ;
|
|
}
|
|
#undef Aat
|
|
#undef at
|
|
}
|
|
|
|
/** @internal
|
|
** @brief Refine the location of a local extremum of a 2D map
|
|
** @param refined refined extremum (out).
|
|
** @param map a 2D array representing the map.
|
|
** @param width of the map.
|
|
** @param height of the map.
|
|
** @param x initial x position.
|
|
** @param y initial y position.
|
|
** @return a flat indicating whether the extrema refinement was stable.
|
|
**/
|
|
|
|
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)
|
|
{
|
|
vl_size const xo = 1 ;
|
|
vl_size const yo = width ;
|
|
|
|
double Dx=0,Dy=0,Dxx=0,Dyy=0,Dxy=0;
|
|
double A [2*2], b [2] ;
|
|
|
|
#define at(dx,dy) (*(pt + (dx)*xo + (dy)*yo ))
|
|
#define Aat(i,j) (A[(i)+(j)*2])
|
|
|
|
float const * pt ;
|
|
vl_index dx = 0 ;
|
|
vl_index dy = 0 ;
|
|
vl_index iter ;
|
|
int err ;
|
|
|
|
assert (map) ;
|
|
assert (1 <= x && x <= (signed)width - 2) ;
|
|
assert (1 <= y && y <= (signed)height - 2) ;
|
|
|
|
for (iter = 0 ; iter < 5 ; ++iter) {
|
|
x += dx ;
|
|
y += dy ;
|
|
pt = map + x*xo + y*yo ;
|
|
|
|
/* compute the gradient */
|
|
Dx = 0.5 * (at(+1,0) - at(-1,0)) ;
|
|
Dy = 0.5 * (at(0,+1) - at(0,-1));
|
|
|
|
/* compute the Hessian */
|
|
Dxx = (at(+1,0) + at(-1,0) - 2.0 * at(0,0)) ;
|
|
Dyy = (at(0,+1) + at(0,-1) - 2.0 * at(0,0)) ;
|
|
Dxy = 0.25 * (at(+1,+1) + at(-1,-1) - at(-1,+1) - at(+1,-1)) ;
|
|
|
|
/* solve linear system */
|
|
Aat(0,0) = Dxx ;
|
|
Aat(1,1) = Dyy ;
|
|
Aat(0,1) = Aat(1,0) = Dxy ;
|
|
|
|
b[0] = - Dx ;
|
|
b[1] = - Dy ;
|
|
|
|
err = vl_solve_linear_system_2(b, A, b) ;
|
|
|
|
if (err != VL_ERR_OK) {
|
|
b[0] = 0 ;
|
|
b[1] = 0 ;
|
|
break ;
|
|
}
|
|
|
|
/* Keep going if there is sufficient translation */
|
|
|
|
dx = (b[0] > 0.6 && x < (signed)width - 2 ? 1 : 0)
|
|
+ (b[0] < -0.6 && x > 1 ? -1 : 0) ;
|
|
|
|
dy = (b[1] > 0.6 && y < (signed)height - 2 ? 1 : 0)
|
|
+ (b[1] < -0.6 && y > 1 ? -1 : 0) ;
|
|
|
|
if (dx == 0 && dy == 0) break ;
|
|
}
|
|
|
|
/* check threshold and other conditions */
|
|
{
|
|
double peakScore = at(0,0) + 0.5 * (Dx * b[0] + Dy * b[1]) ;
|
|
double alpha = (Dxx+Dyy)*(Dxx+Dyy) / (Dxx*Dyy - Dxy*Dxy) ;
|
|
double edgeScore ;
|
|
|
|
if (alpha < 0) {
|
|
/* not an extremum */
|
|
edgeScore = VL_INFINITY_D ;
|
|
} else {
|
|
edgeScore = (0.5*alpha - 1) + sqrt(VL_MAX(0.25*alpha - 1,0)*alpha) ;
|
|
}
|
|
|
|
refined->xi = x ;
|
|
refined->yi = y ;
|
|
refined->x = x + b[0] ;
|
|
refined->y = y + b[1] ;
|
|
refined->peakScore = peakScore ;
|
|
refined->edgeScore = edgeScore ;
|
|
|
|
return
|
|
err == VL_ERR_OK &&
|
|
vl_abs_d(b[0]) < 1.5 &&
|
|
vl_abs_d(b[1]) < 1.5 &&
|
|
0 <= refined->x && refined->x <= (signed)width - 1 &&
|
|
0 <= refined->y && refined->y <= (signed)height - 1 ;
|
|
}
|
|
#undef Aat
|
|
#undef at
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Covarant detector */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
#define VL_COVDET_MAX_NUM_ORIENTATIONS 4
|
|
#define VL_COVDET_MAX_NUM_LAPLACIAN_SCALES 4
|
|
#define VL_COVDET_AA_PATCH_RESOLUTION 20
|
|
#define VL_COVDET_AA_MAX_NUM_ITERATIONS 15
|
|
#define VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS 36
|
|
#define VL_COVDET_AA_RELATIVE_INTEGRATION_SIGMA 3
|
|
#define VL_COVDET_AA_RELATIVE_DERIVATIVE_SIGMA 1
|
|
#define VL_COVDET_AA_MAX_ANISOTROPY 5
|
|
#define VL_COVDET_AA_CONVERGENCE_THRESHOLD 1.001
|
|
#define VL_COVDET_AA_ACCURATE_SMOOTHING VL_FALSE
|
|
#define VL_COVDET_AA_PATCH_EXTENT (3*VL_COVDET_AA_RELATIVE_INTEGRATION_SIGMA)
|
|
#define VL_COVDET_OR_ADDITIONAL_PEAKS_RELATIVE_SIZE 0.8
|
|
#define VL_COVDET_LAP_NUM_LEVELS 10
|
|
#define VL_COVDET_LAP_PATCH_RESOLUTION 16
|
|
#define VL_COVDET_LAP_DEF_PEAK_THRESHOLD 0.01
|
|
#define VL_COVDET_DOG_DEF_PEAK_THRESHOLD VL_COVDET_LAP_DEF_PEAK_THRESHOLD
|
|
#define VL_COVDET_DOG_DEF_EDGE_THRESHOLD 10.0
|
|
#define VL_COVDET_HARRIS_DEF_PEAK_THRESHOLD 0.000002
|
|
#define VL_COVDET_HARRIS_DEF_EDGE_THRESHOLD 10.0
|
|
#define VL_COVDET_HESSIAN_DEF_PEAK_THRESHOLD 0.003
|
|
#define VL_COVDET_HESSIAN_DEF_EDGE_THRESHOLD 10.0
|
|
|
|
/** @brief Covariant feature detector */
|
|
struct _VlCovDet
|
|
{
|
|
VlScaleSpace *gss ; /**< Gaussian scale space. */
|
|
VlScaleSpace *css ; /**< Cornerness scale space. */
|
|
VlCovDetMethod method ; /**< feature extraction method. */
|
|
double peakThreshold ; /**< peak threshold. */
|
|
double edgeThreshold ; /**< edge threshold. */
|
|
double lapPeakThreshold; /**< peak threshold for Laplacian scale selection. */
|
|
vl_size octaveResolution ; /**< resolution of each octave. */
|
|
vl_index firstOctave ; /**< index of the first octave. */
|
|
|
|
double nonExtremaSuppression ;
|
|
vl_size numNonExtremaSuppressed ;
|
|
|
|
VlCovDetFeature *features ;
|
|
vl_size numFeatures ;
|
|
vl_size numFeatureBufferSize ;
|
|
|
|
float * patch ;
|
|
vl_size patchBufferSize ;
|
|
|
|
vl_bool transposed ;
|
|
VlCovDetFeatureOrientation orientations [VL_COVDET_MAX_NUM_ORIENTATIONS] ;
|
|
VlCovDetFeatureLaplacianScale scales [VL_COVDET_MAX_NUM_LAPLACIAN_SCALES] ;
|
|
|
|
vl_bool aaAccurateSmoothing ;
|
|
float aaPatch [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
|
|
float aaPatchX [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
|
|
float aaPatchY [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
|
|
float aaMask [(2*VL_COVDET_AA_PATCH_RESOLUTION+1)*(2*VL_COVDET_AA_PATCH_RESOLUTION+1)] ;
|
|
|
|
float lapPatch [(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)*(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)] ;
|
|
float laplacians [(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)*(2*VL_COVDET_LAP_PATCH_RESOLUTION+1)*VL_COVDET_LAP_NUM_LEVELS] ;
|
|
vl_size numFeaturesWithNumScales [VL_COVDET_MAX_NUM_LAPLACIAN_SCALES + 1] ;
|
|
} ;
|
|
|
|
VlEnumerator vlCovdetMethods [VL_COVDET_METHOD_NUM] = {
|
|
{"DoG" , (vl_index)VL_COVDET_METHOD_DOG },
|
|
{"Hessian", (vl_index)VL_COVDET_METHOD_HESSIAN },
|
|
{"HessianLaplace", (vl_index)VL_COVDET_METHOD_HESSIAN_LAPLACE },
|
|
{"HarrisLaplace", (vl_index)VL_COVDET_METHOD_HARRIS_LAPLACE },
|
|
{"MultiscaleHessian", (vl_index)VL_COVDET_METHOD_MULTISCALE_HESSIAN},
|
|
{"MultiscaleHarris", (vl_index)VL_COVDET_METHOD_MULTISCALE_HARRIS },
|
|
{0, 0 }
|
|
} ;
|
|
|
|
/** @brief Create a new object instance
|
|
** @param method method for covariant feature detection.
|
|
** @return new covariant detector.
|
|
**/
|
|
|
|
VlCovDet *
|
|
vl_covdet_new (VlCovDetMethod method)
|
|
{
|
|
VlCovDet * self = vl_calloc(sizeof(VlCovDet),1) ;
|
|
self->method = method ;
|
|
self->octaveResolution = 3 ;
|
|
self->firstOctave = -1 ;
|
|
switch (self->method) {
|
|
case VL_COVDET_METHOD_DOG :
|
|
self->peakThreshold = VL_COVDET_DOG_DEF_PEAK_THRESHOLD ;
|
|
self->edgeThreshold = VL_COVDET_DOG_DEF_EDGE_THRESHOLD ;
|
|
self->lapPeakThreshold = 0 ; /* not used */
|
|
break ;
|
|
case VL_COVDET_METHOD_HARRIS_LAPLACE:
|
|
case VL_COVDET_METHOD_MULTISCALE_HARRIS:
|
|
self->peakThreshold = VL_COVDET_HARRIS_DEF_PEAK_THRESHOLD ;
|
|
self->edgeThreshold = VL_COVDET_HARRIS_DEF_EDGE_THRESHOLD ;
|
|
self->lapPeakThreshold = VL_COVDET_LAP_DEF_PEAK_THRESHOLD ;
|
|
break ;
|
|
case VL_COVDET_METHOD_HESSIAN :
|
|
case VL_COVDET_METHOD_HESSIAN_LAPLACE:
|
|
case VL_COVDET_METHOD_MULTISCALE_HESSIAN:
|
|
self->peakThreshold = VL_COVDET_HESSIAN_DEF_PEAK_THRESHOLD ;
|
|
self->edgeThreshold = VL_COVDET_HESSIAN_DEF_EDGE_THRESHOLD ;
|
|
self->lapPeakThreshold = VL_COVDET_LAP_DEF_PEAK_THRESHOLD ;
|
|
break;
|
|
default:
|
|
assert(0) ;
|
|
}
|
|
|
|
self->nonExtremaSuppression = 0.5 ;
|
|
self->features = NULL ;
|
|
self->numFeatures = 0 ;
|
|
self->numFeatureBufferSize = 0 ;
|
|
self->patch = NULL ;
|
|
self->patchBufferSize = 0 ;
|
|
self->transposed = VL_FALSE ;
|
|
self->aaAccurateSmoothing = VL_COVDET_AA_ACCURATE_SMOOTHING ;
|
|
|
|
{
|
|
vl_index const w = VL_COVDET_AA_PATCH_RESOLUTION ;
|
|
vl_index i,j ;
|
|
double step = (2.0 * VL_COVDET_AA_PATCH_EXTENT) / (2*w+1) ;
|
|
double sigma = VL_COVDET_AA_RELATIVE_INTEGRATION_SIGMA ;
|
|
for (j = -w ; j <= w ; ++j) {
|
|
for (i = -w ; i <= w ; ++i) {
|
|
double dx = i*step/sigma ;
|
|
double dy = j*step/sigma ;
|
|
self->aaMask[(i+w) + (2*w+1)*(j+w)] = exp(-0.5*(dx*dx+dy*dy)) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
/*
|
|
Covers one octave of Laplacian filters, from sigma=1 to sigma=2.
|
|
The spatial sampling step is 0.5.
|
|
*/
|
|
vl_index s ;
|
|
for (s = 0 ; s < VL_COVDET_LAP_NUM_LEVELS ; ++s) {
|
|
double sigmaLap = pow(2.0, -0.5 +
|
|
(double)s / (VL_COVDET_LAP_NUM_LEVELS - 1)) ;
|
|
double const sigmaImage = 1.0 / sqrt(2.0) ;
|
|
double const step = 0.5 * sigmaImage ;
|
|
double const sigmaDelta = sqrt(sigmaLap*sigmaLap - sigmaImage*sigmaImage) ;
|
|
vl_size const w = VL_COVDET_LAP_PATCH_RESOLUTION ;
|
|
vl_size const num = 2 * w + 1 ;
|
|
float * pt = self->laplacians + s * (num * num) ;
|
|
|
|
memset(pt, 0, num * num * sizeof(float)) ;
|
|
|
|
#define at(x,y) pt[(x+w)+(y+w)*(2*w+1)]
|
|
at(0,0) = - 4.0 ;
|
|
at(-1,0) = 1.0 ;
|
|
at(+1,0) = 1.0 ;
|
|
at(0,1) = 1.0 ;
|
|
at(0,-1) = 1.0 ;
|
|
#undef at
|
|
|
|
vl_imsmooth_f(pt, num,
|
|
pt, num, num, num,
|
|
sigmaDelta / step, sigmaDelta / step) ;
|
|
|
|
#if 0
|
|
{
|
|
char name [200] ;
|
|
snprintf(name, 200, "/tmp/%f-lap.pgm", sigmaDelta) ;
|
|
vl_pgm_write_f(name, pt, num, num) ;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
return self ;
|
|
}
|
|
|
|
/** @brief Reset object
|
|
** @param self object.
|
|
**
|
|
** This function removes any buffered features and frees other
|
|
** internal buffers.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_reset (VlCovDet * self)
|
|
{
|
|
if (self->features) {
|
|
vl_free(self->features) ;
|
|
self->features = NULL ;
|
|
}
|
|
if (self->css) {
|
|
vl_scalespace_delete(self->css) ;
|
|
self->css = NULL ;
|
|
}
|
|
if (self->gss) {
|
|
vl_scalespace_delete(self->gss) ;
|
|
self->gss = NULL ;
|
|
}
|
|
}
|
|
|
|
/** @brief Delete object instance
|
|
** @param self object.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_delete (VlCovDet * self)
|
|
{
|
|
vl_covdet_reset(self) ;
|
|
if (self->patch) vl_free (self->patch) ;
|
|
vl_free(self) ;
|
|
}
|
|
|
|
/** @brief Append a feature to the internal buffer.
|
|
** @param self object.
|
|
** @param feature a pointer to the feature to append.
|
|
** @return status.
|
|
**
|
|
** The feature is copied. The function may fail with @c status
|
|
** equal to ::VL_ERR_ALLOC if there is insufficient memory.
|
|
**/
|
|
|
|
int
|
|
vl_covdet_append_feature (VlCovDet * self, VlCovDetFeature const * feature)
|
|
{
|
|
vl_size requiredSize ;
|
|
assert(self) ;
|
|
assert(feature) ;
|
|
self->numFeatures ++ ;
|
|
requiredSize = self->numFeatures * sizeof(VlCovDetFeature) ;
|
|
if (requiredSize > self->numFeatureBufferSize) {
|
|
int err = _vl_resize_buffer((void**)&self->features, &self->numFeatureBufferSize,
|
|
(self->numFeatures + 1000) * sizeof(VlCovDetFeature)) ;
|
|
if (err) {
|
|
self->numFeatures -- ;
|
|
return err ;
|
|
}
|
|
}
|
|
self->features[self->numFeatures - 1] = *feature ;
|
|
return VL_ERR_OK ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Process a new image */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/** @brief Detect features in an image
|
|
** @param self object.
|
|
** @param image image to process.
|
|
** @param width image width.
|
|
** @param height image height.
|
|
** @return status.
|
|
**
|
|
** @a width and @a height must be at least one pixel. The function
|
|
** fails by returing ::VL_ERR_ALLOC if the memory is insufficient.
|
|
**/
|
|
|
|
int
|
|
vl_covdet_put_image (VlCovDet * self,
|
|
float const * image,
|
|
vl_size width, vl_size height)
|
|
{
|
|
vl_size const minOctaveSize = 16 ;
|
|
vl_index lastOctave ;
|
|
vl_index octaveFirstSubdivision ;
|
|
vl_index octaveLastSubdivision ;
|
|
VlScaleSpaceGeometry geom = vl_scalespace_get_default_geometry(width,height) ;
|
|
|
|
assert (self) ;
|
|
assert (image) ;
|
|
assert (width >= 1) ;
|
|
assert (height >= 1) ;
|
|
|
|
/* (minOctaveSize - 1) 2^lastOctave <= min(width,height) - 1 */
|
|
lastOctave = vl_floor_d(vl_log2_d(VL_MIN((double)width-1,(double)height-1) / (minOctaveSize - 1))) ;
|
|
|
|
if (self->method == VL_COVDET_METHOD_DOG) {
|
|
octaveFirstSubdivision = -1 ;
|
|
octaveLastSubdivision = self->octaveResolution + 1 ;
|
|
} else if (self->method == VL_COVDET_METHOD_HESSIAN) {
|
|
octaveFirstSubdivision = -1 ;
|
|
octaveLastSubdivision = self->octaveResolution ;
|
|
} else {
|
|
octaveFirstSubdivision = 0 ;
|
|
octaveLastSubdivision = self->octaveResolution - 1 ;
|
|
}
|
|
|
|
geom.width = width ;
|
|
geom.height = height ;
|
|
geom.firstOctave = self->firstOctave ;
|
|
geom.lastOctave = lastOctave ;
|
|
geom.octaveResolution = self->octaveResolution ;
|
|
geom.octaveFirstSubdivision = octaveFirstSubdivision ;
|
|
geom.octaveLastSubdivision = octaveLastSubdivision ;
|
|
|
|
if (self->gss == NULL ||
|
|
! vl_scalespacegeometry_is_equal (geom,
|
|
vl_scalespace_get_geometry(self->gss)))
|
|
{
|
|
if (self->gss) vl_scalespace_delete(self->gss) ;
|
|
self->gss = vl_scalespace_new_with_geometry(geom) ;
|
|
if (self->gss == NULL) return VL_ERR_ALLOC ;
|
|
}
|
|
vl_scalespace_put_image(self->gss, image) ;
|
|
return VL_ERR_OK ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Cornerness measures */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/** @brief Scaled derminant of the Hessian filter
|
|
** @param hessian output image.
|
|
** @param image input image.
|
|
** @param width image width.
|
|
** @param height image height.
|
|
** @param step image sampling step (pixel size).
|
|
** @param sigma Gaussian smoothing of the input image.
|
|
**/
|
|
|
|
static void
|
|
_vl_det_hessian_response (float * hessian,
|
|
float const * image,
|
|
vl_size width, vl_size height,
|
|
double step, double sigma)
|
|
{
|
|
float factor = (float) pow(sigma/step, 4.0) ;
|
|
vl_index const xo = 1 ; /* x-stride */
|
|
vl_index const yo = width; /* y-stride */
|
|
vl_size r, c;
|
|
|
|
float p11, p12, p13, p21, p22, p23, p31, p32, p33;
|
|
|
|
/* setup input pointer to be centered at 0,1 */
|
|
float const *in = image + yo ;
|
|
|
|
/* setup output pointer to be centered at 1,1 */
|
|
float *out = hessian + xo + yo;
|
|
|
|
/* move 3x3 window and convolve */
|
|
for (r = 1; r < height - 1; ++r)
|
|
{
|
|
/* fill in shift registers at the beginning of the row */
|
|
p11 = in[-yo]; p12 = in[xo - yo];
|
|
p21 = in[ 0]; p22 = in[xo ];
|
|
p31 = in[+yo]; p32 = in[xo + yo];
|
|
/* setup input pointer to (2,1) of the 3x3 square */
|
|
in += 2;
|
|
for (c = 1; c < width - 1; ++c)
|
|
{
|
|
float Lxx, Lyy, Lxy;
|
|
/* fetch remaining values (last column) */
|
|
p13 = in[-yo]; p23 = *in; p33 = in[+yo];
|
|
|
|
/* Compute 3x3 Hessian values from pixel differences. */
|
|
Lxx = (-p21 + 2*p22 - p23);
|
|
Lyy = (-p12 + 2*p22 - p32);
|
|
Lxy = ((p11 - p31 - p13 + p33)/4.0f);
|
|
|
|
/* normalize and write out */
|
|
*out = (Lxx * Lyy - Lxy * Lxy) * factor ;
|
|
|
|
/* move window */
|
|
p11=p12; p12=p13;
|
|
p21=p22; p22=p23;
|
|
p31=p32; p32=p33;
|
|
|
|
/* move input/output pointers */
|
|
in++; out++;
|
|
}
|
|
out += 2;
|
|
}
|
|
|
|
/* Copy the computed values to borders */
|
|
in = hessian + yo + xo ;
|
|
out = hessian + xo ;
|
|
|
|
/* Top row without corners */
|
|
memcpy(out, in, (width - 2)*sizeof(float));
|
|
out--;
|
|
in -= yo;
|
|
|
|
/* Left border columns without last row */
|
|
for (r = 0; r < height - 1; r++){
|
|
*out = *in;
|
|
*(out + yo - 1) = *(in + yo - 3);
|
|
in += yo;
|
|
out += yo;
|
|
}
|
|
|
|
/* Bottom corners */
|
|
in -= yo;
|
|
*out = *in;
|
|
*(out + yo - 1) = *(in + yo - 3);
|
|
|
|
/* Bottom row without corners */
|
|
out++;
|
|
memcpy(out, in, (width - 2)*sizeof(float));
|
|
}
|
|
|
|
/** @brief Scale-normalised Harris response
|
|
** @param harris output image.
|
|
** @param image input image.
|
|
** @param width image width.
|
|
** @param height image height.
|
|
** @param step image sampling step (pixel size).
|
|
** @param sigma Gaussian smoothing of the input image.
|
|
** @param sigmaI integration scale.
|
|
** @param alpha factor in the definition of the Harris score.
|
|
**/
|
|
|
|
static void
|
|
_vl_harris_response (float * harris,
|
|
float const * image,
|
|
vl_size width, vl_size height,
|
|
double step, double sigma,
|
|
double sigmaI, double alpha)
|
|
{
|
|
float factor = (float) pow(sigma/step, 4.0) ;
|
|
vl_index k ;
|
|
|
|
float * LxLx ;
|
|
float * LyLy ;
|
|
float * LxLy ;
|
|
|
|
LxLx = vl_malloc(sizeof(float) * width * height) ;
|
|
LyLy = vl_malloc(sizeof(float) * width * height) ;
|
|
LxLy = vl_malloc(sizeof(float) * width * height) ;
|
|
|
|
vl_imgradient_f (LxLx, LyLy, 1, width, image, width, height, width) ;
|
|
|
|
for (k = 0 ; k < (signed)(width * height) ; ++k) {
|
|
float dx = LxLx[k] ;
|
|
float dy = LyLy[k] ;
|
|
LxLx[k] = dx*dx ;
|
|
LyLy[k] = dy*dy ;
|
|
LxLy[k] = dx*dy ;
|
|
}
|
|
|
|
vl_imsmooth_f(LxLx, width, LxLx, width, height, width,
|
|
sigmaI / step, sigmaI / step) ;
|
|
|
|
vl_imsmooth_f(LyLy, width, LyLy, width, height, width,
|
|
sigmaI / step, sigmaI / step) ;
|
|
|
|
vl_imsmooth_f(LxLy, width, LxLy, width, height, width,
|
|
sigmaI / step, sigmaI / step) ;
|
|
|
|
for (k = 0 ; k < (signed)(width * height) ; ++k) {
|
|
float a = LxLx[k] ;
|
|
float b = LyLy[k] ;
|
|
float c = LxLy[k] ;
|
|
|
|
float determinant = a * b - c * c ;
|
|
float trace = a + b ;
|
|
|
|
harris[k] = factor * (determinant - alpha * (trace * trace)) ;
|
|
}
|
|
|
|
vl_free(LxLy) ;
|
|
vl_free(LyLy) ;
|
|
vl_free(LxLx) ;
|
|
}
|
|
|
|
/** @brief Difference of Gaussian
|
|
** @param dog output image.
|
|
** @param level1 input image at the smaller Gaussian scale.
|
|
** @param level2 input image at the larger Gaussian scale.
|
|
** @param width image width.
|
|
** @param height image height.
|
|
**/
|
|
|
|
static void
|
|
_vl_dog_response (float * dog,
|
|
float const * level1,
|
|
float const * level2,
|
|
vl_size width, vl_size height)
|
|
{
|
|
vl_index k ;
|
|
for (k = 0 ; k < (signed)(width*height) ; ++k) {
|
|
dog[k] = level2[k] - level1[k] ;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Detect features */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/** @brief Detect scale-space features
|
|
** @param self object.
|
|
**
|
|
** This function runs the configured feature detector on the image
|
|
** that was passed by using ::vl_covdet_put_image.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_detect (VlCovDet * self, vl_size max_num_features)
|
|
{
|
|
VlScaleSpaceGeometry geom = vl_scalespace_get_geometry(self->gss) ;
|
|
VlScaleSpaceGeometry cgeom ;
|
|
float * levelxx = NULL ;
|
|
float * levelyy = NULL ;
|
|
float * levelxy = NULL ;
|
|
vl_index o, s ;
|
|
|
|
assert (self) ;
|
|
assert (self->gss) ;
|
|
|
|
/* clear previous detections if any */
|
|
self->numFeatures = 0 ;
|
|
|
|
/* prepare buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
cgeom = geom ;
|
|
if (self->method == VL_COVDET_METHOD_DOG) {
|
|
cgeom.octaveLastSubdivision -= 1 ;
|
|
}
|
|
if (!self->css ||
|
|
!vl_scalespacegeometry_is_equal(cgeom,
|
|
vl_scalespace_get_geometry(self->css)))
|
|
{
|
|
if (self->css) vl_scalespace_delete(self->css) ;
|
|
self->css = vl_scalespace_new_with_geometry(cgeom) ;
|
|
}
|
|
if (self->method == VL_COVDET_METHOD_HARRIS_LAPLACE ||
|
|
self->method == VL_COVDET_METHOD_MULTISCALE_HARRIS) {
|
|
VlScaleSpaceOctaveGeometry oct = vl_scalespace_get_octave_geometry(self->gss, geom.firstOctave) ;
|
|
levelxx = vl_malloc(oct.width * oct.height * sizeof(float)) ;
|
|
levelyy = vl_malloc(oct.width * oct.height * sizeof(float)) ;
|
|
levelxy = vl_malloc(oct.width * oct.height * sizeof(float)) ;
|
|
}
|
|
|
|
/* compute cornerness ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
for (o = cgeom.firstOctave ; o <= cgeom.lastOctave ; ++o) {
|
|
VlScaleSpaceOctaveGeometry oct = vl_scalespace_get_octave_geometry(self->css, o) ;
|
|
|
|
for (s = cgeom.octaveFirstSubdivision ; s <= cgeom.octaveLastSubdivision ; ++s) {
|
|
float * level = vl_scalespace_get_level(self->gss, o, s) ;
|
|
float * clevel = vl_scalespace_get_level(self->css, o, s) ;
|
|
double sigma = vl_scalespace_get_level_sigma(self->css, o, s) ;
|
|
switch (self->method) {
|
|
case VL_COVDET_METHOD_DOG:
|
|
_vl_dog_response(clevel,
|
|
vl_scalespace_get_level(self->gss, o, s + 1),
|
|
level,
|
|
oct.width, oct.height) ;
|
|
break ;
|
|
|
|
case VL_COVDET_METHOD_HARRIS_LAPLACE:
|
|
case VL_COVDET_METHOD_MULTISCALE_HARRIS:
|
|
_vl_harris_response(clevel,
|
|
level, oct.width, oct.height, oct.step,
|
|
sigma, 1.4 * sigma, 0.05) ;
|
|
break ;
|
|
|
|
case VL_COVDET_METHOD_HESSIAN:
|
|
case VL_COVDET_METHOD_HESSIAN_LAPLACE:
|
|
case VL_COVDET_METHOD_MULTISCALE_HESSIAN:
|
|
_vl_det_hessian_response(clevel, level, oct.width, oct.height, oct.step, sigma) ;
|
|
break ;
|
|
|
|
default:
|
|
assert(0) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* find and refine local maxima ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
{
|
|
vl_index * extrema = NULL ;
|
|
vl_size extremaBufferSize = 0 ;
|
|
vl_size numExtrema ;
|
|
vl_size index ;
|
|
for (o = cgeom.lastOctave; o >= cgeom.firstOctave; --o) {
|
|
VlScaleSpaceOctaveGeometry octgeom = vl_scalespace_get_octave_geometry(self->css, o) ;
|
|
double step = octgeom.step ;
|
|
vl_size width = octgeom.width ;
|
|
vl_size height = octgeom.height ;
|
|
vl_size depth = cgeom.octaveLastSubdivision - cgeom.octaveFirstSubdivision + 1 ;
|
|
|
|
switch (self->method) {
|
|
case VL_COVDET_METHOD_DOG:
|
|
case VL_COVDET_METHOD_HESSIAN:
|
|
{
|
|
/* scale-space extrema */
|
|
float const * octave =
|
|
vl_scalespace_get_level(self->css, o, cgeom.octaveFirstSubdivision) ;
|
|
numExtrema = vl_find_local_extrema_3(&extrema, &extremaBufferSize,
|
|
octave, width, height, depth,
|
|
0.8 * self->peakThreshold);
|
|
for (index = 0 ; index < numExtrema ; ++index) {
|
|
VlCovDetExtremum3 refined ;
|
|
VlCovDetFeature feature ;
|
|
vl_bool ok ;
|
|
memset(&feature, 0, sizeof(feature)) ;
|
|
ok = vl_refine_local_extreum_3(&refined,
|
|
octave, width, height, depth,
|
|
extrema[3*index+0],
|
|
extrema[3*index+1],
|
|
extrema[3*index+2]) ;
|
|
ok &= fabs(refined.peakScore) > self->peakThreshold ;
|
|
ok &= refined.edgeScore < self->edgeThreshold ;
|
|
if (ok) {
|
|
double sigma = cgeom.baseScale *
|
|
pow(2.0, o + (refined.z + cgeom.octaveFirstSubdivision)
|
|
/ cgeom.octaveResolution) ;
|
|
feature.frame.x = refined.x * step ;
|
|
feature.frame.y = refined.y * step ;
|
|
feature.frame.a11 = sigma ;
|
|
feature.frame.a12 = 0.0 ;
|
|
feature.frame.a21 = 0.0 ;
|
|
feature.frame.a22 = sigma ;
|
|
feature.o = o ;
|
|
feature.s = round(refined.z) ;
|
|
feature.peakScore = refined.peakScore ;
|
|
feature.edgeScore = refined.edgeScore ;
|
|
vl_covdet_append_feature(self, &feature) ;
|
|
}
|
|
}
|
|
break ;
|
|
}
|
|
|
|
default:
|
|
{
|
|
for (s = cgeom.octaveFirstSubdivision ; s < cgeom.octaveLastSubdivision ; ++s) {
|
|
/* space extrema */
|
|
float const * level = vl_scalespace_get_level(self->css,o,s) ;
|
|
numExtrema = vl_find_local_extrema_2(&extrema, &extremaBufferSize,
|
|
level,
|
|
width, height,
|
|
0.8 * self->peakThreshold);
|
|
for (index = 0 ; index < numExtrema ; ++index) {
|
|
VlCovDetExtremum2 refined ;
|
|
VlCovDetFeature feature ;
|
|
vl_bool ok ;
|
|
memset(&feature, 0, sizeof(feature)) ;
|
|
ok = vl_refine_local_extreum_2(&refined,
|
|
level, width, height,
|
|
extrema[2*index+0],
|
|
extrema[2*index+1]);
|
|
ok &= fabs(refined.peakScore) > self->peakThreshold ;
|
|
ok &= refined.edgeScore < self->edgeThreshold ;
|
|
if (ok) {
|
|
double sigma = cgeom.baseScale *
|
|
pow(2.0, o + (double)s / cgeom.octaveResolution) ;
|
|
feature.frame.x = refined.x * step ;
|
|
feature.frame.y = refined.y * step ;
|
|
feature.frame.a11 = sigma ;
|
|
feature.frame.a12 = 0.0 ;
|
|
feature.frame.a21 = 0.0 ;
|
|
feature.frame.a22 = sigma ;
|
|
feature.o = o ;
|
|
feature.s = s ;
|
|
feature.peakScore = refined.peakScore ;
|
|
feature.edgeScore = refined.edgeScore ;
|
|
vl_covdet_append_feature(self, &feature) ;
|
|
}
|
|
}
|
|
}
|
|
break ;
|
|
}
|
|
}
|
|
if (self->numFeatures >= max_num_features) {
|
|
break;
|
|
}
|
|
} /* next octave */
|
|
|
|
if (extrema) { vl_free(extrema) ; extrema = 0 ; }
|
|
}
|
|
|
|
/* Laplacian scale selection for certain methods */
|
|
switch (self->method) {
|
|
case VL_COVDET_METHOD_HARRIS_LAPLACE :
|
|
case VL_COVDET_METHOD_HESSIAN_LAPLACE :
|
|
vl_covdet_extract_laplacian_scales (self) ;
|
|
break ;
|
|
default:
|
|
break ;
|
|
}
|
|
|
|
if (self->nonExtremaSuppression) {
|
|
vl_index i, j ;
|
|
double tol = self->nonExtremaSuppression ;
|
|
self->numNonExtremaSuppressed = 0 ;
|
|
for (i = 0 ; i < (signed)self->numFeatures ; ++i) {
|
|
double x = self->features[i].frame.x ;
|
|
double y = self->features[i].frame.y ;
|
|
double sigma = self->features[i].frame.a11 ;
|
|
double score = self->features[i].peakScore ;
|
|
if (score == 0) continue ;
|
|
|
|
for (j = 0 ; j < (signed)self->numFeatures ; ++j) {
|
|
double dx_ = self->features[j].frame.x - x ;
|
|
double dy_ = self->features[j].frame.y - y ;
|
|
double sigma_ = self->features[j].frame.a11 ;
|
|
double score_ = self->features[j].peakScore ;
|
|
if (score_ == 0) continue ;
|
|
if (sigma < (1+tol) * sigma_ &&
|
|
sigma_ < (1+tol) * sigma &&
|
|
vl_abs_d(dx_) < tol * sigma &&
|
|
vl_abs_d(dy_) < tol * sigma &&
|
|
vl_abs_d(score) > vl_abs_d(score_)) {
|
|
self->features[j].peakScore = 0 ;
|
|
self->numNonExtremaSuppressed ++ ;
|
|
}
|
|
}
|
|
}
|
|
j = 0 ;
|
|
for (i = 0 ; i < (signed)self->numFeatures ; ++i) {
|
|
VlCovDetFeature feature = self->features[i] ;
|
|
if (self->features[i].peakScore != 0) {
|
|
self->features[j++] = feature ;
|
|
}
|
|
}
|
|
self->numFeatures = j ;
|
|
}
|
|
|
|
if (levelxx) vl_free(levelxx) ;
|
|
if (levelyy) vl_free(levelyy) ;
|
|
if (levelxy) vl_free(levelxy) ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Extract patches */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/** @internal
|
|
** @brief Helper for extracting patches
|
|
** @param self object.
|
|
** @param[out] sigma1 actual patch smoothing along the first axis.
|
|
** @param[out] sigma2 actual patch smoothing along the second axis.
|
|
** @param patch buffer.
|
|
** @param resolution patch resolution.
|
|
** @param extent patch extent.
|
|
** @param sigma desired smoothing in the patch frame.
|
|
** @param A_ linear transfomration from patch to image.
|
|
** @param T_ translation from patch to image.
|
|
** @param d1 first singular value @a A.
|
|
** @param d2 second singular value of @a A.
|
|
**/
|
|
|
|
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)
|
|
{
|
|
vl_index o, s ;
|
|
double factor ;
|
|
double sigma_ ;
|
|
float const * level ;
|
|
vl_size width, height ;
|
|
double step ;
|
|
|
|
double A [4] = {A_[0], A_[1], A_[2], A_[3]} ;
|
|
double T [2] = {T_[0], T_[1]} ;
|
|
|
|
VlScaleSpaceGeometry geom = vl_scalespace_get_geometry(self->gss) ;
|
|
VlScaleSpaceOctaveGeometry oct ;
|
|
|
|
/* Starting from a pre-smoothed image at scale sigma_
|
|
because of the mapping A the resulting smoothing in
|
|
the warped patch is S, where
|
|
|
|
sigma_^2 I = A S A',
|
|
|
|
S = sigma_^2 inv(A) inv(A)' = sigma_^2 V D^-2 V',
|
|
|
|
A = U D V'.
|
|
|
|
Thus we rotate A by V to obtain an axis-aligned smoothing:
|
|
|
|
A = U*D,
|
|
|
|
S = sigma_^2 D^-2.
|
|
|
|
Then we search the scale-space for the best sigma_ such
|
|
that the target smoothing is approximated from below:
|
|
|
|
max sigma_(o,s) : simga_(o,s) factor <= sigma,
|
|
factor = max{abs(D11), abs(D22)}.
|
|
*/
|
|
|
|
|
|
/*
|
|
Determine the best level (o,s) such that sigma_(o,s) factor <= sigma.
|
|
This can be obtained by scanning octaves from smallest to largest
|
|
and stopping when no level in the octave satisfies the relation.
|
|
|
|
Given the range of octave availables, do the best you can.
|
|
*/
|
|
|
|
factor = 1.0 / VL_MIN(d1, d2) ;
|
|
|
|
for (o = geom.firstOctave + 1 ; o <= geom.lastOctave ; ++o) {
|
|
s = vl_floor_d(vl_log2_d(sigma / (factor * geom.baseScale)) - o) ;
|
|
s = VL_MAX(s, geom.octaveFirstSubdivision) ;
|
|
s = VL_MIN(s, geom.octaveLastSubdivision) ;
|
|
sigma_ = geom.baseScale * pow(2.0, o + (double)s / geom.octaveResolution) ;
|
|
/*VL_PRINTF(".. %d D=%g %g; sigma_=%g factor*sigma_=%g\n", o, d1, d2, sigma_, factor* sigma_) ;*/
|
|
if (factor * sigma_ > sigma) {
|
|
o -- ;
|
|
break ;
|
|
}
|
|
}
|
|
o = VL_MIN(o, geom.lastOctave) ;
|
|
s = vl_floor_d(vl_log2_d(sigma / (factor * geom.baseScale)) - o) ;
|
|
s = VL_MAX(s, geom.octaveFirstSubdivision) ;
|
|
s = VL_MIN(s, geom.octaveLastSubdivision) ;
|
|
sigma_ = geom.baseScale * pow(2.0, o + (double)s / geom.octaveResolution) ;
|
|
if (sigma1) *sigma1 = sigma_ / d1 ;
|
|
if (sigma2) *sigma2 = sigma_ / d2 ;
|
|
|
|
/*VL_PRINTF("%d %d %g %g %g %g\n", o, s, factor, sigma_, factor * sigma_, sigma) ;*/
|
|
|
|
/*
|
|
Now the scale space level to be used for this warping has been
|
|
determined.
|
|
|
|
If the patch is partially or completely out of the image boundary,
|
|
create a padded copy of the required region first.
|
|
*/
|
|
|
|
level = vl_scalespace_get_level(self->gss, o, s) ;
|
|
oct = vl_scalespace_get_octave_geometry(self->gss, o) ;
|
|
width = oct.width ;
|
|
height = oct.height ;
|
|
step = oct.step ;
|
|
|
|
A[0] /= step ;
|
|
A[1] /= step ;
|
|
A[2] /= step ;
|
|
A[3] /= step ;
|
|
T[0] /= step ;
|
|
T[1] /= step ;
|
|
|
|
{
|
|
/*
|
|
Warp the patch domain [x0hat,y0hat,x1hat,y1hat] to the image domain/
|
|
Obtain a box [x0,y0,x1,y1] enclosing that wrapped box, and then
|
|
an integer vertexes version [x0i, y0i, x1i, y1i], making room
|
|
for one pixel at the boundary to simplify bilinear interpolation
|
|
later on.
|
|
*/
|
|
vl_index x0i, y0i, x1i, y1i ;
|
|
double x0 = +VL_INFINITY_D ;
|
|
double x1 = -VL_INFINITY_D ;
|
|
double y0 = +VL_INFINITY_D ;
|
|
double y1 = -VL_INFINITY_D ;
|
|
double boxx [4] = {extent, extent, -extent, -extent} ;
|
|
double boxy [4] = {-extent, extent, extent, -extent} ;
|
|
int i ;
|
|
for (i = 0 ; i < 4 ; ++i) {
|
|
double x = A[0] * boxx[i] + A[2] * boxy[i] + T[0] ;
|
|
double y = A[1] * boxx[i] + A[3] * boxy[i] + T[1] ;
|
|
x0 = VL_MIN(x0, x) ;
|
|
x1 = VL_MAX(x1, x) ;
|
|
y0 = VL_MIN(y0, y) ;
|
|
y1 = VL_MAX(y1, y) ;
|
|
}
|
|
|
|
/* Leave one pixel border for bilinear interpolation. */
|
|
x0i = floor(x0) - 1 ;
|
|
y0i = floor(y0) - 1 ;
|
|
x1i = ceil(x1) + 1 ;
|
|
y1i = ceil(y1) + 1 ;
|
|
|
|
/*
|
|
If the box [x0i,y0i,x1i,y1i] is not fully contained in the
|
|
image domain, then create a copy of this region by padding
|
|
the image. The image is extended by continuity.
|
|
*/
|
|
|
|
if (x0i < 0 || x1i > (signed)width-1 ||
|
|
y0i < 0 || y1i > (signed)height-1) {
|
|
vl_index xi, yi ;
|
|
|
|
/* compute the amount of l,r,t,b padding needed to complete the patch */
|
|
vl_index padx0 = VL_MAX(0, - x0i) ;
|
|
vl_index pady0 = VL_MAX(0, - y0i) ;
|
|
vl_index padx1 = VL_MAX(0, x1i - ((signed)width - 1)) ;
|
|
vl_index pady1 = VL_MAX(0, y1i - ((signed)height - 1)) ;
|
|
|
|
/* make enough room for the patch */
|
|
vl_index patchWidth = x1i - x0i + 1 ;
|
|
vl_index patchHeight = y1i - y0i + 1 ;
|
|
vl_size patchBufferSize = patchWidth * patchHeight * sizeof(float) ;
|
|
if (patchBufferSize > self->patchBufferSize) {
|
|
int err = _vl_resize_buffer((void**)&self->patch, &self->patchBufferSize, patchBufferSize) ;
|
|
if (err) return vl_set_last_error(VL_ERR_ALLOC, NULL) ;
|
|
}
|
|
|
|
if (pady0 < patchHeight - pady1) {
|
|
/* start by filling the central horizontal band */
|
|
for (yi = y0i + pady0 ; yi < y0i + patchHeight - pady1 ; ++ yi) {
|
|
float *dst = self->patch + (yi - y0i) * patchWidth ;
|
|
float const *src = level + yi * width + VL_MIN(VL_MAX(0, x0i),(signed)width-1) ;
|
|
for (xi = x0i ; xi < x0i + padx0 ; ++xi) *dst++ = *src ;
|
|
for ( ; xi < x0i + patchWidth - padx1 - 2 ; ++xi) *dst++ = *src++ ;
|
|
for ( ; xi < x0i + patchWidth ; ++xi) *dst++ = *src ;
|
|
}
|
|
/* now extend the central band up and down */
|
|
for (yi = 0 ; yi < pady0 ; ++yi) {
|
|
memcpy(self->patch + yi * patchWidth,
|
|
self->patch + pady0 * patchWidth,
|
|
patchWidth * sizeof(float)) ;
|
|
}
|
|
for (yi = patchHeight - pady1 ; yi < patchHeight ; ++yi) {
|
|
memcpy(self->patch + yi * patchWidth,
|
|
self->patch + (patchHeight - pady1 - 1) * patchWidth,
|
|
patchWidth * sizeof(float)) ;
|
|
}
|
|
} else {
|
|
/* should be handled better! */
|
|
memset(self->patch, 0, self->patchBufferSize) ;
|
|
}
|
|
#if 0
|
|
{
|
|
char name [200] ;
|
|
snprintf(name, 200, "/tmp/%20.0f-ext.pgm", 1e10*vl_get_cpu_time()) ;
|
|
vl_pgm_write_f(name, patch, patchWidth, patchWidth) ;
|
|
}
|
|
#endif
|
|
|
|
level = self->patch ;
|
|
width = patchWidth ;
|
|
height = patchHeight ;
|
|
T[0] -= x0i ;
|
|
T[1] -= y0i ;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Resample by using bilinear interpolation.
|
|
*/
|
|
{
|
|
float * pt = patch ;
|
|
double yhat = -extent ;
|
|
vl_index xxi ;
|
|
vl_index yyi ;
|
|
double stephat = extent / resolution ;
|
|
|
|
for (yyi = 0 ; yyi < 2 * (signed)resolution + 1 ; ++yyi) {
|
|
double xhat = -extent ;
|
|
double rx = A[2] * yhat + T[0] ;
|
|
double ry = A[3] * yhat + T[1] ;
|
|
for (xxi = 0 ; xxi < 2 * (signed)resolution + 1 ; ++xxi) {
|
|
double x = A[0] * xhat + rx ;
|
|
double y = A[1] * xhat + ry ;
|
|
vl_index xi = vl_floor_d(x) ;
|
|
vl_index yi = vl_floor_d(y) ;
|
|
double i00 = level[yi * width + xi] ;
|
|
double i10 = level[yi * width + xi + 1] ;
|
|
double i01 = level[(yi + 1) * width + xi] ;
|
|
double i11 = level[(yi + 1) * width + xi + 1] ;
|
|
double wx = x - xi ;
|
|
double wy = y - yi ;
|
|
|
|
assert(xi >= 0 && xi <= (signed)width - 1) ;
|
|
assert(yi >= 0 && yi <= (signed)height - 1) ;
|
|
|
|
*pt++ =
|
|
(1.0 - wy) * ((1.0 - wx) * i00 + wx * i10) +
|
|
wy * ((1.0 - wx) * i01 + wx * i11) ;
|
|
|
|
xhat += stephat ;
|
|
}
|
|
yhat += stephat ;
|
|
}
|
|
}
|
|
#if 0
|
|
{
|
|
char name [200] ;
|
|
snprintf(name, 200, "/tmp/%20.0f.pgm", 1e10*vl_get_cpu_time()) ;
|
|
vl_pgm_write_f(name, patch, 2*resolution+1, 2*resolution+1) ;
|
|
}
|
|
#endif
|
|
return VL_ERR_OK ;
|
|
}
|
|
|
|
/** @brief Helper for extracting patches
|
|
** @param self object.
|
|
** @param patch buffer.
|
|
** @param resolution patch resolution.
|
|
** @param extent patch extent.
|
|
** @param sigma desired smoothing in the patch frame.
|
|
** @param frame feature frame.
|
|
**
|
|
** The function considers a patch of extent <code>[-extent,extent]</code>
|
|
** on each side, with a side counting <code>2*resolution+1</code> pixels.
|
|
** In attempts to extract from the scale space a patch
|
|
** based on the affine warping specified by @a frame in such a way
|
|
** that the resulting smoothing of the image is @a sigma (in the
|
|
** patch frame).
|
|
**
|
|
** The transformation is specified by the matrices @c A and @c T
|
|
** embedded in the feature @a frame. Note that this transformation maps
|
|
** pixels from the patch frame to the image frame.
|
|
**/
|
|
|
|
vl_bool
|
|
vl_covdet_extract_patch_for_frame (VlCovDet * self,
|
|
float * patch,
|
|
vl_size resolution,
|
|
double extent,
|
|
double sigma,
|
|
VlFrameOrientedEllipse frame)
|
|
{
|
|
double A[2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
|
|
double T[2] = {frame.x, frame.y} ;
|
|
double D[4], U[4], V[4] ;
|
|
|
|
vl_svd2(D, U, V, A) ;
|
|
|
|
return vl_covdet_extract_patch_helper
|
|
(self, NULL, NULL, patch, resolution, extent, sigma, A, T, D[0], D[3]) ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Affine shape */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/** @brief Extract the affine shape for a feature frame
|
|
** @param self object.
|
|
** @param adapted the shape-adapted frame.
|
|
** @param frame the input frame.
|
|
** @return ::VL_ERR_OK if affine adaptation is successful.
|
|
**
|
|
** This function may fail if adaptation is unsuccessful or if
|
|
** memory is insufficient.
|
|
**/
|
|
|
|
int
|
|
vl_covdet_extract_affine_shape_for_frame (VlCovDet * self,
|
|
VlFrameOrientedEllipse * adapted,
|
|
VlFrameOrientedEllipse frame)
|
|
{
|
|
vl_index iter = 0 ;
|
|
|
|
double A [2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
|
|
double T [2] = {frame.x, frame.y} ;
|
|
double U [2*2] ;
|
|
double V [2*2] ;
|
|
double D [2*2] ;
|
|
double M [2*2] ;
|
|
double P [2*2] ;
|
|
double P_ [2*2] ;
|
|
double Q [2*2] ;
|
|
double sigma1, sigma2 ;
|
|
double sigmaD = VL_COVDET_AA_RELATIVE_DERIVATIVE_SIGMA ;
|
|
double factor ;
|
|
double anisotropy ;
|
|
double referenceScale ;
|
|
vl_size const resolution = VL_COVDET_AA_PATCH_RESOLUTION ;
|
|
vl_size const side = 2*VL_COVDET_AA_PATCH_RESOLUTION + 1 ;
|
|
double const extent = VL_COVDET_AA_PATCH_EXTENT ;
|
|
|
|
|
|
*adapted = frame ;
|
|
|
|
while (1) {
|
|
double lxx = 0, lxy = 0, lyy = 0 ;
|
|
vl_index k ;
|
|
int err ;
|
|
|
|
/* A = U D V' */
|
|
vl_svd2(D, U, V, A) ;
|
|
anisotropy = VL_MAX(D[0]/D[3], D[3]/D[0]) ;
|
|
|
|
/* VL_PRINTF("anisot: %g\n", anisotropy); */
|
|
|
|
if (anisotropy > VL_COVDET_AA_MAX_ANISOTROPY) {
|
|
/* diverged, give up with current solution */
|
|
break ;
|
|
}
|
|
|
|
/* make sure that the smallest singluar value stays fixed
|
|
after the first iteration */
|
|
if (iter == 0) {
|
|
referenceScale = VL_MIN(D[0], D[3]) ;
|
|
factor = 1.0 ;
|
|
} else {
|
|
factor = referenceScale / VL_MIN(D[0],D[3]) ;
|
|
}
|
|
|
|
D[0] *= factor ;
|
|
D[3] *= factor ;
|
|
|
|
A[0] = U[0] * D[0] ;
|
|
A[1] = U[1] * D[0] ;
|
|
A[2] = U[2] * D[3] ;
|
|
A[3] = U[3] * D[3] ;
|
|
|
|
adapted->a11 = A[0] ;
|
|
adapted->a21 = A[1] ;
|
|
adapted->a12 = A[2] ;
|
|
adapted->a22 = A[3] ;
|
|
|
|
if (++iter >= VL_COVDET_AA_MAX_NUM_ITERATIONS) break ;
|
|
|
|
err = vl_covdet_extract_patch_helper(self,
|
|
&sigma1, &sigma2,
|
|
self->aaPatch,
|
|
resolution,
|
|
extent,
|
|
sigmaD,
|
|
A, T, D[0], D[3]) ;
|
|
if (err) return err ;
|
|
|
|
if (self->aaAccurateSmoothing ) {
|
|
double deltaSigma1 = sqrt(VL_MAX(sigmaD*sigmaD - sigma1*sigma1,0)) ;
|
|
double deltaSigma2 = sqrt(VL_MAX(sigmaD*sigmaD - sigma2*sigma2,0)) ;
|
|
double stephat = extent / resolution ;
|
|
vl_imsmooth_f(self->aaPatch, side,
|
|
self->aaPatch, side, side, side,
|
|
deltaSigma1 / stephat, deltaSigma2 / stephat) ;
|
|
}
|
|
|
|
/* compute second moment matrix */
|
|
vl_imgradient_f (self->aaPatchX, self->aaPatchY, 1, side,
|
|
self->aaPatch, side, side, side) ;
|
|
|
|
for (k = 0 ; k < (signed)(side*side) ; ++k) {
|
|
double lx = self->aaPatchX[k] ;
|
|
double ly = self->aaPatchY[k] ;
|
|
lxx += lx * lx * self->aaMask[k] ;
|
|
lyy += ly * ly * self->aaMask[k] ;
|
|
lxy += lx * ly * self->aaMask[k] ;
|
|
}
|
|
M[0] = lxx ;
|
|
M[1] = lxy ;
|
|
M[2] = lxy ;
|
|
M[3] = lyy ;
|
|
|
|
if (lxx == 0 || lyy == 0) {
|
|
*adapted = frame ;
|
|
break ;
|
|
}
|
|
|
|
/* decompose M = P * Q * P' */
|
|
vl_svd2 (Q, P, P_, M) ;
|
|
|
|
/*
|
|
Setting A <- A * dA results in M to change approximatively as
|
|
|
|
M --> dA' M dA = dA' P Q P dA
|
|
|
|
To make this proportional to the identity, we set
|
|
|
|
dA ~= P Q^1/2
|
|
|
|
we also make it so the smallest singular value of A is unchanged.
|
|
*/
|
|
|
|
if (Q[3]/Q[0] < VL_COVDET_AA_CONVERGENCE_THRESHOLD &&
|
|
Q[0]/Q[3] < VL_COVDET_AA_CONVERGENCE_THRESHOLD) {
|
|
break ;
|
|
}
|
|
|
|
{
|
|
double Ap [4] ;
|
|
double q0 = sqrt(Q[0]) ;
|
|
double q1 = sqrt(Q[3]) ;
|
|
Ap[0] = (A[0] * P[0] + A[2] * P[1]) / q0 ;
|
|
Ap[1] = (A[1] * P[0] + A[3] * P[1]) / q0 ;
|
|
Ap[2] = (A[0] * P[2] + A[2] * P[3]) / q1 ;
|
|
Ap[3] = (A[1] * P[2] + A[3] * P[3]) / q1 ;
|
|
memcpy(A,Ap,4*sizeof(double)) ;
|
|
}
|
|
|
|
} /* next iteration */
|
|
|
|
/*
|
|
Make upright.
|
|
|
|
Shape adaptation does not estimate rotation. This is fixed by default
|
|
so that a selected axis is not rotated at all (usually this is the
|
|
vertical axis for upright features). To do so, the frame is rotated
|
|
as follows.
|
|
*/
|
|
{
|
|
double A [2*2] = {adapted->a11, adapted->a21, adapted->a12, adapted->a22} ;
|
|
double ref [2] ;
|
|
double ref_ [2] ;
|
|
double angle ;
|
|
double angle_ ;
|
|
double dangle ;
|
|
double r1, r2 ;
|
|
|
|
if (self->transposed) {
|
|
/* up is the x axis */
|
|
ref[0] = 1 ;
|
|
ref[1] = 0 ;
|
|
} else {
|
|
/* up is the y axis */
|
|
ref[0] = 0 ;
|
|
ref[1] = 1 ;
|
|
}
|
|
|
|
vl_solve_linear_system_2 (ref_, A, ref) ;
|
|
angle = atan2(ref[1], ref[0]) ;
|
|
angle_ = atan2(ref_[1], ref_[0]) ;
|
|
dangle = angle_ - angle ;
|
|
r1 = cos(dangle) ;
|
|
r2 = sin(dangle) ;
|
|
adapted->a11 = + A[0] * r1 + A[2] * r2 ;
|
|
adapted->a21 = + A[1] * r1 + A[3] * r2 ;
|
|
adapted->a12 = - A[0] * r2 + A[2] * r1 ;
|
|
adapted->a22 = - A[1] * r2 + A[3] * r1 ;
|
|
}
|
|
|
|
return VL_ERR_OK ;
|
|
}
|
|
|
|
/** @brief Extract the affine shape for the stored features
|
|
** @param self object.
|
|
**
|
|
** This function may discard features for which no affine
|
|
** shape can reliably be detected.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_extract_affine_shape (VlCovDet * self)
|
|
{
|
|
vl_index i, j = 0 ;
|
|
vl_size numFeatures = vl_covdet_get_num_features(self) ;
|
|
VlCovDetFeature * feature = vl_covdet_get_features(self);
|
|
for (i = 0 ; i < (signed)numFeatures ; ++i) {
|
|
int status ;
|
|
VlFrameOrientedEllipse adapted ;
|
|
status = vl_covdet_extract_affine_shape_for_frame(self, &adapted, feature[i].frame) ;
|
|
if (status == VL_ERR_OK) {
|
|
feature[j] = feature[i] ;
|
|
feature[j].frame = adapted ;
|
|
++ j ;
|
|
}
|
|
}
|
|
self->numFeatures = j ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Orientation */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
static int
|
|
_vl_covdet_compare_orientations_descending (void const * a_,
|
|
void const * b_)
|
|
{
|
|
VlCovDetFeatureOrientation const * a = a_ ;
|
|
VlCovDetFeatureOrientation const * b = b_ ;
|
|
if (a->score > b->score) return -1 ;
|
|
if (a->score < b->score) return +1 ;
|
|
return 0 ;
|
|
}
|
|
|
|
/** @brief Extract the orientation(s) for a feature
|
|
** @param self object.
|
|
** @param numOrientations the number of detected orientations.
|
|
** @param frame pose of the feature.
|
|
** @return an array of detected orientations with their scores.
|
|
**
|
|
** The returned array is a matrix of size @f$ 2 \times n @f$
|
|
** where <em>n</em> is the number of detected orientations.
|
|
**
|
|
** The function returns @c NULL if memory is insufficient.
|
|
**/
|
|
|
|
VlCovDetFeatureOrientation *
|
|
vl_covdet_extract_orientations_for_frame (VlCovDet * self,
|
|
vl_size * numOrientations,
|
|
VlFrameOrientedEllipse frame)
|
|
{
|
|
int err ;
|
|
vl_index k, i ;
|
|
vl_index iter ;
|
|
|
|
double extent = VL_COVDET_AA_PATCH_EXTENT ;
|
|
vl_size resolution = VL_COVDET_AA_PATCH_RESOLUTION ;
|
|
vl_size side = 2 * resolution + 1 ;
|
|
|
|
vl_size const numBins = VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS ;
|
|
double hist [VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS] ;
|
|
double const binExtent = 2 * VL_PI / VL_COVDET_OR_NUM_ORIENTATION_HISTOGAM_BINS ;
|
|
double const peakRelativeSize = VL_COVDET_OR_ADDITIONAL_PEAKS_RELATIVE_SIZE ;
|
|
double maxPeakValue ;
|
|
|
|
double A [2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
|
|
double T [2] = {frame.x, frame.y} ;
|
|
double U [2*2] ;
|
|
double V [2*2] ;
|
|
double D [2*2] ;
|
|
double sigma1, sigma2 ;
|
|
double sigmaD = 1.0 ;
|
|
double theta0 ;
|
|
|
|
assert(self);
|
|
assert(numOrientations) ;
|
|
|
|
/*
|
|
The goal is to estimate a rotation R(theta) such that the patch given
|
|
by the transformation A R(theta) has the strongest average
|
|
gradient pointing right (or down for transposed conventions).
|
|
|
|
To compensate for tha anisotropic smoothing due to warping,
|
|
A is decomposed as A = U D V' and the patch is warped by
|
|
U D only, meaning that the matrix R_(theta) will be estimated instead,
|
|
where:
|
|
|
|
A R(theta) = U D V' R(theta) = U D R_(theta)
|
|
|
|
such that R(theta) = V R(theta). That is an extra rotation of
|
|
theta0 = atan2(V(2,1),V(1,1)).
|
|
*/
|
|
|
|
/* axis aligned anisotropic smoothing for easier compensation */
|
|
vl_svd2(D, U, V, A) ;
|
|
|
|
A[0] = U[0] * D[0] ;
|
|
A[1] = U[1] * D[0] ;
|
|
A[2] = U[2] * D[3] ;
|
|
A[3] = U[3] * D[3] ;
|
|
|
|
theta0 = atan2(V[1],V[0]) ;
|
|
|
|
err = vl_covdet_extract_patch_helper(self,
|
|
&sigma1, &sigma2,
|
|
self->aaPatch,
|
|
resolution,
|
|
extent,
|
|
sigmaD,
|
|
A, T, D[0], D[3]) ;
|
|
|
|
if (err) {
|
|
*numOrientations = 0 ;
|
|
return NULL ;
|
|
}
|
|
|
|
if (1) {
|
|
double deltaSigma1 = sqrt(VL_MAX(sigmaD*sigmaD - sigma1*sigma1,0)) ;
|
|
double deltaSigma2 = sqrt(VL_MAX(sigmaD*sigmaD - sigma2*sigma2,0)) ;
|
|
double stephat = extent / resolution ;
|
|
vl_imsmooth_f(self->aaPatch, side,
|
|
self->aaPatch, side, side, side,
|
|
deltaSigma1 / stephat, deltaSigma2 / stephat) ;
|
|
}
|
|
|
|
/* histogram of oriented gradients */
|
|
vl_imgradient_polar_f (self->aaPatchX, self->aaPatchY, 1, side,
|
|
self->aaPatch, side, side, side) ;
|
|
|
|
memset (hist, 0, sizeof(double) * numBins) ;
|
|
|
|
for (k = 0 ; k < (signed)(side*side) ; ++k) {
|
|
double modulus = self->aaPatchX[k] ;
|
|
double angle = self->aaPatchY[k] ;
|
|
double weight = self->aaMask[k] ;
|
|
|
|
double x = angle / binExtent ;
|
|
vl_index bin = vl_floor_d(x) ;
|
|
double w2 = x - bin ;
|
|
double w1 = 1.0 - w2 ;
|
|
|
|
hist[(bin + numBins) % numBins] += w1 * (modulus * weight) ;
|
|
hist[(bin + numBins + 1) % numBins] += w2 * (modulus * weight) ;
|
|
}
|
|
|
|
/* smooth histogram */
|
|
for (iter = 0; iter < 6; iter ++) {
|
|
double prev = hist [numBins - 1] ;
|
|
double first = hist [0] ;
|
|
vl_index i ;
|
|
for (i = 0; i < (signed)numBins - 1; ++i) {
|
|
double curr = (prev + hist[i] + hist[(i + 1) % numBins]) / 3.0 ;
|
|
prev = hist[i] ;
|
|
hist[i] = curr ;
|
|
}
|
|
hist[i] = (prev + hist[i] + first) / 3.0 ;
|
|
}
|
|
|
|
/* find the histogram maximum */
|
|
maxPeakValue = 0 ;
|
|
for (i = 0 ; i < (signed)numBins ; ++i) {
|
|
maxPeakValue = VL_MAX (maxPeakValue, hist[i]) ;
|
|
}
|
|
|
|
/* find peaks within 80% from max */
|
|
*numOrientations = 0 ;
|
|
for(i = 0 ; i < (signed)numBins ; ++i) {
|
|
double h0 = hist [i] ;
|
|
double hm = hist [(i - 1 + numBins) % numBins] ;
|
|
double hp = hist [(i + 1 + numBins) % numBins] ;
|
|
|
|
/* is this a peak? */
|
|
if (h0 > peakRelativeSize * maxPeakValue && h0 > hm && h0 > hp) {
|
|
/* quadratic interpolation */
|
|
double di = - 0.5 * (hp - hm) / (hp + hm - 2 * h0) ;
|
|
double th = binExtent * (i + di) + theta0 ;
|
|
if (self->transposed) {
|
|
/* the axis to the right is y, measure orientations from this */
|
|
th = th - VL_PI/2 ;
|
|
}
|
|
self->orientations[*numOrientations].angle = th ;
|
|
self->orientations[*numOrientations].score = h0 ;
|
|
*numOrientations += 1 ;
|
|
//VL_PRINTF("%d %g\n", *numOrientations, th) ;
|
|
|
|
if (*numOrientations >= VL_COVDET_MAX_NUM_ORIENTATIONS) break ;
|
|
}
|
|
}
|
|
|
|
/* sort the orientations by decreasing scores */
|
|
qsort(self->orientations,
|
|
*numOrientations,
|
|
sizeof(VlCovDetFeatureOrientation),
|
|
_vl_covdet_compare_orientations_descending) ;
|
|
|
|
return self->orientations ;
|
|
}
|
|
|
|
/** @brief Extract the orientation(s) for the stored features.
|
|
** @param self object.
|
|
**
|
|
** Note that, since more than one orientation can be detected
|
|
** for each feature, this function may create copies of them,
|
|
** one for each orientation.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_extract_orientations (VlCovDet * self)
|
|
{
|
|
vl_index i, j ;
|
|
vl_size numFeatures = vl_covdet_get_num_features(self) ;
|
|
for (i = 0 ; i < (signed)numFeatures ; ++i) {
|
|
vl_size numOrientations ;
|
|
VlCovDetFeature feature = self->features[i] ;
|
|
VlCovDetFeatureOrientation* orientations =
|
|
vl_covdet_extract_orientations_for_frame(self, &numOrientations, feature.frame) ;
|
|
|
|
for (j = 0 ; j < (signed)numOrientations ; ++j) {
|
|
double A [2*2] = {
|
|
feature.frame.a11,
|
|
feature.frame.a21,
|
|
feature.frame.a12,
|
|
feature.frame.a22} ;
|
|
double r1 = cos(orientations[j].angle) ;
|
|
double r2 = sin(orientations[j].angle) ;
|
|
VlCovDetFeature * oriented ;
|
|
|
|
if (j == 0) {
|
|
oriented = & self->features[i] ;
|
|
} else {
|
|
vl_covdet_append_feature(self, &feature) ;
|
|
oriented = & self->features[self->numFeatures -1] ;
|
|
}
|
|
|
|
oriented->orientationScore = orientations[j].score ;
|
|
oriented->frame.a11 = + A[0] * r1 + A[2] * r2 ;
|
|
oriented->frame.a21 = + A[1] * r1 + A[3] * r2 ;
|
|
oriented->frame.a12 = - A[0] * r2 + A[2] * r1 ;
|
|
oriented->frame.a22 = - A[1] * r2 + A[3] * r1 ;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Laplacian scales */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/** @brief Extract the Laplacian scale(s) for a feature frame.
|
|
** @param self object.
|
|
** @param numScales the number of detected scales.
|
|
** @param frame pose of the feature.
|
|
** @return an array of detected scales.
|
|
**
|
|
** The function returns @c NULL if memory is insufficient.
|
|
**/
|
|
|
|
VlCovDetFeatureLaplacianScale *
|
|
vl_covdet_extract_laplacian_scales_for_frame (VlCovDet * self,
|
|
vl_size * numScales,
|
|
VlFrameOrientedEllipse frame)
|
|
{
|
|
/*
|
|
We try to explore one octave, with the nominal detection scale 1.0
|
|
(in the patch reference frame) in the middle. Thus the goal is to sample
|
|
the response of the tr-Laplacian operator at logarithmically
|
|
spaced scales in 1/sqrt(2), sqrt(2).
|
|
|
|
To this end, the patch is warped with a smoothing of at most
|
|
sigmaImage = 1 / sqrt(2) (beginning of the scale), sampled at
|
|
roughly twice the Nyquist frequency (so step = 1 / (2*sqrt(2))).
|
|
This maes it possible to approximate the Laplacian operator at
|
|
that scale by simple finite differences.
|
|
|
|
*/
|
|
int err ;
|
|
double const sigmaImage = 1.0 / sqrt(2.0) ;
|
|
double const step = 0.5 * sigmaImage ;
|
|
double actualSigmaImage ;
|
|
vl_size const resolution = VL_COVDET_LAP_PATCH_RESOLUTION ;
|
|
vl_size const num = 2 * resolution + 1 ;
|
|
double extent = step * resolution ;
|
|
double scores [VL_COVDET_LAP_NUM_LEVELS] ;
|
|
double factor = 1.0 ;
|
|
float const * pt ;
|
|
vl_index k ;
|
|
|
|
double A[2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
|
|
double T[2] = {frame.x, frame.y} ;
|
|
double D[4], U[4], V[4] ;
|
|
double sigma1, sigma2 ;
|
|
|
|
assert(self) ;
|
|
assert(numScales) ;
|
|
|
|
*numScales = 0 ;
|
|
|
|
vl_svd2(D, U, V, A) ;
|
|
|
|
err = vl_covdet_extract_patch_helper
|
|
(self, &sigma1, &sigma2, self->lapPatch, resolution, extent, sigmaImage, A, T, D[0], D[3]) ;
|
|
if (err) return NULL ;
|
|
|
|
/* the actual smoothing after warping is never the target one */
|
|
if (sigma1 == sigma2) {
|
|
actualSigmaImage = sigma1 ;
|
|
} else {
|
|
/* here we could compensate */
|
|
actualSigmaImage = sqrt(sigma1*sigma2) ;
|
|
}
|
|
|
|
/* now multiply by the bank of Laplacians */
|
|
pt = self->laplacians ;
|
|
for (k = 0 ; k < VL_COVDET_LAP_NUM_LEVELS ; ++k) {
|
|
vl_index q ;
|
|
double score = 0 ;
|
|
double sigmaLap = pow(2.0, -0.5 + (double)k / (VL_COVDET_LAP_NUM_LEVELS - 1)) ;
|
|
/* note that the sqrt argument cannot be negative since by construction
|
|
sigmaLap >= sigmaImage */
|
|
sigmaLap = sqrt(sigmaLap*sigmaLap
|
|
- sigmaImage*sigmaImage
|
|
+ actualSigmaImage*actualSigmaImage) ;
|
|
|
|
for (q = 0 ; q < (signed)(num * num) ; ++q) {
|
|
score += (*pt++) * self->lapPatch[q] ;
|
|
}
|
|
scores[k] = score * sigmaLap * sigmaLap ;
|
|
}
|
|
|
|
/* find and interpolate maxima */
|
|
for (k = 1 ; k < VL_COVDET_LAP_NUM_LEVELS - 1 ; ++k) {
|
|
double a = scores[k-1] ;
|
|
double b = scores[k] ;
|
|
double c = scores[k+1] ;
|
|
double t = self->lapPeakThreshold ;
|
|
|
|
if ((((b > a) && (b > c)) || ((b < a) && (b < c))) && (vl_abs_d(b) >= t)) {
|
|
double dk = - 0.5 * (c - a) / (c + a - 2 * b) ;
|
|
double s = k + dk ;
|
|
double sigmaLap = pow(2.0, -0.5 + s / (VL_COVDET_LAP_NUM_LEVELS - 1)) ;
|
|
double scale ;
|
|
sigmaLap = sqrt(sigmaLap*sigmaLap
|
|
- sigmaImage*sigmaImage
|
|
+ actualSigmaImage*actualSigmaImage) ;
|
|
scale = sigmaLap / 1.0 ;
|
|
/*
|
|
VL_PRINTF("** k:%d, s:%f, sigmaLapFilter:%f, sigmaLap%f, scale:%f (%f %f %f)\n",
|
|
k,s,sigmaLapFilter,sigmaLap,scale,a,b,c) ;
|
|
*/
|
|
if (*numScales < VL_COVDET_MAX_NUM_LAPLACIAN_SCALES) {
|
|
self->scales[*numScales].scale = scale * factor ;
|
|
self->scales[*numScales].score = b + 0.5 * (c - a) * dk ;
|
|
*numScales += 1 ;
|
|
}
|
|
}
|
|
}
|
|
return self->scales ;
|
|
}
|
|
|
|
/** @brief Extract the Laplacian scales for the stored features
|
|
** @param self object.
|
|
**
|
|
** Note that, since more than one orientation can be detected
|
|
** for each feature, this function may create copies of them,
|
|
** one for each orientation.
|
|
**/
|
|
void
|
|
vl_covdet_extract_laplacian_scales (VlCovDet * self)
|
|
{
|
|
vl_index i, j ;
|
|
vl_bool dropFeaturesWithoutScale = VL_TRUE ;
|
|
vl_size numFeatures = vl_covdet_get_num_features(self) ;
|
|
memset(self->numFeaturesWithNumScales, 0,
|
|
sizeof(self->numFeaturesWithNumScales)) ;
|
|
|
|
for (i = 0 ; i < (signed)numFeatures ; ++i) {
|
|
vl_size numScales ;
|
|
VlCovDetFeature feature = self->features[i] ;
|
|
VlCovDetFeatureLaplacianScale const * scales =
|
|
vl_covdet_extract_laplacian_scales_for_frame(self, &numScales, feature.frame) ;
|
|
|
|
self->numFeaturesWithNumScales[numScales] ++ ;
|
|
|
|
if (numScales == 0 && dropFeaturesWithoutScale) {
|
|
self->features[i].peakScore = 0 ;
|
|
}
|
|
|
|
for (j = 0 ; j < (signed)numScales ; ++j) {
|
|
VlCovDetFeature * scaled ;
|
|
|
|
if (j == 0) {
|
|
scaled = & self->features[i] ;
|
|
} else {
|
|
vl_covdet_append_feature(self, &feature) ;
|
|
scaled = & self->features[self->numFeatures -1] ;
|
|
}
|
|
|
|
scaled->laplacianScaleScore = scales[j].score ;
|
|
scaled->frame.a11 *= scales[j].scale ;
|
|
scaled->frame.a21 *= scales[j].scale ;
|
|
scaled->frame.a12 *= scales[j].scale ;
|
|
scaled->frame.a22 *= scales[j].scale ;
|
|
}
|
|
}
|
|
if (dropFeaturesWithoutScale) {
|
|
j = 0 ;
|
|
for (i = 0 ; i < (signed)self->numFeatures ; ++i) {
|
|
VlCovDetFeature feature = self->features[i] ;
|
|
if (feature.peakScore) {
|
|
self->features[j++] = feature ;
|
|
}
|
|
}
|
|
self->numFeatures = j ;
|
|
}
|
|
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Checking that features are inside an image */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
vl_bool
|
|
_vl_covdet_check_frame_inside (VlCovDet * self, VlFrameOrientedEllipse frame, double margin)
|
|
{
|
|
double extent = margin ;
|
|
double A [2*2] = {frame.a11, frame.a21, frame.a12, frame.a22} ;
|
|
double T[2] = {frame.x, frame.y} ;
|
|
double x0 = +VL_INFINITY_D ;
|
|
double x1 = -VL_INFINITY_D ;
|
|
double y0 = +VL_INFINITY_D ;
|
|
double y1 = -VL_INFINITY_D ;
|
|
double boxx [4] = {extent, extent, -extent, -extent} ;
|
|
double boxy [4] = {-extent, extent, extent, -extent} ;
|
|
VlScaleSpaceGeometry geom = vl_scalespace_get_geometry(self->gss) ;
|
|
int i ;
|
|
for (i = 0 ; i < 4 ; ++i) {
|
|
double x = A[0] * boxx[i] + A[2] * boxy[i] + T[0] ;
|
|
double y = A[1] * boxx[i] + A[3] * boxy[i] + T[1] ;
|
|
x0 = VL_MIN(x0, x) ;
|
|
x1 = VL_MAX(x1, x) ;
|
|
y0 = VL_MIN(y0, y) ;
|
|
y1 = VL_MAX(y1, y) ;
|
|
}
|
|
|
|
return
|
|
0 <= x0 && x1 <= geom.width-1 &&
|
|
0 <= y0 && y1 <= geom.height-1 ;
|
|
}
|
|
|
|
/** @brief Drop features (partially) outside the image
|
|
** @param self object.
|
|
** @param margin geometric marging.
|
|
**
|
|
** The feature extent is defined by @c maring. A bounding box
|
|
** in the normalised feature frame containin a circle of radius
|
|
** @a maring is created and mapped to the image by
|
|
** the feature frame transformation. Then the feature
|
|
** is dropped if the bounding box is not contained in the image.
|
|
**
|
|
** For example, setting @c margin to zero drops a feature only
|
|
** if its center is not contained.
|
|
**
|
|
** Typically a valua of @c margin equal to 1 or 2 is used.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_drop_features_outside (VlCovDet * self, double margin)
|
|
{
|
|
vl_index i, j = 0 ;
|
|
vl_size numFeatures = vl_covdet_get_num_features(self) ;
|
|
for (i = 0 ; i < (signed)numFeatures ; ++i) {
|
|
vl_bool inside =
|
|
_vl_covdet_check_frame_inside (self, self->features[i].frame, margin) ;
|
|
if (inside) {
|
|
self->features[j] = self->features[i] ;
|
|
++j ;
|
|
}
|
|
}
|
|
self->numFeatures = j ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/* Setters and getters */
|
|
/* ---------------------------------------------------------------- */
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get wether images are passed in transposed
|
|
** @param self object.
|
|
** @return whether images are transposed.
|
|
**/
|
|
vl_bool
|
|
vl_covdet_get_transposed (VlCovDet const * self)
|
|
{
|
|
return self->transposed ;
|
|
}
|
|
|
|
/** @brief Set the index of the first octave
|
|
** @param self object.
|
|
** @param t whether images are transposed.
|
|
**/
|
|
void
|
|
vl_covdet_set_transposed (VlCovDet * self, vl_bool t)
|
|
{
|
|
self->transposed = t ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get the edge threshold
|
|
** @param self object.
|
|
** @return the edge threshold.
|
|
**/
|
|
double
|
|
vl_covdet_get_edge_threshold (VlCovDet const * self)
|
|
{
|
|
return self->edgeThreshold ;
|
|
}
|
|
|
|
/** @brief Set the edge threshold
|
|
** @param self object.
|
|
** @param edgeThreshold the edge threshold.
|
|
**
|
|
** The edge threshold must be non-negative.
|
|
**/
|
|
void
|
|
vl_covdet_set_edge_threshold (VlCovDet * self, double edgeThreshold)
|
|
{
|
|
assert(edgeThreshold >= 0) ;
|
|
self->edgeThreshold = edgeThreshold ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get the peak threshold
|
|
** @param self object.
|
|
** @return the peak threshold.
|
|
**/
|
|
double
|
|
vl_covdet_get_peak_threshold (VlCovDet const * self)
|
|
{
|
|
return self->peakThreshold ;
|
|
}
|
|
|
|
/** @brief Set the peak threshold
|
|
** @param self object.
|
|
** @param peakThreshold the peak threshold.
|
|
**
|
|
** The peak threshold must be non-negative.
|
|
**/
|
|
void
|
|
vl_covdet_set_peak_threshold (VlCovDet * self, double peakThreshold)
|
|
{
|
|
assert(peakThreshold >= 0) ;
|
|
self->peakThreshold = peakThreshold ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get the Laplacian peak threshold
|
|
** @param self object.
|
|
** @return the Laplacian peak threshold.
|
|
**
|
|
** This parameter affects only the detecors using the Laplacian
|
|
** scale selectino method such as Harris-Laplace.
|
|
**/
|
|
double
|
|
vl_covdet_get_laplacian_peak_threshold (VlCovDet const * self)
|
|
{
|
|
return self->lapPeakThreshold ;
|
|
}
|
|
|
|
/** @brief Set the Laplacian peak threshold
|
|
** @param self object.
|
|
** @param peakThreshold the Laplacian peak threshold.
|
|
**
|
|
** The peak threshold must be non-negative.
|
|
**/
|
|
void
|
|
vl_covdet_set_laplacian_peak_threshold (VlCovDet * self, double peakThreshold)
|
|
{
|
|
assert(peakThreshold >= 0) ;
|
|
self->lapPeakThreshold = peakThreshold ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get the index of the first octave
|
|
** @param self object.
|
|
** @return index of the first octave.
|
|
**/
|
|
vl_index
|
|
vl_covdet_get_first_octave (VlCovDet const * self)
|
|
{
|
|
return self->firstOctave ;
|
|
}
|
|
|
|
/** @brief Set the index of the first octave
|
|
** @param self object.
|
|
** @param o index of the first octave.
|
|
**
|
|
** Calling this function resets the detector.
|
|
**/
|
|
void
|
|
vl_covdet_set_first_octave (VlCovDet * self, vl_index o)
|
|
{
|
|
self->firstOctave = o ;
|
|
vl_covdet_reset(self) ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get the octave resolution.
|
|
** @param self object.
|
|
** @return octave resolution.
|
|
**/
|
|
|
|
vl_size
|
|
vl_covdet_get_octave_resolution (VlCovDet const * self)
|
|
{
|
|
return self->octaveResolution ;
|
|
}
|
|
|
|
/** @brief Set the octave resolutuon.
|
|
** @param self object.
|
|
** @param r octave resoltuion.
|
|
**
|
|
** Calling this function resets the detector.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_set_octave_resolution (VlCovDet * self, vl_size r)
|
|
{
|
|
self->octaveResolution = r ;
|
|
vl_covdet_reset(self) ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get whether affine adaptation uses accurate smoothing.
|
|
** @param self object.
|
|
** @return @c true if accurate smoothing is used.
|
|
**/
|
|
|
|
vl_bool
|
|
vl_covdet_get_aa_accurate_smoothing (VlCovDet const * self)
|
|
{
|
|
return self->aaAccurateSmoothing ;
|
|
}
|
|
|
|
/** @brief Set whether affine adaptation uses accurate smoothing.
|
|
** @param self object.
|
|
** @param x whether accurate smoothing should be usd.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_set_aa_accurate_smoothing (VlCovDet * self, vl_bool x)
|
|
{
|
|
self->aaAccurateSmoothing = x ;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get the non-extrema suppression threshold
|
|
** @param self object.
|
|
** @return threshold.
|
|
**/
|
|
|
|
double
|
|
vl_covdet_get_non_extrema_suppression_threshold (VlCovDet const * self)
|
|
{
|
|
return self->nonExtremaSuppression ;
|
|
}
|
|
|
|
/** @brief Set the non-extrema suppression threshod
|
|
** @param self object.
|
|
** @param x threshold.
|
|
**/
|
|
|
|
void
|
|
vl_covdet_set_non_extrema_suppression_threshold (VlCovDet * self, double x)
|
|
{
|
|
self->nonExtremaSuppression = x ;
|
|
}
|
|
|
|
/** @brief Get the number of non-extrema suppressed
|
|
** @param self object.
|
|
** @return number.
|
|
**/
|
|
|
|
vl_size
|
|
vl_covdet_get_num_non_extrema_suppressed (VlCovDet const * self)
|
|
{
|
|
return self->numNonExtremaSuppressed ;
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
/** @brief Get number of stored frames
|
|
** @return number of frames stored in the detector.
|
|
**/
|
|
vl_size
|
|
vl_covdet_get_num_features (VlCovDet const * self)
|
|
{
|
|
return self->numFeatures ;
|
|
}
|
|
|
|
/** @brief Get the stored frames
|
|
** @return frames stored in the detector.
|
|
**/
|
|
VlCovDetFeature *
|
|
vl_covdet_get_features (VlCovDet * self)
|
|
{
|
|
return self->features ;
|
|
}
|
|
|
|
/** @brief Get the Gaussian scale space
|
|
** @return Gaussian scale space.
|
|
**
|
|
** A Gaussian scale space exists only after calling ::vl_covdet_put_image.
|
|
** Otherwise the function returns @c NULL.
|
|
**/
|
|
|
|
VlScaleSpace *
|
|
vl_covdet_get_gss (VlCovDet const * self)
|
|
{
|
|
return self->gss ;
|
|
}
|
|
|
|
/** @brief Get the cornerness measure scale space
|
|
** @return cornerness measure scale space.
|
|
**
|
|
** A cornerness measure scale space exists only after calling
|
|
** ::vl_covdet_detect. Otherwise the function returns @c NULL.
|
|
**/
|
|
|
|
VlScaleSpace *
|
|
vl_covdet_get_css (VlCovDet const * self)
|
|
{
|
|
return self->css ;
|
|
}
|
|
|
|
/** @brief Get the number of features found with a certain number of scales
|
|
** @param self object.
|
|
** @param numScales length of the histogram (out).
|
|
** @return histogram.
|
|
**
|
|
** Calling this function makes sense only after running a detector
|
|
** that uses the Laplacian as a secondary measure for scale
|
|
** detection
|
|
**/
|
|
|
|
vl_size const *
|
|
vl_covdet_get_laplacian_scales_statistics (VlCovDet const * self,
|
|
vl_size * numScales)
|
|
{
|
|
*numScales = VL_COVDET_MAX_NUM_LAPLACIAN_SCALES ;
|
|
return self->numFeaturesWithNumScales ;
|
|
}
|