mirror of
https://github.com/opencv/opencv.git
synced 2026-09-25 04:09:57 +03:00
Merge pull request #25292 from kaingwade:features2d_parts_to_contrib
Features2d cleanup: Move several feature detectors and descriptors to opencv_contrib #25292 features2d cleanup: #24999 The PR moves KAZE, AKAZE, AgastFeatureDetector, BRISK and BOW to opencv_contrib/xfeatures2d. Related PR: opencv/opencv_contrib#3709
This commit is contained in:
@@ -1,12 +1,3 @@
|
||||
@incollection{ABD12,
|
||||
author = {Alcantarilla, Pablo Fern{\'a}ndez and Bartoli, Adrien and Davison, Andrew J},
|
||||
title = {KAZE features},
|
||||
booktitle = {Computer Vision--ECCV 2012},
|
||||
year = {2012},
|
||||
pages = {214--227},
|
||||
publisher = {Springer},
|
||||
url = {https://www.doc.ic.ac.uk/~ajd/Publications/alcantarilla_etal_eccv2012.pdf}
|
||||
}
|
||||
@article{ANB13,
|
||||
author = {Pablo Fern{\'{a}}ndez Alcantarilla and Jes{\'{u}}s Nuevo and Adrien Bartoli},
|
||||
editor = {Tilo Burghardt and Dima Damen and Walterio W. Mayol{-}Cuevas and Majid Mirmehdi},
|
||||
@@ -597,14 +588,6 @@
|
||||
doi = {10.5201/ipol.2011.my-asift},
|
||||
url = {http://www.ipol.im/pub/algo/my_affine_sift/}
|
||||
}
|
||||
@inproceedings{LCS11,
|
||||
author = {Leutenegger, Stefan and Chli, Margarita and Siegwart, Roland Yves},
|
||||
title = {BRISK: Binary robust invariant scalable keypoints},
|
||||
booktitle = {Computer Vision (ICCV), 2011 IEEE International Conference on},
|
||||
year = {2011},
|
||||
pages = {2548--2555},
|
||||
publisher = {IEEE}
|
||||
}
|
||||
@article{Louhichi07,
|
||||
author = {Louhichi, H. and Fournel, T. and Lavest, J. M. and Ben Aissia, H.},
|
||||
title = {Self-calibration of Scheimpflug cameras: an easy protocol},
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
BRIEF (Binary Robust Independent Elementary Features) {#tutorial_py_brief}
|
||||
=====================================================
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
In this chapter
|
||||
- We will see the basics of BRIEF algorithm
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
We know SIFT uses 128-dim vector for descriptors. Since it is using floating point numbers, it takes
|
||||
basically 512 bytes. Similarly SURF also takes minimum of 256 bytes (for 64-dim). Creating such a
|
||||
vector for thousands of features takes a lot of memory which are not feasible for resource-constraint
|
||||
applications especially for embedded systems. Larger the memory, longer the time it takes for
|
||||
matching.
|
||||
|
||||
But all these dimensions may not be needed for actual matching. We can compress it using several
|
||||
methods like PCA, LDA etc. Even other methods like hashing using LSH (Locality Sensitive Hashing) is
|
||||
used to convert these SIFT descriptors in floating point numbers to binary strings. These binary
|
||||
strings are used to match features using Hamming distance. This provides better speed-up because
|
||||
finding hamming distance is just applying XOR and bit count, which are very fast in modern CPUs with
|
||||
SSE instructions. But here, we need to find the descriptors first, then only we can apply hashing,
|
||||
which doesn't solve our initial problem on memory.
|
||||
|
||||
BRIEF comes into picture at this moment. It provides a shortcut to find the binary strings directly
|
||||
without finding descriptors. It takes smoothened image patch and selects a set of \f$n_d\f$ (x,y)
|
||||
location pairs in an unique way (explained in paper). Then some pixel intensity comparisons are done
|
||||
on these location pairs. For eg, let first location pairs be \f$p\f$ and \f$q\f$. If \f$I(p) < I(q)\f$, then its
|
||||
result is 1, else it is 0. This is applied for all the \f$n_d\f$ location pairs to get a
|
||||
\f$n_d\f$-dimensional bitstring.
|
||||
|
||||
This \f$n_d\f$ can be 128, 256 or 512. OpenCV supports all of these, but by default, it would be 256
|
||||
(OpenCV represents it in bytes. So the values will be 16, 32 and 64). So once you get this, you can
|
||||
use Hamming Distance to match these descriptors.
|
||||
|
||||
One important point is that BRIEF is a feature descriptor, it doesn't provide any method to find the
|
||||
features. So you will have to use any other feature detectors like SIFT, SURF etc. The paper
|
||||
recommends to use CenSurE which is a fast detector and BRIEF works even slightly better for CenSurE
|
||||
points than for SURF points.
|
||||
|
||||
In short, BRIEF is a faster method feature descriptor calculation and matching. It also provides
|
||||
high recognition rate unless there is large in-plane rotation.
|
||||
|
||||
STAR(CenSurE) in OpenCV
|
||||
------
|
||||
STAR is a feature detector derived from CenSurE.
|
||||
Unlike CenSurE however, which uses polygons like squares, hexagons and octagons to approach a circle,
|
||||
Star emulates a circle with 2 overlapping squares: 1 upright and 1 45-degree rotated. These polygons are bi-level.
|
||||
They can be seen as polygons with thick borders. The borders and the enclosed area have weights of opposing signs.
|
||||
This has better computational characteristics than other scale-space detectors and it is capable of real-time implementation.
|
||||
In contrast to SIFT and SURF, which find extrema at sub-sampled pixels that compromises accuracy at larger scales,
|
||||
CenSurE creates a feature vector using full spatial resolution at all scales in the pyramid.
|
||||
BRIEF in OpenCV
|
||||
---------------
|
||||
|
||||
Below code shows the computation of BRIEF descriptors with the help of CenSurE detector.
|
||||
|
||||
note, that you need [opencv contrib](https://github.com/opencv/opencv_contrib)) to use this.
|
||||
@code{.py}
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
img = cv.imread('simple.jpg', cv.IMREAD_GRAYSCALE)
|
||||
|
||||
# Initiate FAST detector
|
||||
star = cv.xfeatures2d.StarDetector_create()
|
||||
|
||||
# Initiate BRIEF extractor
|
||||
brief = cv.xfeatures2d.BriefDescriptorExtractor_create()
|
||||
|
||||
# find the keypoints with STAR
|
||||
kp = star.detect(img,None)
|
||||
|
||||
# compute the descriptors with BRIEF
|
||||
kp, des = brief.compute(img, kp)
|
||||
|
||||
print( brief.descriptorSize() )
|
||||
print( des.shape )
|
||||
@endcode
|
||||
The function brief.getDescriptorSize() gives the \f$n_d\f$ size used in bytes. By default it is 32. Next one
|
||||
is matching, which will be done in another chapter.
|
||||
|
||||
Additional Resources
|
||||
--------------------
|
||||
|
||||
-# Michael Calonder, Vincent Lepetit, Christoph Strecha, and Pascal Fua, "BRIEF: Binary Robust
|
||||
Independent Elementary Features", 11th European Conference on Computer Vision (ECCV), Heraklion,
|
||||
Crete. LNCS Springer, September 2010.
|
||||
2. [LSH (Locality Sensitive Hashing)](https://en.wikipedia.org/wiki/Locality-sensitive_hashing) at wikipedia.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.7 KiB |
@@ -1,157 +0,0 @@
|
||||
Introduction to SURF (Speeded-Up Robust Features) {#tutorial_py_surf_intro}
|
||||
=================================================
|
||||
|
||||
Goal
|
||||
----
|
||||
|
||||
In this chapter,
|
||||
- We will see the basics of SURF
|
||||
- We will see SURF functionalities in OpenCV
|
||||
|
||||
Theory
|
||||
------
|
||||
|
||||
In last chapter, we saw SIFT for keypoint detection and description. But it was comparatively slow
|
||||
and people needed more speeded-up version. In 2006, three people, Bay, H., Tuytelaars, T. and Van
|
||||
Gool, L, published another paper, "SURF: Speeded Up Robust Features" which introduced a new
|
||||
algorithm called SURF. As name suggests, it is a speeded-up version of SIFT.
|
||||
|
||||
In SIFT, Lowe approximated Laplacian of Gaussian with Difference of Gaussian for finding
|
||||
scale-space. SURF goes a little further and approximates LoG with Box Filter. Below image shows a
|
||||
demonstration of such an approximation. One big advantage of this approximation is that, convolution
|
||||
with box filter can be easily calculated with the help of integral images. And it can be done in
|
||||
parallel for different scales. Also the SURF rely on determinant of Hessian matrix for both scale
|
||||
and location.
|
||||
|
||||

|
||||
|
||||
For orientation assignment, SURF uses wavelet responses in horizontal and vertical direction for a
|
||||
neighbourhood of size 6s. Adequate gaussian weights are also applied to it. Then they are plotted in
|
||||
a space as given in below image. The dominant orientation is estimated by calculating the sum of all
|
||||
responses within a sliding orientation window of angle 60 degrees. Interesting thing is that,
|
||||
wavelet response can be found out using integral images very easily at any scale. For many
|
||||
applications, rotation invariance is not required, so no need of finding this orientation, which
|
||||
speeds up the process. SURF provides such a functionality called Upright-SURF or U-SURF. It improves
|
||||
speed and is robust upto \f$\pm 15^{\circ}\f$. OpenCV supports both, depending upon the flag,
|
||||
**upright**. If it is 0, orientation is calculated. If it is 1, orientation is not calculated and it
|
||||
is faster.
|
||||
|
||||

|
||||
|
||||
For feature description, SURF uses Wavelet responses in horizontal and vertical direction (again,
|
||||
use of integral images makes things easier). A neighbourhood of size 20sX20s is taken around the
|
||||
keypoint where s is the size. It is divided into 4x4 subregions. For each subregion, horizontal and
|
||||
vertical wavelet responses are taken and a vector is formed like this,
|
||||
\f$v=( \sum{d_x}, \sum{d_y}, \sum{|d_x|}, \sum{|d_y|})\f$. This when represented as a vector gives SURF
|
||||
feature descriptor with total 64 dimensions. Lower the dimension, higher the speed of computation
|
||||
and matching, but provide better distinctiveness of features.
|
||||
|
||||
For more distinctiveness, SURF feature descriptor has an extended 128 dimension version. The sums of
|
||||
\f$d_x\f$ and \f$|d_x|\f$ are computed separately for \f$d_y < 0\f$ and \f$d_y \geq 0\f$. Similarly, the sums of
|
||||
\f$d_y\f$ and \f$|d_y|\f$ are split up according to the sign of \f$d_x\f$ , thereby doubling the number of
|
||||
features. It doesn't add much computation complexity. OpenCV supports both by setting the value of
|
||||
flag **extended** with 0 and 1 for 64-dim and 128-dim respectively (default is 128-dim)
|
||||
|
||||
Another important improvement is the use of sign of Laplacian (trace of Hessian Matrix) for
|
||||
underlying interest point. It adds no computation cost since it is already computed during
|
||||
detection. The sign of the Laplacian distinguishes bright blobs on dark backgrounds from the reverse
|
||||
situation. In the matching stage, we only compare features if they have the same type of contrast
|
||||
(as shown in image below). This minimal information allows for faster matching, without reducing the
|
||||
descriptor's performance.
|
||||
|
||||

|
||||
|
||||
In short, SURF adds a lot of features to improve the speed in every step. Analysis shows it is 3
|
||||
times faster than SIFT while performance is comparable to SIFT. SURF is good at handling images with
|
||||
blurring and rotation, but not good at handling viewpoint change and illumination change.
|
||||
|
||||
SURF in OpenCV
|
||||
--------------
|
||||
|
||||
OpenCV provides SURF functionalities just like SIFT. You initiate a SURF object with some optional
|
||||
conditions like 64/128-dim descriptors, Upright/Normal SURF etc. All the details are well explained
|
||||
in docs. Then as we did in SIFT, we can use SURF.detect(), SURF.compute() etc for finding keypoints
|
||||
and descriptors.
|
||||
|
||||
First we will see a simple demo on how to find SURF keypoints and descriptors and draw it. All
|
||||
examples are shown in Python terminal since it is just same as SIFT only.
|
||||
@code{.py}
|
||||
>>> img = cv.imread('fly.png', cv.IMREAD_GRAYSCALE)
|
||||
|
||||
# Create SURF object. You can specify params here or later.
|
||||
# Here I set Hessian Threshold to 400
|
||||
>>> surf = cv.xfeatures2d.SURF_create(400)
|
||||
|
||||
# Find keypoints and descriptors directly
|
||||
>>> kp, des = surf.detectAndCompute(img,None)
|
||||
|
||||
>>> len(kp)
|
||||
699
|
||||
@endcode
|
||||
1199 keypoints is too much to show in a picture. We reduce it to some 50 to draw it on an image.
|
||||
While matching, we may need all those features, but not now. So we increase the Hessian Threshold.
|
||||
@code{.py}
|
||||
# Check present Hessian threshold
|
||||
>>> print( surf.getHessianThreshold() )
|
||||
400.0
|
||||
|
||||
# We set it to some 50000. Remember, it is just for representing in picture.
|
||||
# In actual cases, it is better to have a value 300-500
|
||||
>>> surf.setHessianThreshold(50000)
|
||||
|
||||
# Again compute keypoints and check its number.
|
||||
>>> kp, des = surf.detectAndCompute(img,None)
|
||||
|
||||
>>> print( len(kp) )
|
||||
47
|
||||
@endcode
|
||||
It is less than 50. Let's draw it on the image.
|
||||
@code{.py}
|
||||
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
|
||||
|
||||
>>> plt.imshow(img2),plt.show()
|
||||
@endcode
|
||||
See the result below. You can see that SURF is more like a blob detector. It detects the white blobs
|
||||
on wings of butterfly. You can test it with other images.
|
||||
|
||||

|
||||
|
||||
Now I want to apply U-SURF, so that it won't find the orientation.
|
||||
@code{.py}
|
||||
# Check upright flag, if it False, set it to True
|
||||
>>> print( surf.getUpright() )
|
||||
False
|
||||
|
||||
>>> surf.setUpright(True)
|
||||
|
||||
# Recompute the feature points and draw it
|
||||
>>> kp = surf.detect(img,None)
|
||||
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
|
||||
|
||||
>>> plt.imshow(img2),plt.show()
|
||||
@endcode
|
||||
See the results below. All the orientations are shown in same direction. It is faster than
|
||||
previous. If you are working on cases where orientation is not a problem (like panorama stitching)
|
||||
etc, this is better.
|
||||
|
||||

|
||||
|
||||
Finally we check the descriptor size and change it to 128 if it is only 64-dim.
|
||||
@code{.py}
|
||||
# Find size of descriptor
|
||||
>>> print( surf.descriptorSize() )
|
||||
64
|
||||
|
||||
# That means flag, "extended" is False.
|
||||
>>> surf.getExtended()
|
||||
False
|
||||
|
||||
# So we make it to True to get 128-dim descriptors.
|
||||
>>> surf.setExtended(True)
|
||||
>>> kp, des = surf.detectAndCompute(img,None)
|
||||
>>> print( surf.descriptorSize() )
|
||||
128
|
||||
>>> print( des.shape )
|
||||
(47, 128)
|
||||
@endcode
|
||||
Remaining part is matching which we will do in another chapter.
|
||||
@@ -22,28 +22,15 @@ Feature Detection and Description {#tutorial_py_table_of_contents_feature2d}
|
||||
is not good enough when scale of image changes. Lowe developed a breakthrough method to find
|
||||
scale-invariant features and it is called SIFT
|
||||
|
||||
- @subpage tutorial_py_surf_intro
|
||||
|
||||
SIFT is really good,
|
||||
but not fast enough, so people came up with a speeded-up version called SURF.
|
||||
|
||||
- @subpage tutorial_py_fast
|
||||
|
||||
All the above feature
|
||||
detection methods are good in some way. But they are not fast enough to work in real-time
|
||||
applications like SLAM. There comes the FAST algorithm, which is really "FAST".
|
||||
|
||||
- @subpage tutorial_py_brief
|
||||
|
||||
SIFT uses a feature
|
||||
descriptor with 128 floating point numbers. Consider thousands of such features. It takes lots of
|
||||
memory and more time for matching. We can compress it to make it faster. But still we have to
|
||||
calculate it first. There comes BRIEF which gives the shortcut to find binary descriptors with
|
||||
less memory, faster matching, still higher recognition rate.
|
||||
|
||||
- @subpage tutorial_py_orb
|
||||
|
||||
SIFT and SURF are good in what they do, but what if you have to pay a few dollars every year to use them in your applications? Yeah, they are patented!!! To solve that problem, OpenCV devs came up with a new "FREE" alternative to SIFT & SURF, and that is ORB.
|
||||
SURF is good in what it does, but what if you have to pay a few dollars every year to use it in your applications? Yeah, it is patented!!! To solve that problem, OpenCV devs came up with a new "FREE" alternative to SIFT & SURF, and that is ORB.
|
||||
|
||||
- @subpage tutorial_py_matcher
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ number of inliers (i.e. matches that fit in the given homography).
|
||||
You can find expanded version of this example here:
|
||||
<https://github.com/pablofdezalc/test_kaze_akaze_opencv>
|
||||
|
||||
\warning You need the [OpenCV contrib module *xfeatures2d*](https://github.com/opencv/opencv_contrib/tree/5.x/modules/xfeatures2d) to be able to use the AKAZE features.
|
||||
|
||||
Data
|
||||
----
|
||||
|
||||
@@ -42,7 +44,7 @@ You can find the images (*graf1.png*, *graf3.png*) and homography (*H1to3p.xml*)
|
||||
|
||||
@add_toggle_cpp
|
||||
- **Downloadable code**: Click
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/5.x/samples/cpp/tutorial_code/features2D/AKAZE_match.cpp)
|
||||
[here](https://github.com/opencv/opencv/5.x/samples/cpp/tutorial_code/features2D/AKAZE_match.cpp)
|
||||
|
||||
- **Code at glance:**
|
||||
@include samples/cpp/tutorial_code/features2D/AKAZE_match.cpp
|
||||
@@ -50,7 +52,7 @@ You can find the images (*graf1.png*, *graf3.png*) and homography (*H1to3p.xml*)
|
||||
|
||||
@add_toggle_java
|
||||
- **Downloadable code**: Click
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/5.x/samples/java/tutorial_code/features2D/akaze_matching/AKAZEMatchDemo.java)
|
||||
[here](https://github.com/opencv/opencv/5.x/samples/java/tutorial_code/features2D/akaze_matching/AKAZEMatchDemo.java)
|
||||
|
||||
- **Code at glance:**
|
||||
@include samples/java/tutorial_code/features2D/akaze_matching/AKAZEMatchDemo.java
|
||||
@@ -58,7 +60,7 @@ You can find the images (*graf1.png*, *graf3.png*) and homography (*H1to3p.xml*)
|
||||
|
||||
@add_toggle_python
|
||||
- **Downloadable code**: Click
|
||||
[here](https://raw.githubusercontent.com/opencv/opencv/5.x/samples/python/tutorial_code/features2D/akaze_matching/AKAZE_match.py)
|
||||
[here](https://github.com/opencv/opencv/5.x/samples/python/tutorial_code/features2D/akaze_matching/AKAZE_match.py)
|
||||
|
||||
- **Code at glance:**
|
||||
@include samples/python/tutorial_code/features2D/akaze_matching/AKAZE_match.py
|
||||
|
||||
@@ -17,6 +17,8 @@ Introduction
|
||||
In this tutorial we will compare *AKAZE* and *ORB* local features using them to find matches between
|
||||
video frames and track object movements.
|
||||
|
||||
\warning You need the [OpenCV contrib module *xfeatures2d*](https://github.com/opencv/opencv_contrib/tree/5.x/modules/xfeatures2d) to be able to use the AKAZE features.
|
||||
|
||||
The algorithm is as follows:
|
||||
|
||||
- Detect and describe keypoints on the first frame, manually set object boundaries
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,284 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use autodie; # die if problem reading or writing a file
|
||||
|
||||
my $filein = "./agast.txt";
|
||||
my $fileout = "./agast_new.txt";
|
||||
my $i1=1;
|
||||
my $i2=1;
|
||||
my $i3=1;
|
||||
my $tmp;
|
||||
my $ifcount0=0;
|
||||
my $ifcount1=0;
|
||||
my $ifcount2=0;
|
||||
my $ifcount3=0;
|
||||
my $ifcount4=0;
|
||||
my $elsecount;
|
||||
my $myfirstline = $ARGV[0];
|
||||
my $mylastline = $ARGV[1];
|
||||
my $tablename = $ARGV[2];
|
||||
my @array0 = ();
|
||||
my @array1 = ();
|
||||
my @array2 = ();
|
||||
my @array3 = ();
|
||||
my $homogeneous;
|
||||
my $success_homogeneous;
|
||||
my $structured;
|
||||
my $success_structured;
|
||||
|
||||
open(my $in1, "<", $filein) or die "Can't open $filein: $!";
|
||||
open(my $out, ">", $fileout) or die "Can't open $fileout: $!";
|
||||
|
||||
|
||||
$array0[0] = 0;
|
||||
$i1=1;
|
||||
while (my $line1 = <$in1>)
|
||||
{
|
||||
chomp $line1;
|
||||
$array0[$i1] = 0;
|
||||
if (($i1>=$myfirstline)&&($i1<=$mylastline))
|
||||
{
|
||||
if($line1=~/if\(ptr\[offset(\d+)/)
|
||||
{
|
||||
if($line1=~/if\(ptr\[offset(\d+).*\>.*cb/)
|
||||
{
|
||||
$tmp=$1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if($line1=~/if\(ptr\[offset(\d+).*\<.*c\_b/)
|
||||
{
|
||||
$tmp=$1+128;
|
||||
}
|
||||
else
|
||||
{
|
||||
die "invalid array index!"
|
||||
}
|
||||
}
|
||||
$array1[$ifcount1] = $tmp;
|
||||
$array0[$ifcount1] = $i1;
|
||||
$ifcount1++;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
$i1++;
|
||||
}
|
||||
$homogeneous=$ifcount1;
|
||||
$success_homogeneous=$ifcount1+1;
|
||||
$structured=$ifcount1+2;
|
||||
$success_structured=$ifcount1+3;
|
||||
|
||||
close $in1 or die "Can't close $filein: $!";
|
||||
|
||||
open($in1, "<", $filein) or die "Can't open $filein: $!";
|
||||
|
||||
|
||||
$i1=1;
|
||||
while (my $line1 = <$in1>)
|
||||
{
|
||||
chomp $line1;
|
||||
if (($i1>=$myfirstline)&&($i1<=$mylastline))
|
||||
{
|
||||
if ($array0[$ifcount2] == $i1)
|
||||
{
|
||||
$array2[$ifcount2]=0;
|
||||
$array3[$ifcount2]=0;
|
||||
if ($array0[$ifcount2+1] == ($i1+1))
|
||||
{
|
||||
$array2[$ifcount2]=($ifcount2+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
open(my $in2, "<", $filein) or die "Can't open $filein: $!";
|
||||
$i2=1;
|
||||
while (my $line2 = <$in2>)
|
||||
{
|
||||
chomp $line2;
|
||||
if ($i2 == $i1)
|
||||
{
|
||||
last;
|
||||
}
|
||||
$i2++;
|
||||
}
|
||||
my $line2 = <$in2>;
|
||||
chomp $line2;
|
||||
if ($line2=~/goto (\w+)/)
|
||||
{
|
||||
$tmp=$1;
|
||||
if ($tmp eq "homogeneous")
|
||||
{
|
||||
$array2[$ifcount2]=$homogeneous;
|
||||
}
|
||||
if ($tmp eq "success_homogeneous")
|
||||
{
|
||||
$array2[$ifcount2]=$success_homogeneous;
|
||||
}
|
||||
if ($tmp eq "structured")
|
||||
{
|
||||
$array2[$ifcount2]=$structured;
|
||||
}
|
||||
if ($tmp eq "success_structured")
|
||||
{
|
||||
$array2[$ifcount2]=$success_structured;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
die "goto expected: $!";
|
||||
}
|
||||
close $in2 or die "Can't close $filein: $!";
|
||||
}
|
||||
#find next else and interpret it
|
||||
open(my $in3, "<", $filein) or die "Can't open $filein: $!";
|
||||
$i3=1;
|
||||
$ifcount3=0;
|
||||
$elsecount=0;
|
||||
while (my $line3 = <$in3>)
|
||||
{
|
||||
chomp $line3;
|
||||
$i3++;
|
||||
if ($i3 == $i1)
|
||||
{
|
||||
last;
|
||||
}
|
||||
}
|
||||
while (my $line3 = <$in3>)
|
||||
{
|
||||
chomp $line3;
|
||||
$ifcount3++;
|
||||
if (($elsecount==0)&&($i3>$i1))
|
||||
{
|
||||
if ($line3=~/goto (\w+)/)
|
||||
{
|
||||
$tmp=$1;
|
||||
if ($tmp eq "homogeneous")
|
||||
{
|
||||
$array3[$ifcount2]=$homogeneous;
|
||||
}
|
||||
if ($tmp eq "success_homogeneous")
|
||||
{
|
||||
$array3[$ifcount2]=$success_homogeneous;
|
||||
}
|
||||
if ($tmp eq "structured")
|
||||
{
|
||||
$array3[$ifcount2]=$structured;
|
||||
}
|
||||
if ($tmp eq "success_structured")
|
||||
{
|
||||
$array3[$ifcount2]=$success_structured;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($line3=~/if\(ptr\[offset/)
|
||||
{
|
||||
$ifcount4=0;
|
||||
while ($array0[$ifcount4]!=$i3)
|
||||
{
|
||||
$ifcount4++;
|
||||
if ($ifcount4==$ifcount1)
|
||||
{
|
||||
die "if else match expected: $!";
|
||||
}
|
||||
$array3[$ifcount2]=$ifcount4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
die "elseif or elsegoto match expected: $!";
|
||||
}
|
||||
}
|
||||
last;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($line3=~/if\(ptr\[offset/)
|
||||
{
|
||||
$elsecount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($line3=~/else/)
|
||||
{
|
||||
$elsecount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
$i3++;
|
||||
}
|
||||
printf("%3d [%3d][0x%08x]\n", $array0[$ifcount2], $ifcount2, (($array1[$ifcount2]&15)<<28)|($array2[$ifcount2]<<16)|(($array1[$ifcount2]&128)<<5)|($array3[$ifcount2]));
|
||||
close $in3 or die "Can't close $filein: $!";
|
||||
$ifcount2++;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
$i1++;
|
||||
}
|
||||
|
||||
printf(" [%3d][0x%08x]\n", $homogeneous, 252);
|
||||
printf(" [%3d][0x%08x]\n", $success_homogeneous, 253);
|
||||
printf(" [%3d][0x%08x]\n", $structured, 254);
|
||||
printf(" [%3d][0x%08x]\n", $success_structured, 255);
|
||||
|
||||
close $in1 or die "Can't close $filein: $!";
|
||||
|
||||
$ifcount0=0;
|
||||
$ifcount2=0;
|
||||
printf $out " static const unsigned long %s[] = {\n ", $tablename;
|
||||
while ($ifcount0 < $ifcount1)
|
||||
{
|
||||
printf $out "0x%08x, ", (($array1[$ifcount0]&15)<<28)|($array2[$ifcount0]<<16)|(($array1[$ifcount0]&128)<<5)|($array3[$ifcount0]);
|
||||
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
if ($ifcount2==8)
|
||||
{
|
||||
$ifcount2=0;
|
||||
printf $out "\n";
|
||||
printf $out " ";
|
||||
}
|
||||
|
||||
}
|
||||
printf $out "0x%08x, ", 252;
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
if ($ifcount2==8)
|
||||
{
|
||||
$ifcount2=0;
|
||||
printf $out "\n";
|
||||
printf $out " ";
|
||||
}
|
||||
printf $out "0x%08x, ", 253;
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
if ($ifcount2==8)
|
||||
{
|
||||
$ifcount2=0;
|
||||
printf $out "\n";
|
||||
printf $out " ";
|
||||
}
|
||||
printf $out "0x%08x, ", 254;
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
if ($ifcount2==8)
|
||||
{
|
||||
$ifcount2=0;
|
||||
printf $out "\n";
|
||||
printf $out " ";
|
||||
}
|
||||
printf $out "0x%08x\n", 255;
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
printf $out " };\n\n";
|
||||
|
||||
$#array0 = -1;
|
||||
$#array1 = -1;
|
||||
$#array2 = -1;
|
||||
$#array3 = -1;
|
||||
|
||||
close $out or die "Can't close $fileout: $!";
|
||||
@@ -1,244 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use autodie; # die if problem reading or writing a file
|
||||
|
||||
my $filein = "./agast_score.txt";
|
||||
my $fileout = "./agast_new.txt";
|
||||
my $i1=1;
|
||||
my $i2=1;
|
||||
my $i3=1;
|
||||
my $tmp;
|
||||
my $ifcount0=0;
|
||||
my $ifcount1=0;
|
||||
my $ifcount2=0;
|
||||
my $ifcount3=0;
|
||||
my $ifcount4=0;
|
||||
my $elsecount;
|
||||
my $myfirstline = $ARGV[0];
|
||||
my $mylastline = $ARGV[1];
|
||||
my $tablename = $ARGV[2];
|
||||
my @array0 = ();
|
||||
my @array1 = ();
|
||||
my @array2 = ();
|
||||
my @array3 = ();
|
||||
my $is_not_a_corner;
|
||||
my $is_a_corner;
|
||||
|
||||
open(my $in1, "<", $filein) or die "Can't open $filein: $!";
|
||||
open(my $out, ">", $fileout) or die "Can't open $fileout: $!";
|
||||
|
||||
|
||||
$array0[0] = 0;
|
||||
$i1=1;
|
||||
while (my $line1 = <$in1>)
|
||||
{
|
||||
chomp $line1;
|
||||
$array0[$i1] = 0;
|
||||
if (($i1>=$myfirstline)&&($i1<=$mylastline))
|
||||
{
|
||||
if($line1=~/if\(ptr\[offset(\d+)/)
|
||||
{
|
||||
if($line1=~/if\(ptr\[offset(\d+).*\>.*cb/)
|
||||
{
|
||||
$tmp=$1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if($line1=~/if\(ptr\[offset(\d+).*\<.*c\_b/)
|
||||
{
|
||||
$tmp=$1+128;
|
||||
}
|
||||
else
|
||||
{
|
||||
die "invalid array index!"
|
||||
}
|
||||
}
|
||||
$array1[$ifcount1] = $tmp;
|
||||
$array0[$ifcount1] = $i1;
|
||||
$ifcount1++;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
$i1++;
|
||||
}
|
||||
$is_not_a_corner=$ifcount1;
|
||||
$is_a_corner=$ifcount1+1;
|
||||
|
||||
close $in1 or die "Can't close $filein: $!";
|
||||
|
||||
open($in1, "<", $filein) or die "Can't open $filein: $!";
|
||||
|
||||
|
||||
$i1=1;
|
||||
while (my $line1 = <$in1>)
|
||||
{
|
||||
chomp $line1;
|
||||
if (($i1>=$myfirstline)&&($i1<=$mylastline))
|
||||
{
|
||||
if ($array0[$ifcount2] == $i1)
|
||||
{
|
||||
$array2[$ifcount2]=0;
|
||||
$array3[$ifcount2]=0;
|
||||
if ($array0[$ifcount2+1] == ($i1+1))
|
||||
{
|
||||
$array2[$ifcount2]=($ifcount2+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
open(my $in2, "<", $filein) or die "Can't open $filein: $!";
|
||||
$i2=1;
|
||||
while (my $line2 = <$in2>)
|
||||
{
|
||||
chomp $line2;
|
||||
if ($i2 == $i1)
|
||||
{
|
||||
last;
|
||||
}
|
||||
$i2++;
|
||||
}
|
||||
my $line2 = <$in2>;
|
||||
chomp $line2;
|
||||
if ($line2=~/goto (\w+)/)
|
||||
{
|
||||
$tmp=$1;
|
||||
if ($tmp eq "is_not_a_corner")
|
||||
{
|
||||
$array2[$ifcount2]=$is_not_a_corner;
|
||||
}
|
||||
if ($tmp eq "is_a_corner")
|
||||
{
|
||||
$array2[$ifcount2]=$is_a_corner;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
die "goto expected: $!";
|
||||
}
|
||||
close $in2 or die "Can't close $filein: $!";
|
||||
}
|
||||
#find next else and interpret it
|
||||
open(my $in3, "<", $filein) or die "Can't open $filein: $!";
|
||||
$i3=1;
|
||||
$ifcount3=0;
|
||||
$elsecount=0;
|
||||
while (my $line3 = <$in3>)
|
||||
{
|
||||
chomp $line3;
|
||||
$i3++;
|
||||
if ($i3 == $i1)
|
||||
{
|
||||
last;
|
||||
}
|
||||
}
|
||||
while (my $line3 = <$in3>)
|
||||
{
|
||||
chomp $line3;
|
||||
$ifcount3++;
|
||||
if (($elsecount==0)&&($i3>$i1))
|
||||
{
|
||||
if ($line3=~/goto (\w+)/)
|
||||
{
|
||||
$tmp=$1;
|
||||
if ($tmp eq "is_not_a_corner")
|
||||
{
|
||||
$array3[$ifcount2]=$is_not_a_corner;
|
||||
}
|
||||
if ($tmp eq "is_a_corner")
|
||||
{
|
||||
$array3[$ifcount2]=$is_a_corner;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($line3=~/if\(ptr\[offset/)
|
||||
{
|
||||
$ifcount4=0;
|
||||
while ($array0[$ifcount4]!=$i3)
|
||||
{
|
||||
$ifcount4++;
|
||||
if ($ifcount4==$ifcount1)
|
||||
{
|
||||
die "if else match expected: $!";
|
||||
}
|
||||
$array3[$ifcount2]=$ifcount4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
die "elseif or elsegoto match expected: $!";
|
||||
}
|
||||
}
|
||||
last;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($line3=~/if\(ptr\[offset/)
|
||||
{
|
||||
$elsecount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($line3=~/else/)
|
||||
{
|
||||
$elsecount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
$i3++;
|
||||
}
|
||||
printf("%3d [%3d][0x%08x]\n", $array0[$ifcount2], $ifcount2, (($array1[$ifcount2]&15)<<28)|($array2[$ifcount2]<<16)|(($array1[$ifcount2]&128)<<5)|($array3[$ifcount2]));
|
||||
close $in3 or die "Can't close $filein: $!";
|
||||
$ifcount2++;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
$i1++;
|
||||
}
|
||||
|
||||
printf(" [%3d][0x%08x]\n", $is_not_a_corner, 254);
|
||||
printf(" [%3d][0x%08x]\n", $is_a_corner, 255);
|
||||
|
||||
close $in1 or die "Can't close $filein: $!";
|
||||
|
||||
$ifcount0=0;
|
||||
$ifcount2=0;
|
||||
printf $out " static const unsigned long %s[] = {\n ", $tablename;
|
||||
while ($ifcount0 < $ifcount1)
|
||||
{
|
||||
printf $out "0x%08x, ", (($array1[$ifcount0]&15)<<28)|($array2[$ifcount0]<<16)|(($array1[$ifcount0]&128)<<5)|($array3[$ifcount0]);
|
||||
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
if ($ifcount2==8)
|
||||
{
|
||||
$ifcount2=0;
|
||||
printf $out "\n";
|
||||
printf $out " ";
|
||||
}
|
||||
|
||||
}
|
||||
printf $out "0x%08x, ", 254;
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
if ($ifcount2==8)
|
||||
{
|
||||
$ifcount2=0;
|
||||
printf $out "\n";
|
||||
printf $out " ";
|
||||
}
|
||||
printf $out "0x%08x\n", 255;
|
||||
$ifcount0++;
|
||||
$ifcount2++;
|
||||
printf $out " };\n\n";
|
||||
|
||||
$#array0 = -1;
|
||||
$#array1 = -1;
|
||||
$#array2 = -1;
|
||||
$#array3 = -1;
|
||||
|
||||
close $out or die "Can't close $fileout: $!";
|
||||
@@ -1,32 +0,0 @@
|
||||
perl read_file_score32.pl 9059 9385 table_5_8_corner_struct
|
||||
move agast_new.txt agast_score_table.txt
|
||||
perl read_file_score32.pl 2215 3387 table_7_12d_corner_struct
|
||||
copy /A agast_score_table.txt + agast_new.txt agast_score_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_score32.pl 3428 9022 table_7_12s_corner_struct
|
||||
copy /A agast_score_table.txt + agast_new.txt agast_score_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_score32.pl 118 2174 table_9_16_corner_struct
|
||||
copy /A agast_score_table.txt + agast_new.txt agast_score_table.txt
|
||||
del agast_new.txt
|
||||
|
||||
perl read_file_nondiff32.pl 103 430 table_5_8_struct1
|
||||
move agast_new.txt agast_table.txt
|
||||
perl read_file_nondiff32.pl 440 779 table_5_8_struct2
|
||||
copy /A agast_table.txt + agast_new.txt agast_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_nondiff32.pl 869 2042 table_7_12d_struct1
|
||||
copy /A agast_table.txt + agast_new.txt agast_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_nondiff32.pl 2052 3225 table_7_12d_struct2
|
||||
copy /A agast_table.txt + agast_new.txt agast_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_nondiff32.pl 3315 4344 table_7_12s_struct1
|
||||
copy /A agast_table.txt + agast_new.txt agast_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_nondiff32.pl 4354 5308 table_7_12s_struct2
|
||||
copy /A agast_table.txt + agast_new.txt agast_table.txt
|
||||
del agast_new.txt
|
||||
perl read_file_nondiff32.pl 5400 7454 table_9_16_struct
|
||||
copy /A agast_table.txt + agast_new.txt agast_table.txt
|
||||
del agast_new.txt
|
||||
@@ -62,9 +62,6 @@
|
||||
All objects that implement vector descriptor matchers inherit the DescriptorMatcher interface.
|
||||
|
||||
@defgroup features2d_draw Drawing Function of Keypoints and Matches
|
||||
@defgroup features2d_category Object Categorization
|
||||
|
||||
This section describes approaches based on local 2D features and used to categorize objects.
|
||||
|
||||
@defgroup feature2d_hal Hardware Acceleration Layer
|
||||
@{
|
||||
@@ -341,71 +338,6 @@ typedef SIFT SiftFeatureDetector;
|
||||
typedef SIFT SiftDescriptorExtractor;
|
||||
|
||||
|
||||
/** @brief Class implementing the BRISK keypoint detector and descriptor extractor, described in @cite LCS11 .
|
||||
*/
|
||||
class CV_EXPORTS_W BRISK : public Feature2D
|
||||
{
|
||||
public:
|
||||
/** @brief The BRISK constructor
|
||||
|
||||
@param thresh AGAST detection threshold score.
|
||||
@param octaves detection octaves. Use 0 to do single scale.
|
||||
@param patternScale apply this scale to the pattern used for sampling the neighbourhood of a
|
||||
keypoint.
|
||||
*/
|
||||
CV_WRAP static Ptr<BRISK> create(int thresh=30, int octaves=3, float patternScale=1.0f);
|
||||
|
||||
/** @brief The BRISK constructor for a custom pattern
|
||||
|
||||
@param radiusList defines the radii (in pixels) where the samples around a keypoint are taken (for
|
||||
keypoint scale 1).
|
||||
@param numberList defines the number of sampling points on the sampling circle. Must be the same
|
||||
size as radiusList..
|
||||
@param dMax threshold for the short pairings used for descriptor formation (in pixels for keypoint
|
||||
scale 1).
|
||||
@param dMin threshold for the long pairings used for orientation determination (in pixels for
|
||||
keypoint scale 1).
|
||||
@param indexChange index remapping of the bits. */
|
||||
CV_WRAP static Ptr<BRISK> create(const std::vector<float> &radiusList, const std::vector<int> &numberList,
|
||||
float dMax=5.85f, float dMin=8.2f, const std::vector<int>& indexChange=std::vector<int>());
|
||||
|
||||
/** @brief The BRISK constructor for a custom pattern, detection threshold and octaves
|
||||
|
||||
@param thresh AGAST detection threshold score.
|
||||
@param octaves detection octaves. Use 0 to do single scale.
|
||||
@param radiusList defines the radii (in pixels) where the samples around a keypoint are taken (for
|
||||
keypoint scale 1).
|
||||
@param numberList defines the number of sampling points on the sampling circle. Must be the same
|
||||
size as radiusList..
|
||||
@param dMax threshold for the short pairings used for descriptor formation (in pixels for keypoint
|
||||
scale 1).
|
||||
@param dMin threshold for the long pairings used for orientation determination (in pixels for
|
||||
keypoint scale 1).
|
||||
@param indexChange index remapping of the bits. */
|
||||
CV_WRAP static Ptr<BRISK> create(int thresh, int octaves, const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList, float dMax=5.85f, float dMin=8.2f,
|
||||
const std::vector<int>& indexChange=std::vector<int>());
|
||||
CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
|
||||
|
||||
/** @brief Set detection threshold.
|
||||
@param threshold AGAST detection threshold score.
|
||||
*/
|
||||
CV_WRAP virtual void setThreshold(int threshold) = 0;
|
||||
CV_WRAP virtual int getThreshold() const = 0;
|
||||
|
||||
/** @brief Set detection octaves.
|
||||
@param octaves detection octaves. Use 0 to do single scale.
|
||||
*/
|
||||
CV_WRAP virtual void setOctaves(int octaves) = 0;
|
||||
CV_WRAP virtual int getOctaves() const = 0;
|
||||
/** @brief Set detection patternScale.
|
||||
@param patternScale apply this scale to the pattern used for sampling the neighbourhood of a
|
||||
keypoint.
|
||||
*/
|
||||
CV_WRAP virtual void setPatternScale(float patternScale) = 0;
|
||||
CV_WRAP virtual float getPatternScale() const = 0;
|
||||
};
|
||||
|
||||
/** @brief Class implementing the ORB (*oriented BRIEF*) keypoint detector and descriptor extractor
|
||||
|
||||
described in @cite RRKB11 . The algorithm uses FAST in pyramids to detect stable keypoints, selects
|
||||
@@ -616,56 +548,6 @@ CV_EXPORTS void FAST( InputArray image, CV_OUT std::vector<KeyPoint>& keypoints,
|
||||
int threshold, bool nonmaxSuppression=true, FastFeatureDetector::DetectorType type=FastFeatureDetector::TYPE_9_16 );
|
||||
|
||||
|
||||
/** @brief Wrapping class for feature detection using the AGAST method. :
|
||||
*/
|
||||
class CV_EXPORTS_W AgastFeatureDetector : public Feature2D
|
||||
{
|
||||
public:
|
||||
enum DetectorType
|
||||
{
|
||||
AGAST_5_8 = 0, AGAST_7_12d = 1, AGAST_7_12s = 2, OAST_9_16 = 3,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
THRESHOLD = 10000, NONMAX_SUPPRESSION = 10001,
|
||||
};
|
||||
|
||||
CV_WRAP static Ptr<AgastFeatureDetector> create( int threshold=10,
|
||||
bool nonmaxSuppression=true,
|
||||
AgastFeatureDetector::DetectorType type = AgastFeatureDetector::OAST_9_16);
|
||||
|
||||
CV_WRAP virtual void setThreshold(int threshold) = 0;
|
||||
CV_WRAP virtual int getThreshold() const = 0;
|
||||
|
||||
CV_WRAP virtual void setNonmaxSuppression(bool f) = 0;
|
||||
CV_WRAP virtual bool getNonmaxSuppression() const = 0;
|
||||
|
||||
CV_WRAP virtual void setType(AgastFeatureDetector::DetectorType type) = 0;
|
||||
CV_WRAP virtual AgastFeatureDetector::DetectorType getType() const = 0;
|
||||
CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @brief Detects corners using the AGAST algorithm
|
||||
|
||||
@param image grayscale image where keypoints (corners) are detected.
|
||||
@param keypoints keypoints detected on the image.
|
||||
@param threshold threshold on difference between intensity of the central pixel and pixels of a
|
||||
circle around this pixel.
|
||||
@param nonmaxSuppression if true, non-maximum suppression is applied to detected keypoints (corners).
|
||||
@param type one of the four neighborhoods as defined in the paper:
|
||||
AgastFeatureDetector::AGAST_5_8, AgastFeatureDetector::AGAST_7_12d,
|
||||
AgastFeatureDetector::AGAST_7_12s, AgastFeatureDetector::OAST_9_16
|
||||
|
||||
For non-Intel platforms, there is a tree optimised variant of AGAST with same numerical results.
|
||||
The 32-bit binary tree tables were generated automatically from original code using perl script.
|
||||
The perl script and examples of tree generation are placed in features2d/doc folder.
|
||||
Detects corners using the AGAST algorithm by @cite mair2010_agast .
|
||||
|
||||
*/
|
||||
CV_EXPORTS void AGAST( InputArray image, CV_OUT std::vector<KeyPoint>& keypoints,
|
||||
int threshold, bool nonmaxSuppression=true, AgastFeatureDetector::DetectorType type=AgastFeatureDetector::OAST_9_16 );
|
||||
|
||||
/** @brief Wrapping class for feature detection using the goodFeaturesToTrack function. :
|
||||
*/
|
||||
class CV_EXPORTS_W GFTTDetector : public Feature2D
|
||||
@@ -773,134 +655,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
/** @brief Class implementing the KAZE keypoint detector and descriptor extractor, described in @cite ABD12 .
|
||||
|
||||
@note AKAZE descriptor can only be used with KAZE or AKAZE keypoints .. [ABD12] KAZE Features. Pablo
|
||||
F. Alcantarilla, Adrien Bartoli and Andrew J. Davison. In European Conference on Computer Vision
|
||||
(ECCV), Fiorenze, Italy, October 2012.
|
||||
*/
|
||||
class CV_EXPORTS_W KAZE : public Feature2D
|
||||
{
|
||||
public:
|
||||
enum DiffusivityType
|
||||
{
|
||||
DIFF_PM_G1 = 0,
|
||||
DIFF_PM_G2 = 1,
|
||||
DIFF_WEICKERT = 2,
|
||||
DIFF_CHARBONNIER = 3
|
||||
};
|
||||
|
||||
/** @brief The KAZE constructor
|
||||
|
||||
@param extended Set to enable extraction of extended (128-byte) descriptor.
|
||||
@param upright Set to enable use of upright descriptors (non rotation-invariant).
|
||||
@param threshold Detector response threshold to accept point
|
||||
@param nOctaves Maximum octave evolution of the image
|
||||
@param nOctaveLayers Default number of sublevels per scale level
|
||||
@param diffusivity Diffusivity type. DIFF_PM_G1, DIFF_PM_G2, DIFF_WEICKERT or
|
||||
DIFF_CHARBONNIER
|
||||
*/
|
||||
CV_WRAP static Ptr<KAZE> create(bool extended=false, bool upright=false,
|
||||
float threshold = 0.001f,
|
||||
int nOctaves = 4, int nOctaveLayers = 4,
|
||||
KAZE::DiffusivityType diffusivity = KAZE::DIFF_PM_G2);
|
||||
|
||||
CV_WRAP virtual void setExtended(bool extended) = 0;
|
||||
CV_WRAP virtual bool getExtended() const = 0;
|
||||
|
||||
CV_WRAP virtual void setUpright(bool upright) = 0;
|
||||
CV_WRAP virtual bool getUpright() const = 0;
|
||||
|
||||
CV_WRAP virtual void setThreshold(double threshold) = 0;
|
||||
CV_WRAP virtual double getThreshold() const = 0;
|
||||
|
||||
CV_WRAP virtual void setNOctaves(int octaves) = 0;
|
||||
CV_WRAP virtual int getNOctaves() const = 0;
|
||||
|
||||
CV_WRAP virtual void setNOctaveLayers(int octaveLayers) = 0;
|
||||
CV_WRAP virtual int getNOctaveLayers() const = 0;
|
||||
|
||||
CV_WRAP virtual void setDiffusivity(KAZE::DiffusivityType diff) = 0;
|
||||
CV_WRAP virtual KAZE::DiffusivityType getDiffusivity() const = 0;
|
||||
CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
/** @brief Class implementing the AKAZE keypoint detector and descriptor extractor, described in @cite ANB13.
|
||||
|
||||
@details AKAZE descriptors can only be used with KAZE or AKAZE keypoints. This class is thread-safe.
|
||||
|
||||
@note When you need descriptors use Feature2D::detectAndCompute, which
|
||||
provides better performance. When using Feature2D::detect followed by
|
||||
Feature2D::compute scale space pyramid is computed twice.
|
||||
|
||||
@note AKAZE implements T-API. When image is passed as UMat some parts of the algorithm
|
||||
will use OpenCL.
|
||||
|
||||
@note [ANB13] Fast Explicit Diffusion for Accelerated Features in Nonlinear
|
||||
Scale Spaces. Pablo F. Alcantarilla, Jesús Nuevo and Adrien Bartoli. In
|
||||
British Machine Vision Conference (BMVC), Bristol, UK, September 2013.
|
||||
|
||||
*/
|
||||
class CV_EXPORTS_W AKAZE : public Feature2D
|
||||
{
|
||||
public:
|
||||
// AKAZE descriptor type
|
||||
enum DescriptorType
|
||||
{
|
||||
DESCRIPTOR_KAZE_UPRIGHT = 2, ///< Upright descriptors, not invariant to rotation
|
||||
DESCRIPTOR_KAZE = 3,
|
||||
DESCRIPTOR_MLDB_UPRIGHT = 4, ///< Upright descriptors, not invariant to rotation
|
||||
DESCRIPTOR_MLDB = 5
|
||||
};
|
||||
|
||||
/** @brief The AKAZE constructor
|
||||
|
||||
@param descriptor_type Type of the extracted descriptor: DESCRIPTOR_KAZE,
|
||||
DESCRIPTOR_KAZE_UPRIGHT, DESCRIPTOR_MLDB or DESCRIPTOR_MLDB_UPRIGHT.
|
||||
@param descriptor_size Size of the descriptor in bits. 0 -\> Full size
|
||||
@param descriptor_channels Number of channels in the descriptor (1, 2, 3)
|
||||
@param threshold Detector response threshold to accept point
|
||||
@param nOctaves Maximum octave evolution of the image
|
||||
@param nOctaveLayers Default number of sublevels per scale level
|
||||
@param diffusivity Diffusivity type. DIFF_PM_G1, DIFF_PM_G2, DIFF_WEICKERT or
|
||||
DIFF_CHARBONNIER
|
||||
@param max_points Maximum amount of returned points. In case if image contains
|
||||
more features, then the features with highest response are returned.
|
||||
Negative value means no limitation.
|
||||
*/
|
||||
CV_WRAP static Ptr<AKAZE> create(AKAZE::DescriptorType descriptor_type = AKAZE::DESCRIPTOR_MLDB,
|
||||
int descriptor_size = 0, int descriptor_channels = 3,
|
||||
float threshold = 0.001f, int nOctaves = 4,
|
||||
int nOctaveLayers = 4, KAZE::DiffusivityType diffusivity = KAZE::DIFF_PM_G2,
|
||||
int max_points = -1);
|
||||
|
||||
CV_WRAP virtual void setDescriptorType(AKAZE::DescriptorType dtype) = 0;
|
||||
CV_WRAP virtual AKAZE::DescriptorType getDescriptorType() const = 0;
|
||||
|
||||
CV_WRAP virtual void setDescriptorSize(int dsize) = 0;
|
||||
CV_WRAP virtual int getDescriptorSize() const = 0;
|
||||
|
||||
CV_WRAP virtual void setDescriptorChannels(int dch) = 0;
|
||||
CV_WRAP virtual int getDescriptorChannels() const = 0;
|
||||
|
||||
CV_WRAP virtual void setThreshold(double threshold) = 0;
|
||||
CV_WRAP virtual double getThreshold() const = 0;
|
||||
|
||||
CV_WRAP virtual void setNOctaves(int octaves) = 0;
|
||||
CV_WRAP virtual int getNOctaves() const = 0;
|
||||
|
||||
CV_WRAP virtual void setNOctaveLayers(int octaveLayers) = 0;
|
||||
CV_WRAP virtual int getNOctaveLayers() const = 0;
|
||||
|
||||
CV_WRAP virtual void setDiffusivity(KAZE::DiffusivityType diff) = 0;
|
||||
CV_WRAP virtual KAZE::DiffusivityType getDiffusivity() const = 0;
|
||||
CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
|
||||
|
||||
CV_WRAP virtual void setMaxPoints(int max_points) = 0;
|
||||
CV_WRAP virtual int getMaxPoints() const = 0;
|
||||
};
|
||||
|
||||
|
||||
/****************************************************************************************\
|
||||
* Distance *
|
||||
\****************************************************************************************/
|
||||
@@ -1424,165 +1178,6 @@ CV_EXPORTS int getNearestPoint( const std::vector<Point2f>& recallPrecisionCurve
|
||||
|
||||
//! @}
|
||||
|
||||
/****************************************************************************************\
|
||||
* Bag of visual words *
|
||||
\****************************************************************************************/
|
||||
|
||||
//! @addtogroup features2d_category
|
||||
//! @{
|
||||
|
||||
/** @brief Abstract base class for training the *bag of visual words* vocabulary from a set of descriptors.
|
||||
|
||||
For details, see, for example, *Visual Categorization with Bags of Keypoints* by Gabriella Csurka,
|
||||
Christopher R. Dance, Lixin Fan, Jutta Willamowski, Cedric Bray, 2004. :
|
||||
*/
|
||||
class CV_EXPORTS_W BOWTrainer
|
||||
{
|
||||
public:
|
||||
BOWTrainer();
|
||||
virtual ~BOWTrainer();
|
||||
|
||||
/** @brief Adds descriptors to a training set.
|
||||
|
||||
@param descriptors Descriptors to add to a training set. Each row of the descriptors matrix is a
|
||||
descriptor.
|
||||
|
||||
The training set is clustered using clustermethod to construct the vocabulary.
|
||||
*/
|
||||
CV_WRAP void add( const Mat& descriptors );
|
||||
|
||||
/** @brief Returns a training set of descriptors.
|
||||
*/
|
||||
CV_WRAP const std::vector<Mat>& getDescriptors() const;
|
||||
|
||||
/** @brief Returns the count of all descriptors stored in the training set.
|
||||
*/
|
||||
CV_WRAP int descriptorsCount() const;
|
||||
|
||||
CV_WRAP virtual void clear();
|
||||
|
||||
/** @overload */
|
||||
CV_WRAP virtual Mat cluster() const = 0;
|
||||
|
||||
/** @brief Clusters train descriptors.
|
||||
|
||||
@param descriptors Descriptors to cluster. Each row of the descriptors matrix is a descriptor.
|
||||
Descriptors are not added to the inner train descriptor set.
|
||||
|
||||
The vocabulary consists of cluster centers. So, this method returns the vocabulary. In the first
|
||||
variant of the method, train descriptors stored in the object are clustered. In the second variant,
|
||||
input descriptors are clustered.
|
||||
*/
|
||||
CV_WRAP virtual Mat cluster( const Mat& descriptors ) const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<Mat> descriptors;
|
||||
int size;
|
||||
};
|
||||
|
||||
/** @brief kmeans -based class to train visual vocabulary using the *bag of visual words* approach. :
|
||||
*/
|
||||
class CV_EXPORTS_W BOWKMeansTrainer : public BOWTrainer
|
||||
{
|
||||
public:
|
||||
/** @brief The constructor.
|
||||
|
||||
@see cv::kmeans
|
||||
*/
|
||||
CV_WRAP BOWKMeansTrainer( int clusterCount, const TermCriteria& termcrit=TermCriteria(),
|
||||
int attempts=3, int flags=KMEANS_PP_CENTERS );
|
||||
virtual ~BOWKMeansTrainer();
|
||||
|
||||
// Returns trained vocabulary (i.e. cluster centers).
|
||||
CV_WRAP virtual Mat cluster() const CV_OVERRIDE;
|
||||
CV_WRAP virtual Mat cluster( const Mat& descriptors ) const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
int clusterCount;
|
||||
TermCriteria termcrit;
|
||||
int attempts;
|
||||
int flags;
|
||||
};
|
||||
|
||||
/** @brief Class to compute an image descriptor using the *bag of visual words*.
|
||||
|
||||
Such a computation consists of the following steps:
|
||||
|
||||
1. Compute descriptors for a given image and its keypoints set.
|
||||
2. Find the nearest visual words from the vocabulary for each keypoint descriptor.
|
||||
3. Compute the bag-of-words image descriptor as is a normalized histogram of vocabulary words
|
||||
encountered in the image. The i-th bin of the histogram is a frequency of i-th word of the
|
||||
vocabulary in the given image.
|
||||
*/
|
||||
class CV_EXPORTS_W BOWImgDescriptorExtractor
|
||||
{
|
||||
public:
|
||||
/** @brief The constructor.
|
||||
|
||||
@param dextractor Descriptor extractor that is used to compute descriptors for an input image and
|
||||
its keypoints.
|
||||
@param dmatcher Descriptor matcher that is used to find the nearest word of the trained vocabulary
|
||||
for each keypoint descriptor of the image.
|
||||
*/
|
||||
CV_WRAP BOWImgDescriptorExtractor( const Ptr<Feature2D>& dextractor,
|
||||
const Ptr<DescriptorMatcher>& dmatcher );
|
||||
/** @overload */
|
||||
BOWImgDescriptorExtractor( const Ptr<DescriptorMatcher>& dmatcher );
|
||||
virtual ~BOWImgDescriptorExtractor();
|
||||
|
||||
/** @brief Sets a visual vocabulary.
|
||||
|
||||
@param vocabulary Vocabulary (can be trained using the inheritor of BOWTrainer ). Each row of the
|
||||
vocabulary is a visual word (cluster center).
|
||||
*/
|
||||
CV_WRAP void setVocabulary( const Mat& vocabulary );
|
||||
|
||||
/** @brief Returns the set vocabulary.
|
||||
*/
|
||||
CV_WRAP const Mat& getVocabulary() const;
|
||||
|
||||
/** @brief Computes an image descriptor using the set visual vocabulary.
|
||||
|
||||
@param image Image, for which the descriptor is computed.
|
||||
@param keypoints Keypoints detected in the input image.
|
||||
@param imgDescriptor Computed output image descriptor.
|
||||
@param pointIdxsOfClusters Indices of keypoints that belong to the cluster. This means that
|
||||
pointIdxsOfClusters[i] are keypoint indices that belong to the i -th cluster (word of vocabulary)
|
||||
returned if it is non-zero.
|
||||
@param descriptors Descriptors of the image keypoints that are returned if they are non-zero.
|
||||
*/
|
||||
void compute( InputArray image, std::vector<KeyPoint>& keypoints, OutputArray imgDescriptor,
|
||||
std::vector<std::vector<int> >* pointIdxsOfClusters=0, Mat* descriptors=0 );
|
||||
/** @overload
|
||||
@param keypointDescriptors Computed descriptors to match with vocabulary.
|
||||
@param imgDescriptor Computed output image descriptor.
|
||||
@param pointIdxsOfClusters Indices of keypoints that belong to the cluster. This means that
|
||||
pointIdxsOfClusters[i] are keypoint indices that belong to the i -th cluster (word of vocabulary)
|
||||
returned if it is non-zero.
|
||||
*/
|
||||
void compute( InputArray keypointDescriptors, OutputArray imgDescriptor,
|
||||
std::vector<std::vector<int> >* pointIdxsOfClusters=0 );
|
||||
// compute() is not constant because DescriptorMatcher::match is not constant
|
||||
|
||||
CV_WRAP_AS(compute) void compute2( const Mat& image, std::vector<KeyPoint>& keypoints, CV_OUT Mat& imgDescriptor )
|
||||
{ compute(image,keypoints,imgDescriptor); }
|
||||
|
||||
/** @brief Returns an image descriptor size if the vocabulary is set. Otherwise, it returns 0.
|
||||
*/
|
||||
CV_WRAP int descriptorSize() const;
|
||||
|
||||
/** @brief Returns an image descriptor type.
|
||||
*/
|
||||
CV_WRAP int descriptorType() const;
|
||||
|
||||
protected:
|
||||
Mat vocabulary;
|
||||
Ptr<DescriptorExtractor> dextractor;
|
||||
Ptr<DescriptorMatcher> dmatcher;
|
||||
};
|
||||
|
||||
//! @} features2d_category
|
||||
|
||||
} /* namespace cv */
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package org.opencv.test.features2d;
|
||||
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
import org.opencv.features2d.AgastFeatureDetector;
|
||||
|
||||
public class AGASTFeatureDetectorTest extends OpenCVTestCase {
|
||||
|
||||
AgastFeatureDetector detector;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
detector = AgastFeatureDetector.create(); // default (10,true,3)
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
assertNotNull(detector);
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPointListOfMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPointMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testRead() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("xml");
|
||||
writeFile(filename, "<?xml version=\"1.0\"?>\n<opencv_storage>\n<name>Feature2D.AgastFeatureDetector</name>\n<threshold>11</threshold>\n<nonmaxSuppression>0</nonmaxSuppression>\n<type>2</type>\n</opencv_storage>\n");
|
||||
|
||||
detector.read(filename);
|
||||
|
||||
assertEquals(11, detector.getThreshold());
|
||||
assertEquals(false, detector.getNonmaxSuppression());
|
||||
assertEquals(2, detector.getType());
|
||||
}
|
||||
|
||||
public void testReadYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.AgastFeatureDetector\"\nthreshold: 11\nnonmaxSuppression: 0\ntype: 2\n");
|
||||
|
||||
detector.read(filename);
|
||||
|
||||
assertEquals(11, detector.getThreshold());
|
||||
assertEquals(false, detector.getNonmaxSuppression());
|
||||
assertEquals(2, detector.getType());
|
||||
}
|
||||
|
||||
public void testWrite() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("xml");
|
||||
|
||||
detector.write(filename);
|
||||
|
||||
String truth = "<?xml version=\"1.0\"?>\n<opencv_storage>\n<name>Feature2D.AgastFeatureDetector</name>\n<threshold>10</threshold>\n<nonmaxSuppression>1</nonmaxSuppression>\n<type>3</type>\n</opencv_storage>\n";
|
||||
String actual = readFile(filename);
|
||||
actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation
|
||||
assertEquals(truth, actual);
|
||||
}
|
||||
|
||||
public void testWriteYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
|
||||
detector.write(filename);
|
||||
|
||||
String truth = "%YAML:1.0\n---\nname: \"Feature2D.AgastFeatureDetector\"\nthreshold: 10\nnonmaxSuppression: 1\ntype: 3\n";
|
||||
String actual = readFile(filename);
|
||||
actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation
|
||||
assertEquals(truth, actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.opencv.test.features2d;
|
||||
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
import org.opencv.features2d.AKAZE;
|
||||
|
||||
public class AKAZEDescriptorExtractorTest extends OpenCVTestCase {
|
||||
|
||||
AKAZE extractor;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = AKAZE.create(); // default (5,0,3,0.001f,4,4,1)
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
assertNotNull(extractor);
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPointListOfMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPointMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testReadYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
writeFile(filename, "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.AKAZE\"\ndescriptor: 4\ndescriptor_channels: 2\ndescriptor_size: 32\nthreshold: 0.125\noctaves: 3\nsublevels: 5\ndiffusivity: 2\n");
|
||||
|
||||
extractor.read(filename);
|
||||
|
||||
assertEquals(4, extractor.getDescriptorType());
|
||||
assertEquals(2, extractor.getDescriptorChannels());
|
||||
assertEquals(32, extractor.getDescriptorSize());
|
||||
assertEquals(0.125, extractor.getThreshold());
|
||||
assertEquals(3, extractor.getNOctaves());
|
||||
assertEquals(5, extractor.getNOctaveLayers());
|
||||
assertEquals(2, extractor.getDiffusivity());
|
||||
}
|
||||
|
||||
public void testWriteYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
|
||||
extractor.write(filename);
|
||||
|
||||
String truth = "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.AKAZE\"\ndescriptor: 5\ndescriptor_channels: 3\ndescriptor_size: 0\nthreshold: 0.0010000000474974513\noctaves: 4\nsublevels: 4\ndiffusivity: 1\nmax_points: -1\n";
|
||||
String actual = readFile(filename);
|
||||
actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation
|
||||
assertEquals(truth, actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.opencv.test.features2d;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfKeyPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.KeyPoint;
|
||||
import org.opencv.features2d.ORB;
|
||||
import org.opencv.features2d.DescriptorMatcher;
|
||||
import org.opencv.features2d.BOWImgDescriptorExtractor;
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
public class BOWImgDescriptorExtractorTest extends OpenCVTestCase {
|
||||
|
||||
ORB extractor;
|
||||
DescriptorMatcher matcher;
|
||||
int matSize;
|
||||
|
||||
public static void assertDescriptorsClose(Mat expected, Mat actual, int allowedDistance) {
|
||||
double distance = Core.norm(expected, actual, Core.NORM_HAMMING);
|
||||
assertTrue("expected:<" + allowedDistance + "> but was:<" + distance + ">", distance <= allowedDistance);
|
||||
}
|
||||
|
||||
private Mat getTestImg() {
|
||||
Mat cross = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255));
|
||||
Imgproc.line(cross, new Point(20, matSize / 2), new Point(matSize - 21, matSize / 2), new Scalar(100), 2);
|
||||
Imgproc.line(cross, new Point(matSize / 2, 20), new Point(matSize / 2, matSize - 21), new Scalar(100), 2);
|
||||
|
||||
return cross;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = ORB.create();
|
||||
matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);
|
||||
matSize = 100;
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
BOWImgDescriptorExtractor bow = new BOWImgDescriptorExtractor(extractor, matcher);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package org.opencv.test.features2d;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfKeyPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.KeyPoint;
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.opencv.features2d.Feature2D;
|
||||
|
||||
public class BRIEFDescriptorExtractorTest extends OpenCVTestCase {
|
||||
|
||||
Feature2D extractor;
|
||||
int matSize;
|
||||
|
||||
private Mat getTestImg() {
|
||||
Mat cross = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255));
|
||||
Imgproc.line(cross, new Point(20, matSize / 2), new Point(matSize - 21, matSize / 2), new Scalar(100), 2);
|
||||
Imgproc.line(cross, new Point(matSize / 2, 20), new Point(matSize / 2, matSize - 21), new Scalar(100), 2);
|
||||
|
||||
return cross;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = createClassInstance(XFEATURES2D+"BriefDescriptorExtractor", DEFAULT_FACTORY, null, null);
|
||||
matSize = 100;
|
||||
}
|
||||
|
||||
public void testComputeListOfMatListOfListOfKeyPointListOfMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testComputeMatListOfKeyPointMat() {
|
||||
KeyPoint point = new KeyPoint(55.775577545166016f, 44.224422454833984f, 16, 9.754629f, 8617.863f, 1, -1);
|
||||
MatOfKeyPoint keypoints = new MatOfKeyPoint(point);
|
||||
Mat img = getTestImg();
|
||||
Mat descriptors = new Mat();
|
||||
|
||||
extractor.compute(img, keypoints, descriptors);
|
||||
|
||||
Mat truth = new Mat(1, 32, CvType.CV_8UC1) {
|
||||
{
|
||||
put(0, 0, 96, 0, 76, 24, 47, 182, 68, 137,
|
||||
149, 195, 67, 16, 187, 224, 74, 8,
|
||||
82, 169, 87, 70, 44, 4, 192, 56,
|
||||
13, 128, 44, 106, 146, 72, 194, 245);
|
||||
}
|
||||
};
|
||||
|
||||
assertMatEqual(truth, descriptors);
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
assertNotNull(extractor);
|
||||
}
|
||||
|
||||
public void testDescriptorSize() {
|
||||
assertEquals(32, extractor.descriptorSize());
|
||||
}
|
||||
|
||||
public void testDescriptorType() {
|
||||
assertEquals(CvType.CV_8U, extractor.descriptorType());
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
// assertFalse(extractor.empty());
|
||||
fail("Not yet implemented"); // BRIEF does not override empty() method
|
||||
}
|
||||
|
||||
public void testRead() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
writeFile(filename, "%YAML:1.0\n---\ndescriptorSize: 64\n");
|
||||
|
||||
extractor.read(filename);
|
||||
|
||||
assertEquals(64, extractor.descriptorSize());
|
||||
}
|
||||
|
||||
public void testWrite() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("xml");
|
||||
|
||||
extractor.write(filename);
|
||||
|
||||
String truth = "<?xml version=\"1.0\"?>\n<opencv_storage>\n<name>Feature2D.BRIEF</name>\n<descriptorSize>32</descriptorSize>\n<use_orientation>0</use_orientation>\n</opencv_storage>\n";
|
||||
assertEquals(truth, readFile(filename));
|
||||
}
|
||||
|
||||
public void testWriteYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
|
||||
extractor.write(filename);
|
||||
|
||||
String truth = "%YAML:1.0\n---\nname: \"Feature2D.BRIEF\"\ndescriptorSize: 32\nuse_orientation: 0\n";
|
||||
assertEquals(truth, readFile(filename));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.opencv.test.features2d;
|
||||
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
import org.opencv.features2d.BRISK;
|
||||
|
||||
public class BRISKDescriptorExtractorTest extends OpenCVTestCase {
|
||||
|
||||
BRISK extractor;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = BRISK.create(); // default (30,3,1)
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
assertNotNull(extractor);
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPointListOfMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPointMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testReadYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.BRISK\"\nthreshold: 31\noctaves: 4\npatternScale: 1.1\n");
|
||||
|
||||
extractor.read(filename);
|
||||
|
||||
assertEquals(31, extractor.getThreshold());
|
||||
assertEquals(4, extractor.getOctaves());
|
||||
assertEquals(1.1f, extractor.getPatternScale());
|
||||
}
|
||||
|
||||
public void testWriteYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
|
||||
extractor.write(filename);
|
||||
|
||||
String truth = "%YAML:1.0\n---\nname: \"Feature2D.BRISK\"\nthreshold: 30\noctaves: 3\npatternScale: 1.\n";
|
||||
String actual = readFile(filename);
|
||||
actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation
|
||||
assertEquals(truth, actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package org.opencv.test.features2d;
|
||||
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
import org.opencv.features2d.KAZE;
|
||||
|
||||
public class KAZEDescriptorExtractorTest extends OpenCVTestCase {
|
||||
|
||||
KAZE extractor;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = KAZE.create(); // default (false,false,0.001f,4,4,1)
|
||||
}
|
||||
|
||||
public void testCreate() {
|
||||
assertNotNull(extractor);
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectListOfMatListOfListOfKeyPointListOfMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPoint() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testDetectMatListOfKeyPointMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testReadYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
writeFile(filename, "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.KAZE\"\nextended: 1\nupright: 1\nthreshold: 0.125\noctaves: 3\nsublevels: 5\ndiffusivity: 2\n");
|
||||
|
||||
extractor.read(filename);
|
||||
|
||||
assertEquals(true, extractor.getExtended());
|
||||
assertEquals(true, extractor.getUpright());
|
||||
assertEquals(0.125, extractor.getThreshold());
|
||||
assertEquals(3, extractor.getNOctaves());
|
||||
assertEquals(5, extractor.getNOctaveLayers());
|
||||
assertEquals(2, extractor.getDiffusivity());
|
||||
}
|
||||
|
||||
public void testWriteYml() {
|
||||
String filename = OpenCVTestRunner.getTempFileName("yml");
|
||||
|
||||
extractor.write(filename);
|
||||
|
||||
String truth = "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.KAZE\"\nextended: 0\nupright: 0\nthreshold: 0.0010000000474974513\noctaves: 4\nsublevels: 4\ndiffusivity: 1\n";
|
||||
String actual = readFile(filename);
|
||||
actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation
|
||||
assertEquals(truth, actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,16 +2,12 @@
|
||||
"whitelist":
|
||||
{
|
||||
"Feature2D": ["detect", "compute", "detectAndCompute", "descriptorSize", "descriptorType", "defaultNorm", "empty", "getDefaultName"],
|
||||
"BRISK": ["create", "getDefaultName"],
|
||||
"ORB": ["create", "setMaxFeatures", "setScaleFactor", "setNLevels", "setEdgeThreshold", "setFastThreshold", "setFirstLevel", "setWTA_K", "setScoreType", "setPatchSize", "getFastThreshold", "getDefaultName"],
|
||||
"MSER": ["create", "detectRegions", "setDelta", "getDelta", "setMinArea", "getMinArea", "setMaxArea", "getMaxArea", "setPass2Only", "getPass2Only", "getDefaultName"],
|
||||
"FastFeatureDetector": ["create", "setThreshold", "getThreshold", "setNonmaxSuppression", "getNonmaxSuppression", "setType", "getType", "getDefaultName"],
|
||||
"AgastFeatureDetector": ["create", "setThreshold", "getThreshold", "setNonmaxSuppression", "getNonmaxSuppression", "setType", "getType", "getDefaultName"],
|
||||
"GFTTDetector": ["create", "setMaxFeatures", "getMaxFeatures", "setQualityLevel", "getQualityLevel", "setMinDistance", "getMinDistance", "setBlockSize", "getBlockSize", "setHarrisDetector", "getHarrisDetector", "setK", "getK", "getDefaultName"],
|
||||
"SimpleBlobDetector": ["create", "setParams", "getParams", "getDefaultName"],
|
||||
"SimpleBlobDetector_Params": [],
|
||||
"KAZE": ["create", "setExtended", "getExtended", "setUpright", "getUpright", "setThreshold", "getThreshold", "setNOctaves", "getNOctaves", "setNOctaveLayers", "getNOctaveLayers", "setDiffusivity", "getDiffusivity", "getDefaultName"],
|
||||
"AKAZE": ["create", "setDescriptorType", "getDescriptorType", "setDescriptorSize", "getDescriptorSize", "setDescriptorChannels", "getDescriptorChannels", "setThreshold", "getThreshold", "setNOctaves", "getNOctaves", "setNOctaveLayers", "getNOctaveLayers", "setDiffusivity", "getDiffusivity", "getDefaultName"],
|
||||
"DescriptorMatcher": ["add", "clear", "empty", "isMaskSupported", "train", "match", "knnMatch", "radiusMatch", "clone", "create"],
|
||||
"BFMatcher": ["isMaskSupported", "create"],
|
||||
"": ["drawKeypoints", "drawMatches", "drawMatchesKnn"]
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
}
|
||||
},
|
||||
"enum_fix" : {
|
||||
"FastFeatureDetector" : { "DetectorType": "FastDetectorType" },
|
||||
"AgastFeatureDetector" : { "DetectorType": "AgastDetectorType" }
|
||||
"FastFeatureDetector" : { "DetectorType": "FastDetectorType" }
|
||||
},
|
||||
"func_arg_fix" : {
|
||||
"Feature2D": {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#ifdef HAVE_OPENCV_FEATURES2D
|
||||
typedef SimpleBlobDetector::Params SimpleBlobDetector_Params;
|
||||
typedef AKAZE::DescriptorType AKAZE_DescriptorType;
|
||||
typedef AgastFeatureDetector::DetectorType AgastFeatureDetector_DetectorType;
|
||||
typedef FastFeatureDetector::DetectorType FastFeatureDetector_DetectorType;
|
||||
typedef DescriptorMatcher::MatcherType DescriptorMatcher_MatcherType;
|
||||
typedef KAZE::DiffusivityType KAZE_DiffusivityType;
|
||||
typedef ORB::ScoreType ORB_ScoreType;
|
||||
#endif
|
||||
@@ -92,7 +92,7 @@ TrackedTarget = namedtuple('TrackedTarget', 'target, p0, p1, H, quad')
|
||||
|
||||
class PlaneTracker:
|
||||
def __init__(self):
|
||||
self.detector = cv.AKAZE_create(threshold = 0.003)
|
||||
self.detector = cv.ORB_create( nfeatures = 1000 )
|
||||
self.matcher = cv.FlannBasedMatcher(flann_params, {}) # bug : need to pass empty dict (#1329)
|
||||
self.targets = []
|
||||
self.frame_points = []
|
||||
|
||||
@@ -29,7 +29,7 @@ OCL_PERF_TEST_P(feature2d, detect, testing::Combine(Feature2DType::all(), TEST_I
|
||||
|
||||
OCL_PERF_TEST_P(feature2d, extract, testing::Combine(testing::Values(DETECTORS_EXTRACTORS), TEST_IMAGES))
|
||||
{
|
||||
Ptr<Feature2D> detector = AKAZE::create();
|
||||
Ptr<Feature2D> detector = ORB::create();
|
||||
Ptr<Feature2D> extractor = getFeature2D(get<0>(GetParam()));
|
||||
std::string filename = getDataPath(get<1>(GetParam()));
|
||||
Mat mimg = imread(filename, IMREAD_GRAYSCALE);
|
||||
|
||||
@@ -25,7 +25,7 @@ PERF_TEST_P(feature2d, detect, testing::Combine(Feature2DType::all(), TEST_IMAGE
|
||||
|
||||
PERF_TEST_P(feature2d, extract, testing::Combine(testing::Values(DETECTORS_EXTRACTORS), TEST_IMAGES))
|
||||
{
|
||||
Ptr<Feature2D> detector = AKAZE::create();
|
||||
Ptr<Feature2D> detector = ORB::create();
|
||||
Ptr<Feature2D> extractor = getFeature2D(get<0>(GetParam()));
|
||||
std::string filename = getDataPath(get<1>(GetParam()));
|
||||
Mat img = imread(filename, IMREAD_GRAYSCALE);
|
||||
|
||||
@@ -13,15 +13,10 @@ namespace opencv_test
|
||||
FAST_DEFAULT, FAST_20_TRUE_TYPE5_8, FAST_20_TRUE_TYPE7_12, FAST_20_TRUE_TYPE9_16, \
|
||||
FAST_20_FALSE_TYPE5_8, FAST_20_FALSE_TYPE7_12, FAST_20_FALSE_TYPE9_16, \
|
||||
\
|
||||
AGAST_DEFAULT, AGAST_5_8, AGAST_7_12d, AGAST_7_12s, AGAST_OAST_9_16, \
|
||||
\
|
||||
MSER_DEFAULT
|
||||
|
||||
#define DETECTORS_EXTRACTORS \
|
||||
ORB_DEFAULT, ORB_1500_13_1, \
|
||||
AKAZE_DEFAULT, AKAZE_DESCRIPTOR_KAZE, \
|
||||
BRISK_DEFAULT, \
|
||||
KAZE_DEFAULT, \
|
||||
SIFT_DEFAULT
|
||||
|
||||
#define CV_ENUM_EXPAND(name, ...) CV_ENUM(name, __VA_ARGS__)
|
||||
@@ -58,24 +53,6 @@ static inline Ptr<Feature2D> getFeature2D(Feature2DType type)
|
||||
return FastFeatureDetector::create(20, false, FastFeatureDetector::TYPE_7_12);
|
||||
case FAST_20_FALSE_TYPE9_16:
|
||||
return FastFeatureDetector::create(20, false, FastFeatureDetector::TYPE_9_16);
|
||||
case AGAST_DEFAULT:
|
||||
return AgastFeatureDetector::create();
|
||||
case AGAST_5_8:
|
||||
return AgastFeatureDetector::create(70, true, AgastFeatureDetector::AGAST_5_8);
|
||||
case AGAST_7_12d:
|
||||
return AgastFeatureDetector::create(70, true, AgastFeatureDetector::AGAST_7_12d);
|
||||
case AGAST_7_12s:
|
||||
return AgastFeatureDetector::create(70, true, AgastFeatureDetector::AGAST_7_12s);
|
||||
case AGAST_OAST_9_16:
|
||||
return AgastFeatureDetector::create(70, true, AgastFeatureDetector::OAST_9_16);
|
||||
case AKAZE_DEFAULT:
|
||||
return AKAZE::create();
|
||||
case AKAZE_DESCRIPTOR_KAZE:
|
||||
return AKAZE::create(AKAZE::DESCRIPTOR_KAZE);
|
||||
case BRISK_DEFAULT:
|
||||
return BRISK::create();
|
||||
case KAZE_DEFAULT:
|
||||
return KAZE::create();
|
||||
case MSER_DEFAULT:
|
||||
return MSER::create();
|
||||
case SIFT_DEFAULT:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,69 +0,0 @@
|
||||
/* This is AGAST and OAST, an optimal and accelerated corner detector
|
||||
based on the accelerated segment tests
|
||||
Below is the original copyright and the references */
|
||||
|
||||
/*
|
||||
Copyright (C) 2010 Elmar Mair
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
*Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
*Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
*Neither the name of the University of Cambridge nor the names of
|
||||
its contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
The references are:
|
||||
* Adaptive and Generic Corner Detection Based on the Accelerated Segment Test,
|
||||
Elmar Mair and Gregory D. Hager and Darius Burschka
|
||||
and Michael Suppa and Gerhard Hirzinger ECCV 2010
|
||||
URL: http://www6.in.tum.de/Main/ResearchAgast
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_AGAST_HPP__
|
||||
#define __OPENCV_FEATURES_2D_AGAST_HPP__
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "precomp.hpp"
|
||||
namespace cv
|
||||
{
|
||||
|
||||
#if !(defined __i386__ || defined(_M_IX86) || defined __x86_64__ || defined(_M_X64))
|
||||
int agast_tree_search(const uint32_t table_struct32[], int pixel_[], const unsigned char* const ptr, int threshold);
|
||||
int AGAST_ALL_SCORE(const uchar* ptr, const int pixel[], int threshold, AgastFeatureDetector::DetectorType agasttype);
|
||||
#endif //!(defined __i386__ || defined(_M_IX86) || defined __x86_64__ || defined(_M_X64))
|
||||
|
||||
|
||||
void makeAgastOffsets(int pixel[16], int row_stride, AgastFeatureDetector::DetectorType type);
|
||||
|
||||
template<AgastFeatureDetector::DetectorType type>
|
||||
int agast_cornerScore(const uchar* ptr, const int pixel[], int threshold);
|
||||
|
||||
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,276 +0,0 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2008, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
/*
|
||||
OpenCV wrapper of reference implementation of
|
||||
[1] Fast Explicit Diffusion for Accelerated Features in Nonlinear Scale Spaces.
|
||||
Pablo F. Alcantarilla, J. Nuevo and Adrien Bartoli.
|
||||
In British Machine Vision Conference (BMVC), Bristol, UK, September 2013
|
||||
http://www.robesafe.com/personal/pablo.alcantarilla/papers/Alcantarilla13bmvc.pdf
|
||||
@author Eugene Khvedchenya <ekhvedchenya@gmail.com>
|
||||
*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "kaze/AKAZEFeatures.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
class AKAZE_Impl : public AKAZE
|
||||
{
|
||||
public:
|
||||
AKAZE_Impl(DescriptorType _descriptor_type, int _descriptor_size, int _descriptor_channels,
|
||||
float _threshold, int _octaves, int _sublevels, KAZE::DiffusivityType _diffusivity, int _max_points)
|
||||
: descriptor(_descriptor_type)
|
||||
, descriptor_channels(_descriptor_channels)
|
||||
, descriptor_size(_descriptor_size)
|
||||
, threshold(_threshold)
|
||||
, octaves(_octaves)
|
||||
, sublevels(_sublevels)
|
||||
, diffusivity(_diffusivity)
|
||||
, max_points(_max_points)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~AKAZE_Impl() CV_OVERRIDE
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void setDescriptorType(DescriptorType dtype) CV_OVERRIDE{ descriptor = dtype; }
|
||||
DescriptorType getDescriptorType() const CV_OVERRIDE{ return descriptor; }
|
||||
|
||||
void setDescriptorSize(int dsize) CV_OVERRIDE { descriptor_size = dsize; }
|
||||
int getDescriptorSize() const CV_OVERRIDE { return descriptor_size; }
|
||||
|
||||
void setDescriptorChannels(int dch) CV_OVERRIDE { descriptor_channels = dch; }
|
||||
int getDescriptorChannels() const CV_OVERRIDE { return descriptor_channels; }
|
||||
|
||||
void setThreshold(double threshold_) CV_OVERRIDE { threshold = (float)threshold_; }
|
||||
double getThreshold() const CV_OVERRIDE { return threshold; }
|
||||
|
||||
void setNOctaves(int octaves_) CV_OVERRIDE { octaves = octaves_; }
|
||||
int getNOctaves() const CV_OVERRIDE { return octaves; }
|
||||
|
||||
void setNOctaveLayers(int octaveLayers_) CV_OVERRIDE { sublevels = octaveLayers_; }
|
||||
int getNOctaveLayers() const CV_OVERRIDE { return sublevels; }
|
||||
|
||||
void setDiffusivity(KAZE::DiffusivityType diff_) CV_OVERRIDE{ diffusivity = diff_; }
|
||||
KAZE::DiffusivityType getDiffusivity() const CV_OVERRIDE{ return diffusivity; }
|
||||
|
||||
void setMaxPoints(int max_points_) CV_OVERRIDE { max_points = max_points_; }
|
||||
int getMaxPoints() const CV_OVERRIDE { return max_points; }
|
||||
|
||||
// returns the descriptor size in bytes
|
||||
int descriptorSize() const CV_OVERRIDE
|
||||
{
|
||||
switch (descriptor)
|
||||
{
|
||||
case DESCRIPTOR_KAZE:
|
||||
case DESCRIPTOR_KAZE_UPRIGHT:
|
||||
return 64;
|
||||
|
||||
case DESCRIPTOR_MLDB:
|
||||
case DESCRIPTOR_MLDB_UPRIGHT:
|
||||
// We use the full length binary descriptor -> 486 bits
|
||||
if (descriptor_size == 0)
|
||||
{
|
||||
int t = (6 + 36 + 120) * descriptor_channels;
|
||||
return divUp(t, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We use the random bit selection length binary descriptor
|
||||
return divUp(descriptor_size, 8);
|
||||
}
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// returns the descriptor type
|
||||
int descriptorType() const CV_OVERRIDE
|
||||
{
|
||||
switch (descriptor)
|
||||
{
|
||||
case DESCRIPTOR_KAZE:
|
||||
case DESCRIPTOR_KAZE_UPRIGHT:
|
||||
return CV_32F;
|
||||
|
||||
case DESCRIPTOR_MLDB:
|
||||
case DESCRIPTOR_MLDB_UPRIGHT:
|
||||
return CV_8U;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// returns the default norm type
|
||||
int defaultNorm() const CV_OVERRIDE
|
||||
{
|
||||
switch (descriptor)
|
||||
{
|
||||
case DESCRIPTOR_KAZE:
|
||||
case DESCRIPTOR_KAZE_UPRIGHT:
|
||||
return NORM_L2;
|
||||
|
||||
case DESCRIPTOR_MLDB:
|
||||
case DESCRIPTOR_MLDB_UPRIGHT:
|
||||
return NORM_HAMMING;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void detectAndCompute(InputArray image, InputArray mask,
|
||||
std::vector<KeyPoint>& keypoints,
|
||||
OutputArray descriptors,
|
||||
bool useProvidedKeypoints) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
CV_Assert( ! image.empty() );
|
||||
|
||||
AKAZEOptions options;
|
||||
options.descriptor = descriptor;
|
||||
options.descriptor_channels = descriptor_channels;
|
||||
options.descriptor_size = descriptor_size;
|
||||
options.img_width = image.cols();
|
||||
options.img_height = image.rows();
|
||||
options.dthreshold = threshold;
|
||||
options.omax = octaves;
|
||||
options.nsublevels = sublevels;
|
||||
options.diffusivity = diffusivity;
|
||||
|
||||
AKAZEFeatures impl(options);
|
||||
impl.Create_Nonlinear_Scale_Space(image);
|
||||
|
||||
if (!useProvidedKeypoints)
|
||||
{
|
||||
impl.Feature_Detection(keypoints);
|
||||
}
|
||||
|
||||
if (!mask.empty())
|
||||
{
|
||||
KeyPointsFilter::runByPixelsMask(keypoints, mask.getMat());
|
||||
}
|
||||
|
||||
if (max_points > 0 && (int)keypoints.size() > max_points) {
|
||||
std::partial_sort(keypoints.begin(), keypoints.begin() + max_points, keypoints.end(),
|
||||
[](const cv::KeyPoint& k1, const cv::KeyPoint& k2) {return k1.response > k2.response;});
|
||||
keypoints.erase(keypoints.begin() + max_points, keypoints.end());
|
||||
}
|
||||
|
||||
if(descriptors.needed())
|
||||
{
|
||||
impl.Compute_Descriptors(keypoints, descriptors);
|
||||
|
||||
CV_Assert((descriptors.empty() || descriptors.cols() == descriptorSize()));
|
||||
CV_Assert((descriptors.empty() || (descriptors.type() == descriptorType())));
|
||||
}
|
||||
}
|
||||
|
||||
void write(FileStorage& fs) const CV_OVERRIDE
|
||||
{
|
||||
writeFormat(fs);
|
||||
fs << "name" << getDefaultName();
|
||||
fs << "descriptor" << descriptor;
|
||||
fs << "descriptor_channels" << descriptor_channels;
|
||||
fs << "descriptor_size" << descriptor_size;
|
||||
fs << "threshold" << threshold;
|
||||
fs << "octaves" << octaves;
|
||||
fs << "sublevels" << sublevels;
|
||||
fs << "diffusivity" << diffusivity;
|
||||
fs << "max_points" << max_points;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn) CV_OVERRIDE
|
||||
{
|
||||
// if node is empty, keep previous value
|
||||
if (!fn["descriptor"].empty())
|
||||
descriptor = static_cast<DescriptorType>((int)fn["descriptor"]);
|
||||
if (!fn["descriptor_channels"].empty())
|
||||
descriptor_channels = (int)fn["descriptor_channels"];
|
||||
if (!fn["descriptor_size"].empty())
|
||||
descriptor_size = (int)fn["descriptor_size"];
|
||||
if (!fn["threshold"].empty())
|
||||
threshold = (float)fn["threshold"];
|
||||
if (!fn["octaves"].empty())
|
||||
octaves = (int)fn["octaves"];
|
||||
if (!fn["sublevels"].empty())
|
||||
sublevels = (int)fn["sublevels"];
|
||||
if (!fn["diffusivity"].empty())
|
||||
diffusivity = static_cast<KAZE::DiffusivityType>((int)fn["diffusivity"]);
|
||||
if (!fn["max_points"].empty())
|
||||
max_points = (int)fn["max_points"];
|
||||
}
|
||||
|
||||
DescriptorType descriptor;
|
||||
int descriptor_channels;
|
||||
int descriptor_size;
|
||||
float threshold;
|
||||
int octaves;
|
||||
int sublevels;
|
||||
KAZE::DiffusivityType diffusivity;
|
||||
int max_points;
|
||||
};
|
||||
|
||||
Ptr<AKAZE> AKAZE::create(DescriptorType descriptor_type,
|
||||
int descriptor_size, int descriptor_channels,
|
||||
float threshold, int octaves,
|
||||
int sublevels, KAZE::DiffusivityType diffusivity, int max_points)
|
||||
{
|
||||
return makePtr<AKAZE_Impl>(descriptor_type, descriptor_size, descriptor_channels,
|
||||
threshold, octaves, sublevels, diffusivity, max_points);
|
||||
}
|
||||
|
||||
String AKAZE::getDefaultName() const
|
||||
{
|
||||
return (Feature2D::getDefaultName() + ".AKAZE");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
BOWTrainer::BOWTrainer() : size(0)
|
||||
{}
|
||||
|
||||
BOWTrainer::~BOWTrainer()
|
||||
{}
|
||||
|
||||
void BOWTrainer::add( const Mat& _descriptors )
|
||||
{
|
||||
CV_Assert( !_descriptors.empty() );
|
||||
if( !descriptors.empty() )
|
||||
{
|
||||
CV_Assert( descriptors[0].cols == _descriptors.cols );
|
||||
CV_Assert( descriptors[0].type() == _descriptors.type() );
|
||||
size += _descriptors.rows;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = _descriptors.rows;
|
||||
}
|
||||
|
||||
descriptors.push_back(_descriptors);
|
||||
}
|
||||
|
||||
const std::vector<Mat>& BOWTrainer::getDescriptors() const
|
||||
{
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
int BOWTrainer::descriptorsCount() const
|
||||
{
|
||||
return descriptors.empty() ? 0 : size;
|
||||
}
|
||||
|
||||
void BOWTrainer::clear()
|
||||
{
|
||||
descriptors.clear();
|
||||
}
|
||||
|
||||
BOWKMeansTrainer::BOWKMeansTrainer( int _clusterCount, const TermCriteria& _termcrit,
|
||||
int _attempts, int _flags ) :
|
||||
clusterCount(_clusterCount), termcrit(_termcrit), attempts(_attempts), flags(_flags)
|
||||
{}
|
||||
|
||||
Mat BOWKMeansTrainer::cluster() const
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
CV_Assert( !descriptors.empty() );
|
||||
|
||||
Mat mergedDescriptors( descriptorsCount(), descriptors[0].cols, descriptors[0].type() );
|
||||
for( size_t i = 0, start = 0; i < descriptors.size(); i++ )
|
||||
{
|
||||
Mat submut = mergedDescriptors.rowRange((int)start, (int)(start + descriptors[i].rows));
|
||||
descriptors[i].copyTo(submut);
|
||||
start += descriptors[i].rows;
|
||||
}
|
||||
return cluster( mergedDescriptors );
|
||||
}
|
||||
|
||||
BOWKMeansTrainer::~BOWKMeansTrainer()
|
||||
{}
|
||||
|
||||
Mat BOWKMeansTrainer::cluster( const Mat& _descriptors ) const
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
Mat labels, vocabulary;
|
||||
kmeans( _descriptors, clusterCount, labels, termcrit, attempts, flags, vocabulary );
|
||||
return vocabulary;
|
||||
}
|
||||
|
||||
|
||||
BOWImgDescriptorExtractor::BOWImgDescriptorExtractor( const Ptr<DescriptorExtractor>& _dextractor,
|
||||
const Ptr<DescriptorMatcher>& _dmatcher ) :
|
||||
dextractor(_dextractor), dmatcher(_dmatcher)
|
||||
{}
|
||||
|
||||
BOWImgDescriptorExtractor::BOWImgDescriptorExtractor( const Ptr<DescriptorMatcher>& _dmatcher ) :
|
||||
dmatcher(_dmatcher)
|
||||
{}
|
||||
|
||||
BOWImgDescriptorExtractor::~BOWImgDescriptorExtractor()
|
||||
{}
|
||||
|
||||
void BOWImgDescriptorExtractor::setVocabulary( const Mat& _vocabulary )
|
||||
{
|
||||
dmatcher->clear();
|
||||
vocabulary = _vocabulary;
|
||||
dmatcher->add( std::vector<Mat>(1, vocabulary) );
|
||||
}
|
||||
|
||||
const Mat& BOWImgDescriptorExtractor::getVocabulary() const
|
||||
{
|
||||
return vocabulary;
|
||||
}
|
||||
|
||||
void BOWImgDescriptorExtractor::compute( InputArray image, std::vector<KeyPoint>& keypoints, OutputArray imgDescriptor,
|
||||
std::vector<std::vector<int> >* pointIdxsOfClusters, Mat* descriptors )
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
imgDescriptor.release();
|
||||
|
||||
if( keypoints.empty() )
|
||||
return;
|
||||
|
||||
// Compute descriptors for the image.
|
||||
Mat _descriptors;
|
||||
dextractor->compute( image, keypoints, _descriptors );
|
||||
|
||||
compute( _descriptors, imgDescriptor, pointIdxsOfClusters );
|
||||
|
||||
// Add the descriptors of image keypoints
|
||||
if (descriptors) {
|
||||
*descriptors = _descriptors.clone();
|
||||
}
|
||||
}
|
||||
|
||||
int BOWImgDescriptorExtractor::descriptorSize() const
|
||||
{
|
||||
return vocabulary.empty() ? 0 : vocabulary.rows;
|
||||
}
|
||||
|
||||
int BOWImgDescriptorExtractor::descriptorType() const
|
||||
{
|
||||
return CV_32FC1;
|
||||
}
|
||||
|
||||
void BOWImgDescriptorExtractor::compute( InputArray keypointDescriptors, OutputArray _imgDescriptor, std::vector<std::vector<int> >* pointIdxsOfClusters )
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
CV_Assert( !vocabulary.empty() );
|
||||
CV_Assert(!keypointDescriptors.empty());
|
||||
|
||||
int clusterCount = descriptorSize(); // = vocabulary.rows
|
||||
|
||||
// Match keypoint descriptors to cluster center (to vocabulary)
|
||||
std::vector<DMatch> matches;
|
||||
dmatcher->match( keypointDescriptors, matches );
|
||||
|
||||
// Compute image descriptor
|
||||
if( pointIdxsOfClusters )
|
||||
{
|
||||
pointIdxsOfClusters->clear();
|
||||
pointIdxsOfClusters->resize(clusterCount);
|
||||
}
|
||||
|
||||
_imgDescriptor.create(1, clusterCount, descriptorType());
|
||||
_imgDescriptor.setTo(Scalar::all(0));
|
||||
|
||||
Mat imgDescriptor = _imgDescriptor.getMat();
|
||||
|
||||
float *dptr = imgDescriptor.ptr<float>();
|
||||
for( size_t i = 0; i < matches.size(); i++ )
|
||||
{
|
||||
int queryIdx = matches[i].queryIdx;
|
||||
int trainIdx = matches[i].trainIdx; // cluster index
|
||||
CV_Assert( queryIdx == (int)i );
|
||||
|
||||
dptr[trainIdx] = dptr[trainIdx] + 1.f;
|
||||
if( pointIdxsOfClusters )
|
||||
(*pointIdxsOfClusters)[trainIdx].push_back( queryIdx );
|
||||
}
|
||||
|
||||
// Normalize image descriptor.
|
||||
imgDescriptor /= keypointDescriptors.size().height;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,2433 +0,0 @@
|
||||
/*********************************************************************
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* Copyright (C) 2011 The Autonomous Systems Lab (ASL), ETH Zurich,
|
||||
* Stefan Leutenegger, Simon Lynen and Margarita Chli.
|
||||
* Copyright (c) 2009, Willow Garage, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of the Willow Garage nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*********************************************************************/
|
||||
|
||||
/*
|
||||
BRISK - Binary Robust Invariant Scalable Keypoints
|
||||
Reference implementation of
|
||||
[1] Stefan Leutenegger,Margarita Chli and Roland Siegwart, BRISK:
|
||||
Binary Robust Invariant Scalable Keypoints, in Proceedings of
|
||||
the IEEE International Conference on Computer Vision (ICCV2011).
|
||||
*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include <fstream>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "agast_score.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class BRISK_Impl CV_FINAL : public BRISK
|
||||
{
|
||||
public:
|
||||
explicit BRISK_Impl(int _threshold=30, int _octaves=3, float _patternScale=1.0f);
|
||||
// custom setup
|
||||
explicit BRISK_Impl(const std::vector<float> &radiusList, const std::vector<int> &numberList,
|
||||
float dMax=5.85f, float dMin=8.2f, const std::vector<int> indexChange=std::vector<int>());
|
||||
|
||||
explicit BRISK_Impl(int thresh, int octaves, const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList, float dMax=5.85f, float dMin=8.2f,
|
||||
const std::vector<int> indexChange=std::vector<int>());
|
||||
|
||||
virtual ~BRISK_Impl();
|
||||
|
||||
void read( const FileNode& fn) CV_OVERRIDE;
|
||||
void write( FileStorage& fs) const CV_OVERRIDE;
|
||||
|
||||
int descriptorSize() const CV_OVERRIDE
|
||||
{
|
||||
return strings_;
|
||||
}
|
||||
|
||||
int descriptorType() const CV_OVERRIDE
|
||||
{
|
||||
return CV_8U;
|
||||
}
|
||||
|
||||
int defaultNorm() const CV_OVERRIDE
|
||||
{
|
||||
return NORM_HAMMING;
|
||||
}
|
||||
|
||||
virtual void setThreshold(int threshold_in) CV_OVERRIDE
|
||||
{
|
||||
threshold = threshold_in;
|
||||
}
|
||||
|
||||
virtual int getThreshold() const CV_OVERRIDE
|
||||
{
|
||||
return threshold;
|
||||
}
|
||||
|
||||
virtual void setOctaves(int octaves_in) CV_OVERRIDE
|
||||
{
|
||||
octaves = octaves_in;
|
||||
}
|
||||
|
||||
virtual int getOctaves() const CV_OVERRIDE
|
||||
{
|
||||
return octaves;
|
||||
}
|
||||
virtual void setPatternScale(float _patternScale) CV_OVERRIDE
|
||||
{
|
||||
patternScale = _patternScale;
|
||||
std::vector<float> rList;
|
||||
std::vector<int> nList;
|
||||
|
||||
// this is the standard pattern found to be suitable also
|
||||
rList.resize(5);
|
||||
nList.resize(5);
|
||||
const double f = 0.85 * patternScale;
|
||||
|
||||
rList[0] = (float)(f * 0.);
|
||||
rList[1] = (float)(f * 2.9);
|
||||
rList[2] = (float)(f * 4.9);
|
||||
rList[3] = (float)(f * 7.4);
|
||||
rList[4] = (float)(f * 10.8);
|
||||
|
||||
nList[0] = 1;
|
||||
nList[1] = 10;
|
||||
nList[2] = 14;
|
||||
nList[3] = 15;
|
||||
nList[4] = 20;
|
||||
|
||||
generateKernel(rList, nList, (float)(5.85 * patternScale), (float)(8.2 * patternScale));
|
||||
}
|
||||
virtual float getPatternScale() const CV_OVERRIDE
|
||||
{
|
||||
return patternScale;
|
||||
}
|
||||
|
||||
// call this to generate the kernel:
|
||||
// circle of radius r (pixels), with n points;
|
||||
// short pairings with dMax, long pairings with dMin
|
||||
void generateKernel(const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList, float dMax=5.85f, float dMin=8.2f,
|
||||
const std::vector<int> &indexChange=std::vector<int>());
|
||||
|
||||
void detectAndCompute( InputArray image, InputArray mask,
|
||||
CV_OUT std::vector<KeyPoint>& keypoints,
|
||||
OutputArray descriptors,
|
||||
bool useProvidedKeypoints ) CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
void computeKeypointsNoOrientation(InputArray image, InputArray mask, std::vector<KeyPoint>& keypoints) const;
|
||||
void computeDescriptorsAndOrOrientation(InputArray image, InputArray mask, std::vector<KeyPoint>& keypoints,
|
||||
OutputArray descriptors, bool doDescriptors, bool doOrientation,
|
||||
bool useProvidedKeypoints) const;
|
||||
|
||||
// Feature parameters
|
||||
CV_PROP_RW int threshold;
|
||||
CV_PROP_RW int octaves;
|
||||
CV_PROP_RW float patternScale;
|
||||
|
||||
// some helper structures for the Brisk pattern representation
|
||||
struct BriskPatternPoint{
|
||||
float x; // x coordinate relative to center
|
||||
float y; // x coordinate relative to center
|
||||
float sigma; // Gaussian smoothing sigma
|
||||
};
|
||||
struct BriskShortPair{
|
||||
unsigned int i; // index of the first pattern point
|
||||
unsigned int j; // index of other pattern point
|
||||
};
|
||||
struct BriskLongPair{
|
||||
unsigned int i; // index of the first pattern point
|
||||
unsigned int j; // index of other pattern point
|
||||
int weighted_dx; // 1024.0/dx
|
||||
int weighted_dy; // 1024.0/dy
|
||||
};
|
||||
inline int smoothedIntensity(const cv::Mat& image,
|
||||
const cv::Mat& integral,const float key_x,
|
||||
const float key_y, const unsigned int scale,
|
||||
const unsigned int rot, const unsigned int point) const;
|
||||
// pattern properties
|
||||
BriskPatternPoint* patternPoints_; //[i][rotation][scale]
|
||||
unsigned int points_; // total number of collocation points
|
||||
float* scaleList_; // lists the scaling per scale index [scale]
|
||||
unsigned int* sizeList_; // lists the total pattern size per scale index [scale]
|
||||
static const unsigned int scales_; // scales discretization
|
||||
static const float scalerange_; // span of sizes 40->4 Octaves - else, this needs to be adjusted...
|
||||
static const unsigned int n_rot_; // discretization of the rotation look-up
|
||||
|
||||
// pairs
|
||||
int strings_; // number of uchars the descriptor consists of
|
||||
float dMax_; // short pair maximum distance
|
||||
float dMin_; // long pair maximum distance
|
||||
BriskShortPair* shortPairs_; // d<_dMax
|
||||
BriskLongPair* longPairs_; // d>_dMin
|
||||
unsigned int noShortPairs_; // number of shortParis
|
||||
unsigned int noLongPairs_; // number of longParis
|
||||
|
||||
// general
|
||||
static const float basicSize_;
|
||||
|
||||
private:
|
||||
BRISK_Impl(const BRISK_Impl &); // copy disabled
|
||||
BRISK_Impl& operator=(const BRISK_Impl &); // assign disabled
|
||||
};
|
||||
|
||||
|
||||
// a layer in the Brisk detector pyramid
|
||||
class BriskLayer
|
||||
{
|
||||
public:
|
||||
// constructor arguments
|
||||
struct CommonParams
|
||||
{
|
||||
static const int HALFSAMPLE = 0;
|
||||
static const int TWOTHIRDSAMPLE = 1;
|
||||
};
|
||||
// construct a base layer
|
||||
BriskLayer(const cv::Mat& img, float scale = 1.0f, float offset = 0.0f);
|
||||
// derive a layer
|
||||
BriskLayer(const BriskLayer& layer, int mode);
|
||||
|
||||
// Agast without non-max suppression
|
||||
void
|
||||
getAgastPoints(int threshold, std::vector<cv::KeyPoint>& keypoints);
|
||||
|
||||
// get scores - attention, this is in layer coordinates, not scale=1 coordinates!
|
||||
inline int
|
||||
getAgastScore(int x, int y, int threshold) const;
|
||||
inline int
|
||||
getAgastScore_5_8(int x, int y, int threshold) const;
|
||||
inline int
|
||||
getAgastScore(float xf, float yf, int threshold, float scale = 1.0f) const;
|
||||
|
||||
// accessors
|
||||
inline const cv::Mat&
|
||||
img() const
|
||||
{
|
||||
return img_;
|
||||
}
|
||||
inline const cv::Mat&
|
||||
scores() const
|
||||
{
|
||||
return scores_;
|
||||
}
|
||||
inline float
|
||||
scale() const
|
||||
{
|
||||
return scale_;
|
||||
}
|
||||
inline float
|
||||
offset() const
|
||||
{
|
||||
return offset_;
|
||||
}
|
||||
|
||||
// half sampling
|
||||
static inline void
|
||||
halfsample(const cv::Mat& srcimg, cv::Mat& dstimg);
|
||||
// two third sampling
|
||||
static inline void
|
||||
twothirdsample(const cv::Mat& srcimg, cv::Mat& dstimg);
|
||||
|
||||
private:
|
||||
// access gray values (smoothed/interpolated)
|
||||
inline int
|
||||
value(const cv::Mat& mat, float xf, float yf, float scale) const;
|
||||
// the image
|
||||
cv::Mat img_;
|
||||
// its Agast scores
|
||||
cv::Mat_<uchar> scores_;
|
||||
// coordinate transformation
|
||||
float scale_;
|
||||
float offset_;
|
||||
// agast
|
||||
cv::Ptr<cv::AgastFeatureDetector> oast_9_16_;
|
||||
int pixel_5_8_[25];
|
||||
int pixel_9_16_[25];
|
||||
};
|
||||
|
||||
class BriskScaleSpace
|
||||
{
|
||||
public:
|
||||
// construct telling the octaves number:
|
||||
BriskScaleSpace(int _octaves = 3);
|
||||
~BriskScaleSpace();
|
||||
|
||||
// construct the image pyramids
|
||||
void
|
||||
constructPyramid(const cv::Mat& image);
|
||||
|
||||
// get Keypoints
|
||||
void
|
||||
getKeypoints(const int _threshold, std::vector<cv::KeyPoint>& keypoints);
|
||||
|
||||
protected:
|
||||
// nonmax suppression:
|
||||
inline bool
|
||||
isMax2D(const int layer, const int x_layer, const int y_layer);
|
||||
// 1D (scale axis) refinement:
|
||||
inline float
|
||||
refine1D(const float s_05, const float s0, const float s05, float& max) const; // around octave
|
||||
inline float
|
||||
refine1D_1(const float s_05, const float s0, const float s05, float& max) const; // around intra
|
||||
inline float
|
||||
refine1D_2(const float s_05, const float s0, const float s05, float& max) const; // around octave 0 only
|
||||
// 2D maximum refinement:
|
||||
inline float
|
||||
subpixel2D(const int s_0_0, const int s_0_1, const int s_0_2, const int s_1_0, const int s_1_1, const int s_1_2,
|
||||
const int s_2_0, const int s_2_1, const int s_2_2, float& delta_x, float& delta_y) const;
|
||||
// 3D maximum refinement centered around (x_layer,y_layer)
|
||||
inline float
|
||||
refine3D(const int layer, const int x_layer, const int y_layer, float& x, float& y, float& scale, bool& ismax) const;
|
||||
|
||||
// interpolated score access with recalculation when needed:
|
||||
inline int
|
||||
getScoreAbove(const int layer, const int x_layer, const int y_layer) const;
|
||||
inline int
|
||||
getScoreBelow(const int layer, const int x_layer, const int y_layer) const;
|
||||
|
||||
// return the maximum of score patches above or below
|
||||
inline float
|
||||
getScoreMaxAbove(const int layer, const int x_layer, const int y_layer, const int threshold, bool& ismax,
|
||||
float& dx, float& dy) const;
|
||||
inline float
|
||||
getScoreMaxBelow(const int layer, const int x_layer, const int y_layer, const int threshold, bool& ismax,
|
||||
float& dx, float& dy) const;
|
||||
|
||||
// the image pyramids:
|
||||
int layers_;
|
||||
std::vector<BriskLayer> pyramid_;
|
||||
|
||||
// some constant parameters:
|
||||
static const float safetyFactor_;
|
||||
static const float basicSize_;
|
||||
};
|
||||
|
||||
const float BRISK_Impl::basicSize_ = 12.0f;
|
||||
const unsigned int BRISK_Impl::scales_ = 64;
|
||||
const float BRISK_Impl::scalerange_ = 30.f; // 40->4 Octaves - else, this needs to be adjusted...
|
||||
const unsigned int BRISK_Impl::n_rot_ = 1024; // discretization of the rotation look-up
|
||||
|
||||
const float BriskScaleSpace::safetyFactor_ = 1.0f;
|
||||
const float BriskScaleSpace::basicSize_ = 12.0f;
|
||||
|
||||
// constructors
|
||||
BRISK_Impl::BRISK_Impl(int _threshold, int _octaves, float _patternScale)
|
||||
{
|
||||
threshold = _threshold;
|
||||
octaves = _octaves;
|
||||
|
||||
setPatternScale(_patternScale);
|
||||
}
|
||||
|
||||
BRISK_Impl::BRISK_Impl(const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList,
|
||||
float dMax, float dMin,
|
||||
const std::vector<int> indexChange)
|
||||
{
|
||||
generateKernel(radiusList, numberList, dMax, dMin, indexChange);
|
||||
threshold = 20;
|
||||
octaves = 3;
|
||||
}
|
||||
|
||||
BRISK_Impl::BRISK_Impl(int thresh,
|
||||
int octaves_in,
|
||||
const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList,
|
||||
float dMax, float dMin,
|
||||
const std::vector<int> indexChange)
|
||||
{
|
||||
generateKernel(radiusList, numberList, dMax, dMin, indexChange);
|
||||
threshold = thresh;
|
||||
octaves = octaves_in;
|
||||
}
|
||||
|
||||
void BRISK_Impl::read( const FileNode& fn)
|
||||
{
|
||||
// if node is empty, keep previous value
|
||||
if (!fn["threshold"].empty())
|
||||
fn["threshold"] >> threshold;
|
||||
if (!fn["octaves"].empty())
|
||||
fn["octaves"] >> octaves;
|
||||
if (!fn["patternScale"].empty())
|
||||
{
|
||||
float _patternScale;
|
||||
fn["patternScale"] >> _patternScale;
|
||||
setPatternScale(_patternScale);
|
||||
}
|
||||
}
|
||||
void BRISK_Impl::write( FileStorage& fs) const
|
||||
{
|
||||
if(fs.isOpened())
|
||||
{
|
||||
fs << "name" << getDefaultName();
|
||||
fs << "threshold" << threshold;
|
||||
fs << "octaves" << octaves;
|
||||
fs << "patternScale" << patternScale;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BRISK_Impl::generateKernel(const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList,
|
||||
float dMax, float dMin,
|
||||
const std::vector<int>& _indexChange)
|
||||
{
|
||||
std::vector<int> indexChange = _indexChange;
|
||||
dMax_ = dMax;
|
||||
dMin_ = dMin;
|
||||
|
||||
// get the total number of points
|
||||
const int rings = (int)radiusList.size();
|
||||
CV_Assert(radiusList.size() != 0 && radiusList.size() == numberList.size());
|
||||
points_ = 0; // remember the total number of points
|
||||
double sineThetaLookupTable[n_rot_];
|
||||
double cosThetaLookupTable[n_rot_];
|
||||
for (int ring = 0; ring < rings; ring++)
|
||||
{
|
||||
points_ += numberList[ring];
|
||||
}
|
||||
|
||||
// using a sine/cosine approximation for the lookup table
|
||||
// utilizes the trig identities:
|
||||
// sin(a + b) = sin(a)cos(b) + cos(a)sin(b)
|
||||
// cos(a + b) = cos(a)cos(b) - sin(a)sin(b)
|
||||
// and the fact that sin(0) = 0, cos(0) = 1
|
||||
double cosval = 1., sinval = 0.;
|
||||
double dcos = cos(2*CV_PI/double(n_rot_)), dsin = sin(2*CV_PI/double(n_rot_));
|
||||
for( size_t rot = 0; rot < n_rot_; ++rot)
|
||||
{
|
||||
sineThetaLookupTable[rot] = sinval;
|
||||
cosThetaLookupTable[rot] = cosval;
|
||||
double t = sinval*dcos + cosval*dsin;
|
||||
cosval = cosval*dcos - sinval*dsin;
|
||||
sinval = t;
|
||||
}
|
||||
// set up the patterns
|
||||
patternPoints_ = new BriskPatternPoint[points_ * scales_ * n_rot_];
|
||||
|
||||
// define the scale discretization:
|
||||
static const float lb_scale = (float)(std::log(scalerange_) / std::log(2.0));
|
||||
static const float lb_scale_step = lb_scale / (scales_);
|
||||
|
||||
scaleList_ = new float[scales_];
|
||||
sizeList_ = new unsigned int[scales_];
|
||||
|
||||
const float sigma_scale = 1.3f;
|
||||
|
||||
for (unsigned int scale = 0; scale < scales_; ++scale) {
|
||||
scaleList_[scale] = (float) std::pow((double) 2.0, (double) (scale * lb_scale_step));
|
||||
sizeList_[scale] = 0;
|
||||
BriskPatternPoint *patternIteratorOuter = patternPoints_ + (scale * n_rot_ * points_);
|
||||
// generate the pattern points look-up
|
||||
for (int ring = 0; ring < rings; ++ring) {
|
||||
double scaleRadiusProduct = scaleList_[scale] * radiusList[ring];
|
||||
float patternSigma = 0.0f;
|
||||
if (ring == 0) {
|
||||
patternSigma = sigma_scale * scaleList_[scale] * 0.5f;
|
||||
} else {
|
||||
patternSigma = (float) (sigma_scale * scaleList_[scale] * (double(radiusList[ring]))
|
||||
* sin(CV_PI / numberList[ring]));
|
||||
}
|
||||
// adapt the sizeList if necessary
|
||||
const unsigned int size = cvCeil(((scaleList_[scale] * radiusList[ring]) + patternSigma)) + 1;
|
||||
if (sizeList_[scale] < size) {
|
||||
sizeList_[scale] = size;
|
||||
}
|
||||
for (int num = 0; num < numberList[ring]; ++num) {
|
||||
BriskPatternPoint *patternIterator = patternIteratorOuter;
|
||||
double alpha = (double(num)) * 2 * CV_PI / double(numberList[ring]);
|
||||
double sine_alpha = sin(alpha);
|
||||
double cosine_alpha = cos(alpha);
|
||||
|
||||
for (size_t rot = 0; rot < n_rot_; ++rot) {
|
||||
double cosine_theta = cosThetaLookupTable[rot];
|
||||
double sine_theta = sineThetaLookupTable[rot];
|
||||
|
||||
// the actual coordinates on the circle
|
||||
// sin(a + b) = sin(a) cos(b) + cos(a) sin(b)
|
||||
// cos(a + b) = cos(a) cos(b) - sin(a) sin(b)
|
||||
patternIterator->x = (float) (scaleRadiusProduct *
|
||||
(cosine_theta * cosine_alpha -
|
||||
sine_theta * sine_alpha)); // feature rotation plus angle of the point
|
||||
patternIterator->y = (float) (scaleRadiusProduct *
|
||||
(sine_theta * cosine_alpha + cosine_theta * sine_alpha));
|
||||
patternIterator->sigma = patternSigma;
|
||||
// and the gaussian kernel sigma
|
||||
// increment the iterator
|
||||
patternIterator += points_;
|
||||
}
|
||||
++patternIteratorOuter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now also generate pairings
|
||||
shortPairs_ = new BriskShortPair[points_ * (points_ - 1) / 2];
|
||||
longPairs_ = new BriskLongPair[points_ * (points_ - 1) / 2];
|
||||
noShortPairs_ = 0;
|
||||
noLongPairs_ = 0;
|
||||
|
||||
// fill indexChange with 0..n if empty
|
||||
unsigned int indSize = (unsigned int)indexChange.size();
|
||||
if (indSize == 0)
|
||||
{
|
||||
indexChange.resize(points_ * (points_ - 1) / 2);
|
||||
indSize = (unsigned int)indexChange.size();
|
||||
|
||||
for (unsigned int i = 0; i < indSize; i++)
|
||||
indexChange[i] = i;
|
||||
}
|
||||
const float dMin_sq = dMin_ * dMin_;
|
||||
const float dMax_sq = dMax_ * dMax_;
|
||||
for (unsigned int i = 1; i < points_; i++)
|
||||
{
|
||||
for (unsigned int j = 0; j < i; j++)
|
||||
{ //(find all the pairs)
|
||||
// point pair distance:
|
||||
const float dx = patternPoints_[j].x - patternPoints_[i].x;
|
||||
const float dy = patternPoints_[j].y - patternPoints_[i].y;
|
||||
const float norm_sq = (dx * dx + dy * dy);
|
||||
if (norm_sq > dMin_sq)
|
||||
{
|
||||
// save to long pairs
|
||||
BriskLongPair& longPair = longPairs_[noLongPairs_];
|
||||
longPair.weighted_dx = int((dx / (norm_sq)) * 2048.0 + 0.5);
|
||||
longPair.weighted_dy = int((dy / (norm_sq)) * 2048.0 + 0.5);
|
||||
longPair.i = i;
|
||||
longPair.j = j;
|
||||
++noLongPairs_;
|
||||
}
|
||||
else if (norm_sq < dMax_sq)
|
||||
{
|
||||
// save to short pairs
|
||||
CV_Assert(noShortPairs_ < indSize);
|
||||
// make sure the user passes something sensible
|
||||
BriskShortPair& shortPair = shortPairs_[indexChange[noShortPairs_]];
|
||||
shortPair.j = j;
|
||||
shortPair.i = i;
|
||||
++noShortPairs_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no bits:
|
||||
strings_ = (int) ceil((float(noShortPairs_)) / 128.0) * 4 * 4;
|
||||
}
|
||||
|
||||
// simple alternative:
|
||||
inline int
|
||||
BRISK_Impl::smoothedIntensity(const cv::Mat& image, const cv::Mat& integral, const float key_x,
|
||||
const float key_y, const unsigned int scale, const unsigned int rot,
|
||||
const unsigned int point) const
|
||||
{
|
||||
|
||||
// get the float position
|
||||
const BriskPatternPoint& briskPoint = patternPoints_[scale * n_rot_ * points_ + rot * points_ + point];
|
||||
const float xf = briskPoint.x + key_x;
|
||||
const float yf = briskPoint.y + key_y;
|
||||
const int x = int(xf);
|
||||
const int y = int(yf);
|
||||
const int& imagecols = image.cols;
|
||||
|
||||
// get the sigma:
|
||||
const float sigma_half = briskPoint.sigma;
|
||||
const float area = 4.0f * sigma_half * sigma_half;
|
||||
|
||||
// calculate output:
|
||||
int ret_val;
|
||||
if (sigma_half < 0.5)
|
||||
{
|
||||
//interpolation multipliers:
|
||||
const int r_x = (int)((xf - x) * 1024);
|
||||
const int r_y = (int)((yf - y) * 1024);
|
||||
const int r_x_1 = (1024 - r_x);
|
||||
const int r_y_1 = (1024 - r_y);
|
||||
const uchar* ptr = &image.at<uchar>(y, x);
|
||||
size_t step = image.step;
|
||||
// just interpolate:
|
||||
ret_val = r_x_1 * r_y_1 * ptr[0] + r_x * r_y_1 * ptr[1] +
|
||||
r_x * r_y * ptr[step] + r_x_1 * r_y * ptr[step+1];
|
||||
return (ret_val + 512) / 1024;
|
||||
}
|
||||
|
||||
// this is the standard case (simple, not speed optimized yet):
|
||||
|
||||
// scaling:
|
||||
const int scaling = (int)(4194304.0 / area);
|
||||
const int scaling2 = int(float(scaling) * area / 1024.0);
|
||||
CV_Assert(scaling2 != 0);
|
||||
|
||||
// the integral image is larger:
|
||||
const int integralcols = imagecols + 1;
|
||||
|
||||
// calculate borders
|
||||
const float x_1 = xf - sigma_half;
|
||||
const float x1 = xf + sigma_half;
|
||||
const float y_1 = yf - sigma_half;
|
||||
const float y1 = yf + sigma_half;
|
||||
|
||||
const int x_left = int(x_1 + 0.5);
|
||||
const int y_top = int(y_1 + 0.5);
|
||||
const int x_right = int(x1 + 0.5);
|
||||
const int y_bottom = int(y1 + 0.5);
|
||||
|
||||
// overlap area - multiplication factors:
|
||||
const float r_x_1 = float(x_left) - x_1 + 0.5f;
|
||||
const float r_y_1 = float(y_top) - y_1 + 0.5f;
|
||||
const float r_x1 = x1 - float(x_right) + 0.5f;
|
||||
const float r_y1 = y1 - float(y_bottom) + 0.5f;
|
||||
const int dx = x_right - x_left - 1;
|
||||
const int dy = y_bottom - y_top - 1;
|
||||
const int A = (int)((r_x_1 * r_y_1) * scaling);
|
||||
const int B = (int)((r_x1 * r_y_1) * scaling);
|
||||
const int C = (int)((r_x1 * r_y1) * scaling);
|
||||
const int D = (int)((r_x_1 * r_y1) * scaling);
|
||||
const int r_x_1_i = (int)(r_x_1 * scaling);
|
||||
const int r_y_1_i = (int)(r_y_1 * scaling);
|
||||
const int r_x1_i = (int)(r_x1 * scaling);
|
||||
const int r_y1_i = (int)(r_y1 * scaling);
|
||||
|
||||
if (dx + dy > 2)
|
||||
{
|
||||
// now the calculation:
|
||||
const uchar* ptr = image.ptr() + x_left + imagecols * y_top;
|
||||
// first the corners:
|
||||
ret_val = A * int(*ptr);
|
||||
ptr += dx + 1;
|
||||
ret_val += B * int(*ptr);
|
||||
ptr += dy * imagecols + 1;
|
||||
ret_val += C * int(*ptr);
|
||||
ptr -= dx + 1;
|
||||
ret_val += D * int(*ptr);
|
||||
|
||||
// next the edges:
|
||||
const int* ptr_integral = integral.ptr<int>() + x_left + integralcols * y_top + 1;
|
||||
// find a simple path through the different surface corners
|
||||
const int tmp1 = (*ptr_integral);
|
||||
ptr_integral += dx;
|
||||
const int tmp2 = (*ptr_integral);
|
||||
ptr_integral += integralcols;
|
||||
const int tmp3 = (*ptr_integral);
|
||||
ptr_integral++;
|
||||
const int tmp4 = (*ptr_integral);
|
||||
ptr_integral += dy * integralcols;
|
||||
const int tmp5 = (*ptr_integral);
|
||||
ptr_integral--;
|
||||
const int tmp6 = (*ptr_integral);
|
||||
ptr_integral += integralcols;
|
||||
const int tmp7 = (*ptr_integral);
|
||||
ptr_integral -= dx;
|
||||
const int tmp8 = (*ptr_integral);
|
||||
ptr_integral -= integralcols;
|
||||
const int tmp9 = (*ptr_integral);
|
||||
ptr_integral--;
|
||||
const int tmp10 = (*ptr_integral);
|
||||
ptr_integral -= dy * integralcols;
|
||||
const int tmp11 = (*ptr_integral);
|
||||
ptr_integral++;
|
||||
const int tmp12 = (*ptr_integral);
|
||||
|
||||
// assign the weighted surface integrals:
|
||||
const int upper = (tmp3 - tmp2 + tmp1 - tmp12) * r_y_1_i;
|
||||
const int middle = (tmp6 - tmp3 + tmp12 - tmp9) * scaling;
|
||||
const int left = (tmp9 - tmp12 + tmp11 - tmp10) * r_x_1_i;
|
||||
const int right = (tmp5 - tmp4 + tmp3 - tmp6) * r_x1_i;
|
||||
const int bottom = (tmp7 - tmp6 + tmp9 - tmp8) * r_y1_i;
|
||||
|
||||
return (ret_val + upper + middle + left + right + bottom + scaling2 / 2) / scaling2;
|
||||
}
|
||||
|
||||
// now the calculation:
|
||||
const uchar* ptr = image.ptr() + x_left + imagecols * y_top;
|
||||
// first row:
|
||||
ret_val = A * int(*ptr);
|
||||
ptr++;
|
||||
const uchar* end1 = ptr + dx;
|
||||
for (; ptr < end1; ptr++)
|
||||
{
|
||||
ret_val += r_y_1_i * int(*ptr);
|
||||
}
|
||||
ret_val += B * int(*ptr);
|
||||
// middle ones:
|
||||
ptr += imagecols - dx - 1;
|
||||
const uchar* end_j = ptr + dy * imagecols;
|
||||
for (; ptr < end_j; ptr += imagecols - dx - 1)
|
||||
{
|
||||
ret_val += r_x_1_i * int(*ptr);
|
||||
ptr++;
|
||||
const uchar* end2 = ptr + dx;
|
||||
for (; ptr < end2; ptr++)
|
||||
{
|
||||
ret_val += int(*ptr) * scaling;
|
||||
}
|
||||
ret_val += r_x1_i * int(*ptr);
|
||||
}
|
||||
// last row:
|
||||
ret_val += D * int(*ptr);
|
||||
ptr++;
|
||||
const uchar* end3 = ptr + dx;
|
||||
for (; ptr < end3; ptr++)
|
||||
{
|
||||
ret_val += r_y1_i * int(*ptr);
|
||||
}
|
||||
ret_val += C * int(*ptr);
|
||||
|
||||
return (ret_val + scaling2 / 2) / scaling2;
|
||||
}
|
||||
|
||||
inline bool
|
||||
RoiPredicate(const float minX, const float minY, const float maxX, const float maxY, const KeyPoint& keyPt)
|
||||
{
|
||||
const Point2f& pt = keyPt.pt;
|
||||
return (pt.x < minX) || (pt.x >= maxX) || (pt.y < minY) || (pt.y >= maxY);
|
||||
}
|
||||
|
||||
// computes the descriptor
|
||||
void
|
||||
BRISK_Impl::detectAndCompute( InputArray _image, InputArray _mask, std::vector<KeyPoint>& keypoints,
|
||||
OutputArray _descriptors, bool useProvidedKeypoints)
|
||||
{
|
||||
bool doOrientation=true;
|
||||
|
||||
// If the user specified cv::noArray(), this will yield false. Otherwise it will return true.
|
||||
bool doDescriptors = _descriptors.needed();
|
||||
|
||||
computeDescriptorsAndOrOrientation(_image, _mask, keypoints, _descriptors, doDescriptors, doOrientation,
|
||||
useProvidedKeypoints);
|
||||
}
|
||||
|
||||
void
|
||||
BRISK_Impl::computeDescriptorsAndOrOrientation(InputArray _image, InputArray _mask, std::vector<KeyPoint>& keypoints,
|
||||
OutputArray _descriptors, bool doDescriptors, bool doOrientation,
|
||||
bool useProvidedKeypoints) const
|
||||
{
|
||||
Mat image = _image.getMat(), mask = _mask.getMat();
|
||||
if( image.type() != CV_8UC1 )
|
||||
cvtColor(image, image, COLOR_BGR2GRAY);
|
||||
|
||||
if (!useProvidedKeypoints)
|
||||
{
|
||||
doOrientation = true;
|
||||
computeKeypointsNoOrientation(_image, _mask, keypoints);
|
||||
}
|
||||
|
||||
//Remove keypoints very close to the border
|
||||
size_t ksize = keypoints.size();
|
||||
std::vector<int> kscales; // remember the scale per keypoint
|
||||
kscales.resize(ksize);
|
||||
static const float log2 = 0.693147180559945f;
|
||||
static const float lb_scalerange = (float)(std::log(scalerange_) / (log2));
|
||||
std::vector<cv::KeyPoint>::iterator beginning = keypoints.begin();
|
||||
std::vector<int>::iterator beginningkscales = kscales.begin();
|
||||
static const float basicSize06 = basicSize_ * 0.6f;
|
||||
for (size_t k = 0; k < ksize; k++)
|
||||
{
|
||||
unsigned int scale;
|
||||
scale = std::max((int) (scales_ / lb_scalerange * (std::log(keypoints[k].size / (basicSize06)) / log2) + 0.5), 0);
|
||||
// saturate
|
||||
if (scale >= scales_)
|
||||
scale = scales_ - 1;
|
||||
kscales[k] = scale;
|
||||
const int border = sizeList_[scale];
|
||||
const int border_x = image.cols - border;
|
||||
const int border_y = image.rows - border;
|
||||
if (RoiPredicate((float)border, (float)border, (float)border_x, (float)border_y, keypoints[k]))
|
||||
{
|
||||
keypoints.erase(beginning + k);
|
||||
kscales.erase(beginningkscales + k);
|
||||
if (k == 0)
|
||||
{
|
||||
beginning = keypoints.begin();
|
||||
beginningkscales = kscales.begin();
|
||||
}
|
||||
ksize--;
|
||||
k--;
|
||||
}
|
||||
}
|
||||
|
||||
// first, calculate the integral image over the whole image:
|
||||
// current integral image
|
||||
cv::Mat _integral; // the integral image
|
||||
cv::integral(image, _integral);
|
||||
|
||||
int* _values = new int[points_]; // for temporary use
|
||||
|
||||
// resize the descriptors:
|
||||
cv::Mat descriptors;
|
||||
if (doDescriptors)
|
||||
{
|
||||
_descriptors.create((int)ksize, strings_, CV_8U);
|
||||
descriptors = _descriptors.getMat();
|
||||
descriptors.setTo(0);
|
||||
}
|
||||
|
||||
// now do the extraction for all keypoints:
|
||||
|
||||
// temporary variables containing gray values at sample points:
|
||||
int t1;
|
||||
int t2;
|
||||
|
||||
// the feature orientation
|
||||
const uchar* ptr = descriptors.ptr();
|
||||
for (size_t k = 0; k < ksize; k++)
|
||||
{
|
||||
cv::KeyPoint& kp = keypoints[k];
|
||||
const int& scale = kscales[k];
|
||||
const float& x = kp.pt.x;
|
||||
const float& y = kp.pt.y;
|
||||
|
||||
if (doOrientation)
|
||||
{
|
||||
// get the gray values in the unrotated pattern
|
||||
for (unsigned int i = 0; i < points_; i++)
|
||||
{
|
||||
_values[i] = smoothedIntensity(image, _integral, x, y, scale, 0, i);
|
||||
}
|
||||
|
||||
int direction0 = 0;
|
||||
int direction1 = 0;
|
||||
// now iterate through the long pairings
|
||||
const BriskLongPair* max = longPairs_ + noLongPairs_;
|
||||
for (BriskLongPair* iter = longPairs_; iter < max; ++iter)
|
||||
{
|
||||
CV_Assert(iter->i < points_ && iter->j < points_);
|
||||
t1 = *(_values + iter->i);
|
||||
t2 = *(_values + iter->j);
|
||||
const int delta_t = (t1 - t2);
|
||||
// update the direction:
|
||||
const int tmp0 = delta_t * (iter->weighted_dx) / 1024;
|
||||
const int tmp1 = delta_t * (iter->weighted_dy) / 1024;
|
||||
direction0 += tmp0;
|
||||
direction1 += tmp1;
|
||||
}
|
||||
kp.angle = (float)(atan2((float) direction1, (float) direction0) / CV_PI * 180.0);
|
||||
|
||||
if (!doDescriptors)
|
||||
{
|
||||
if (kp.angle < 0)
|
||||
kp.angle += 360.f;
|
||||
}
|
||||
}
|
||||
|
||||
if (!doDescriptors)
|
||||
continue;
|
||||
|
||||
int theta;
|
||||
if (kp.angle==-1)
|
||||
{
|
||||
// don't compute the gradient direction, just assign a rotation of 0
|
||||
theta = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
theta = (int) (n_rot_ * (kp.angle / (360.0)) + 0.5);
|
||||
if (theta < 0)
|
||||
theta += n_rot_;
|
||||
if (theta >= int(n_rot_))
|
||||
theta -= n_rot_;
|
||||
}
|
||||
|
||||
if (kp.angle < 0)
|
||||
kp.angle += 360.f;
|
||||
|
||||
// now also extract the stuff for the actual direction:
|
||||
// let us compute the smoothed values
|
||||
int shifter = 0;
|
||||
|
||||
//unsigned int mean=0;
|
||||
// get the gray values in the rotated pattern
|
||||
for (unsigned int i = 0; i < points_; i++)
|
||||
{
|
||||
_values[i] = smoothedIntensity(image, _integral, x, y, scale, theta, i);
|
||||
}
|
||||
|
||||
// now iterate through all the pairings
|
||||
unsigned int* ptr2 = (unsigned int*) ptr;
|
||||
const BriskShortPair* max = shortPairs_ + noShortPairs_;
|
||||
for (BriskShortPair* iter = shortPairs_; iter < max; ++iter)
|
||||
{
|
||||
CV_Assert(iter->i < points_ && iter->j < points_);
|
||||
t1 = *(_values + iter->i);
|
||||
t2 = *(_values + iter->j);
|
||||
if (t1 > t2)
|
||||
{
|
||||
*ptr2 |= ((1) << shifter);
|
||||
|
||||
} // else already initialized with zero
|
||||
// take care of the iterators:
|
||||
++shifter;
|
||||
if (shifter == 32)
|
||||
{
|
||||
shifter = 0;
|
||||
++ptr2;
|
||||
}
|
||||
}
|
||||
|
||||
ptr += strings_;
|
||||
}
|
||||
|
||||
// clean-up
|
||||
delete[] _values;
|
||||
}
|
||||
|
||||
|
||||
BRISK_Impl::~BRISK_Impl()
|
||||
{
|
||||
delete[] patternPoints_;
|
||||
delete[] shortPairs_;
|
||||
delete[] longPairs_;
|
||||
delete[] scaleList_;
|
||||
delete[] sizeList_;
|
||||
}
|
||||
|
||||
void
|
||||
BRISK_Impl::computeKeypointsNoOrientation(InputArray _image, InputArray _mask, std::vector<KeyPoint>& keypoints) const
|
||||
{
|
||||
Mat image = _image.getMat(), mask = _mask.getMat();
|
||||
if( image.type() != CV_8UC1 )
|
||||
cvtColor(_image, image, COLOR_BGR2GRAY);
|
||||
|
||||
BriskScaleSpace briskScaleSpace(octaves);
|
||||
briskScaleSpace.constructPyramid(image);
|
||||
briskScaleSpace.getKeypoints(threshold, keypoints);
|
||||
|
||||
// remove invalid points
|
||||
KeyPointsFilter::runByPixelsMask(keypoints, mask);
|
||||
}
|
||||
|
||||
// construct telling the octaves number:
|
||||
BriskScaleSpace::BriskScaleSpace(int _octaves)
|
||||
{
|
||||
if (_octaves == 0)
|
||||
layers_ = 1;
|
||||
else
|
||||
layers_ = 2 * _octaves;
|
||||
}
|
||||
BriskScaleSpace::~BriskScaleSpace()
|
||||
{
|
||||
|
||||
}
|
||||
// construct the image pyramids
|
||||
void
|
||||
BriskScaleSpace::constructPyramid(const cv::Mat& image)
|
||||
{
|
||||
|
||||
// set correct size:
|
||||
pyramid_.clear();
|
||||
|
||||
// fill the pyramid:
|
||||
pyramid_.push_back(BriskLayer(image.clone()));
|
||||
if (layers_ > 1)
|
||||
{
|
||||
pyramid_.push_back(BriskLayer(pyramid_.back(), BriskLayer::CommonParams::TWOTHIRDSAMPLE));
|
||||
}
|
||||
const int octaves2 = layers_;
|
||||
|
||||
for (uchar i = 2; i < octaves2; i += 2)
|
||||
{
|
||||
pyramid_.push_back(BriskLayer(pyramid_[i - 2], BriskLayer::CommonParams::HALFSAMPLE));
|
||||
pyramid_.push_back(BriskLayer(pyramid_[i - 1], BriskLayer::CommonParams::HALFSAMPLE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BriskScaleSpace::getKeypoints(const int threshold_, std::vector<cv::KeyPoint>& keypoints)
|
||||
{
|
||||
// make sure keypoints is empty
|
||||
keypoints.resize(0);
|
||||
keypoints.reserve(2000);
|
||||
|
||||
// assign thresholds
|
||||
int safeThreshold_ = (int)(threshold_ * safetyFactor_);
|
||||
std::vector<std::vector<cv::KeyPoint> > agastPoints;
|
||||
agastPoints.resize(layers_);
|
||||
|
||||
// go through the octaves and intra layers and calculate agast corner scores:
|
||||
for (int i = 0; i < layers_; i++)
|
||||
{
|
||||
// call OAST16_9 without nms
|
||||
BriskLayer& l = pyramid_[i];
|
||||
l.getAgastPoints(safeThreshold_, agastPoints[i]);
|
||||
}
|
||||
|
||||
if (layers_ == 1)
|
||||
{
|
||||
// just do a simple 2d subpixel refinement...
|
||||
const size_t num = agastPoints[0].size();
|
||||
for (size_t n = 0; n < num; n++)
|
||||
{
|
||||
const cv::Point2f& point = agastPoints.at(0)[n].pt;
|
||||
// first check if it is a maximum:
|
||||
if (!isMax2D(0, (int)point.x, (int)point.y))
|
||||
continue;
|
||||
|
||||
// let's do the subpixel and float scale refinement:
|
||||
BriskLayer& l = pyramid_[0];
|
||||
int s_0_0 = l.getAgastScore(point.x - 1, point.y - 1, 1);
|
||||
int s_1_0 = l.getAgastScore(point.x, point.y - 1, 1);
|
||||
int s_2_0 = l.getAgastScore(point.x + 1, point.y - 1, 1);
|
||||
int s_2_1 = l.getAgastScore(point.x + 1, point.y, 1);
|
||||
int s_1_1 = l.getAgastScore(point.x, point.y, 1);
|
||||
int s_0_1 = l.getAgastScore(point.x - 1, point.y, 1);
|
||||
int s_0_2 = l.getAgastScore(point.x - 1, point.y + 1, 1);
|
||||
int s_1_2 = l.getAgastScore(point.x, point.y + 1, 1);
|
||||
int s_2_2 = l.getAgastScore(point.x + 1, point.y + 1, 1);
|
||||
float delta_x, delta_y;
|
||||
float max = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x, delta_y);
|
||||
|
||||
// store:
|
||||
keypoints.push_back(cv::KeyPoint(float(point.x) + delta_x, float(point.y) + delta_y, basicSize_, -1, max, 0));
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float x, y, scale, score;
|
||||
for (int i = 0; i < layers_; i++)
|
||||
{
|
||||
BriskLayer& l = pyramid_[i];
|
||||
const size_t num = agastPoints[i].size();
|
||||
if (i == layers_ - 1)
|
||||
{
|
||||
for (size_t n = 0; n < num; n++)
|
||||
{
|
||||
const cv::Point2f& point = agastPoints.at(i)[n].pt;
|
||||
// consider only 2D maxima...
|
||||
if (!isMax2D(i, (int)point.x, (int)point.y))
|
||||
continue;
|
||||
|
||||
bool ismax;
|
||||
float dx, dy;
|
||||
getScoreMaxBelow(i, (int)point.x, (int)point.y, l.getAgastScore(point.x, point.y, safeThreshold_), ismax, dx, dy);
|
||||
if (!ismax)
|
||||
continue;
|
||||
|
||||
// get the patch on this layer:
|
||||
int s_0_0 = l.getAgastScore(point.x - 1, point.y - 1, 1);
|
||||
int s_1_0 = l.getAgastScore(point.x, point.y - 1, 1);
|
||||
int s_2_0 = l.getAgastScore(point.x + 1, point.y - 1, 1);
|
||||
int s_2_1 = l.getAgastScore(point.x + 1, point.y, 1);
|
||||
int s_1_1 = l.getAgastScore(point.x, point.y, 1);
|
||||
int s_0_1 = l.getAgastScore(point.x - 1, point.y, 1);
|
||||
int s_0_2 = l.getAgastScore(point.x - 1, point.y + 1, 1);
|
||||
int s_1_2 = l.getAgastScore(point.x, point.y + 1, 1);
|
||||
int s_2_2 = l.getAgastScore(point.x + 1, point.y + 1, 1);
|
||||
float delta_x, delta_y;
|
||||
float max = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x, delta_y);
|
||||
|
||||
// store:
|
||||
keypoints.push_back(
|
||||
cv::KeyPoint((float(point.x) + delta_x) * l.scale() + l.offset(),
|
||||
(float(point.y) + delta_y) * l.scale() + l.offset(), basicSize_ * l.scale(), -1, max, i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not the last layer:
|
||||
for (size_t n = 0; n < num; n++)
|
||||
{
|
||||
const cv::Point2f& point = agastPoints.at(i)[n].pt;
|
||||
|
||||
// first check if it is a maximum:
|
||||
if (!isMax2D(i, (int)point.x, (int)point.y))
|
||||
continue;
|
||||
|
||||
// let's do the subpixel and float scale refinement:
|
||||
bool ismax=false;
|
||||
score = refine3D(i, (int)point.x, (int)point.y, x, y, scale, ismax);
|
||||
if (!ismax)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// finally store the detected keypoint:
|
||||
if (score > float(threshold_))
|
||||
{
|
||||
keypoints.push_back(cv::KeyPoint(x, y, basicSize_ * scale, -1, score, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// interpolated score access with recalculation when needed:
|
||||
inline int
|
||||
BriskScaleSpace::getScoreAbove(const int layer, const int x_layer, const int y_layer) const
|
||||
{
|
||||
CV_Assert(layer < layers_-1);
|
||||
const BriskLayer& l = pyramid_[layer + 1];
|
||||
if (layer % 2 == 0)
|
||||
{ // octave
|
||||
const int sixths_x = 4 * x_layer - 1;
|
||||
const int x_above = sixths_x / 6;
|
||||
const int sixths_y = 4 * y_layer - 1;
|
||||
const int y_above = sixths_y / 6;
|
||||
const int r_x = (sixths_x % 6);
|
||||
const int r_x_1 = 6 - r_x;
|
||||
const int r_y = (sixths_y % 6);
|
||||
const int r_y_1 = 6 - r_y;
|
||||
uchar score = 0xFF
|
||||
& ((r_x_1 * r_y_1 * l.getAgastScore(x_above, y_above, 1) + r_x * r_y_1
|
||||
* l.getAgastScore(x_above + 1, y_above, 1)
|
||||
+ r_x_1 * r_y * l.getAgastScore(x_above, y_above + 1, 1)
|
||||
+ r_x * r_y * l.getAgastScore(x_above + 1, y_above + 1, 1) + 18)
|
||||
/ 36);
|
||||
|
||||
return score;
|
||||
}
|
||||
else
|
||||
{ // intra
|
||||
const int eighths_x = 6 * x_layer - 1;
|
||||
const int x_above = eighths_x / 8;
|
||||
const int eighths_y = 6 * y_layer - 1;
|
||||
const int y_above = eighths_y / 8;
|
||||
const int r_x = (eighths_x % 8);
|
||||
const int r_x_1 = 8 - r_x;
|
||||
const int r_y = (eighths_y % 8);
|
||||
const int r_y_1 = 8 - r_y;
|
||||
uchar score = 0xFF
|
||||
& ((r_x_1 * r_y_1 * l.getAgastScore(x_above, y_above, 1) + r_x * r_y_1
|
||||
* l.getAgastScore(x_above + 1, y_above, 1)
|
||||
+ r_x_1 * r_y * l.getAgastScore(x_above, y_above + 1, 1)
|
||||
+ r_x * r_y * l.getAgastScore(x_above + 1, y_above + 1, 1) + 32)
|
||||
/ 64);
|
||||
return score;
|
||||
}
|
||||
}
|
||||
inline int
|
||||
BriskScaleSpace::getScoreBelow(const int layer, const int x_layer, const int y_layer) const
|
||||
{
|
||||
CV_Assert(layer);
|
||||
const BriskLayer& l = pyramid_[layer - 1];
|
||||
int sixth_x;
|
||||
int quarter_x;
|
||||
float xf;
|
||||
int sixth_y;
|
||||
int quarter_y;
|
||||
float yf;
|
||||
|
||||
// scaling:
|
||||
float offs;
|
||||
float area;
|
||||
int scaling;
|
||||
int scaling2;
|
||||
|
||||
if (layer % 2 == 0)
|
||||
{ // octave
|
||||
sixth_x = 8 * x_layer + 1;
|
||||
xf = float(sixth_x) / 6.0f;
|
||||
sixth_y = 8 * y_layer + 1;
|
||||
yf = float(sixth_y) / 6.0f;
|
||||
|
||||
// scaling:
|
||||
offs = 2.0f / 3.0f;
|
||||
area = 4.0f * offs * offs;
|
||||
scaling = (int)(4194304.0 / area);
|
||||
scaling2 = (int)(float(scaling) * area);
|
||||
}
|
||||
else
|
||||
{
|
||||
quarter_x = 6 * x_layer + 1;
|
||||
xf = float(quarter_x) / 4.0f;
|
||||
quarter_y = 6 * y_layer + 1;
|
||||
yf = float(quarter_y) / 4.0f;
|
||||
|
||||
// scaling:
|
||||
offs = 3.0f / 4.0f;
|
||||
area = 4.0f * offs * offs;
|
||||
scaling = (int)(4194304.0 / area);
|
||||
scaling2 = (int)(float(scaling) * area);
|
||||
}
|
||||
|
||||
// calculate borders
|
||||
const float x_1 = xf - offs;
|
||||
const float x1 = xf + offs;
|
||||
const float y_1 = yf - offs;
|
||||
const float y1 = yf + offs;
|
||||
|
||||
const int x_left = int(x_1 + 0.5);
|
||||
const int y_top = int(y_1 + 0.5);
|
||||
const int x_right = int(x1 + 0.5);
|
||||
const int y_bottom = int(y1 + 0.5);
|
||||
|
||||
// overlap area - multiplication factors:
|
||||
const float r_x_1 = float(x_left) - x_1 + 0.5f;
|
||||
const float r_y_1 = float(y_top) - y_1 + 0.5f;
|
||||
const float r_x1 = x1 - float(x_right) + 0.5f;
|
||||
const float r_y1 = y1 - float(y_bottom) + 0.5f;
|
||||
const int dx = x_right - x_left - 1;
|
||||
const int dy = y_bottom - y_top - 1;
|
||||
const int A = (int)((r_x_1 * r_y_1) * scaling);
|
||||
const int B = (int)((r_x1 * r_y_1) * scaling);
|
||||
const int C = (int)((r_x1 * r_y1) * scaling);
|
||||
const int D = (int)((r_x_1 * r_y1) * scaling);
|
||||
const int r_x_1_i = (int)(r_x_1 * scaling);
|
||||
const int r_y_1_i = (int)(r_y_1 * scaling);
|
||||
const int r_x1_i = (int)(r_x1 * scaling);
|
||||
const int r_y1_i = (int)(r_y1 * scaling);
|
||||
|
||||
// first row:
|
||||
int ret_val = A * int(l.getAgastScore(x_left, y_top, 1));
|
||||
for (int X = 1; X <= dx; X++)
|
||||
{
|
||||
ret_val += r_y_1_i * int(l.getAgastScore(x_left + X, y_top, 1));
|
||||
}
|
||||
ret_val += B * int(l.getAgastScore(x_left + dx + 1, y_top, 1));
|
||||
// middle ones:
|
||||
for (int Y = 1; Y <= dy; Y++)
|
||||
{
|
||||
ret_val += r_x_1_i * int(l.getAgastScore(x_left, y_top + Y, 1));
|
||||
|
||||
for (int X = 1; X <= dx; X++)
|
||||
{
|
||||
ret_val += int(l.getAgastScore(x_left + X, y_top + Y, 1)) * scaling;
|
||||
}
|
||||
ret_val += r_x1_i * int(l.getAgastScore(x_left + dx + 1, y_top + Y, 1));
|
||||
}
|
||||
// last row:
|
||||
ret_val += D * int(l.getAgastScore(x_left, y_top + dy + 1, 1));
|
||||
for (int X = 1; X <= dx; X++)
|
||||
{
|
||||
ret_val += r_y1_i * int(l.getAgastScore(x_left + X, y_top + dy + 1, 1));
|
||||
}
|
||||
ret_val += C * int(l.getAgastScore(x_left + dx + 1, y_top + dy + 1, 1));
|
||||
|
||||
return ((ret_val + scaling2 / 2) / scaling2);
|
||||
}
|
||||
|
||||
inline bool
|
||||
BriskScaleSpace::isMax2D(const int layer, const int x_layer, const int y_layer)
|
||||
{
|
||||
const cv::Mat& scores = pyramid_[layer].scores();
|
||||
const int scorescols = scores.cols;
|
||||
const uchar* data = scores.ptr() + y_layer * scorescols + x_layer;
|
||||
// decision tree:
|
||||
const uchar center = (*data);
|
||||
data--;
|
||||
const uchar s_10 = *data;
|
||||
if (center < s_10)
|
||||
return false;
|
||||
data += 2;
|
||||
const uchar s10 = *data;
|
||||
if (center < s10)
|
||||
return false;
|
||||
data -= (scorescols + 1);
|
||||
const uchar s0_1 = *data;
|
||||
if (center < s0_1)
|
||||
return false;
|
||||
data += 2 * scorescols;
|
||||
const uchar s01 = *data;
|
||||
if (center < s01)
|
||||
return false;
|
||||
data--;
|
||||
const uchar s_11 = *data;
|
||||
if (center < s_11)
|
||||
return false;
|
||||
data += 2;
|
||||
const uchar s11 = *data;
|
||||
if (center < s11)
|
||||
return false;
|
||||
data -= 2 * scorescols;
|
||||
const uchar s1_1 = *data;
|
||||
if (center < s1_1)
|
||||
return false;
|
||||
data -= 2;
|
||||
const uchar s_1_1 = *data;
|
||||
if (center < s_1_1)
|
||||
return false;
|
||||
|
||||
// reject neighbor maxima
|
||||
std::vector<int> delta;
|
||||
// put together a list of 2d-offsets to where the maximum is also reached
|
||||
if (center == s_1_1)
|
||||
{
|
||||
delta.push_back(-1);
|
||||
delta.push_back(-1);
|
||||
}
|
||||
if (center == s0_1)
|
||||
{
|
||||
delta.push_back(0);
|
||||
delta.push_back(-1);
|
||||
}
|
||||
if (center == s1_1)
|
||||
{
|
||||
delta.push_back(1);
|
||||
delta.push_back(-1);
|
||||
}
|
||||
if (center == s_10)
|
||||
{
|
||||
delta.push_back(-1);
|
||||
delta.push_back(0);
|
||||
}
|
||||
if (center == s10)
|
||||
{
|
||||
delta.push_back(1);
|
||||
delta.push_back(0);
|
||||
}
|
||||
if (center == s_11)
|
||||
{
|
||||
delta.push_back(-1);
|
||||
delta.push_back(1);
|
||||
}
|
||||
if (center == s01)
|
||||
{
|
||||
delta.push_back(0);
|
||||
delta.push_back(1);
|
||||
}
|
||||
if (center == s11)
|
||||
{
|
||||
delta.push_back(1);
|
||||
delta.push_back(1);
|
||||
}
|
||||
const unsigned int deltasize = (unsigned int)delta.size();
|
||||
if (deltasize != 0)
|
||||
{
|
||||
// in this case, we have to analyze the situation more carefully:
|
||||
// the values are gaussian blurred and then we really decide
|
||||
int smoothedcenter = 4 * center + 2 * (s_10 + s10 + s0_1 + s01) + s_1_1 + s1_1 + s_11 + s11;
|
||||
for (unsigned int i = 0; i < deltasize; i += 2)
|
||||
{
|
||||
data = scores.ptr() + (y_layer - 1 + delta[i + 1]) * scorescols + x_layer + delta[i] - 1;
|
||||
int othercenter = *data;
|
||||
data++;
|
||||
othercenter += 2 * (*data);
|
||||
data++;
|
||||
othercenter += *data;
|
||||
data += scorescols;
|
||||
othercenter += 2 * (*data);
|
||||
data--;
|
||||
othercenter += 4 * (*data);
|
||||
data--;
|
||||
othercenter += 2 * (*data);
|
||||
data += scorescols;
|
||||
othercenter += *data;
|
||||
data++;
|
||||
othercenter += 2 * (*data);
|
||||
data++;
|
||||
othercenter += *data;
|
||||
if (othercenter > smoothedcenter)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3D maximum refinement centered around (x_layer,y_layer)
|
||||
inline float
|
||||
BriskScaleSpace::refine3D(const int layer, const int x_layer, const int y_layer, float& x, float& y, float& scale,
|
||||
bool& ismax) const
|
||||
{
|
||||
ismax = true;
|
||||
const BriskLayer& thisLayer = pyramid_[layer];
|
||||
const int center = thisLayer.getAgastScore(x_layer, y_layer, 1);
|
||||
|
||||
// check and get above maximum:
|
||||
float delta_x_above = 0, delta_y_above = 0;
|
||||
float max_above = getScoreMaxAbove(layer, x_layer, y_layer, center, ismax, delta_x_above, delta_y_above);
|
||||
|
||||
if (!ismax)
|
||||
return 0.0f;
|
||||
|
||||
float max; // to be returned
|
||||
|
||||
if (layer % 2 == 0)
|
||||
{ // on octave
|
||||
// treat the patch below:
|
||||
float delta_x_below, delta_y_below;
|
||||
float max_below_float;
|
||||
int max_below = 0;
|
||||
if (layer == 0)
|
||||
{
|
||||
// guess the lower intra octave...
|
||||
const BriskLayer& l = pyramid_[0];
|
||||
int s_0_0 = l.getAgastScore_5_8(x_layer - 1, y_layer - 1, 1);
|
||||
max_below = s_0_0;
|
||||
int s_1_0 = l.getAgastScore_5_8(x_layer, y_layer - 1, 1);
|
||||
max_below = std::max(s_1_0, max_below);
|
||||
int s_2_0 = l.getAgastScore_5_8(x_layer + 1, y_layer - 1, 1);
|
||||
max_below = std::max(s_2_0, max_below);
|
||||
int s_2_1 = l.getAgastScore_5_8(x_layer + 1, y_layer, 1);
|
||||
max_below = std::max(s_2_1, max_below);
|
||||
int s_1_1 = l.getAgastScore_5_8(x_layer, y_layer, 1);
|
||||
max_below = std::max(s_1_1, max_below);
|
||||
int s_0_1 = l.getAgastScore_5_8(x_layer - 1, y_layer, 1);
|
||||
max_below = std::max(s_0_1, max_below);
|
||||
int s_0_2 = l.getAgastScore_5_8(x_layer - 1, y_layer + 1, 1);
|
||||
max_below = std::max(s_0_2, max_below);
|
||||
int s_1_2 = l.getAgastScore_5_8(x_layer, y_layer + 1, 1);
|
||||
max_below = std::max(s_1_2, max_below);
|
||||
int s_2_2 = l.getAgastScore_5_8(x_layer + 1, y_layer + 1, 1);
|
||||
max_below = std::max(s_2_2, max_below);
|
||||
|
||||
subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x_below, delta_y_below);
|
||||
max_below_float = (float)max_below;
|
||||
}
|
||||
else
|
||||
{
|
||||
max_below_float = getScoreMaxBelow(layer, x_layer, y_layer, center, ismax, delta_x_below, delta_y_below);
|
||||
if (!ismax)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get the patch on this layer:
|
||||
int s_0_0 = thisLayer.getAgastScore(x_layer - 1, y_layer - 1, 1);
|
||||
int s_1_0 = thisLayer.getAgastScore(x_layer, y_layer - 1, 1);
|
||||
int s_2_0 = thisLayer.getAgastScore(x_layer + 1, y_layer - 1, 1);
|
||||
int s_2_1 = thisLayer.getAgastScore(x_layer + 1, y_layer, 1);
|
||||
int s_1_1 = thisLayer.getAgastScore(x_layer, y_layer, 1);
|
||||
int s_0_1 = thisLayer.getAgastScore(x_layer - 1, y_layer, 1);
|
||||
int s_0_2 = thisLayer.getAgastScore(x_layer - 1, y_layer + 1, 1);
|
||||
int s_1_2 = thisLayer.getAgastScore(x_layer, y_layer + 1, 1);
|
||||
int s_2_2 = thisLayer.getAgastScore(x_layer + 1, y_layer + 1, 1);
|
||||
float delta_x_layer, delta_y_layer;
|
||||
float max_layer = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x_layer,
|
||||
delta_y_layer);
|
||||
|
||||
// calculate the relative scale (1D maximum):
|
||||
if (layer == 0)
|
||||
{
|
||||
scale = refine1D_2(max_below_float, std::max(float(center), max_layer), max_above, max);
|
||||
}
|
||||
else
|
||||
scale = refine1D(max_below_float, std::max(float(center), max_layer), max_above, max);
|
||||
|
||||
if (scale > 1.0)
|
||||
{
|
||||
// interpolate the position:
|
||||
const float r0 = (1.5f - scale) / .5f;
|
||||
const float r1 = 1.0f - r0;
|
||||
x = (r0 * delta_x_layer + r1 * delta_x_above + float(x_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
y = (r0 * delta_y_layer + r1 * delta_y_above + float(y_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layer == 0)
|
||||
{
|
||||
// interpolate the position:
|
||||
const float r0 = (scale - 0.5f) / 0.5f;
|
||||
const float r_1 = 1.0f - r0;
|
||||
x = r0 * delta_x_layer + r_1 * delta_x_below + float(x_layer);
|
||||
y = r0 * delta_y_layer + r_1 * delta_y_below + float(y_layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// interpolate the position:
|
||||
const float r0 = (scale - 0.75f) / 0.25f;
|
||||
const float r_1 = 1.0f - r0;
|
||||
x = (r0 * delta_x_layer + r_1 * delta_x_below + float(x_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
y = (r0 * delta_y_layer + r_1 * delta_y_below + float(y_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// on intra
|
||||
// check the patch below:
|
||||
float delta_x_below, delta_y_below;
|
||||
float max_below = getScoreMaxBelow(layer, x_layer, y_layer, center, ismax, delta_x_below, delta_y_below);
|
||||
if (!ismax)
|
||||
return 0.0f;
|
||||
|
||||
// get the patch on this layer:
|
||||
int s_0_0 = thisLayer.getAgastScore(x_layer - 1, y_layer - 1, 1);
|
||||
int s_1_0 = thisLayer.getAgastScore(x_layer, y_layer - 1, 1);
|
||||
int s_2_0 = thisLayer.getAgastScore(x_layer + 1, y_layer - 1, 1);
|
||||
int s_2_1 = thisLayer.getAgastScore(x_layer + 1, y_layer, 1);
|
||||
int s_1_1 = thisLayer.getAgastScore(x_layer, y_layer, 1);
|
||||
int s_0_1 = thisLayer.getAgastScore(x_layer - 1, y_layer, 1);
|
||||
int s_0_2 = thisLayer.getAgastScore(x_layer - 1, y_layer + 1, 1);
|
||||
int s_1_2 = thisLayer.getAgastScore(x_layer, y_layer + 1, 1);
|
||||
int s_2_2 = thisLayer.getAgastScore(x_layer + 1, y_layer + 1, 1);
|
||||
float delta_x_layer, delta_y_layer;
|
||||
float max_layer = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, delta_x_layer,
|
||||
delta_y_layer);
|
||||
|
||||
// calculate the relative scale (1D maximum):
|
||||
scale = refine1D_1(max_below, std::max(float(center), max_layer), max_above, max);
|
||||
if (scale > 1.0)
|
||||
{
|
||||
// interpolate the position:
|
||||
const float r0 = 4.0f - scale * 3.0f;
|
||||
const float r1 = 1.0f - r0;
|
||||
x = (r0 * delta_x_layer + r1 * delta_x_above + float(x_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
y = (r0 * delta_y_layer + r1 * delta_y_above + float(y_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// interpolate the position:
|
||||
const float r0 = scale * 3.0f - 2.0f;
|
||||
const float r_1 = 1.0f - r0;
|
||||
x = (r0 * delta_x_layer + r_1 * delta_x_below + float(x_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
y = (r0 * delta_y_layer + r_1 * delta_y_below + float(y_layer)) * thisLayer.scale() + thisLayer.offset();
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the absolute scale:
|
||||
scale *= thisLayer.scale();
|
||||
|
||||
// that's it, return the refined maximum:
|
||||
return max;
|
||||
}
|
||||
|
||||
// return the maximum of score patches above or below
|
||||
inline float
|
||||
BriskScaleSpace::getScoreMaxAbove(const int layer, const int x_layer, const int y_layer, const int threshold,
|
||||
bool& ismax, float& dx, float& dy) const
|
||||
{
|
||||
|
||||
ismax = false;
|
||||
// relevant floating point coordinates
|
||||
float x_1;
|
||||
float x1;
|
||||
float y_1;
|
||||
float y1;
|
||||
|
||||
// the layer above
|
||||
CV_Assert(layer + 1 < layers_);
|
||||
const BriskLayer& layerAbove = pyramid_[layer + 1];
|
||||
|
||||
if (layer % 2 == 0)
|
||||
{
|
||||
// octave
|
||||
x_1 = float(4 * (x_layer) - 1 - 2) / 6.0f;
|
||||
x1 = float(4 * (x_layer) - 1 + 2) / 6.0f;
|
||||
y_1 = float(4 * (y_layer) - 1 - 2) / 6.0f;
|
||||
y1 = float(4 * (y_layer) - 1 + 2) / 6.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// intra
|
||||
x_1 = float(6 * (x_layer) - 1 - 3) / 8.0f;
|
||||
x1 = float(6 * (x_layer) - 1 + 3) / 8.0f;
|
||||
y_1 = float(6 * (y_layer) - 1 - 3) / 8.0f;
|
||||
y1 = float(6 * (y_layer) - 1 + 3) / 8.0f;
|
||||
}
|
||||
|
||||
// check the first row
|
||||
int max_x = (int)x_1 + 1;
|
||||
int max_y = (int)y_1 + 1;
|
||||
float tmp_max;
|
||||
float maxval = (float)layerAbove.getAgastScore(x_1, y_1, 1);
|
||||
if (maxval > threshold)
|
||||
return 0;
|
||||
for (int x = (int)x_1 + 1; x <= int(x1); x++)
|
||||
{
|
||||
tmp_max = (float)layerAbove.getAgastScore(float(x), y_1, 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = x;
|
||||
}
|
||||
}
|
||||
tmp_max = (float)layerAbove.getAgastScore(x1, y_1, 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = int(x1);
|
||||
}
|
||||
|
||||
// middle rows
|
||||
for (int y = (int)y_1 + 1; y <= int(y1); y++)
|
||||
{
|
||||
tmp_max = (float)layerAbove.getAgastScore(x_1, float(y), 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = int(x_1 + 1);
|
||||
max_y = y;
|
||||
}
|
||||
for (int x = (int)x_1 + 1; x <= int(x1); x++)
|
||||
{
|
||||
tmp_max = (float)layerAbove.getAgastScore(x, y, 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = x;
|
||||
max_y = y;
|
||||
}
|
||||
}
|
||||
tmp_max = (float)layerAbove.getAgastScore(x1, float(y), 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = int(x1);
|
||||
max_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// bottom row
|
||||
tmp_max = (float)layerAbove.getAgastScore(x_1, y1, 1);
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = int(x_1 + 1);
|
||||
max_y = int(y1);
|
||||
}
|
||||
for (int x = (int)x_1 + 1; x <= int(x1); x++)
|
||||
{
|
||||
tmp_max = (float)layerAbove.getAgastScore(float(x), y1, 1);
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = x;
|
||||
max_y = int(y1);
|
||||
}
|
||||
}
|
||||
tmp_max = (float)layerAbove.getAgastScore(x1, y1, 1);
|
||||
if (tmp_max > maxval)
|
||||
{
|
||||
maxval = tmp_max;
|
||||
max_x = int(x1);
|
||||
max_y = int(y1);
|
||||
}
|
||||
|
||||
//find dx/dy:
|
||||
int s_0_0 = layerAbove.getAgastScore(max_x - 1, max_y - 1, 1);
|
||||
int s_1_0 = layerAbove.getAgastScore(max_x, max_y - 1, 1);
|
||||
int s_2_0 = layerAbove.getAgastScore(max_x + 1, max_y - 1, 1);
|
||||
int s_2_1 = layerAbove.getAgastScore(max_x + 1, max_y, 1);
|
||||
int s_1_1 = layerAbove.getAgastScore(max_x, max_y, 1);
|
||||
int s_0_1 = layerAbove.getAgastScore(max_x - 1, max_y, 1);
|
||||
int s_0_2 = layerAbove.getAgastScore(max_x - 1, max_y + 1, 1);
|
||||
int s_1_2 = layerAbove.getAgastScore(max_x, max_y + 1, 1);
|
||||
int s_2_2 = layerAbove.getAgastScore(max_x + 1, max_y + 1, 1);
|
||||
float dx_1, dy_1;
|
||||
float refined_max = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, dx_1, dy_1);
|
||||
|
||||
// calculate dx/dy in above coordinates
|
||||
float real_x = float(max_x) + dx_1;
|
||||
float real_y = float(max_y) + dy_1;
|
||||
bool returnrefined = true;
|
||||
if (layer % 2 == 0)
|
||||
{
|
||||
dx = (real_x * 6.0f + 1.0f) / 4.0f - float(x_layer);
|
||||
dy = (real_y * 6.0f + 1.0f) / 4.0f - float(y_layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
dx = (real_x * 8.0f + 1.0f) / 6.0f - float(x_layer);
|
||||
dy = (real_y * 8.0f + 1.0f) / 6.0f - float(y_layer);
|
||||
}
|
||||
|
||||
// saturate
|
||||
if (dx > 1.0f)
|
||||
{
|
||||
dx = 1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
if (dx < -1.0f)
|
||||
{
|
||||
dx = -1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
if (dy > 1.0f)
|
||||
{
|
||||
dy = 1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
if (dy < -1.0f)
|
||||
{
|
||||
dy = -1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
|
||||
// done and ok.
|
||||
ismax = true;
|
||||
if (returnrefined)
|
||||
{
|
||||
return std::max(refined_max, maxval);
|
||||
}
|
||||
return maxval;
|
||||
}
|
||||
|
||||
inline float
|
||||
BriskScaleSpace::getScoreMaxBelow(const int layer, const int x_layer, const int y_layer, const int threshold,
|
||||
bool& ismax, float& dx, float& dy) const
|
||||
{
|
||||
ismax = false;
|
||||
|
||||
// relevant floating point coordinates
|
||||
float x_1;
|
||||
float x1;
|
||||
float y_1;
|
||||
float y1;
|
||||
|
||||
if (layer % 2 == 0)
|
||||
{
|
||||
// octave
|
||||
x_1 = float(8 * (x_layer) + 1 - 4) / 6.0f;
|
||||
x1 = float(8 * (x_layer) + 1 + 4) / 6.0f;
|
||||
y_1 = float(8 * (y_layer) + 1 - 4) / 6.0f;
|
||||
y1 = float(8 * (y_layer) + 1 + 4) / 6.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
x_1 = float(6 * (x_layer) + 1 - 3) / 4.0f;
|
||||
x1 = float(6 * (x_layer) + 1 + 3) / 4.0f;
|
||||
y_1 = float(6 * (y_layer) + 1 - 3) / 4.0f;
|
||||
y1 = float(6 * (y_layer) + 1 + 3) / 4.0f;
|
||||
}
|
||||
|
||||
// the layer below
|
||||
CV_Assert(layer > 0);
|
||||
const BriskLayer& layerBelow = pyramid_[layer - 1];
|
||||
|
||||
// check the first row
|
||||
int max_x = (int)x_1 + 1;
|
||||
int max_y = (int)y_1 + 1;
|
||||
float tmp_max;
|
||||
float max = (float)layerBelow.getAgastScore(x_1, y_1, 1);
|
||||
if (max > threshold)
|
||||
return 0;
|
||||
for (int x = (int)x_1 + 1; x <= int(x1); x++)
|
||||
{
|
||||
tmp_max = (float)layerBelow.getAgastScore(float(x), y_1, 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = x;
|
||||
}
|
||||
}
|
||||
tmp_max = (float)layerBelow.getAgastScore(x1, y_1, 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = int(x1);
|
||||
}
|
||||
|
||||
// middle rows
|
||||
for (int y = (int)y_1 + 1; y <= int(y1); y++)
|
||||
{
|
||||
tmp_max = (float)layerBelow.getAgastScore(x_1, float(y), 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = int(x_1 + 1);
|
||||
max_y = y;
|
||||
}
|
||||
for (int x = (int)x_1 + 1; x <= int(x1); x++)
|
||||
{
|
||||
tmp_max = (float)layerBelow.getAgastScore(x, y, 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max == max)
|
||||
{
|
||||
const int t1 = 2
|
||||
* (layerBelow.getAgastScore(x - 1, y, 1) + layerBelow.getAgastScore(x + 1, y, 1)
|
||||
+ layerBelow.getAgastScore(x, y + 1, 1) + layerBelow.getAgastScore(x, y - 1, 1))
|
||||
+ (layerBelow.getAgastScore(x + 1, y + 1, 1) + layerBelow.getAgastScore(x - 1, y + 1, 1)
|
||||
+ layerBelow.getAgastScore(x + 1, y - 1, 1) + layerBelow.getAgastScore(x - 1, y - 1, 1));
|
||||
const int t2 = 2
|
||||
* (layerBelow.getAgastScore(max_x - 1, max_y, 1) + layerBelow.getAgastScore(max_x + 1, max_y, 1)
|
||||
+ layerBelow.getAgastScore(max_x, max_y + 1, 1) + layerBelow.getAgastScore(max_x, max_y - 1, 1))
|
||||
+ (layerBelow.getAgastScore(max_x + 1, max_y + 1, 1) + layerBelow.getAgastScore(max_x - 1,
|
||||
max_y + 1, 1)
|
||||
+ layerBelow.getAgastScore(max_x + 1, max_y - 1, 1)
|
||||
+ layerBelow.getAgastScore(max_x - 1, max_y - 1, 1));
|
||||
if (t1 > t2)
|
||||
{
|
||||
max_x = x;
|
||||
max_y = y;
|
||||
}
|
||||
}
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = x;
|
||||
max_y = y;
|
||||
}
|
||||
}
|
||||
tmp_max = (float)layerBelow.getAgastScore(x1, float(y), 1);
|
||||
if (tmp_max > threshold)
|
||||
return 0;
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = int(x1);
|
||||
max_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// bottom row
|
||||
tmp_max = (float)layerBelow.getAgastScore(x_1, y1, 1);
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = int(x_1 + 1);
|
||||
max_y = int(y1);
|
||||
}
|
||||
for (int x = (int)x_1 + 1; x <= int(x1); x++)
|
||||
{
|
||||
tmp_max = (float)layerBelow.getAgastScore(float(x), y1, 1);
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = x;
|
||||
max_y = int(y1);
|
||||
}
|
||||
}
|
||||
tmp_max = (float)layerBelow.getAgastScore(x1, y1, 1);
|
||||
if (tmp_max > max)
|
||||
{
|
||||
max = tmp_max;
|
||||
max_x = int(x1);
|
||||
max_y = int(y1);
|
||||
}
|
||||
|
||||
//find dx/dy:
|
||||
int s_0_0 = layerBelow.getAgastScore(max_x - 1, max_y - 1, 1);
|
||||
int s_1_0 = layerBelow.getAgastScore(max_x, max_y - 1, 1);
|
||||
int s_2_0 = layerBelow.getAgastScore(max_x + 1, max_y - 1, 1);
|
||||
int s_2_1 = layerBelow.getAgastScore(max_x + 1, max_y, 1);
|
||||
int s_1_1 = layerBelow.getAgastScore(max_x, max_y, 1);
|
||||
int s_0_1 = layerBelow.getAgastScore(max_x - 1, max_y, 1);
|
||||
int s_0_2 = layerBelow.getAgastScore(max_x - 1, max_y + 1, 1);
|
||||
int s_1_2 = layerBelow.getAgastScore(max_x, max_y + 1, 1);
|
||||
int s_2_2 = layerBelow.getAgastScore(max_x + 1, max_y + 1, 1);
|
||||
float dx_1, dy_1;
|
||||
float refined_max = subpixel2D(s_0_0, s_0_1, s_0_2, s_1_0, s_1_1, s_1_2, s_2_0, s_2_1, s_2_2, dx_1, dy_1);
|
||||
|
||||
// calculate dx/dy in above coordinates
|
||||
float real_x = float(max_x) + dx_1;
|
||||
float real_y = float(max_y) + dy_1;
|
||||
bool returnrefined = true;
|
||||
if (layer % 2 == 0)
|
||||
{
|
||||
dx = (float)((real_x * 6.0 + 1.0) / 8.0) - float(x_layer);
|
||||
dy = (float)((real_y * 6.0 + 1.0) / 8.0) - float(y_layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
dx = (float)((real_x * 4.0 - 1.0) / 6.0) - float(x_layer);
|
||||
dy = (float)((real_y * 4.0 - 1.0) / 6.0) - float(y_layer);
|
||||
}
|
||||
|
||||
// saturate
|
||||
if (dx > 1.0)
|
||||
{
|
||||
dx = 1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
if (dx < -1.0f)
|
||||
{
|
||||
dx = -1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
if (dy > 1.0f)
|
||||
{
|
||||
dy = 1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
if (dy < -1.0f)
|
||||
{
|
||||
dy = -1.0f;
|
||||
returnrefined = false;
|
||||
}
|
||||
|
||||
// done and ok.
|
||||
ismax = true;
|
||||
if (returnrefined)
|
||||
{
|
||||
return std::max(refined_max, max);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
inline float
|
||||
BriskScaleSpace::refine1D(const float s_05, const float s0, const float s05, float& max) const
|
||||
{
|
||||
int i_05 = int(1024.0 * s_05 + 0.5);
|
||||
int i0 = int(1024.0 * s0 + 0.5);
|
||||
int i05 = int(1024.0 * s05 + 0.5);
|
||||
|
||||
// 16.0000 -24.0000 8.0000
|
||||
// -40.0000 54.0000 -14.0000
|
||||
// 24.0000 -27.0000 6.0000
|
||||
|
||||
int three_a = 16 * i_05 - 24 * i0 + 8 * i05;
|
||||
// second derivative must be negative:
|
||||
if (three_a >= 0)
|
||||
{
|
||||
if (s0 >= s_05 && s0 >= s05)
|
||||
{
|
||||
max = s0;
|
||||
return 1.0f;
|
||||
}
|
||||
if (s_05 >= s0 && s_05 >= s05)
|
||||
{
|
||||
max = s_05;
|
||||
return 0.75f;
|
||||
}
|
||||
if (s05 >= s0 && s05 >= s_05)
|
||||
{
|
||||
max = s05;
|
||||
return 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
int three_b = -40 * i_05 + 54 * i0 - 14 * i05;
|
||||
// calculate max location:
|
||||
float ret_val = -float(three_b) / float(2 * three_a);
|
||||
// saturate and return
|
||||
if (ret_val < 0.75)
|
||||
ret_val = 0.75;
|
||||
else if (ret_val > 1.5)
|
||||
ret_val = 1.5; // allow to be slightly off bounds ...?
|
||||
int three_c = +24 * i_05 - 27 * i0 + 6 * i05;
|
||||
max = float(three_c) + float(three_a) * ret_val * ret_val + float(three_b) * ret_val;
|
||||
max /= 3072.0f;
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
inline float
|
||||
BriskScaleSpace::refine1D_1(const float s_05, const float s0, const float s05, float& max) const
|
||||
{
|
||||
int i_05 = int(1024.0 * s_05 + 0.5);
|
||||
int i0 = int(1024.0 * s0 + 0.5);
|
||||
int i05 = int(1024.0 * s05 + 0.5);
|
||||
|
||||
// 4.5000 -9.0000 4.5000
|
||||
//-10.5000 18.0000 -7.5000
|
||||
// 6.0000 -8.0000 3.0000
|
||||
|
||||
int two_a = 9 * i_05 - 18 * i0 + 9 * i05;
|
||||
// second derivative must be negative:
|
||||
if (two_a >= 0)
|
||||
{
|
||||
if (s0 >= s_05 && s0 >= s05)
|
||||
{
|
||||
max = s0;
|
||||
return 1.0f;
|
||||
}
|
||||
if (s_05 >= s0 && s_05 >= s05)
|
||||
{
|
||||
max = s_05;
|
||||
return 0.6666666666666666666666666667f;
|
||||
}
|
||||
if (s05 >= s0 && s05 >= s_05)
|
||||
{
|
||||
max = s05;
|
||||
return 1.3333333333333333333333333333f;
|
||||
}
|
||||
}
|
||||
|
||||
int two_b = -21 * i_05 + 36 * i0 - 15 * i05;
|
||||
// calculate max location:
|
||||
float ret_val = -float(two_b) / float(2 * two_a);
|
||||
// saturate and return
|
||||
if (ret_val < 0.6666666666666666666666666667f)
|
||||
ret_val = 0.666666666666666666666666667f;
|
||||
else if (ret_val > 1.33333333333333333333333333f)
|
||||
ret_val = 1.333333333333333333333333333f;
|
||||
int two_c = +12 * i_05 - 16 * i0 + 6 * i05;
|
||||
max = float(two_c) + float(two_a) * ret_val * ret_val + float(two_b) * ret_val;
|
||||
max /= 2048.0f;
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
inline float
|
||||
BriskScaleSpace::refine1D_2(const float s_05, const float s0, const float s05, float& max) const
|
||||
{
|
||||
int i_05 = int(1024.0 * s_05 + 0.5);
|
||||
int i0 = int(1024.0 * s0 + 0.5);
|
||||
int i05 = int(1024.0 * s05 + 0.5);
|
||||
|
||||
// 18.0000 -30.0000 12.0000
|
||||
// -45.0000 65.0000 -20.0000
|
||||
// 27.0000 -30.0000 8.0000
|
||||
|
||||
int a = 2 * i_05 - 4 * i0 + 2 * i05;
|
||||
// second derivative must be negative:
|
||||
if (a >= 0)
|
||||
{
|
||||
if (s0 >= s_05 && s0 >= s05)
|
||||
{
|
||||
max = s0;
|
||||
return 1.0f;
|
||||
}
|
||||
if (s_05 >= s0 && s_05 >= s05)
|
||||
{
|
||||
max = s_05;
|
||||
return 0.7f;
|
||||
}
|
||||
if (s05 >= s0 && s05 >= s_05)
|
||||
{
|
||||
max = s05;
|
||||
return 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
int b = -5 * i_05 + 8 * i0 - 3 * i05;
|
||||
// calculate max location:
|
||||
float ret_val = -float(b) / float(2 * a);
|
||||
// saturate and return
|
||||
if (ret_val < 0.7f)
|
||||
ret_val = 0.7f;
|
||||
else if (ret_val > 1.5f)
|
||||
ret_val = 1.5f; // allow to be slightly off bounds ...?
|
||||
int c = +3 * i_05 - 3 * i0 + 1 * i05;
|
||||
max = float(c) + float(a) * ret_val * ret_val + float(b) * ret_val;
|
||||
max /= 1024;
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
inline float
|
||||
BriskScaleSpace::subpixel2D(const int s_0_0, const int s_0_1, const int s_0_2, const int s_1_0, const int s_1_1,
|
||||
const int s_1_2, const int s_2_0, const int s_2_1, const int s_2_2, float& delta_x,
|
||||
float& delta_y) const
|
||||
{
|
||||
|
||||
// the coefficients of the 2d quadratic function least-squares fit:
|
||||
int tmp1 = s_0_0 + s_0_2 - 2 * s_1_1 + s_2_0 + s_2_2;
|
||||
int coeff1 = 3 * (tmp1 + s_0_1 - ((s_1_0 + s_1_2) << 1) + s_2_1);
|
||||
int coeff2 = 3 * (tmp1 - ((s_0_1 + s_2_1) << 1) + s_1_0 + s_1_2);
|
||||
int tmp2 = s_0_2 - s_2_0;
|
||||
int tmp3 = (s_0_0 + tmp2 - s_2_2);
|
||||
int tmp4 = tmp3 - 2 * tmp2;
|
||||
int coeff3 = -3 * (tmp3 + s_0_1 - s_2_1);
|
||||
int coeff4 = -3 * (tmp4 + s_1_0 - s_1_2);
|
||||
int coeff5 = (s_0_0 - s_0_2 - s_2_0 + s_2_2) << 2;
|
||||
int coeff6 = -(s_0_0 + s_0_2 - ((s_1_0 + s_0_1 + s_1_2 + s_2_1) << 1) - 5 * s_1_1 + s_2_0 + s_2_2) << 1;
|
||||
|
||||
// 2nd derivative test:
|
||||
int H_det = 4 * coeff1 * coeff2 - coeff5 * coeff5;
|
||||
|
||||
if (H_det == 0)
|
||||
{
|
||||
delta_x = 0.0f;
|
||||
delta_y = 0.0f;
|
||||
return float(coeff6) / 18.0f;
|
||||
}
|
||||
|
||||
if (!(H_det > 0 && coeff1 < 0))
|
||||
{
|
||||
// The maximum must be at the one of the 4 patch corners.
|
||||
int tmp_max = coeff3 + coeff4 + coeff5;
|
||||
delta_x = 1.0f;
|
||||
delta_y = 1.0f;
|
||||
|
||||
int tmp = -coeff3 + coeff4 - coeff5;
|
||||
if (tmp > tmp_max)
|
||||
{
|
||||
tmp_max = tmp;
|
||||
delta_x = -1.0f;
|
||||
delta_y = 1.0f;
|
||||
}
|
||||
tmp = coeff3 - coeff4 - coeff5;
|
||||
if (tmp > tmp_max)
|
||||
{
|
||||
tmp_max = tmp;
|
||||
delta_x = 1.0f;
|
||||
delta_y = -1.0f;
|
||||
}
|
||||
tmp = -coeff3 - coeff4 + coeff5;
|
||||
if (tmp > tmp_max)
|
||||
{
|
||||
tmp_max = tmp;
|
||||
delta_x = -1.0f;
|
||||
delta_y = -1.0f;
|
||||
}
|
||||
return float(tmp_max + coeff1 + coeff2 + coeff6) / 18.0f;
|
||||
}
|
||||
|
||||
// this is hopefully the normal outcome of the Hessian test
|
||||
delta_x = float(2 * coeff2 * coeff3 - coeff4 * coeff5) / float(-H_det);
|
||||
delta_y = float(2 * coeff1 * coeff4 - coeff3 * coeff5) / float(-H_det);
|
||||
// TODO: this is not correct, but easy, so perform a real boundary maximum search:
|
||||
bool tx = false;
|
||||
bool tx_ = false;
|
||||
bool ty = false;
|
||||
bool ty_ = false;
|
||||
if (delta_x > 1.0)
|
||||
tx = true;
|
||||
else if (delta_x < -1.0)
|
||||
tx_ = true;
|
||||
if (delta_y > 1.0)
|
||||
ty = true;
|
||||
if (delta_y < -1.0)
|
||||
ty_ = true;
|
||||
|
||||
if (tx || tx_ || ty || ty_)
|
||||
{
|
||||
// get two candidates:
|
||||
float delta_x1 = 0.0f, delta_x2 = 0.0f, delta_y1 = 0.0f, delta_y2 = 0.0f;
|
||||
if (tx)
|
||||
{
|
||||
delta_x1 = 1.0f;
|
||||
delta_y1 = -float(coeff4 + coeff5) / float(2 * coeff2);
|
||||
if (delta_y1 > 1.0f)
|
||||
delta_y1 = 1.0f;
|
||||
else if (delta_y1 < -1.0f)
|
||||
delta_y1 = -1.0f;
|
||||
}
|
||||
else if (tx_)
|
||||
{
|
||||
delta_x1 = -1.0f;
|
||||
delta_y1 = -float(coeff4 - coeff5) / float(2 * coeff2);
|
||||
if (delta_y1 > 1.0f)
|
||||
delta_y1 = 1.0f;
|
||||
else if (delta_y1 < -1.0)
|
||||
delta_y1 = -1.0f;
|
||||
}
|
||||
if (ty)
|
||||
{
|
||||
delta_y2 = 1.0f;
|
||||
delta_x2 = -float(coeff3 + coeff5) / float(2 * coeff1);
|
||||
if (delta_x2 > 1.0f)
|
||||
delta_x2 = 1.0f;
|
||||
else if (delta_x2 < -1.0f)
|
||||
delta_x2 = -1.0f;
|
||||
}
|
||||
else if (ty_)
|
||||
{
|
||||
delta_y2 = -1.0f;
|
||||
delta_x2 = -float(coeff3 - coeff5) / float(2 * coeff1);
|
||||
if (delta_x2 > 1.0f)
|
||||
delta_x2 = 1.0f;
|
||||
else if (delta_x2 < -1.0f)
|
||||
delta_x2 = -1.0f;
|
||||
}
|
||||
// insert both options for evaluation which to pick
|
||||
float max1 = (coeff1 * delta_x1 * delta_x1 + coeff2 * delta_y1 * delta_y1 + coeff3 * delta_x1 + coeff4 * delta_y1
|
||||
+ coeff5 * delta_x1 * delta_y1 + coeff6)
|
||||
/ 18.0f;
|
||||
float max2 = (coeff1 * delta_x2 * delta_x2 + coeff2 * delta_y2 * delta_y2 + coeff3 * delta_x2 + coeff4 * delta_y2
|
||||
+ coeff5 * delta_x2 * delta_y2 + coeff6)
|
||||
/ 18.0f;
|
||||
if (max1 > max2)
|
||||
{
|
||||
delta_x = delta_x1;
|
||||
delta_y = delta_y1;
|
||||
return max1;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta_x = delta_x2;
|
||||
delta_y = delta_y2;
|
||||
return max2;
|
||||
}
|
||||
}
|
||||
|
||||
// this is the case of the maximum inside the boundaries:
|
||||
return (coeff1 * delta_x * delta_x + coeff2 * delta_y * delta_y + coeff3 * delta_x + coeff4 * delta_y
|
||||
+ coeff5 * delta_x * delta_y + coeff6)
|
||||
/ 18.0f;
|
||||
}
|
||||
|
||||
// construct a layer
|
||||
BriskLayer::BriskLayer(const cv::Mat& img_in, float scale_in, float offset_in)
|
||||
{
|
||||
img_ = img_in;
|
||||
scores_ = cv::Mat_<uchar>::zeros(img_in.rows, img_in.cols);
|
||||
// attention: this means that the passed image reference must point to persistent memory
|
||||
scale_ = scale_in;
|
||||
offset_ = offset_in;
|
||||
// create an agast detector
|
||||
oast_9_16_ = AgastFeatureDetector::create(1, false, AgastFeatureDetector::OAST_9_16);
|
||||
makeAgastOffsets(pixel_5_8_, (int)img_.step, AgastFeatureDetector::AGAST_5_8);
|
||||
makeAgastOffsets(pixel_9_16_, (int)img_.step, AgastFeatureDetector::OAST_9_16);
|
||||
}
|
||||
// derive a layer
|
||||
BriskLayer::BriskLayer(const BriskLayer& layer, int mode)
|
||||
{
|
||||
if (mode == CommonParams::HALFSAMPLE)
|
||||
{
|
||||
img_.create(layer.img().rows / 2, layer.img().cols / 2, CV_8U);
|
||||
halfsample(layer.img(), img_);
|
||||
scale_ = layer.scale() * 2;
|
||||
offset_ = 0.5f * scale_ - 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
img_.create(2 * (layer.img().rows / 3), 2 * (layer.img().cols / 3), CV_8U);
|
||||
twothirdsample(layer.img(), img_);
|
||||
scale_ = layer.scale() * 1.5f;
|
||||
offset_ = 0.5f * scale_ - 0.5f;
|
||||
}
|
||||
scores_ = cv::Mat::zeros(img_.rows, img_.cols, CV_8U);
|
||||
oast_9_16_ = AgastFeatureDetector::create(1, false, AgastFeatureDetector::OAST_9_16);
|
||||
makeAgastOffsets(pixel_5_8_, (int)img_.step, AgastFeatureDetector::AGAST_5_8);
|
||||
makeAgastOffsets(pixel_9_16_, (int)img_.step, AgastFeatureDetector::OAST_9_16);
|
||||
}
|
||||
|
||||
// Agast
|
||||
// wraps the agast class
|
||||
void
|
||||
BriskLayer::getAgastPoints(int threshold, std::vector<KeyPoint>& keypoints)
|
||||
{
|
||||
oast_9_16_->setThreshold(threshold);
|
||||
oast_9_16_->detect(img_, keypoints);
|
||||
|
||||
// also write scores
|
||||
const size_t num = keypoints.size();
|
||||
|
||||
for (size_t i = 0; i < num; i++)
|
||||
scores_((int)keypoints[i].pt.y, (int)keypoints[i].pt.x) = saturate_cast<uchar>(keypoints[i].response);
|
||||
}
|
||||
|
||||
inline int
|
||||
BriskLayer::getAgastScore(int x, int y, int threshold) const
|
||||
{
|
||||
if (x < 3 || y < 3)
|
||||
return 0;
|
||||
if (x >= img_.cols - 3 || y >= img_.rows - 3)
|
||||
return 0;
|
||||
uchar& score = (uchar&)scores_(y, x);
|
||||
if (score > 2)
|
||||
{
|
||||
return score;
|
||||
}
|
||||
score = (uchar)agast_cornerScore<AgastFeatureDetector::OAST_9_16>(&img_.at<uchar>(y, x), pixel_9_16_, threshold - 1);
|
||||
if (score < threshold)
|
||||
score = 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
inline int
|
||||
BriskLayer::getAgastScore_5_8(int x, int y, int threshold) const
|
||||
{
|
||||
if (x < 2 || y < 2)
|
||||
return 0;
|
||||
if (x >= img_.cols - 2 || y >= img_.rows - 2)
|
||||
return 0;
|
||||
int score = agast_cornerScore<AgastFeatureDetector::AGAST_5_8>(&img_.at<uchar>(y, x), pixel_5_8_, threshold - 1);
|
||||
if (score < threshold)
|
||||
score = 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
inline int
|
||||
BriskLayer::getAgastScore(float xf, float yf, int threshold_in, float scale_in) const
|
||||
{
|
||||
if (scale_in <= 1.0f)
|
||||
{
|
||||
// just do an interpolation inside the layer
|
||||
const int x = int(xf);
|
||||
const float rx1 = xf - float(x);
|
||||
const float rx = 1.0f - rx1;
|
||||
const int y = int(yf);
|
||||
const float ry1 = yf - float(y);
|
||||
const float ry = 1.0f - ry1;
|
||||
|
||||
return (uchar)(rx * ry * getAgastScore(x, y, threshold_in) + rx1 * ry * getAgastScore(x + 1, y, threshold_in)
|
||||
+ rx * ry1 * getAgastScore(x, y + 1, threshold_in) + rx1 * ry1 * getAgastScore(x + 1, y + 1, threshold_in));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this means we overlap area smoothing
|
||||
const float halfscale = scale_in / 2.0f;
|
||||
// get the scores first:
|
||||
for (int x = int(xf - halfscale); x <= int(xf + halfscale + 1.0f); x++)
|
||||
{
|
||||
for (int y = int(yf - halfscale); y <= int(yf + halfscale + 1.0f); y++)
|
||||
{
|
||||
getAgastScore(x, y, threshold_in);
|
||||
}
|
||||
}
|
||||
// get the smoothed value
|
||||
return value(scores_, xf, yf, scale_in);
|
||||
}
|
||||
}
|
||||
|
||||
// access gray values (smoothed/interpolated)
|
||||
inline int
|
||||
BriskLayer::value(const cv::Mat& mat, float xf, float yf, float scale_in) const
|
||||
{
|
||||
CV_Assert(!mat.empty());
|
||||
// get the position
|
||||
const int x = cvFloor(xf);
|
||||
const int y = cvFloor(yf);
|
||||
const cv::Mat& image = mat;
|
||||
const int& imagecols = image.cols;
|
||||
|
||||
// get the sigma_half:
|
||||
const float sigma_half = scale_in / 2;
|
||||
const float area = 4.0f * sigma_half * sigma_half;
|
||||
// calculate output:
|
||||
int ret_val;
|
||||
if (sigma_half < 0.5)
|
||||
{
|
||||
//interpolation multipliers:
|
||||
const int r_x = (int)((xf - x) * 1024);
|
||||
const int r_y = (int)((yf - y) * 1024);
|
||||
const int r_x_1 = (1024 - r_x);
|
||||
const int r_y_1 = (1024 - r_y);
|
||||
const uchar* ptr = image.ptr() + x + y * imagecols;
|
||||
// just interpolate:
|
||||
ret_val = (r_x_1 * r_y_1 * int(*ptr));
|
||||
ptr++;
|
||||
ret_val += (r_x * r_y_1 * int(*ptr));
|
||||
ptr += imagecols;
|
||||
ret_val += (r_x * r_y * int(*ptr));
|
||||
ptr--;
|
||||
ret_val += (r_x_1 * r_y * int(*ptr));
|
||||
return 0xFF & ((ret_val + 512) / 1024 / 1024);
|
||||
}
|
||||
|
||||
// this is the standard case (simple, not speed optimized yet):
|
||||
|
||||
// scaling:
|
||||
const int scaling = (int)(4194304.0f / area);
|
||||
const int scaling2 = (int)(float(scaling) * area / 1024.0f);
|
||||
CV_Assert(scaling2 != 0);
|
||||
|
||||
// calculate borders
|
||||
const float x_1 = xf - sigma_half;
|
||||
const float x1 = xf + sigma_half;
|
||||
const float y_1 = yf - sigma_half;
|
||||
const float y1 = yf + sigma_half;
|
||||
|
||||
const int x_left = int(x_1 + 0.5);
|
||||
const int y_top = int(y_1 + 0.5);
|
||||
const int x_right = int(x1 + 0.5);
|
||||
const int y_bottom = int(y1 + 0.5);
|
||||
|
||||
// overlap area - multiplication factors:
|
||||
const float r_x_1 = float(x_left) - x_1 + 0.5f;
|
||||
const float r_y_1 = float(y_top) - y_1 + 0.5f;
|
||||
const float r_x1 = x1 - float(x_right) + 0.5f;
|
||||
const float r_y1 = y1 - float(y_bottom) + 0.5f;
|
||||
const int dx = x_right - x_left - 1;
|
||||
const int dy = y_bottom - y_top - 1;
|
||||
const int A = (int)((r_x_1 * r_y_1) * scaling);
|
||||
const int B = (int)((r_x1 * r_y_1) * scaling);
|
||||
const int C = (int)((r_x1 * r_y1) * scaling);
|
||||
const int D = (int)((r_x_1 * r_y1) * scaling);
|
||||
const int r_x_1_i = (int)(r_x_1 * scaling);
|
||||
const int r_y_1_i = (int)(r_y_1 * scaling);
|
||||
const int r_x1_i = (int)(r_x1 * scaling);
|
||||
const int r_y1_i = (int)(r_y1 * scaling);
|
||||
|
||||
// now the calculation:
|
||||
const uchar* ptr = image.ptr() + x_left + imagecols * y_top;
|
||||
// first row:
|
||||
ret_val = A * int(*ptr);
|
||||
ptr++;
|
||||
const uchar* end1 = ptr + dx;
|
||||
for (; ptr < end1; ptr++)
|
||||
{
|
||||
ret_val += r_y_1_i * int(*ptr);
|
||||
}
|
||||
ret_val += B * int(*ptr);
|
||||
// middle ones:
|
||||
ptr += imagecols - dx - 1;
|
||||
const uchar* end_j = ptr + dy * imagecols;
|
||||
for (; ptr < end_j; ptr += imagecols - dx - 1)
|
||||
{
|
||||
ret_val += r_x_1_i * int(*ptr);
|
||||
ptr++;
|
||||
const uchar* end2 = ptr + dx;
|
||||
for (; ptr < end2; ptr++)
|
||||
{
|
||||
ret_val += int(*ptr) * scaling;
|
||||
}
|
||||
ret_val += r_x1_i * int(*ptr);
|
||||
}
|
||||
// last row:
|
||||
ret_val += D * int(*ptr);
|
||||
ptr++;
|
||||
const uchar* end3 = ptr + dx;
|
||||
for (; ptr < end3; ptr++)
|
||||
{
|
||||
ret_val += r_y1_i * int(*ptr);
|
||||
}
|
||||
ret_val += C * int(*ptr);
|
||||
|
||||
return 0xFF & ((ret_val + scaling2 / 2) / scaling2 / 1024);
|
||||
}
|
||||
|
||||
// half sampling
|
||||
inline void
|
||||
BriskLayer::halfsample(const cv::Mat& srcimg, cv::Mat& dstimg)
|
||||
{
|
||||
// make sure the destination image is of the right size:
|
||||
CV_Assert(srcimg.cols / 2 == dstimg.cols);
|
||||
CV_Assert(srcimg.rows / 2 == dstimg.rows);
|
||||
|
||||
// handle non-SSE case
|
||||
resize(srcimg, dstimg, dstimg.size(), 0, 0, INTER_AREA);
|
||||
}
|
||||
|
||||
inline void
|
||||
BriskLayer::twothirdsample(const cv::Mat& srcimg, cv::Mat& dstimg)
|
||||
{
|
||||
// make sure the destination image is of the right size:
|
||||
CV_Assert((srcimg.cols / 3) * 2 == dstimg.cols);
|
||||
CV_Assert((srcimg.rows / 3) * 2 == dstimg.rows);
|
||||
|
||||
resize(srcimg, dstimg, dstimg.size(), 0, 0, INTER_AREA);
|
||||
}
|
||||
|
||||
Ptr<BRISK> BRISK::create(int thresh, int octaves, float patternScale)
|
||||
{
|
||||
return makePtr<BRISK_Impl>(thresh, octaves, patternScale);
|
||||
}
|
||||
|
||||
// custom setup
|
||||
Ptr<BRISK> BRISK::create(const std::vector<float> &radiusList, const std::vector<int> &numberList,
|
||||
float dMax, float dMin, const std::vector<int>& indexChange)
|
||||
{
|
||||
return makePtr<BRISK_Impl>(radiusList, numberList, dMax, dMin, indexChange);
|
||||
}
|
||||
|
||||
Ptr<BRISK> BRISK::create(int thresh, int octaves, const std::vector<float> &radiusList,
|
||||
const std::vector<int> &numberList, float dMax, float dMin,
|
||||
const std::vector<int>& indexChange)
|
||||
{
|
||||
return makePtr<BRISK_Impl>(thresh, octaves, radiusList, numberList, dMax, dMin, indexChange);
|
||||
}
|
||||
|
||||
String BRISK::getDefaultName() const
|
||||
{
|
||||
return (Feature2D::getDefaultName() + ".BRISK");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2008, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
/*
|
||||
OpenCV wrapper of reference implementation of
|
||||
[1] KAZE Features. Pablo F. Alcantarilla, Adrien Bartoli and Andrew J. Davison.
|
||||
In European Conference on Computer Vision (ECCV), Fiorenze, Italy, October 2012
|
||||
http://www.robesafe.com/personal/pablo.alcantarilla/papers/Alcantarilla12eccv.pdf
|
||||
@author Eugene Khvedchenya <ekhvedchenya@gmail.com>
|
||||
*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "kaze/KAZEFeatures.h"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class KAZE_Impl CV_FINAL : public KAZE
|
||||
{
|
||||
public:
|
||||
KAZE_Impl(bool _extended, bool _upright, float _threshold, int _octaves,
|
||||
int _sublevels, KAZE::DiffusivityType _diffusivity)
|
||||
: extended(_extended)
|
||||
, upright(_upright)
|
||||
, threshold(_threshold)
|
||||
, octaves(_octaves)
|
||||
, sublevels(_sublevels)
|
||||
, diffusivity(_diffusivity)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~KAZE_Impl() CV_OVERRIDE {}
|
||||
|
||||
void setExtended(bool extended_) CV_OVERRIDE { extended = extended_; }
|
||||
bool getExtended() const CV_OVERRIDE { return extended; }
|
||||
|
||||
void setUpright(bool upright_) CV_OVERRIDE { upright = upright_; }
|
||||
bool getUpright() const CV_OVERRIDE { return upright; }
|
||||
|
||||
void setThreshold(double threshold_) CV_OVERRIDE { threshold = (float)threshold_; }
|
||||
double getThreshold() const CV_OVERRIDE { return threshold; }
|
||||
|
||||
void setNOctaves(int octaves_) CV_OVERRIDE { octaves = octaves_; }
|
||||
int getNOctaves() const CV_OVERRIDE { return octaves; }
|
||||
|
||||
void setNOctaveLayers(int octaveLayers_) CV_OVERRIDE { sublevels = octaveLayers_; }
|
||||
int getNOctaveLayers() const CV_OVERRIDE { return sublevels; }
|
||||
|
||||
void setDiffusivity(KAZE::DiffusivityType diff_) CV_OVERRIDE{ diffusivity = diff_; }
|
||||
KAZE::DiffusivityType getDiffusivity() const CV_OVERRIDE{ return diffusivity; }
|
||||
|
||||
// returns the descriptor size in bytes
|
||||
int descriptorSize() const CV_OVERRIDE
|
||||
{
|
||||
return extended ? 128 : 64;
|
||||
}
|
||||
|
||||
// returns the descriptor type
|
||||
int descriptorType() const CV_OVERRIDE
|
||||
{
|
||||
return CV_32F;
|
||||
}
|
||||
|
||||
// returns the default norm type
|
||||
int defaultNorm() const CV_OVERRIDE
|
||||
{
|
||||
return NORM_L2;
|
||||
}
|
||||
|
||||
void detectAndCompute(InputArray image, InputArray mask,
|
||||
std::vector<KeyPoint>& keypoints,
|
||||
OutputArray descriptors,
|
||||
bool useProvidedKeypoints) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
cv::Mat img = image.getMat();
|
||||
if (img.channels() > 1)
|
||||
cvtColor(image, img, COLOR_BGR2GRAY);
|
||||
|
||||
Mat img1_32;
|
||||
if ( img.depth() == CV_32F )
|
||||
img1_32 = img;
|
||||
else if ( img.depth() == CV_8U )
|
||||
img.convertTo(img1_32, CV_32F, 1.0 / 255.0, 0);
|
||||
else if ( img.depth() == CV_16U )
|
||||
img.convertTo(img1_32, CV_32F, 1.0 / 65535.0, 0);
|
||||
|
||||
CV_Assert( ! img1_32.empty() );
|
||||
|
||||
KAZEOptions options;
|
||||
options.img_width = img.cols;
|
||||
options.img_height = img.rows;
|
||||
options.extended = extended;
|
||||
options.upright = upright;
|
||||
options.dthreshold = threshold;
|
||||
options.omax = octaves;
|
||||
options.nsublevels = sublevels;
|
||||
options.diffusivity = diffusivity;
|
||||
|
||||
KAZEFeatures impl(options);
|
||||
impl.Create_Nonlinear_Scale_Space(img1_32);
|
||||
|
||||
if (!useProvidedKeypoints)
|
||||
{
|
||||
impl.Feature_Detection(keypoints);
|
||||
}
|
||||
|
||||
if (!mask.empty())
|
||||
{
|
||||
cv::KeyPointsFilter::runByPixelsMask(keypoints, mask.getMat());
|
||||
}
|
||||
|
||||
if( descriptors.needed() )
|
||||
{
|
||||
Mat desc;
|
||||
impl.Feature_Description(keypoints, desc);
|
||||
desc.copyTo(descriptors);
|
||||
|
||||
CV_Assert((!desc.rows || desc.cols == descriptorSize()));
|
||||
CV_Assert((!desc.rows || (desc.type() == descriptorType())));
|
||||
}
|
||||
}
|
||||
|
||||
void write(FileStorage& fs) const CV_OVERRIDE
|
||||
{
|
||||
writeFormat(fs);
|
||||
fs << "name" << getDefaultName();
|
||||
fs << "extended" << (int)extended;
|
||||
fs << "upright" << (int)upright;
|
||||
fs << "threshold" << threshold;
|
||||
fs << "octaves" << octaves;
|
||||
fs << "sublevels" << sublevels;
|
||||
fs << "diffusivity" << diffusivity;
|
||||
}
|
||||
|
||||
void read(const FileNode& fn) CV_OVERRIDE
|
||||
{
|
||||
// if node is empty, keep previous value
|
||||
if (!fn["extended"].empty())
|
||||
extended = (int)fn["extended"] != 0;
|
||||
if (!fn["upright"].empty())
|
||||
upright = (int)fn["upright"] != 0;
|
||||
if (!fn["threshold"].empty())
|
||||
threshold = (float)fn["threshold"];
|
||||
if (!fn["octaves"].empty())
|
||||
octaves = (int)fn["octaves"];
|
||||
if (!fn["sublevels"].empty())
|
||||
sublevels = (int)fn["sublevels"];
|
||||
if (!fn["diffusivity"].empty())
|
||||
diffusivity = static_cast<KAZE::DiffusivityType>((int)fn["diffusivity"]);
|
||||
}
|
||||
|
||||
bool extended;
|
||||
bool upright;
|
||||
float threshold;
|
||||
int octaves;
|
||||
int sublevels;
|
||||
KAZE::DiffusivityType diffusivity;
|
||||
};
|
||||
|
||||
Ptr<KAZE> KAZE::create(bool extended, bool upright,
|
||||
float threshold,
|
||||
int octaves, int sublevels,
|
||||
KAZE::DiffusivityType diffusivity)
|
||||
{
|
||||
return makePtr<KAZE_Impl>(extended, upright, threshold, octaves, sublevels, diffusivity);
|
||||
}
|
||||
|
||||
String KAZE::getDefaultName() const
|
||||
{
|
||||
return (Feature2D::getDefaultName() + ".KAZE");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* @file AKAZEConfig.h
|
||||
* @brief AKAZE configuration file
|
||||
* @date Feb 23, 2014
|
||||
* @author Pablo F. Alcantarilla, Jesus Nuevo
|
||||
*/
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_AKAZE_CONFIG_H__
|
||||
#define __OPENCV_FEATURES_2D_AKAZE_CONFIG_H__
|
||||
|
||||
namespace cv
|
||||
{
|
||||
/* ************************************************************************* */
|
||||
/// AKAZE configuration options structure
|
||||
struct AKAZEOptions {
|
||||
|
||||
AKAZEOptions()
|
||||
: omax(4)
|
||||
, nsublevels(4)
|
||||
, img_width(0)
|
||||
, img_height(0)
|
||||
, soffset(1.6f)
|
||||
, derivative_factor(1.5f)
|
||||
, sderivatives(1.0)
|
||||
, diffusivity(KAZE::DIFF_PM_G2)
|
||||
|
||||
, dthreshold(0.001f)
|
||||
, min_dthreshold(0.00001f)
|
||||
|
||||
, descriptor(AKAZE::DESCRIPTOR_MLDB)
|
||||
, descriptor_size(0)
|
||||
, descriptor_channels(3)
|
||||
, descriptor_pattern_size(10)
|
||||
|
||||
, kcontrast(0.001f)
|
||||
, kcontrast_percentile(0.7f)
|
||||
, kcontrast_nbins(300)
|
||||
{
|
||||
}
|
||||
|
||||
int omax; ///< Maximum octave evolution of the image 2^sigma (coarsest scale sigma units)
|
||||
int nsublevels; ///< Default number of sublevels per scale level
|
||||
int img_width; ///< Width of the input image
|
||||
int img_height; ///< Height of the input image
|
||||
float soffset; ///< Base scale offset (sigma units)
|
||||
float derivative_factor; ///< Factor for the multiscale derivatives
|
||||
float sderivatives; ///< Smoothing factor for the derivatives
|
||||
KAZE::DiffusivityType diffusivity; ///< Diffusivity type
|
||||
|
||||
float dthreshold; ///< Detector response threshold to accept point
|
||||
float min_dthreshold; ///< Minimum detector threshold to accept a point
|
||||
|
||||
AKAZE::DescriptorType descriptor; ///< Type of descriptor
|
||||
int descriptor_size; ///< Size of the descriptor in bits. 0->Full size
|
||||
int descriptor_channels; ///< Number of channels in the descriptor (1, 2, 3)
|
||||
int descriptor_pattern_size; ///< Actual patch size is 2*pattern_size*point.scale
|
||||
|
||||
float kcontrast; ///< The contrast factor parameter
|
||||
float kcontrast_percentile; ///< Percentile level for the contrast factor
|
||||
int kcontrast_nbins; ///< Number of bins for the contrast factor histogram
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,2318 +0,0 @@
|
||||
/**
|
||||
* @file AKAZEFeatures.cpp
|
||||
* @brief Main class for detecting and describing binary features in an
|
||||
* accelerated nonlinear scale space
|
||||
* @date Sep 15, 2013
|
||||
* @author Pablo F. Alcantarilla, Jesus Nuevo
|
||||
*/
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "AKAZEFeatures.h"
|
||||
#include "fed.h"
|
||||
#include "nldiffusion_functions.h"
|
||||
#include "utils.h"
|
||||
#include "opencl_kernels_features2d.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// Namespaces
|
||||
namespace cv
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief AKAZEFeatures constructor with input options
|
||||
* @param options AKAZEFeatures configuration options
|
||||
* @note This constructor allocates memory for the nonlinear scale space
|
||||
*/
|
||||
AKAZEFeatures::AKAZEFeatures(const AKAZEOptions& options) : options_(options) {
|
||||
|
||||
ncycles_ = 0;
|
||||
reordering_ = true;
|
||||
|
||||
if (options_.descriptor_size > 0 && options_.descriptor >= AKAZE::DESCRIPTOR_MLDB_UPRIGHT) {
|
||||
generateDescriptorSubsample(descriptorSamples_, descriptorBits_, options_.descriptor_size,
|
||||
options_.descriptor_pattern_size, options_.descriptor_channels);
|
||||
}
|
||||
|
||||
Allocate_Memory_Evolution();
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method allocates the memory for the nonlinear diffusion evolution
|
||||
*/
|
||||
void AKAZEFeatures::Allocate_Memory_Evolution(void) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
float rfactor = 0.0f;
|
||||
int level_height = 0, level_width = 0;
|
||||
|
||||
// maximum size of the area for the descriptor computation
|
||||
float smax = 0.0;
|
||||
if (options_.descriptor == AKAZE::DESCRIPTOR_MLDB_UPRIGHT || options_.descriptor == AKAZE::DESCRIPTOR_MLDB) {
|
||||
smax = 10.0f*sqrtf(2.0f);
|
||||
}
|
||||
else if (options_.descriptor == AKAZE::DESCRIPTOR_KAZE_UPRIGHT || options_.descriptor == AKAZE::DESCRIPTOR_KAZE) {
|
||||
smax = 12.0f*sqrtf(2.0f);
|
||||
}
|
||||
|
||||
// Allocate the dimension of the matrices for the evolution
|
||||
for (int i = 0, power = 1; i <= options_.omax - 1; i++, power *= 2) {
|
||||
rfactor = 1.0f / power;
|
||||
level_height = (int)(options_.img_height*rfactor);
|
||||
level_width = (int)(options_.img_width*rfactor);
|
||||
|
||||
// Smallest possible octave and allow one scale if the image is small
|
||||
if ((level_width < 80 || level_height < 40) && i != 0) {
|
||||
options_.omax = i;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int j = 0; j < options_.nsublevels; j++) {
|
||||
MEvolution step;
|
||||
step.size = Size(level_width, level_height);
|
||||
step.esigma = options_.soffset*pow(2.f, (float)(j) / (float)(options_.nsublevels) + i);
|
||||
step.sigma_size = cvRound(step.esigma * options_.derivative_factor / power); // In fact sigma_size only depends on j
|
||||
step.etime = 0.5f * (step.esigma * step.esigma);
|
||||
step.octave = i;
|
||||
step.sublevel = j;
|
||||
step.octave_ratio = (float)power;
|
||||
step.border = cvRound(smax * step.sigma_size) + 1;
|
||||
|
||||
evolution_.push_back(step);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate memory for the number of cycles and time steps
|
||||
for (size_t i = 1; i < evolution_.size(); i++) {
|
||||
int naux = 0;
|
||||
vector<float> tau;
|
||||
float ttime = 0.0f;
|
||||
ttime = evolution_[i].etime - evolution_[i - 1].etime;
|
||||
naux = fed_tau_by_process_time(ttime, 1, 0.25f, reordering_, tau);
|
||||
nsteps_.push_back(naux);
|
||||
tsteps_.push_back(tau);
|
||||
ncycles_++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief Computes kernel size for Gaussian smoothing if the image
|
||||
* @param sigma Kernel standard deviation
|
||||
* @returns kernel size
|
||||
*/
|
||||
static inline int getGaussianKernelSize(float sigma) {
|
||||
// Compute an appropriate kernel size according to the specified sigma
|
||||
int ksize = (int)cvCeil(2.0f*(1.0f + (sigma - 0.8f) / (0.3f)));
|
||||
ksize |= 1; // kernel should be odd
|
||||
return ksize;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes a scalar non-linear diffusion step
|
||||
* @param Lt Base image in the evolution
|
||||
* @param Lf Conductivity image
|
||||
* @param Lstep Output image that gives the difference between the current
|
||||
* Ld and the next Ld being evolved
|
||||
* @param row_begin row where to start
|
||||
* @param row_end last row to fill exclusive. the range is [row_begin, row_end).
|
||||
* @note Forward Euler Scheme 3x3 stencil
|
||||
* The function c is a scalar value that depends on the gradient norm
|
||||
* dL_by_ds = d(c dL_by_dx)_by_dx + d(c dL_by_dy)_by_dy
|
||||
*/
|
||||
static inline void
|
||||
nld_step_scalar_one_lane(const Mat& Lt, const Mat& Lf, Mat& Lstep, float step_size, int row_begin, int row_end)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
/* The labeling scheme for this five star stencil:
|
||||
[ a ]
|
||||
[ -1 c +1 ]
|
||||
[ b ]
|
||||
*/
|
||||
|
||||
Lstep.create(Lt.size(), Lt.type());
|
||||
const int cols = Lt.cols - 2;
|
||||
int row = row_begin;
|
||||
|
||||
const float *lt_a, *lt_c, *lt_b;
|
||||
const float *lf_a, *lf_c, *lf_b;
|
||||
float *dst;
|
||||
float step_r = 0.f;
|
||||
|
||||
// Process the top row
|
||||
if (row == 0) {
|
||||
lt_c = Lt.ptr<float>(0) + 1; /* Skip the left-most column by +1 */
|
||||
lf_c = Lf.ptr<float>(0) + 1;
|
||||
lt_b = Lt.ptr<float>(1) + 1;
|
||||
lf_b = Lf.ptr<float>(1) + 1;
|
||||
|
||||
// fill the corner to prevent uninitialized values
|
||||
dst = Lstep.ptr<float>(0);
|
||||
dst[0] = 0.0f;
|
||||
++dst;
|
||||
|
||||
for (int j = 0; j < cols; j++) {
|
||||
step_r = (lf_c[j] + lf_c[j + 1])*(lt_c[j + 1] - lt_c[j]) +
|
||||
(lf_c[j] + lf_c[j - 1])*(lt_c[j - 1] - lt_c[j]) +
|
||||
(lf_c[j] + lf_b[j ])*(lt_b[j ] - lt_c[j]);
|
||||
dst[j] = step_r * step_size;
|
||||
}
|
||||
|
||||
// fill the corner to prevent uninitialized values
|
||||
dst[cols] = 0.0f;
|
||||
++row;
|
||||
}
|
||||
|
||||
// Process the middle rows
|
||||
int middle_end = std::min(Lt.rows - 1, row_end);
|
||||
for (; row < middle_end; ++row)
|
||||
{
|
||||
lt_a = Lt.ptr<float>(row - 1);
|
||||
lf_a = Lf.ptr<float>(row - 1);
|
||||
lt_c = Lt.ptr<float>(row );
|
||||
lf_c = Lf.ptr<float>(row );
|
||||
lt_b = Lt.ptr<float>(row + 1);
|
||||
lf_b = Lf.ptr<float>(row + 1);
|
||||
dst = Lstep.ptr<float>(row);
|
||||
|
||||
// The left-most column
|
||||
step_r = (lf_c[0] + lf_c[1])*(lt_c[1] - lt_c[0]) +
|
||||
(lf_c[0] + lf_b[0])*(lt_b[0] - lt_c[0]) +
|
||||
(lf_c[0] + lf_a[0])*(lt_a[0] - lt_c[0]);
|
||||
dst[0] = step_r * step_size;
|
||||
|
||||
lt_a++; lt_c++; lt_b++;
|
||||
lf_a++; lf_c++; lf_b++;
|
||||
dst++;
|
||||
|
||||
// The middle columns
|
||||
for (int j = 0; j < cols; j++)
|
||||
{
|
||||
step_r = (lf_c[j] + lf_c[j + 1])*(lt_c[j + 1] - lt_c[j]) +
|
||||
(lf_c[j] + lf_c[j - 1])*(lt_c[j - 1] - lt_c[j]) +
|
||||
(lf_c[j] + lf_b[j ])*(lt_b[j ] - lt_c[j]) +
|
||||
(lf_c[j] + lf_a[j ])*(lt_a[j ] - lt_c[j]);
|
||||
dst[j] = step_r * step_size;
|
||||
}
|
||||
|
||||
// The right-most column
|
||||
step_r = (lf_c[cols] + lf_c[cols - 1])*(lt_c[cols - 1] - lt_c[cols]) +
|
||||
(lf_c[cols] + lf_b[cols ])*(lt_b[cols ] - lt_c[cols]) +
|
||||
(lf_c[cols] + lf_a[cols ])*(lt_a[cols ] - lt_c[cols]);
|
||||
dst[cols] = step_r * step_size;
|
||||
}
|
||||
|
||||
// Process the bottom row (row == Lt.rows - 1)
|
||||
if (row_end == Lt.rows) {
|
||||
lt_a = Lt.ptr<float>(row - 1) + 1; /* Skip the left-most column by +1 */
|
||||
lf_a = Lf.ptr<float>(row - 1) + 1;
|
||||
lt_c = Lt.ptr<float>(row ) + 1;
|
||||
lf_c = Lf.ptr<float>(row ) + 1;
|
||||
|
||||
// fill the corner to prevent uninitialized values
|
||||
dst = Lstep.ptr<float>(row);
|
||||
dst[0] = 0.0f;
|
||||
++dst;
|
||||
|
||||
for (int j = 0; j < cols; j++) {
|
||||
step_r = (lf_c[j] + lf_c[j + 1])*(lt_c[j + 1] - lt_c[j]) +
|
||||
(lf_c[j] + lf_c[j - 1])*(lt_c[j - 1] - lt_c[j]) +
|
||||
(lf_c[j] + lf_a[j ])*(lt_a[j ] - lt_c[j]);
|
||||
dst[j] = step_r * step_size;
|
||||
}
|
||||
|
||||
// fill the corner to prevent uninitialized values
|
||||
dst[cols] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
class NonLinearScalarDiffusionStep : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
NonLinearScalarDiffusionStep(const Mat& Lt, const Mat& Lf, Mat& Lstep, float step_size)
|
||||
: Lt_(&Lt), Lf_(&Lf), Lstep_(&Lstep), step_size_(step_size)
|
||||
{}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
nld_step_scalar_one_lane(*Lt_, *Lf_, *Lstep_, step_size_, range.start, range.end);
|
||||
}
|
||||
|
||||
private:
|
||||
const Mat* Lt_;
|
||||
const Mat* Lf_;
|
||||
Mat* Lstep_;
|
||||
float step_size_;
|
||||
};
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
static inline bool
|
||||
ocl_non_linear_diffusion_step(InputArray Lt_, InputArray Lf_, OutputArray Lstep_, float step_size)
|
||||
{
|
||||
if(!Lt_.isContinuous())
|
||||
return false;
|
||||
|
||||
UMat Lt = Lt_.getUMat();
|
||||
UMat Lf = Lf_.getUMat();
|
||||
UMat Lstep = Lstep_.getUMat();
|
||||
|
||||
size_t globalSize[] = {(size_t)Lt.cols, (size_t)Lt.rows};
|
||||
|
||||
ocl::Kernel ker("AKAZE_nld_step_scalar", ocl::features2d::akaze_oclsrc);
|
||||
if( ker.empty() )
|
||||
return false;
|
||||
|
||||
return ker.args(
|
||||
ocl::KernelArg::ReadOnly(Lt),
|
||||
ocl::KernelArg::PtrReadOnly(Lf),
|
||||
ocl::KernelArg::PtrWriteOnly(Lstep),
|
||||
step_size).run(2, globalSize, 0, true);
|
||||
}
|
||||
#endif // HAVE_OPENCL
|
||||
|
||||
static inline void
|
||||
non_linear_diffusion_step(InputArray Lt_, InputArray Lf_, OutputArray Lstep_, float step_size)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
Lstep_.create(Lt_.size(), Lt_.type());
|
||||
|
||||
CV_OCL_RUN(Lt_.isUMat() && Lf_.isUMat() && Lstep_.isUMat(),
|
||||
ocl_non_linear_diffusion_step(Lt_, Lf_, Lstep_, step_size));
|
||||
|
||||
Mat Lt = Lt_.getMat();
|
||||
Mat Lf = Lf_.getMat();
|
||||
Mat Lstep = Lstep_.getMat();
|
||||
parallel_for_(Range(0, Lt.rows), NonLinearScalarDiffusionStep(Lt, Lf, Lstep, step_size));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function computes a good empirical value for the k contrast factor
|
||||
* given two gradient images, the percentile (0-1), the temporal storage to hold
|
||||
* gradient norms and the histogram bins
|
||||
* @param Lx Horizontal gradient of the input image
|
||||
* @param Ly Vertical gradient of the input image
|
||||
* @param nbins Number of histogram bins
|
||||
* @return k contrast factor
|
||||
*/
|
||||
static inline float
|
||||
compute_kcontrast(InputArray Lx_, InputArray Ly_, float perc, int nbins)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
CV_Assert(nbins > 2);
|
||||
CV_Assert(!Lx_.empty());
|
||||
|
||||
Mat Lx = Lx_.getMat();
|
||||
Mat Ly = Ly_.getMat();
|
||||
|
||||
// temporary square roots of dot product
|
||||
Mat modgs (Lx.rows - 2, Lx.cols - 2, CV_32F);
|
||||
const int total = modgs.cols * modgs.rows;
|
||||
float *modg = modgs.ptr<float>();
|
||||
float hmax = 0.0f;
|
||||
|
||||
for (int i = 1; i < Lx.rows - 1; i++) {
|
||||
const float *lx = Lx.ptr<float>(i) + 1;
|
||||
const float *ly = Ly.ptr<float>(i) + 1;
|
||||
const int cols = Lx.cols - 2;
|
||||
|
||||
for (int j = 0; j < cols; j++) {
|
||||
float dist = sqrtf(lx[j] * lx[j] + ly[j] * ly[j]);
|
||||
*modg++ = dist;
|
||||
hmax = std::max(hmax, dist);
|
||||
}
|
||||
}
|
||||
modg = modgs.ptr<float>();
|
||||
|
||||
if (hmax == 0.0f)
|
||||
return 0.03f; // e.g. a blank image
|
||||
|
||||
// Compute the bin numbers: the value range [0, hmax] -> [0, nbins-1]
|
||||
modgs *= (nbins - 1) / hmax;
|
||||
|
||||
// Count up histogram
|
||||
std::vector<int> hist(nbins, 0);
|
||||
for (int i = 0; i < total; i++)
|
||||
hist[(int)modg[i]]++;
|
||||
|
||||
// Now find the perc of the histogram percentile
|
||||
const int nthreshold = (int)((total - hist[0]) * perc); // Exclude hist[0] as background
|
||||
int nelements = 0;
|
||||
for (int k = 1; k < nbins; k++) {
|
||||
if (nelements >= nthreshold)
|
||||
return (float)hmax * k / nbins;
|
||||
|
||||
nelements += hist[k];
|
||||
}
|
||||
|
||||
return 0.03f;
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
static inline bool
|
||||
ocl_pm_g2(InputArray Lx_, InputArray Ly_, OutputArray Lflow_, float kcontrast)
|
||||
{
|
||||
UMat Lx = Lx_.getUMat();
|
||||
UMat Ly = Ly_.getUMat();
|
||||
UMat Lflow = Lflow_.getUMat();
|
||||
|
||||
int total = Lx.rows * Lx.cols;
|
||||
size_t globalSize[] = {(size_t)total};
|
||||
|
||||
ocl::Kernel ker("AKAZE_pm_g2", ocl::features2d::akaze_oclsrc);
|
||||
if( ker.empty() )
|
||||
return false;
|
||||
|
||||
return ker.args(
|
||||
ocl::KernelArg::PtrReadOnly(Lx),
|
||||
ocl::KernelArg::PtrReadOnly(Ly),
|
||||
ocl::KernelArg::PtrWriteOnly(Lflow),
|
||||
kcontrast, total).run(1, globalSize, 0, true);
|
||||
}
|
||||
#endif // HAVE_OPENCL
|
||||
|
||||
static inline void
|
||||
compute_diffusivity(InputArray Lx, InputArray Ly, OutputArray Lflow, float kcontrast, KAZE::DiffusivityType diffusivity)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
Lflow.create(Lx.size(), Lx.type());
|
||||
|
||||
switch (diffusivity) {
|
||||
case KAZE::DIFF_PM_G1:
|
||||
pm_g1(Lx, Ly, Lflow, kcontrast);
|
||||
break;
|
||||
case KAZE::DIFF_PM_G2:
|
||||
CV_OCL_RUN(Lx.isUMat() && Ly.isUMat() && Lflow.isUMat(), ocl_pm_g2(Lx, Ly, Lflow, kcontrast));
|
||||
pm_g2(Lx, Ly, Lflow, kcontrast);
|
||||
break;
|
||||
case KAZE::DIFF_WEICKERT:
|
||||
weickert_diffusivity(Lx, Ly, Lflow, kcontrast);
|
||||
break;
|
||||
case KAZE::DIFF_CHARBONNIER:
|
||||
charbonnier_diffusivity(Lx, Ly, Lflow, kcontrast);
|
||||
break;
|
||||
default:
|
||||
CV_Error_(Error::StsError, ("Diffusivity is not supported: %d", static_cast<int>(diffusivity)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts input image to grayscale float image
|
||||
*
|
||||
* @param image any image
|
||||
* @param dst grayscale float image
|
||||
*/
|
||||
static inline void prepareInputImage(InputArray image, OutputArray dst)
|
||||
{
|
||||
Mat img = image.getMat();
|
||||
if (img.channels() > 1)
|
||||
cvtColor(image, img, COLOR_BGR2GRAY);
|
||||
|
||||
if ( img.depth() == CV_32F )
|
||||
dst.assign(img);
|
||||
else if ( img.depth() == CV_8U )
|
||||
img.convertTo(dst, CV_32F, 1.0 / 255.0, 0);
|
||||
else if ( img.depth() == CV_16U )
|
||||
img.convertTo(dst, CV_32F, 1.0 / 65535.0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This method creates the nonlinear scale space for a given image
|
||||
* @param image Input image for which the nonlinear scale space needs to be created
|
||||
*/
|
||||
template<typename MatType>
|
||||
static inline void
|
||||
create_nonlinear_scale_space(InputArray image, const AKAZEOptions &options,
|
||||
const std::vector<std::vector<float > > &tsteps_evolution, std::vector<Evolution<MatType> > &evolution)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
CV_Assert(evolution.size() > 0);
|
||||
|
||||
// convert input to grayscale float image if needed
|
||||
MatType img;
|
||||
prepareInputImage(image, img);
|
||||
|
||||
// create first level of the evolution
|
||||
int ksize = getGaussianKernelSize(options.soffset);
|
||||
GaussianBlur(img, evolution[0].Lsmooth, Size(ksize, ksize), options.soffset, options.soffset, BORDER_REPLICATE);
|
||||
evolution[0].Lsmooth.copyTo(evolution[0].Lt);
|
||||
|
||||
if (evolution.size() == 1) {
|
||||
// we don't need to compute kcontrast factor
|
||||
Compute_Determinant_Hessian_Response(evolution);
|
||||
return;
|
||||
}
|
||||
|
||||
// derivatives, flow and diffusion step
|
||||
MatType Lx, Ly, Lsmooth, Lflow, Lstep;
|
||||
|
||||
// compute derivatives for computing k contrast
|
||||
GaussianBlur(img, Lsmooth, Size(5, 5), 1.0f, 1.0f, BORDER_REPLICATE);
|
||||
Scharr(Lsmooth, Lx, CV_32F, 1, 0, 1, 0, BORDER_DEFAULT);
|
||||
Scharr(Lsmooth, Ly, CV_32F, 0, 1, 1, 0, BORDER_DEFAULT);
|
||||
Lsmooth.release();
|
||||
// compute the kcontrast factor
|
||||
float kcontrast = compute_kcontrast(Lx, Ly, options.kcontrast_percentile, options.kcontrast_nbins);
|
||||
|
||||
// Now generate the rest of evolution levels
|
||||
for (size_t i = 1; i < evolution.size(); i++) {
|
||||
Evolution<MatType> &e = evolution[i];
|
||||
|
||||
if (e.octave > evolution[i - 1].octave) {
|
||||
// new octave will be half the size
|
||||
resize(evolution[i - 1].Lt, e.Lt, e.size, 0, 0, INTER_AREA);
|
||||
kcontrast *= 0.75f;
|
||||
}
|
||||
else {
|
||||
evolution[i - 1].Lt.copyTo(e.Lt);
|
||||
}
|
||||
|
||||
GaussianBlur(e.Lt, e.Lsmooth, Size(5, 5), 1.0f, 1.0f, BORDER_REPLICATE);
|
||||
|
||||
// Compute the Gaussian derivatives Lx and Ly
|
||||
Scharr(e.Lsmooth, Lx, CV_32F, 1, 0, 1.0, 0, BORDER_DEFAULT);
|
||||
Scharr(e.Lsmooth, Ly, CV_32F, 0, 1, 1.0, 0, BORDER_DEFAULT);
|
||||
|
||||
// Compute the conductivity equation
|
||||
compute_diffusivity(Lx, Ly, Lflow, kcontrast, options.diffusivity);
|
||||
|
||||
// Perform Fast Explicit Diffusion on Lt
|
||||
const std::vector<float> &tsteps = tsteps_evolution[i - 1];
|
||||
for (size_t j = 0; j < tsteps.size(); j++) {
|
||||
const float step_size = tsteps[j] * 0.5f;
|
||||
non_linear_diffusion_step(e.Lt, Lflow, Lstep, step_size);
|
||||
add(e.Lt, Lstep, e.Lt);
|
||||
}
|
||||
}
|
||||
|
||||
Compute_Determinant_Hessian_Response(evolution);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts between UMatPyramid and Pyramid and vice versa
|
||||
* @details Matrices in evolution levels will be copied
|
||||
*
|
||||
* @param src source pyramid
|
||||
* @param dst destination pyramid
|
||||
*/
|
||||
template<typename MatTypeSrc, typename MatTypeDst>
|
||||
static inline void
|
||||
convertScalePyramid(const std::vector<Evolution<MatTypeSrc> >& src, std::vector<Evolution<MatTypeDst> > &dst)
|
||||
{
|
||||
dst.resize(src.size());
|
||||
for (size_t i = 0; i < src.size(); ++i) {
|
||||
dst[i] = Evolution<MatTypeDst>(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This method creates the nonlinear scale space for a given image
|
||||
* @param image Input image for which the nonlinear scale space needs to be created
|
||||
*/
|
||||
void AKAZEFeatures::Create_Nonlinear_Scale_Space(InputArray image)
|
||||
{
|
||||
if (ocl::isOpenCLActivated() && image.isUMat()) {
|
||||
// will run OCL version of scale space pyramid
|
||||
UMatPyramid uPyr;
|
||||
// init UMat pyramid with sizes
|
||||
convertScalePyramid(evolution_, uPyr);
|
||||
create_nonlinear_scale_space(image, options_, tsteps_, uPyr);
|
||||
// download pyramid from GPU
|
||||
convertScalePyramid(uPyr, evolution_);
|
||||
} else {
|
||||
// CPU version
|
||||
create_nonlinear_scale_space(image, options_, tsteps_, evolution_);
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
static inline bool
|
||||
ocl_compute_determinant(InputArray Lxx_, InputArray Lxy_, InputArray Lyy_,
|
||||
OutputArray Ldet_, float sigma)
|
||||
{
|
||||
UMat Lxx = Lxx_.getUMat();
|
||||
UMat Lxy = Lxy_.getUMat();
|
||||
UMat Lyy = Lyy_.getUMat();
|
||||
UMat Ldet = Ldet_.getUMat();
|
||||
|
||||
const int total = Lxx.rows * Lxx.cols;
|
||||
size_t globalSize[] = {(size_t)total};
|
||||
|
||||
ocl::Kernel ker("AKAZE_compute_determinant", ocl::features2d::akaze_oclsrc);
|
||||
if( ker.empty() )
|
||||
return false;
|
||||
|
||||
return ker.args(
|
||||
ocl::KernelArg::PtrReadOnly(Lxx),
|
||||
ocl::KernelArg::PtrReadOnly(Lxy),
|
||||
ocl::KernelArg::PtrReadOnly(Lyy),
|
||||
ocl::KernelArg::PtrWriteOnly(Ldet),
|
||||
sigma, total).run(1, globalSize, 0, true);
|
||||
}
|
||||
#endif // HAVE_OPENCL
|
||||
|
||||
/**
|
||||
* @brief Compute determinant from hessians
|
||||
* @details Compute Ldet by (Lxx.mul(Lyy) - Lxy.mul(Lxy)) * sigma
|
||||
*
|
||||
* @param Lxx spatial derivates
|
||||
* @param Lxy spatial derivates
|
||||
* @param Lyy spatial derivates
|
||||
* @param Ldet output determinant
|
||||
* @param sigma determinant will be scaled by this sigma
|
||||
*/
|
||||
static inline void compute_determinant(InputArray Lxx_, InputArray Lxy_, InputArray Lyy_,
|
||||
OutputArray Ldet_, float sigma)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
Ldet_.create(Lxx_.size(), Lxx_.type());
|
||||
|
||||
CV_OCL_RUN(Lxx_.isUMat() && Ldet_.isUMat(), ocl_compute_determinant(Lxx_, Lxy_, Lyy_, Ldet_, sigma));
|
||||
|
||||
// output determinant
|
||||
Mat Lxx = Lxx_.getMat(), Lxy = Lxy_.getMat(), Lyy = Lyy_.getMat(), Ldet = Ldet_.getMat();
|
||||
float *lxx = Lxx.ptr<float>();
|
||||
float *lxy = Lxy.ptr<float>();
|
||||
float *lyy = Lyy.ptr<float>();
|
||||
float *ldet = Ldet.ptr<float>();
|
||||
const int total = Lxx.cols * Lxx.rows;
|
||||
for (int j = 0; j < total; j++) {
|
||||
ldet[j] = (lxx[j] * lyy[j] - lxy[j] * lxy[j]) * sigma;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename MatType>
|
||||
class DeterminantHessianResponse : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
explicit DeterminantHessianResponse(std::vector<Evolution<MatType> >& ev)
|
||||
: evolution_(&ev)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
MatType Lxx, Lxy, Lyy;
|
||||
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Evolution<MatType> &e = (*evolution_)[i];
|
||||
|
||||
// we cannot use cv:Scharr here, because we need to handle also
|
||||
// kernel sizes other than 3, by default we are using 9x9, 5x5 and 7x7
|
||||
|
||||
// compute kernels
|
||||
Mat DxKx, DxKy, DyKx, DyKy;
|
||||
compute_derivative_kernels(DxKx, DxKy, 1, 0, e.sigma_size);
|
||||
compute_derivative_kernels(DyKx, DyKy, 0, 1, e.sigma_size);
|
||||
|
||||
// compute the multiscale derivatives
|
||||
sepFilter2D(e.Lsmooth, e.Lx, CV_32F, DxKx, DxKy);
|
||||
sepFilter2D(e.Lx, Lxx, CV_32F, DxKx, DxKy);
|
||||
sepFilter2D(e.Lx, Lxy, CV_32F, DyKx, DyKy);
|
||||
sepFilter2D(e.Lsmooth, e.Ly, CV_32F, DyKx, DyKy);
|
||||
sepFilter2D(e.Ly, Lyy, CV_32F, DyKx, DyKy);
|
||||
|
||||
// free Lsmooth to same some space in the pyramid, it is not needed anymore
|
||||
e.Lsmooth.release();
|
||||
|
||||
// compute determinant scaled by sigma
|
||||
float sigma_size_quat = (float)(e.sigma_size * e.sigma_size * e.sigma_size * e.sigma_size);
|
||||
compute_determinant(Lxx, Lxy, Lyy, e.Ldet, sigma_size_quat);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Evolution<MatType> >* evolution_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief This method computes the feature detector response for the nonlinear scale space
|
||||
* @details OCL version
|
||||
* @note We use the Hessian determinant as the feature detector response
|
||||
*/
|
||||
static inline void
|
||||
Compute_Determinant_Hessian_Response(UMatPyramid &evolution) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
DeterminantHessianResponse<UMat> body (evolution);
|
||||
body(Range(0, (int)evolution.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This method computes the feature detector response for the nonlinear scale space
|
||||
* @details CPU version
|
||||
* @note We use the Hessian determinant as the feature detector response
|
||||
*/
|
||||
static inline void
|
||||
Compute_Determinant_Hessian_Response(Pyramid &evolution) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
parallel_for_(Range(0, (int)evolution.size()), DeterminantHessianResponse<Mat>(evolution));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
/**
|
||||
* @brief This method selects interesting keypoints through the nonlinear scale space
|
||||
* @param kpts Vector of detected keypoints
|
||||
*/
|
||||
void AKAZEFeatures::Feature_Detection(std::vector<KeyPoint>& kpts)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
kpts.clear();
|
||||
std::vector<Mat> keypoints_by_layers;
|
||||
Find_Scale_Space_Extrema(keypoints_by_layers);
|
||||
Do_Subpixel_Refinement(keypoints_by_layers, kpts);
|
||||
Compute_Keypoints_Orientation(kpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This method searches v for a neighbor point of the point candidate p
|
||||
* @param x Coordinates of the keypoint candidate to search a neighbor
|
||||
* @param y Coordinates of the keypoint candidate to search a neighbor
|
||||
* @param mask Matrix holding keypoints positions
|
||||
* @param search_radius neighbour radius for searching keypoints
|
||||
* @param idx The index to mask, pointing to keypoint found.
|
||||
* @return true if a neighbor point is found; false otherwise
|
||||
*/
|
||||
static inline bool
|
||||
find_neighbor_point(const int x, const int y, const Mat &mask, const int search_radius, int &idx)
|
||||
{
|
||||
// search neighborhood for keypoints
|
||||
for (int i = y - search_radius; i < y + search_radius; ++i) {
|
||||
const uchar *curr = mask.ptr<uchar>(i);
|
||||
for (int j = x - search_radius; j < x + search_radius; ++j) {
|
||||
if (curr[j] == 0) {
|
||||
continue; // skip non-keypoint
|
||||
}
|
||||
// fine-compare with L2 metric (L2 is smaller than our search window)
|
||||
int dx = j - x;
|
||||
int dy = i - y;
|
||||
if (dx * dx + dy * dy <= search_radius * search_radius) {
|
||||
idx = i * mask.cols + j;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find keypoints in parallel for each pyramid layer
|
||||
*/
|
||||
class FindKeypointsSameScale : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
explicit FindKeypointsSameScale(const Pyramid& ev,
|
||||
std::vector<Mat>& kpts, float dthreshold)
|
||||
: evolution_(&ev), keypoints_by_layers_(&kpts), dthreshold_(dthreshold)
|
||||
{}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
const MEvolution &e = (*evolution_)[i];
|
||||
Mat &kpts = (*keypoints_by_layers_)[i];
|
||||
// this mask will hold positions of keypoints in this level
|
||||
kpts = Mat::zeros(e.Ldet.size(), CV_8UC1);
|
||||
|
||||
// if border is too big we shouldn't search any keypoints
|
||||
if (e.border + 1 >= e.Ldet.rows)
|
||||
continue;
|
||||
|
||||
const float * prev = e.Ldet.ptr<float>(e.border - 1);
|
||||
const float * curr = e.Ldet.ptr<float>(e.border );
|
||||
const float * next = e.Ldet.ptr<float>(e.border + 1);
|
||||
const float * ldet = e.Ldet.ptr<float>();
|
||||
uchar *mask = kpts.ptr<uchar>();
|
||||
const int search_radius = e.sigma_size; // size of keypoint in this level
|
||||
|
||||
for (int y = e.border; y < e.Ldet.rows - e.border; y++) {
|
||||
for (int x = e.border; x < e.Ldet.cols - e.border; x++) {
|
||||
const float value = curr[x];
|
||||
|
||||
// Filter the points with the detector threshold
|
||||
if (value <= dthreshold_)
|
||||
continue;
|
||||
if (value <= curr[x-1] || value <= curr[x+1])
|
||||
continue;
|
||||
if (value <= prev[x-1] || value <= prev[x ] || value <= prev[x+1])
|
||||
continue;
|
||||
if (value <= next[x-1] || value <= next[x ] || value <= next[x+1])
|
||||
continue;
|
||||
|
||||
int idx = 0;
|
||||
// Compare response with the same scale
|
||||
if (find_neighbor_point(x, y, kpts, search_radius, idx)) {
|
||||
if (value > ldet[idx]) {
|
||||
mask[idx] = 0; // clear old point - we have better candidate now
|
||||
} else {
|
||||
continue; // there already is a better keypoint
|
||||
}
|
||||
}
|
||||
|
||||
kpts.at<uchar>(y, x) = 1; // we have a new keypoint
|
||||
}
|
||||
|
||||
prev = curr;
|
||||
curr = next;
|
||||
next += e.Ldet.cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const Pyramid* evolution_;
|
||||
std::vector<Mat>* keypoints_by_layers_;
|
||||
float dthreshold_; ///< Detector response threshold to accept point
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This method finds extrema in the nonlinear scale space
|
||||
* @param keypoints_by_layers Output vectors of detected keypoints; one vector for each evolution level
|
||||
*/
|
||||
void AKAZEFeatures::Find_Scale_Space_Extrema(std::vector<Mat>& keypoints_by_layers)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
keypoints_by_layers.resize(evolution_.size());
|
||||
|
||||
// find points in the same level
|
||||
parallel_for_(Range(0, (int)evolution_.size()),
|
||||
FindKeypointsSameScale(evolution_, keypoints_by_layers, options_.dthreshold));
|
||||
|
||||
// Filter points with the lower scale level
|
||||
for (size_t i = 1; i < keypoints_by_layers.size(); i++) {
|
||||
// constants for this level
|
||||
const Mat &keypoints = keypoints_by_layers[i];
|
||||
const uchar *const kpts = keypoints_by_layers[i].ptr<uchar>();
|
||||
uchar *const kpts_prev = keypoints_by_layers[i-1].ptr<uchar>();
|
||||
const float *const ldet = evolution_[i].Ldet.ptr<float>();
|
||||
const float *const ldet_prev = evolution_[i-1].Ldet.ptr<float>();
|
||||
// ratios are just powers of 2
|
||||
const int diff_ratio = (int)evolution_[i].octave_ratio / (int)evolution_[i-1].octave_ratio;
|
||||
const int search_radius = evolution_[i].sigma_size * diff_ratio; // size of keypoint in this level
|
||||
|
||||
size_t j = 0;
|
||||
for (int y = 0; y < keypoints.rows; y++) {
|
||||
for (int x = 0; x < keypoints.cols; x++, j++) {
|
||||
if (kpts[j] == 0) {
|
||||
continue; // skip non-keypoints
|
||||
}
|
||||
int idx = 0;
|
||||
// project point to lower scale layer
|
||||
const int p_x = x * diff_ratio;
|
||||
const int p_y = y * diff_ratio;
|
||||
if (find_neighbor_point(p_x, p_y, keypoints_by_layers[i-1], search_radius, idx)) {
|
||||
if (ldet[j] > ldet_prev[idx]) {
|
||||
kpts_prev[idx] = 0; // clear keypoint in lower layer
|
||||
}
|
||||
// else this pt may be pruned by the upper scale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now filter points with the upper scale level (the other direction)
|
||||
for (int i = (int)keypoints_by_layers.size() - 2; i >= 0; i--) {
|
||||
// constants for this level
|
||||
const Mat &keypoints = keypoints_by_layers[i];
|
||||
const uchar *const kpts = keypoints_by_layers[i].ptr<uchar>();
|
||||
uchar *const kpts_next = keypoints_by_layers[i+1].ptr<uchar>();
|
||||
const float *const ldet = evolution_[i].Ldet.ptr<float>();
|
||||
const float *const ldet_next = evolution_[i+1].Ldet.ptr<float>();
|
||||
// ratios are just powers of 2, i+1 ratio is always greater or equal to i
|
||||
const int diff_ratio = (int)evolution_[i+1].octave_ratio / (int)evolution_[i].octave_ratio;
|
||||
const int search_radius = evolution_[i+1].sigma_size; // size of keypoints in upper level
|
||||
|
||||
size_t j = 0;
|
||||
for (int y = 0; y < keypoints.rows; y++) {
|
||||
for (int x = 0; x < keypoints.cols; x++, j++) {
|
||||
if (kpts[j] == 0) {
|
||||
continue; // skip non-keypoints
|
||||
}
|
||||
int idx = 0;
|
||||
// project point to upper scale layer
|
||||
const int p_x = x / diff_ratio;
|
||||
const int p_y = y / diff_ratio;
|
||||
if (find_neighbor_point(p_x, p_y, keypoints_by_layers[i+1], search_radius, idx)) {
|
||||
if (ldet[j] > ldet_next[idx]) {
|
||||
kpts_next[idx] = 0; // clear keypoint in upper layer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method performs subpixel refinement of the detected keypoints
|
||||
* @param keypoints_by_layers Input vectors of detected keypoints, sorted by evolution levels
|
||||
* @param kpts Output vector of the final refined keypoints
|
||||
*/
|
||||
void AKAZEFeatures::Do_Subpixel_Refinement(
|
||||
std::vector<Mat>& keypoints_by_layers, std::vector<KeyPoint>& output_keypoints)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
for (size_t i = 0; i < keypoints_by_layers.size(); i++) {
|
||||
const MEvolution &e = evolution_[i];
|
||||
const float * const ldet = e.Ldet.ptr<float>();
|
||||
const float ratio = e.octave_ratio;
|
||||
const int cols = e.Ldet.cols;
|
||||
const Mat& keypoints = keypoints_by_layers[i];
|
||||
const uchar *const kpts = keypoints.ptr<uchar>();
|
||||
|
||||
size_t j = 0;
|
||||
for (int y = 0; y < keypoints.rows; y++) {
|
||||
for (int x = 0; x < keypoints.cols; x++, j++) {
|
||||
if (kpts[j] == 0) {
|
||||
continue; // skip non-keypoints
|
||||
}
|
||||
|
||||
// create a new keypoint
|
||||
KeyPoint kp;
|
||||
kp.pt.x = x * e.octave_ratio;
|
||||
kp.pt.y = y * e.octave_ratio;
|
||||
kp.size = e.esigma * options_.derivative_factor;
|
||||
kp.angle = -1;
|
||||
kp.response = ldet[j];
|
||||
kp.octave = e.octave;
|
||||
kp.class_id = static_cast<int>(i);
|
||||
|
||||
// Compute the gradient
|
||||
float Dx = 0.5f * (ldet[ y *cols + x + 1] - ldet[ y *cols + x - 1]);
|
||||
float Dy = 0.5f * (ldet[(y + 1)*cols + x ] - ldet[(y - 1)*cols + x ]);
|
||||
|
||||
// Compute the Hessian
|
||||
float Dxx = ldet[ y *cols + x + 1] + ldet[ y *cols + x - 1] - 2.0f * ldet[y*cols + x];
|
||||
float Dyy = ldet[(y + 1)*cols + x ] + ldet[(y - 1)*cols + x ] - 2.0f * ldet[y*cols + x];
|
||||
float Dxy = 0.25f * (ldet[(y + 1)*cols + x + 1] + ldet[(y - 1)*cols + x - 1] -
|
||||
ldet[(y - 1)*cols + x + 1] - ldet[(y + 1)*cols + x - 1]);
|
||||
|
||||
// Solve the linear system
|
||||
Matx22f A( Dxx, Dxy,
|
||||
Dxy, Dyy );
|
||||
Vec2f b( -Dx, -Dy );
|
||||
Vec2f dst( 0.0f, 0.0f );
|
||||
solve(A, b, dst, DECOMP_LU);
|
||||
|
||||
float dx = dst(0);
|
||||
float dy = dst(1);
|
||||
|
||||
if (fabs(dx) > 1.0f || fabs(dy) > 1.0f)
|
||||
continue; // Ignore the point that is not stable
|
||||
|
||||
// Refine the coordinates
|
||||
kp.pt.x += dx * ratio + .5f*(ratio-1.f);
|
||||
kp.pt.y += dy * ratio + .5f*(ratio-1.f);
|
||||
|
||||
kp.angle = 0.0;
|
||||
kp.size *= 2.0f; // In OpenCV the size of a keypoint is the diameter
|
||||
|
||||
// Push the refined keypoint to the final storage
|
||||
output_keypoints.push_back(kp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
class SURF_Descriptor_Upright_64_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
SURF_Descriptor_Upright_64_Invoker(std::vector<KeyPoint>& kpts, Mat& desc, const Pyramid& evolution)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_SURF_Descriptor_Upright_64((*keypoints_)[i], descriptors_->ptr<float>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_SURF_Descriptor_Upright_64(const KeyPoint& kpt, float* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
const Pyramid* evolution_;
|
||||
};
|
||||
|
||||
class SURF_Descriptor_64_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
SURF_Descriptor_64_Invoker(std::vector<KeyPoint>& kpts, Mat& desc, Pyramid& evolution)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_SURF_Descriptor_64((*keypoints_)[i], descriptors_->ptr<float>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_SURF_Descriptor_64(const KeyPoint& kpt, float* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
};
|
||||
|
||||
class MSURF_Upright_Descriptor_64_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
MSURF_Upright_Descriptor_64_Invoker(std::vector<KeyPoint>& kpts, Mat& desc, Pyramid& evolution)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_MSURF_Upright_Descriptor_64((*keypoints_)[i], descriptors_->ptr<float>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_MSURF_Upright_Descriptor_64(const KeyPoint& kpt, float* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
};
|
||||
|
||||
class MSURF_Descriptor_64_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
MSURF_Descriptor_64_Invoker(std::vector<KeyPoint>& kpts, Mat& desc, Pyramid& evolution)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_MSURF_Descriptor_64((*keypoints_)[i], descriptors_->ptr<float>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_MSURF_Descriptor_64(const KeyPoint& kpt, float* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
};
|
||||
|
||||
class Upright_MLDB_Full_Descriptor_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
Upright_MLDB_Full_Descriptor_Invoker(std::vector<KeyPoint>& kpts, Mat& desc, Pyramid& evolution, AKAZEOptions& options)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
, options_(&options)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_Upright_MLDB_Full_Descriptor((*keypoints_)[i], descriptors_->ptr<unsigned char>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_Upright_MLDB_Full_Descriptor(const KeyPoint& kpt, unsigned char* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
AKAZEOptions* options_;
|
||||
};
|
||||
|
||||
class Upright_MLDB_Descriptor_Subset_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
Upright_MLDB_Descriptor_Subset_Invoker(std::vector<KeyPoint>& kpts,
|
||||
Mat& desc,
|
||||
Pyramid& evolution,
|
||||
AKAZEOptions& options,
|
||||
Mat descriptorSamples,
|
||||
Mat descriptorBits)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
, options_(&options)
|
||||
, descriptorSamples_(descriptorSamples)
|
||||
, descriptorBits_(descriptorBits)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_Upright_MLDB_Descriptor_Subset((*keypoints_)[i], descriptors_->ptr<unsigned char>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_Upright_MLDB_Descriptor_Subset(const KeyPoint& kpt, unsigned char* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
AKAZEOptions* options_;
|
||||
|
||||
Mat descriptorSamples_; // List of positions in the grids to sample LDB bits from.
|
||||
Mat descriptorBits_;
|
||||
};
|
||||
|
||||
class MLDB_Full_Descriptor_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
MLDB_Full_Descriptor_Invoker(std::vector<KeyPoint>& kpts, Mat& desc, Pyramid& evolution, AKAZEOptions& options)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
, options_(&options)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_MLDB_Full_Descriptor((*keypoints_)[i], descriptors_->ptr<unsigned char>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_MLDB_Full_Descriptor(const KeyPoint& kpt, unsigned char* desc, int desc_size) const;
|
||||
void MLDB_Fill_Values(float* values, int sample_step, int level,
|
||||
float xf, float yf, float co, float si, float scale) const;
|
||||
void MLDB_Binary_Comparisons(float* values, unsigned char* desc,
|
||||
int count, int& dpos) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
AKAZEOptions* options_;
|
||||
};
|
||||
|
||||
class MLDB_Descriptor_Subset_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
MLDB_Descriptor_Subset_Invoker(std::vector<KeyPoint>& kpts,
|
||||
Mat& desc,
|
||||
Pyramid& evolution,
|
||||
AKAZEOptions& options,
|
||||
Mat descriptorSamples,
|
||||
Mat descriptorBits)
|
||||
: keypoints_(&kpts)
|
||||
, descriptors_(&desc)
|
||||
, evolution_(&evolution)
|
||||
, options_(&options)
|
||||
, descriptorSamples_(descriptorSamples)
|
||||
, descriptorBits_(descriptorBits)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Get_MLDB_Descriptor_Subset((*keypoints_)[i], descriptors_->ptr<unsigned char>(i), descriptors_->cols);
|
||||
}
|
||||
}
|
||||
|
||||
void Get_MLDB_Descriptor_Subset(const KeyPoint& kpt, unsigned char* desc, int desc_size) const;
|
||||
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
Mat* descriptors_;
|
||||
Pyramid* evolution_;
|
||||
AKAZEOptions* options_;
|
||||
|
||||
Mat descriptorSamples_; // List of positions in the grids to sample LDB bits from.
|
||||
Mat descriptorBits_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This method computes the set of descriptors through the nonlinear scale space
|
||||
* @param kpts Vector of detected keypoints
|
||||
* @param desc Matrix to store the descriptors
|
||||
*/
|
||||
void AKAZEFeatures::Compute_Descriptors(std::vector<KeyPoint>& kpts, OutputArray descriptors)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
for(size_t i = 0; i < kpts.size(); i++)
|
||||
{
|
||||
CV_Assert(0 <= kpts[i].class_id && kpts[i].class_id < static_cast<int>(evolution_.size()));
|
||||
}
|
||||
|
||||
// Allocate memory for the matrix with the descriptors
|
||||
int descriptor_size = 64;
|
||||
int descriptor_type = CV_32FC1;
|
||||
if (options_.descriptor >= AKAZE::DESCRIPTOR_MLDB_UPRIGHT)
|
||||
{
|
||||
int descriptor_bits = (options_.descriptor_size == 0)
|
||||
? (6 + 36 + 120)*options_.descriptor_channels // the full length binary descriptor -> 486 bits
|
||||
: options_.descriptor_size; // the random bit selection length binary descriptor
|
||||
descriptor_size = divUp(descriptor_bits, 8);
|
||||
descriptor_type = CV_8UC1;
|
||||
}
|
||||
descriptors.create((int)kpts.size(), descriptor_size, descriptor_type);
|
||||
|
||||
Mat desc = descriptors.getMat();
|
||||
|
||||
switch (options_.descriptor)
|
||||
{
|
||||
case AKAZE::DESCRIPTOR_KAZE_UPRIGHT: // Upright descriptors, not invariant to rotation
|
||||
{
|
||||
parallel_for_(Range(0, (int)kpts.size()), MSURF_Upright_Descriptor_64_Invoker(kpts, desc, evolution_));
|
||||
}
|
||||
break;
|
||||
case AKAZE::DESCRIPTOR_KAZE:
|
||||
{
|
||||
parallel_for_(Range(0, (int)kpts.size()), MSURF_Descriptor_64_Invoker(kpts, desc, evolution_));
|
||||
}
|
||||
break;
|
||||
case AKAZE::DESCRIPTOR_MLDB_UPRIGHT: // Upright descriptors, not invariant to rotation
|
||||
{
|
||||
if (options_.descriptor_size == 0)
|
||||
parallel_for_(Range(0, (int)kpts.size()), Upright_MLDB_Full_Descriptor_Invoker(kpts, desc, evolution_, options_));
|
||||
else
|
||||
parallel_for_(Range(0, (int)kpts.size()), Upright_MLDB_Descriptor_Subset_Invoker(kpts, desc, evolution_, options_, descriptorSamples_, descriptorBits_));
|
||||
}
|
||||
break;
|
||||
case AKAZE::DESCRIPTOR_MLDB:
|
||||
{
|
||||
if (options_.descriptor_size == 0)
|
||||
parallel_for_(Range(0, (int)kpts.size()), MLDB_Full_Descriptor_Invoker(kpts, desc, evolution_, options_));
|
||||
else
|
||||
parallel_for_(Range(0, (int)kpts.size()), MLDB_Descriptor_Subset_Invoker(kpts, desc, evolution_, options_, descriptorSamples_, descriptorBits_));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function samples the derivative responses Lx and Ly for the points
|
||||
* within the radius of 6*scale from (x0, y0), then multiply 2D Gaussian weight
|
||||
* @param Lx Horizontal derivative
|
||||
* @param Ly Vertical derivative
|
||||
* @param x0 X-coordinate of the center point
|
||||
* @param y0 Y-coordinate of the center point
|
||||
* @param scale The sampling step
|
||||
* @param resX Output array of the weighted horizontal derivative responses
|
||||
* @param resY Output array of the weighted vertical derivative responses
|
||||
*/
|
||||
static inline
|
||||
void Sample_Derivative_Response_Radius6(const Mat &Lx, const Mat &Ly,
|
||||
const int x0, const int y0, const int scale,
|
||||
float *resX, float *resY)
|
||||
{
|
||||
/* ************************************************************************* */
|
||||
/// Lookup table for 2d gaussian (sigma = 2.5) where (0,0) is top left and (6,6) is bottom right
|
||||
static const float gauss25[7][7] =
|
||||
{
|
||||
{ 0.02546481f, 0.02350698f, 0.01849125f, 0.01239505f, 0.00708017f, 0.00344629f, 0.00142946f },
|
||||
{ 0.02350698f, 0.02169968f, 0.01706957f, 0.01144208f, 0.00653582f, 0.00318132f, 0.00131956f },
|
||||
{ 0.01849125f, 0.01706957f, 0.01342740f, 0.00900066f, 0.00514126f, 0.00250252f, 0.00103800f },
|
||||
{ 0.01239505f, 0.01144208f, 0.00900066f, 0.00603332f, 0.00344629f, 0.00167749f, 0.00069579f },
|
||||
{ 0.00708017f, 0.00653582f, 0.00514126f, 0.00344629f, 0.00196855f, 0.00095820f, 0.00039744f },
|
||||
{ 0.00344629f, 0.00318132f, 0.00250252f, 0.00167749f, 0.00095820f, 0.00046640f, 0.00019346f },
|
||||
{ 0.00142946f, 0.00131956f, 0.00103800f, 0.00069579f, 0.00039744f, 0.00019346f, 0.00008024f }
|
||||
};
|
||||
static const struct gtable
|
||||
{
|
||||
float weight[109];
|
||||
int xidx[109];
|
||||
int yidx[109];
|
||||
|
||||
explicit gtable(void)
|
||||
{
|
||||
// Generate the weight and indices by one-time initialization
|
||||
int k = 0;
|
||||
for (int i = -6; i <= 6; ++i) {
|
||||
for (int j = -6; j <= 6; ++j) {
|
||||
if (i*i + j*j < 36) {
|
||||
CV_Assert(k < 109);
|
||||
weight[k] = gauss25[abs(i)][abs(j)];
|
||||
yidx[k] = i;
|
||||
xidx[k] = j;
|
||||
++k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} g;
|
||||
|
||||
CV_Assert(x0 - 6 * scale >= 0 && x0 + 6 * scale < Lx.cols);
|
||||
CV_Assert(y0 - 6 * scale >= 0 && y0 + 6 * scale < Lx.rows);
|
||||
|
||||
for (int i = 0; i < 109; i++)
|
||||
{
|
||||
int y = y0 + g.yidx[i] * scale;
|
||||
int x = x0 + g.xidx[i] * scale;
|
||||
|
||||
float w = g.weight[i];
|
||||
resX[i] = w * Lx.at<float>(y, x);
|
||||
resY[i] = w * Ly.at<float>(y, x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function sorts a[] by quantized float values
|
||||
* @param a[] Input floating point array to sort
|
||||
* @param n The length of a[]
|
||||
* @param quantum The interval to convert a[i]'s float values to integers
|
||||
* @param nkeys a[i] < nkeys * quantum
|
||||
* @param idx[] Output array of the indices: a[idx[i]] forms a sorted array
|
||||
* @param cum[] Output array of the starting indices of quantized floats
|
||||
* @note The values of a[] in [k*quantum, (k + 1)*quantum) is labeled by
|
||||
* the integer k, which is calculated by floor(a[i]/quantum). After sorting,
|
||||
* the values from a[idx[cum[k]]] to a[idx[cum[k+1]-1]] are all labeled by k.
|
||||
* This sorting is unstable to reduce the memory access.
|
||||
*/
|
||||
static inline
|
||||
void quantized_counting_sort(const float a[], const int n,
|
||||
const float quantum, const int nkeys,
|
||||
int idx[/*n*/], int cum[/*nkeys + 1*/])
|
||||
{
|
||||
CV_Assert(nkeys > 0);
|
||||
memset(cum, 0, sizeof(cum[0]) * (nkeys + 1));
|
||||
|
||||
// Count up the quantized values
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int b = (int)(a[i] / quantum);
|
||||
if (b < 0 || b >= nkeys)
|
||||
b = 0;
|
||||
cum[b]++;
|
||||
}
|
||||
|
||||
// Compute the inclusive prefix sum i.e. the end indices; cum[nkeys] is the total
|
||||
for (int i = 1; i <= nkeys; i++)
|
||||
{
|
||||
cum[i] += cum[i - 1];
|
||||
}
|
||||
CV_Assert(cum[nkeys] == n);
|
||||
|
||||
// Generate the sorted indices; cum[] becomes the exclusive prefix sum i.e. the start indices of keys
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int b = (int)(a[i] / quantum);
|
||||
if (b < 0 || b >= nkeys)
|
||||
b = 0;
|
||||
idx[--cum[b]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function computes the main orientation for a given keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @note The orientation is computed using a similar approach as described in the
|
||||
* original SURF method. See Bay et al., Speeded Up Robust Features, ECCV 2006
|
||||
*/
|
||||
static inline
|
||||
void Compute_Main_Orientation(KeyPoint& kpt, const Pyramid& evolution)
|
||||
{
|
||||
// get the right evolution level for this keypoint
|
||||
const MEvolution& e = evolution[kpt.class_id];
|
||||
// Get the information from the keypoint
|
||||
int scale = cvRound(0.5f * kpt.size / e.octave_ratio);
|
||||
int x0 = cvRound(kpt.pt.x / e.octave_ratio);
|
||||
int y0 = cvRound(kpt.pt.y / e.octave_ratio);
|
||||
|
||||
// Sample derivatives responses for the points within radius of 6*scale
|
||||
const int ang_size = 109;
|
||||
float resX[ang_size], resY[ang_size];
|
||||
Sample_Derivative_Response_Radius6(e.Lx, e.Ly, x0, y0, scale, resX, resY);
|
||||
|
||||
// Compute the angle of each gradient vector
|
||||
float Ang[ang_size];
|
||||
hal::fastAtan2(resY, resX, Ang, ang_size, false);
|
||||
|
||||
// Sort by the angles; angles are labeled by slices of 0.15 radian
|
||||
const int slices = 42;
|
||||
const float ang_step = (float)(2.0 * CV_PI / slices);
|
||||
int slice[slices + 1];
|
||||
int sorted_idx[ang_size];
|
||||
quantized_counting_sort(Ang, ang_size, ang_step, slices, sorted_idx, slice);
|
||||
|
||||
// Find the main angle by sliding a window of 7-slice size(=PI/3) around the keypoint
|
||||
const int win = 7;
|
||||
|
||||
float maxX = 0.0f, maxY = 0.0f;
|
||||
for (int i = slice[0]; i < slice[win]; i++) {
|
||||
const int idx = sorted_idx[i];
|
||||
maxX += resX[idx];
|
||||
maxY += resY[idx];
|
||||
}
|
||||
float maxNorm = maxX * maxX + maxY * maxY;
|
||||
|
||||
for (int sn = 1; sn <= slices - win; sn++) {
|
||||
|
||||
if (slice[sn] == slice[sn - 1] && slice[sn + win] == slice[sn + win - 1])
|
||||
continue; // The contents of the window didn't change; don't repeat the computation
|
||||
|
||||
float sumX = 0.0f, sumY = 0.0f;
|
||||
for (int i = slice[sn]; i < slice[sn + win]; i++) {
|
||||
const int idx = sorted_idx[i];
|
||||
sumX += resX[idx];
|
||||
sumY += resY[idx];
|
||||
}
|
||||
|
||||
float norm = sumX * sumX + sumY * sumY;
|
||||
if (norm > maxNorm)
|
||||
maxNorm = norm, maxX = sumX, maxY = sumY; // Found bigger one; update
|
||||
}
|
||||
|
||||
for (int sn = slices - win + 1; sn < slices; sn++) {
|
||||
int remain = sn + win - slices;
|
||||
|
||||
if (slice[sn] == slice[sn - 1] && slice[remain] == slice[remain - 1])
|
||||
continue;
|
||||
|
||||
float sumX = 0.0f, sumY = 0.0f;
|
||||
for (int i = slice[sn]; i < slice[slices]; i++) {
|
||||
const int idx = sorted_idx[i];
|
||||
sumX += resX[idx];
|
||||
sumY += resY[idx];
|
||||
}
|
||||
for (int i = slice[0]; i < slice[remain]; i++) {
|
||||
const int idx = sorted_idx[i];
|
||||
sumX += resX[idx];
|
||||
sumY += resY[idx];
|
||||
}
|
||||
|
||||
float norm = sumX * sumX + sumY * sumY;
|
||||
if (norm > maxNorm)
|
||||
maxNorm = norm, maxX = sumX, maxY = sumY;
|
||||
}
|
||||
|
||||
// Store the final result
|
||||
kpt.angle = fastAtan2(maxY, maxX);
|
||||
}
|
||||
|
||||
class ComputeKeypointOrientation : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
ComputeKeypointOrientation(std::vector<KeyPoint>& kpts,
|
||||
const Pyramid& evolution)
|
||||
: keypoints_(&kpts)
|
||||
, evolution_(&evolution)
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
Compute_Main_Orientation((*keypoints_)[i], *evolution_);
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::vector<KeyPoint>* keypoints_;
|
||||
const Pyramid* evolution_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This method computes the main orientation for a given keypoints
|
||||
* @param kpts Input keypoints
|
||||
*/
|
||||
void AKAZEFeatures::Compute_Keypoints_Orientation(std::vector<KeyPoint>& kpts) const
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
parallel_for_(Range(0, (int)kpts.size()), ComputeKeypointOrientation(kpts, evolution_));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the upright descriptor (not rotation invariant) of
|
||||
* the provided keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
* @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired
|
||||
* from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching,
|
||||
* ECCV 2008
|
||||
*/
|
||||
void MSURF_Upright_Descriptor_64_Invoker::Get_MSURF_Upright_Descriptor_64(const KeyPoint& kpt, float *desc, int desc_size) const {
|
||||
|
||||
const int dsize = 64;
|
||||
CV_Assert(desc_size == dsize);
|
||||
|
||||
float dx = 0.0, dy = 0.0, mdx = 0.0, mdy = 0.0, gauss_s1 = 0.0, gauss_s2 = 0.0;
|
||||
float rx = 0.0, ry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0;
|
||||
float sample_x = 0.0, sample_y = 0.0;
|
||||
int x1 = 0, y1 = 0, sample_step = 0, pattern_size = 0;
|
||||
int x2 = 0, y2 = 0, kx = 0, ky = 0, i = 0, j = 0, dcount = 0;
|
||||
float fx = 0.0, fy = 0.0, ratio = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0;
|
||||
int scale = 0;
|
||||
|
||||
// Subregion centers for the 4x4 gaussian weighting
|
||||
float cx = -0.5f, cy = 0.5f;
|
||||
|
||||
const Pyramid& evolution = *evolution_;
|
||||
|
||||
// Set the descriptor size and the sample and pattern sizes
|
||||
sample_step = 5;
|
||||
pattern_size = 12;
|
||||
|
||||
// Get the information from the keypoint
|
||||
ratio = (float)(1 << kpt.octave);
|
||||
scale = cvRound(0.5f*kpt.size / ratio);
|
||||
const int level = kpt.class_id;
|
||||
const Mat Lx = evolution[level].Lx;
|
||||
const Mat Ly = evolution[level].Ly;
|
||||
yf = kpt.pt.y / ratio;
|
||||
xf = kpt.pt.x / ratio;
|
||||
|
||||
i = -8;
|
||||
|
||||
// Calculate descriptor for this interest point
|
||||
// Area of size 24 s x 24 s
|
||||
while (i < pattern_size) {
|
||||
j = -8;
|
||||
i = i - 4;
|
||||
|
||||
cx += 1.0f;
|
||||
cy = -0.5f;
|
||||
|
||||
while (j < pattern_size) {
|
||||
dx = dy = mdx = mdy = 0.0;
|
||||
cy += 1.0f;
|
||||
j = j - 4;
|
||||
|
||||
ky = i + sample_step;
|
||||
kx = j + sample_step;
|
||||
|
||||
ys = yf + (ky*scale);
|
||||
xs = xf + (kx*scale);
|
||||
|
||||
for (int k = i; k < i + 9; k++) {
|
||||
for (int l = j; l < j + 9; l++) {
|
||||
sample_y = k*scale + yf;
|
||||
sample_x = l*scale + xf;
|
||||
|
||||
//Get the gaussian weighted x and y responses
|
||||
gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.50f*scale);
|
||||
|
||||
y1 = cvFloor(sample_y);
|
||||
x1 = cvFloor(sample_x);
|
||||
|
||||
y2 = y1 + 1;
|
||||
x2 = x1 + 1;
|
||||
|
||||
if (x1 < 0 || y1 < 0 || x2 >= Lx.cols || y2 >= Lx.rows)
|
||||
continue; // FIXIT Boundaries
|
||||
|
||||
fx = sample_x - x1;
|
||||
fy = sample_y - y1;
|
||||
|
||||
res1 = Lx.at<float>(y1, x1);
|
||||
res2 = Lx.at<float>(y1, x2);
|
||||
res3 = Lx.at<float>(y2, x1);
|
||||
res4 = Lx.at<float>(y2, x2);
|
||||
rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
res1 = Ly.at<float>(y1, x1);
|
||||
res2 = Ly.at<float>(y1, x2);
|
||||
res3 = Ly.at<float>(y2, x1);
|
||||
res4 = Ly.at<float>(y2, x2);
|
||||
ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
rx = gauss_s1*rx;
|
||||
ry = gauss_s1*ry;
|
||||
|
||||
// Sum the derivatives to the cumulative descriptor
|
||||
dx += rx;
|
||||
dy += ry;
|
||||
mdx += fabs(rx);
|
||||
mdy += fabs(ry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the values to the descriptor vector
|
||||
gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f);
|
||||
|
||||
desc[dcount++] = dx*gauss_s2;
|
||||
desc[dcount++] = dy*gauss_s2;
|
||||
desc[dcount++] = mdx*gauss_s2;
|
||||
desc[dcount++] = mdy*gauss_s2;
|
||||
|
||||
len += (dx*dx + dy*dy + mdx*mdx + mdy*mdy)*gauss_s2*gauss_s2;
|
||||
|
||||
j += 9;
|
||||
}
|
||||
|
||||
i += 9;
|
||||
}
|
||||
|
||||
CV_Assert(dcount == desc_size);
|
||||
|
||||
// convert to unit vector
|
||||
len = sqrt(len);
|
||||
|
||||
const float len_inv = 1.0f / len;
|
||||
for (i = 0; i < dsize; i++) {
|
||||
desc[i] *= len_inv;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the descriptor of the provided keypoint given the
|
||||
* main orientation of the keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
* @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired
|
||||
* from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching,
|
||||
* ECCV 2008
|
||||
*/
|
||||
void MSURF_Descriptor_64_Invoker::Get_MSURF_Descriptor_64(const KeyPoint& kpt, float *desc, int desc_size) const {
|
||||
|
||||
const int dsize = 64;
|
||||
CV_Assert(desc_size == dsize);
|
||||
|
||||
float dx = 0.0, dy = 0.0, mdx = 0.0, mdy = 0.0, gauss_s1 = 0.0, gauss_s2 = 0.0;
|
||||
float rx = 0.0, ry = 0.0, rrx = 0.0, rry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0;
|
||||
float sample_x = 0.0, sample_y = 0.0, co = 0.0, si = 0.0, angle = 0.0;
|
||||
float fx = 0.0, fy = 0.0, ratio = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0;
|
||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0, sample_step = 0, pattern_size = 0;
|
||||
int kx = 0, ky = 0, i = 0, j = 0, dcount = 0;
|
||||
int scale = 0;
|
||||
|
||||
// Subregion centers for the 4x4 gaussian weighting
|
||||
float cx = -0.5f, cy = 0.5f;
|
||||
|
||||
const Pyramid& evolution = *evolution_;
|
||||
|
||||
// Set the descriptor size and the sample and pattern sizes
|
||||
sample_step = 5;
|
||||
pattern_size = 12;
|
||||
|
||||
// Get the information from the keypoint
|
||||
ratio = (float)(1 << kpt.octave);
|
||||
scale = cvRound(0.5f*kpt.size / ratio);
|
||||
angle = kpt.angle * static_cast<float>(CV_PI / 180.f);
|
||||
const int level = kpt.class_id;
|
||||
const Mat Lx = evolution[level].Lx;
|
||||
const Mat Ly = evolution[level].Ly;
|
||||
yf = kpt.pt.y / ratio;
|
||||
xf = kpt.pt.x / ratio;
|
||||
co = cos(angle);
|
||||
si = sin(angle);
|
||||
|
||||
i = -8;
|
||||
|
||||
// Calculate descriptor for this interest point
|
||||
// Area of size 24 s x 24 s
|
||||
while (i < pattern_size) {
|
||||
j = -8;
|
||||
i = i - 4;
|
||||
|
||||
cx += 1.0f;
|
||||
cy = -0.5f;
|
||||
|
||||
while (j < pattern_size) {
|
||||
dx = dy = mdx = mdy = 0.0;
|
||||
cy += 1.0f;
|
||||
j = j - 4;
|
||||
|
||||
ky = i + sample_step;
|
||||
kx = j + sample_step;
|
||||
|
||||
xs = xf + (-kx*scale*si + ky*scale*co);
|
||||
ys = yf + (kx*scale*co + ky*scale*si);
|
||||
|
||||
for (int k = i; k < i + 9; ++k) {
|
||||
for (int l = j; l < j + 9; ++l) {
|
||||
// Get coords of sample point on the rotated axis
|
||||
sample_y = yf + (l*scale*co + k*scale*si);
|
||||
sample_x = xf + (-l*scale*si + k*scale*co);
|
||||
|
||||
// Get the gaussian weighted x and y responses
|
||||
gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale);
|
||||
|
||||
y1 = cvFloor(sample_y);
|
||||
x1 = cvFloor(sample_x);
|
||||
|
||||
y2 = y1 + 1;
|
||||
x2 = x1 + 1;
|
||||
|
||||
if (x1 < 0 || y1 < 0 || x2 >= Lx.cols || y2 >= Lx.rows)
|
||||
continue; // FIXIT Boundaries
|
||||
|
||||
fx = sample_x - x1;
|
||||
fy = sample_y - y1;
|
||||
|
||||
res1 = Lx.at<float>(y1, x1);
|
||||
res2 = Lx.at<float>(y1, x2);
|
||||
res3 = Lx.at<float>(y2, x1);
|
||||
res4 = Lx.at<float>(y2, x2);
|
||||
rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
res1 = Ly.at<float>(y1, x1);
|
||||
res2 = Ly.at<float>(y1, x2);
|
||||
res3 = Ly.at<float>(y2, x1);
|
||||
res4 = Ly.at<float>(y2, x2);
|
||||
ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
// Get the x and y derivatives on the rotated axis
|
||||
rry = gauss_s1*(rx*co + ry*si);
|
||||
rrx = gauss_s1*(-rx*si + ry*co);
|
||||
|
||||
// Sum the derivatives to the cumulative descriptor
|
||||
dx += rrx;
|
||||
dy += rry;
|
||||
mdx += fabs(rrx);
|
||||
mdy += fabs(rry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the values to the descriptor vector
|
||||
gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f);
|
||||
desc[dcount++] = dx*gauss_s2;
|
||||
desc[dcount++] = dy*gauss_s2;
|
||||
desc[dcount++] = mdx*gauss_s2;
|
||||
desc[dcount++] = mdy*gauss_s2;
|
||||
|
||||
len += (dx*dx + dy*dy + mdx*mdx + mdy*mdy)*gauss_s2*gauss_s2;
|
||||
|
||||
j += 9;
|
||||
}
|
||||
|
||||
i += 9;
|
||||
}
|
||||
|
||||
CV_Assert(dcount == desc_size);
|
||||
|
||||
// convert to unit vector
|
||||
len = sqrt(len);
|
||||
|
||||
const float len_inv = 1.0f / len;
|
||||
for (i = 0; i < dsize; i++) {
|
||||
desc[i] *= len_inv;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the rupright descriptor (not rotation invariant) of
|
||||
* the provided keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
*/
|
||||
void Upright_MLDB_Full_Descriptor_Invoker::Get_Upright_MLDB_Full_Descriptor(const KeyPoint& kpt, unsigned char *desc, int desc_size) const {
|
||||
|
||||
const AKAZEOptions & options = *options_;
|
||||
const Pyramid& evolution = *evolution_;
|
||||
|
||||
// Buffer for the M-LDB descriptor
|
||||
const int max_channels = 3;
|
||||
CV_Assert(options.descriptor_channels <= max_channels);
|
||||
float values[16*max_channels];
|
||||
|
||||
// Get the information from the keypoint
|
||||
const float ratio = (float)(1 << kpt.octave);
|
||||
const int scale = cvRound(0.5f*kpt.size / ratio);
|
||||
const int level = kpt.class_id;
|
||||
const Mat Lx = evolution[level].Lx;
|
||||
const Mat Ly = evolution[level].Ly;
|
||||
const Mat Lt = evolution[level].Lt;
|
||||
const float yf = kpt.pt.y / ratio;
|
||||
const float xf = kpt.pt.x / ratio;
|
||||
|
||||
// For 2x2 grid, 3x3 grid and 4x4 grid
|
||||
const int pattern_size = options_->descriptor_pattern_size;
|
||||
CV_Assert((pattern_size & 1) == 0);
|
||||
const int sample_step[3] = {
|
||||
pattern_size,
|
||||
divUp(pattern_size * 2, 3),
|
||||
divUp(pattern_size, 2)
|
||||
};
|
||||
|
||||
memset(desc, 0, desc_size);
|
||||
|
||||
// For the three grids
|
||||
int dcount1 = 0;
|
||||
for (int z = 0; z < 3; z++) {
|
||||
int dcount2 = 0;
|
||||
const int step = sample_step[z];
|
||||
for (int i = -pattern_size; i < pattern_size; i += step) {
|
||||
for (int j = -pattern_size; j < pattern_size; j += step) {
|
||||
float di = 0.0, dx = 0.0, dy = 0.0;
|
||||
|
||||
int nsamples = 0;
|
||||
for (int k = 0; k < step; k++) {
|
||||
for (int l = 0; l < step; l++) {
|
||||
|
||||
// Get the coordinates of the sample point
|
||||
const float sample_y = yf + (l+j)*scale;
|
||||
const float sample_x = xf + (k+i)*scale;
|
||||
|
||||
const int y1 = cvRound(sample_y);
|
||||
const int x1 = cvRound(sample_x);
|
||||
|
||||
if (y1 < 0 || y1 >= Lt.rows || x1 < 0 || x1 >= Lt.cols)
|
||||
continue; // Boundaries
|
||||
|
||||
const float ri = Lt.at<float>(y1, x1);
|
||||
const float rx = Lx.at<float>(y1, x1);
|
||||
const float ry = Ly.at<float>(y1, x1);
|
||||
|
||||
di += ri;
|
||||
dx += rx;
|
||||
dy += ry;
|
||||
nsamples++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nsamples > 0)
|
||||
{
|
||||
const float nsamples_inv = 1.0f / nsamples;
|
||||
di *= nsamples_inv;
|
||||
dx *= nsamples_inv;
|
||||
dy *= nsamples_inv;
|
||||
}
|
||||
|
||||
float *val = &values[dcount2*max_channels];
|
||||
*(val) = di;
|
||||
*(val+1) = dx;
|
||||
*(val+2) = dy;
|
||||
dcount2++;
|
||||
}
|
||||
}
|
||||
|
||||
// Do binary comparison
|
||||
const int num = (z + 2) * (z + 2);
|
||||
for (int i = 0; i < num; i++) {
|
||||
for (int j = i + 1; j < num; j++) {
|
||||
const float * valI = &values[i*max_channels];
|
||||
const float * valJ = &values[j*max_channels];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
if (*(valI + k) > *(valJ + k)) {
|
||||
desc[dcount1 / 8] |= (1 << (dcount1 % 8));
|
||||
}
|
||||
dcount1++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // for (int z = 0; z < 3; z++)
|
||||
|
||||
CV_Assert(dcount1 <= desc_size*8);
|
||||
CV_Assert(divUp(dcount1, 8) == desc_size);
|
||||
}
|
||||
|
||||
void MLDB_Full_Descriptor_Invoker::MLDB_Fill_Values(float* values, int sample_step, const int level,
|
||||
float xf, float yf, float co, float si, float scale) const
|
||||
{
|
||||
const Pyramid& evolution = *evolution_;
|
||||
int pattern_size = options_->descriptor_pattern_size;
|
||||
int chan = options_->descriptor_channels;
|
||||
const Mat Lx = evolution[level].Lx;
|
||||
const Mat Ly = evolution[level].Ly;
|
||||
const Mat Lt = evolution[level].Lt;
|
||||
|
||||
const Size size = Lt.size();
|
||||
CV_Assert(size == Lx.size());
|
||||
CV_Assert(size == Ly.size());
|
||||
|
||||
int valpos = 0;
|
||||
for (int i = -pattern_size; i < pattern_size; i += sample_step) {
|
||||
for (int j = -pattern_size; j < pattern_size; j += sample_step) {
|
||||
float di = 0.0f, dx = 0.0f, dy = 0.0f;
|
||||
|
||||
int nsamples = 0;
|
||||
for (int k = i; k < i + sample_step; k++) {
|
||||
for (int l = j; l < j + sample_step; l++) {
|
||||
float sample_y = yf + (l*co * scale + k*si*scale);
|
||||
float sample_x = xf + (-l*si * scale + k*co*scale);
|
||||
|
||||
int y1 = cvRound(sample_y);
|
||||
int x1 = cvRound(sample_x);
|
||||
|
||||
if (y1 < 0 || y1 >= Lt.rows || x1 < 0 || x1 >= Lt.cols)
|
||||
continue; // Boundaries
|
||||
|
||||
float ri = Lt.at<float>(y1, x1);
|
||||
di += ri;
|
||||
|
||||
if(chan > 1) {
|
||||
float rx = Lx.at<float>(y1, x1);
|
||||
float ry = Ly.at<float>(y1, x1);
|
||||
if (chan == 2) {
|
||||
dx += sqrtf(rx*rx + ry*ry);
|
||||
}
|
||||
else {
|
||||
float rry = rx*co + ry*si;
|
||||
float rrx = -rx*si + ry*co;
|
||||
dx += rrx;
|
||||
dy += rry;
|
||||
}
|
||||
}
|
||||
nsamples++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nsamples > 0)
|
||||
{
|
||||
const float nsamples_inv = 1.0f / nsamples;
|
||||
di *= nsamples_inv;
|
||||
dx *= nsamples_inv;
|
||||
dy *= nsamples_inv;
|
||||
}
|
||||
|
||||
values[valpos] = di;
|
||||
if (chan > 1) {
|
||||
values[valpos + 1] = dx;
|
||||
}
|
||||
if (chan > 2) {
|
||||
values[valpos + 2] = dy;
|
||||
}
|
||||
valpos += chan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MLDB_Full_Descriptor_Invoker::MLDB_Binary_Comparisons(float* values, unsigned char* desc,
|
||||
int count, int& dpos) const {
|
||||
int chan = options_->descriptor_channels;
|
||||
int* ivalues = (int*) values;
|
||||
for(int i = 0; i < count * chan; i++) {
|
||||
ivalues[i] = CV_TOGGLE_FLT(ivalues[i]);
|
||||
}
|
||||
|
||||
for(int pos = 0; pos < chan; pos++) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
int ival = ivalues[chan * i + pos];
|
||||
for (int j = i + 1; j < count; j++) {
|
||||
if (ival > ivalues[chan * j + pos]) {
|
||||
desc[dpos >> 3] |= (1 << (dpos & 7));
|
||||
}
|
||||
dpos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the descriptor of the provided keypoint given the
|
||||
* main orientation of the keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
*/
|
||||
void MLDB_Full_Descriptor_Invoker::Get_MLDB_Full_Descriptor(const KeyPoint& kpt, unsigned char *desc, int desc_size) const {
|
||||
|
||||
const int max_channels = 3;
|
||||
CV_Assert(options_->descriptor_channels <= max_channels);
|
||||
const int pattern_size = options_->descriptor_pattern_size;
|
||||
|
||||
float values[16*max_channels];
|
||||
CV_Assert((pattern_size & 1) == 0);
|
||||
//const double size_mult[3] = {1, 2.0/3.0, 1.0/2.0};
|
||||
const int sample_step[3] = { // static_cast<int>(ceil(pattern_size * size_mult[lvl]))
|
||||
pattern_size,
|
||||
divUp(pattern_size * 2, 3),
|
||||
divUp(pattern_size, 2)
|
||||
};
|
||||
|
||||
float ratio = (float)(1 << kpt.octave);
|
||||
float scale = (float)cvRound(0.5f*kpt.size / ratio);
|
||||
float xf = kpt.pt.x / ratio;
|
||||
float yf = kpt.pt.y / ratio;
|
||||
float angle = kpt.angle * static_cast<float>(CV_PI / 180.f);
|
||||
float co = cos(angle);
|
||||
float si = sin(angle);
|
||||
|
||||
memset(desc, 0, desc_size);
|
||||
|
||||
int dpos = 0;
|
||||
for(int lvl = 0; lvl < 3; lvl++)
|
||||
{
|
||||
int val_count = (lvl + 2) * (lvl + 2);
|
||||
MLDB_Fill_Values(values, sample_step[lvl], kpt.class_id, xf, yf, co, si, scale);
|
||||
MLDB_Binary_Comparisons(values, desc, val_count, dpos);
|
||||
}
|
||||
|
||||
CV_Assert(dpos == 486);
|
||||
CV_Assert(divUp(dpos, 8) == desc_size);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the M-LDB descriptor of the provided keypoint given the
|
||||
* main orientation of the keypoint. The descriptor is computed based on a subset of
|
||||
* the bits of the whole descriptor
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
*/
|
||||
void MLDB_Descriptor_Subset_Invoker::Get_MLDB_Descriptor_Subset(const KeyPoint& kpt, unsigned char *desc, int desc_size) const {
|
||||
|
||||
float rx = 0.f, ry = 0.f;
|
||||
float sample_x = 0.f, sample_y = 0.f;
|
||||
|
||||
const AKAZEOptions & options = *options_;
|
||||
const Pyramid& evolution = *evolution_;
|
||||
|
||||
// Get the information from the keypoint
|
||||
float ratio = (float)(1 << kpt.octave);
|
||||
int scale = cvRound(0.5f*kpt.size / ratio);
|
||||
float angle = kpt.angle * static_cast<float>(CV_PI / 180.f);
|
||||
const int level = kpt.class_id;
|
||||
const Mat Lx = evolution[level].Lx;
|
||||
const Mat Ly = evolution[level].Ly;
|
||||
const Mat Lt = evolution[level].Lt;
|
||||
float yf = kpt.pt.y / ratio;
|
||||
float xf = kpt.pt.x / ratio;
|
||||
float co = cos(angle);
|
||||
float si = sin(angle);
|
||||
|
||||
// Allocate memory for the matrix of values
|
||||
// Buffer for the M-LDB descriptor
|
||||
const int max_channels = 3;
|
||||
const int channels = options.descriptor_channels;
|
||||
CV_Assert(channels <= max_channels);
|
||||
float values[(4 + 9 + 16)*max_channels] = { 0 };
|
||||
|
||||
// Sample everything, but only do the comparisons
|
||||
const int pattern_size = options.descriptor_pattern_size;
|
||||
CV_Assert((pattern_size & 1) == 0);
|
||||
const int sample_steps[3] = {
|
||||
pattern_size,
|
||||
divUp(pattern_size * 2, 3),
|
||||
divUp(pattern_size, 2)
|
||||
};
|
||||
|
||||
for (int i = 0; i < descriptorSamples_.rows; i++) {
|
||||
const int *coords = descriptorSamples_.ptr<int>(i);
|
||||
CV_Assert(coords[0] >= 0 && coords[0] < 3);
|
||||
const int sample_step = sample_steps[coords[0]];
|
||||
float di = 0.f, dx = 0.f, dy = 0.f;
|
||||
|
||||
for (int k = coords[1]; k < coords[1] + sample_step; k++) {
|
||||
for (int l = coords[2]; l < coords[2] + sample_step; l++) {
|
||||
|
||||
// Get the coordinates of the sample point
|
||||
sample_y = yf + (l*scale*co + k*scale*si);
|
||||
sample_x = xf + (-l*scale*si + k*scale*co);
|
||||
|
||||
const int y1 = cvRound(sample_y);
|
||||
const int x1 = cvRound(sample_x);
|
||||
|
||||
if (x1 < 0 || y1 < 0 || x1 >= Lt.cols || y1 >= Lt.rows)
|
||||
continue; // Boundaries
|
||||
|
||||
di += Lt.at<float>(y1, x1);
|
||||
|
||||
if (options.descriptor_channels > 1) {
|
||||
rx = Lx.at<float>(y1, x1);
|
||||
ry = Ly.at<float>(y1, x1);
|
||||
|
||||
if (options.descriptor_channels == 2) {
|
||||
dx += sqrtf(rx*rx + ry*ry);
|
||||
}
|
||||
else if (options.descriptor_channels == 3) {
|
||||
// Get the x and y derivatives on the rotated axis
|
||||
dx += rx*co + ry*si;
|
||||
dy += -rx*si + ry*co;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float* pValues = &values[channels * i];
|
||||
pValues[0] = di;
|
||||
|
||||
if (channels == 2) {
|
||||
pValues[1] = dx;
|
||||
}
|
||||
else if (channels == 3) {
|
||||
pValues[1] = dx;
|
||||
pValues[2] = dy;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the comparisons
|
||||
const int *comps = descriptorBits_.ptr<int>(0);
|
||||
|
||||
CV_Assert(divUp(descriptorBits_.rows, 8) == desc_size);
|
||||
memset(desc, 0, desc_size);
|
||||
|
||||
for (int i = 0; i<descriptorBits_.rows; i++) {
|
||||
if (values[comps[2 * i]] > values[comps[2 * i + 1]]) {
|
||||
desc[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the upright (not rotation invariant) M-LDB descriptor
|
||||
* of the provided keypoint given the main orientation of the keypoint.
|
||||
* The descriptor is computed based on a subset of the bits of the whole descriptor
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
*/
|
||||
void Upright_MLDB_Descriptor_Subset_Invoker::Get_Upright_MLDB_Descriptor_Subset(const KeyPoint& kpt, unsigned char *desc, int desc_size) const {
|
||||
|
||||
float di = 0.0f, dx = 0.0f, dy = 0.0f;
|
||||
float rx = 0.0f, ry = 0.0f;
|
||||
float sample_x = 0.0f, sample_y = 0.0f;
|
||||
int x1 = 0, y1 = 0;
|
||||
|
||||
const AKAZEOptions & options = *options_;
|
||||
const Pyramid& evolution = *evolution_;
|
||||
|
||||
// Get the information from the keypoint
|
||||
float ratio = (float)(1 << kpt.octave);
|
||||
int scale = cvRound(0.5f*kpt.size / ratio);
|
||||
const int level = kpt.class_id;
|
||||
const Mat Lx = evolution[level].Lx;
|
||||
const Mat Ly = evolution[level].Ly;
|
||||
const Mat Lt = evolution[level].Lt;
|
||||
float yf = kpt.pt.y / ratio;
|
||||
float xf = kpt.pt.x / ratio;
|
||||
|
||||
// Allocate memory for the matrix of values
|
||||
const int max_channels = 3;
|
||||
const int channels = options.descriptor_channels;
|
||||
CV_Assert(channels <= max_channels);
|
||||
float values[(4 + 9 + 16)*max_channels] = { 0 };
|
||||
|
||||
const int pattern_size = options.descriptor_pattern_size;
|
||||
CV_Assert((pattern_size & 1) == 0);
|
||||
const int sample_steps[3] = {
|
||||
pattern_size,
|
||||
divUp(pattern_size * 2, 3),
|
||||
divUp(pattern_size, 2)
|
||||
};
|
||||
|
||||
for (int i = 0; i < descriptorSamples_.rows; i++) {
|
||||
const int *coords = descriptorSamples_.ptr<int>(i);
|
||||
CV_Assert(coords[0] >= 0 && coords[0] < 3);
|
||||
int sample_step = sample_steps[coords[0]];
|
||||
di = 0.0f, dx = 0.0f, dy = 0.0f;
|
||||
|
||||
for (int k = coords[1]; k < coords[1] + sample_step; k++) {
|
||||
for (int l = coords[2]; l < coords[2] + sample_step; l++) {
|
||||
|
||||
// Get the coordinates of the sample point
|
||||
sample_y = yf + l*scale;
|
||||
sample_x = xf + k*scale;
|
||||
|
||||
y1 = cvRound(sample_y);
|
||||
x1 = cvRound(sample_x);
|
||||
|
||||
if (x1 < 0 || y1 < 0 || x1 >= Lt.cols || y1 >= Lt.rows)
|
||||
continue; // Boundaries
|
||||
|
||||
di += Lt.at<float>(y1, x1);
|
||||
|
||||
if (options.descriptor_channels > 1) {
|
||||
rx = Lx.at<float>(y1, x1);
|
||||
ry = Ly.at<float>(y1, x1);
|
||||
|
||||
if (options.descriptor_channels == 2) {
|
||||
dx += sqrtf(rx*rx + ry*ry);
|
||||
}
|
||||
else if (options.descriptor_channels == 3) {
|
||||
dx += rx;
|
||||
dy += ry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float* pValues = &values[channels * i];
|
||||
pValues[0] = di;
|
||||
|
||||
if (options.descriptor_channels == 2) {
|
||||
pValues[1] = dx;
|
||||
}
|
||||
else if (options.descriptor_channels == 3) {
|
||||
pValues[1] = dx;
|
||||
pValues[2] = dy;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the comparisons
|
||||
const int *comps = descriptorBits_.ptr<int>(0);
|
||||
|
||||
CV_Assert(divUp(descriptorBits_.rows, 8) == desc_size);
|
||||
memset(desc, 0, desc_size);
|
||||
|
||||
for (int i = 0; i<descriptorBits_.rows; i++) {
|
||||
if (values[comps[2 * i]] > values[comps[2 * i + 1]]) {
|
||||
desc[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes a (quasi-random) list of bits to be taken
|
||||
* from the full descriptor. To speed the extraction, the function creates
|
||||
* a list of the samples that are involved in generating at least a bit (sampleList)
|
||||
* and a list of the comparisons between those samples (comparisons)
|
||||
* @param sampleList
|
||||
* @param comparisons The matrix with the binary comparisons
|
||||
* @param nbits The number of bits of the descriptor
|
||||
* @param pattern_size The pattern size for the binary descriptor
|
||||
* @param nchannels Number of channels to consider in the descriptor (1-3)
|
||||
* @note The function keeps the 18 bits (3-channels by 6 comparisons) of the
|
||||
* coarser grid, since it provides the most robust estimations
|
||||
*/
|
||||
void generateDescriptorSubsample(Mat& sampleList, Mat& comparisons, int nbits,
|
||||
int pattern_size, int nchannels) {
|
||||
|
||||
int ssz = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int gz = (i + 2)*(i + 2);
|
||||
ssz += gz*(gz - 1) / 2;
|
||||
}
|
||||
ssz *= nchannels;
|
||||
|
||||
CV_Assert(ssz == 162*nchannels);
|
||||
CV_Assert(nbits <= ssz && "Descriptor size can't be bigger than full descriptor (486 = 162*3 - 3 channels)");
|
||||
|
||||
// Since the full descriptor is usually under 10k elements, we pick
|
||||
// the selection from the full matrix. We take as many samples per
|
||||
// pick as the number of channels. For every pick, we
|
||||
// take the two samples involved and put them in the sampling list
|
||||
|
||||
Mat_<int> fullM(ssz / nchannels, 5);
|
||||
for (int i = 0, c = 0; i < 3; i++) {
|
||||
int gdiv = i + 2; //grid divisions, per row
|
||||
int gsz = gdiv*gdiv;
|
||||
int psz = divUp(2*pattern_size, gdiv);
|
||||
|
||||
for (int j = 0; j < gsz; j++) {
|
||||
for (int k = j + 1; k < gsz; k++, c++) {
|
||||
fullM(c, 0) = i;
|
||||
fullM(c, 1) = psz*(j % gdiv) - pattern_size;
|
||||
fullM(c, 2) = psz*(j / gdiv) - pattern_size;
|
||||
fullM(c, 3) = psz*(k % gdiv) - pattern_size;
|
||||
fullM(c, 4) = psz*(k / gdiv) - pattern_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RNG rng(1024);
|
||||
const int npicks = divUp(nbits, nchannels);
|
||||
Mat_<int> comps = Mat_<int>(nchannels * npicks, 2);
|
||||
comps = 1000;
|
||||
|
||||
// Select some samples. A sample includes all channels
|
||||
int count = 0;
|
||||
Mat_<int> samples(29, 3);
|
||||
Mat_<int> fullcopy = fullM.clone();
|
||||
samples = -1;
|
||||
|
||||
for (int i = 0; i < npicks; i++) {
|
||||
int k = rng(fullM.rows - i);
|
||||
if (i < 6) {
|
||||
// Force use of the coarser grid values and comparisons
|
||||
k = i;
|
||||
}
|
||||
|
||||
bool n = true;
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
if (samples(j, 0) == fullcopy(k, 0) && samples(j, 1) == fullcopy(k, 1) && samples(j, 2) == fullcopy(k, 2)) {
|
||||
n = false;
|
||||
comps(i*nchannels, 0) = nchannels*j;
|
||||
comps(i*nchannels + 1, 0) = nchannels*j + 1;
|
||||
comps(i*nchannels + 2, 0) = nchannels*j + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n) {
|
||||
samples(count, 0) = fullcopy(k, 0);
|
||||
samples(count, 1) = fullcopy(k, 1);
|
||||
samples(count, 2) = fullcopy(k, 2);
|
||||
comps(i*nchannels, 0) = nchannels*count;
|
||||
comps(i*nchannels + 1, 0) = nchannels*count + 1;
|
||||
comps(i*nchannels + 2, 0) = nchannels*count + 2;
|
||||
count++;
|
||||
}
|
||||
|
||||
n = true;
|
||||
for (int j = 0; j < count; j++) {
|
||||
if (samples(j, 0) == fullcopy(k, 0) && samples(j, 1) == fullcopy(k, 3) && samples(j, 2) == fullcopy(k, 4)) {
|
||||
n = false;
|
||||
comps(i*nchannels, 1) = nchannels*j;
|
||||
comps(i*nchannels + 1, 1) = nchannels*j + 1;
|
||||
comps(i*nchannels + 2, 1) = nchannels*j + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n) {
|
||||
samples(count, 0) = fullcopy(k, 0);
|
||||
samples(count, 1) = fullcopy(k, 3);
|
||||
samples(count, 2) = fullcopy(k, 4);
|
||||
comps(i*nchannels, 1) = nchannels*count;
|
||||
comps(i*nchannels + 1, 1) = nchannels*count + 1;
|
||||
comps(i*nchannels + 2, 1) = nchannels*count + 2;
|
||||
count++;
|
||||
}
|
||||
|
||||
Mat tmp = fullcopy.row(k);
|
||||
fullcopy.row(fullcopy.rows - i - 1).copyTo(tmp);
|
||||
}
|
||||
|
||||
sampleList = samples.rowRange(0, count).clone();
|
||||
comparisons = comps.rowRange(0, nbits).clone();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* @file AKAZE.h
|
||||
* @brief Main class for detecting and computing binary descriptors in an
|
||||
* accelerated nonlinear scale space
|
||||
* @date Mar 27, 2013
|
||||
* @author Pablo F. Alcantarilla, Jesus Nuevo
|
||||
*/
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_AKAZE_FEATURES_H__
|
||||
#define __OPENCV_FEATURES_2D_AKAZE_FEATURES_H__
|
||||
|
||||
/* ************************************************************************* */
|
||||
// Includes
|
||||
#include "AKAZEConfig.h"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/// A-KAZE nonlinear diffusion filtering evolution
|
||||
template <typename MatType>
|
||||
struct Evolution
|
||||
{
|
||||
Evolution() {
|
||||
etime = 0.0f;
|
||||
esigma = 0.0f;
|
||||
octave = 0;
|
||||
sublevel = 0;
|
||||
sigma_size = 0;
|
||||
octave_ratio = 0.0f;
|
||||
border = 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
explicit Evolution(const Evolution<T> &other) {
|
||||
size = other.size;
|
||||
etime = other.etime;
|
||||
esigma = other.esigma;
|
||||
octave = other.octave;
|
||||
sublevel = other.sublevel;
|
||||
sigma_size = other.sigma_size;
|
||||
octave_ratio = other.octave_ratio;
|
||||
border = other.border;
|
||||
|
||||
other.Lx.copyTo(Lx);
|
||||
other.Ly.copyTo(Ly);
|
||||
other.Lt.copyTo(Lt);
|
||||
other.Lsmooth.copyTo(Lsmooth);
|
||||
other.Ldet.copyTo(Ldet);
|
||||
}
|
||||
|
||||
MatType Lx, Ly; ///< First order spatial derivatives
|
||||
MatType Lt; ///< Evolution image
|
||||
MatType Lsmooth; ///< Smoothed image, used only for computing determinant, released afterwards
|
||||
MatType Ldet; ///< Detector response
|
||||
|
||||
Size size; ///< Size of the layer
|
||||
float etime; ///< Evolution time
|
||||
float esigma; ///< Evolution sigma. For linear diffusion t = sigma^2 / 2
|
||||
int octave; ///< Image octave
|
||||
int sublevel; ///< Image sublevel in each octave
|
||||
int sigma_size; ///< Integer esigma. For computing the feature detector responses
|
||||
float octave_ratio; ///< Scaling ratio of this octave. ratio = 2^octave
|
||||
int border; ///< Width of border where descriptors cannot be computed
|
||||
};
|
||||
|
||||
typedef Evolution<Mat> MEvolution;
|
||||
typedef Evolution<UMat> UEvolution;
|
||||
typedef std::vector<MEvolution> Pyramid;
|
||||
typedef std::vector<UEvolution> UMatPyramid;
|
||||
|
||||
/* ************************************************************************* */
|
||||
// AKAZE Class Declaration
|
||||
class AKAZEFeatures {
|
||||
|
||||
private:
|
||||
|
||||
AKAZEOptions options_; ///< Configuration options for AKAZE
|
||||
Pyramid evolution_; ///< Vector of nonlinear diffusion evolution
|
||||
|
||||
/// FED parameters
|
||||
int ncycles_; ///< Number of cycles
|
||||
bool reordering_; ///< Flag for reordering time steps
|
||||
std::vector<std::vector<float > > tsteps_; ///< Vector of FED dynamic time steps
|
||||
std::vector<int> nsteps_; ///< Vector of number of steps per cycle
|
||||
|
||||
/// Matrices for the M-LDB descriptor computation
|
||||
cv::Mat descriptorSamples_; // List of positions in the grids to sample LDB bits from.
|
||||
cv::Mat descriptorBits_;
|
||||
cv::Mat bitMask_;
|
||||
|
||||
/// Scale Space methods
|
||||
void Allocate_Memory_Evolution();
|
||||
void Find_Scale_Space_Extrema(std::vector<Mat>& keypoints_by_layers);
|
||||
void Do_Subpixel_Refinement(std::vector<Mat>& keypoints_by_layers,
|
||||
std::vector<KeyPoint>& kpts);
|
||||
|
||||
/// Feature description methods
|
||||
void Compute_Keypoints_Orientation(std::vector<cv::KeyPoint>& kpts) const;
|
||||
|
||||
public:
|
||||
/// Constructor with input arguments
|
||||
AKAZEFeatures(const AKAZEOptions& options);
|
||||
void Create_Nonlinear_Scale_Space(InputArray img);
|
||||
void Feature_Detection(std::vector<cv::KeyPoint>& kpts);
|
||||
void Compute_Descriptors(std::vector<cv::KeyPoint>& kpts, OutputArray desc);
|
||||
};
|
||||
|
||||
/* ************************************************************************* */
|
||||
/// Inline functions
|
||||
void generateDescriptorSubsample(cv::Mat& sampleList, cv::Mat& comparisons,
|
||||
int nbits, int pattern_size, int nchannels);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* @file KAZEConfig.h
|
||||
* @brief Configuration file
|
||||
* @date Dec 27, 2011
|
||||
* @author Pablo F. Alcantarilla
|
||||
*/
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_KAZE_CONFIG_H__
|
||||
#define __OPENCV_FEATURES_2D_KAZE_CONFIG_H__
|
||||
|
||||
// OpenCV Includes
|
||||
#include "../precomp.hpp"
|
||||
#include <opencv2/features2d.hpp>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
//*************************************************************************************
|
||||
|
||||
struct KAZEOptions {
|
||||
|
||||
KAZEOptions()
|
||||
: diffusivity(KAZE::DIFF_PM_G2)
|
||||
|
||||
, soffset(1.60f)
|
||||
, omax(4)
|
||||
, nsublevels(4)
|
||||
, img_width(0)
|
||||
, img_height(0)
|
||||
, sderivatives(1.0f)
|
||||
, dthreshold(0.001f)
|
||||
, kcontrast(0.01f)
|
||||
, kcontrast_percentille(0.7f)
|
||||
, kcontrast_bins(300)
|
||||
, upright(false)
|
||||
, extended(false)
|
||||
{
|
||||
}
|
||||
|
||||
KAZE::DiffusivityType diffusivity;
|
||||
float soffset;
|
||||
int omax;
|
||||
int nsublevels;
|
||||
int img_width;
|
||||
int img_height;
|
||||
float sderivatives;
|
||||
float dthreshold;
|
||||
float kcontrast;
|
||||
float kcontrast_percentille;
|
||||
int kcontrast_bins;
|
||||
bool upright;
|
||||
bool extended;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,1216 +0,0 @@
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// KAZE.cpp
|
||||
// Author: Pablo F. Alcantarilla
|
||||
// Institution: University d'Auvergne
|
||||
// Address: Clermont Ferrand, France
|
||||
// Date: 21/01/2012
|
||||
// Email: pablofdezalc@gmail.com
|
||||
//
|
||||
// KAZE Features Copyright 2012, Pablo F. Alcantarilla
|
||||
// All Rights Reserved
|
||||
// See LICENSE for the license information
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* @file KAZEFeatures.cpp
|
||||
* @brief Main class for detecting and describing features in a nonlinear
|
||||
* scale space
|
||||
* @date Jan 21, 2012
|
||||
* @author Pablo F. Alcantarilla
|
||||
*/
|
||||
#include "../precomp.hpp"
|
||||
#include "KAZEFeatures.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// Namespaces
|
||||
using namespace std;
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief KAZE constructor with input options
|
||||
* @param options KAZE configuration options
|
||||
* @note The constructor allocates memory for the nonlinear scale space
|
||||
*/
|
||||
KAZEFeatures::KAZEFeatures(KAZEOptions& options)
|
||||
: options_(options)
|
||||
{
|
||||
ncycles_ = 0;
|
||||
reordering_ = true;
|
||||
|
||||
// Now allocate memory for the evolution
|
||||
Allocate_Memory_Evolution();
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method allocates the memory for the nonlinear diffusion evolution
|
||||
*/
|
||||
void KAZEFeatures::Allocate_Memory_Evolution(void) {
|
||||
|
||||
// Allocate the dimension of the matrices for the evolution
|
||||
for (int i = 0; i <= options_.omax - 1; i++)
|
||||
{
|
||||
for (int j = 0; j <= options_.nsublevels - 1; j++)
|
||||
{
|
||||
TEvolution aux;
|
||||
aux.Lx = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Ly = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Lxx = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Lxy = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Lyy = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Lt = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Lsmooth = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.Ldet = Mat::zeros(options_.img_height, options_.img_width, CV_32F);
|
||||
aux.esigma = options_.soffset*pow((float)2.0f, (float)(j) / (float)(options_.nsublevels)+i);
|
||||
aux.etime = 0.5f*(aux.esigma*aux.esigma);
|
||||
aux.sigma_size = cvRound(aux.esigma);
|
||||
aux.octave = i;
|
||||
aux.sublevel = j;
|
||||
evolution_.push_back(aux);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate memory for the FED number of cycles and time steps
|
||||
for (size_t i = 1; i < evolution_.size(); i++)
|
||||
{
|
||||
int naux = 0;
|
||||
vector<float> tau;
|
||||
float ttime = 0.0;
|
||||
ttime = evolution_[i].etime - evolution_[i - 1].etime;
|
||||
naux = fed_tau_by_process_time(ttime, 1, 0.25f, reordering_, tau);
|
||||
nsteps_.push_back(naux);
|
||||
tsteps_.push_back(tau);
|
||||
ncycles_++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method creates the nonlinear scale space for a given image
|
||||
* @param img Input image for which the nonlinear scale space needs to be created
|
||||
* @return 0 if the nonlinear scale space was created successfully. -1 otherwise
|
||||
*/
|
||||
int KAZEFeatures::Create_Nonlinear_Scale_Space(const Mat &img)
|
||||
{
|
||||
CV_Assert(evolution_.size() > 0);
|
||||
|
||||
// Copy the original image to the first level of the evolution
|
||||
img.copyTo(evolution_[0].Lt);
|
||||
gaussian_2D_convolution(evolution_[0].Lt, evolution_[0].Lt, 0, 0, options_.soffset);
|
||||
gaussian_2D_convolution(evolution_[0].Lt, evolution_[0].Lsmooth, 0, 0, options_.sderivatives);
|
||||
|
||||
// Firstly compute the kcontrast factor
|
||||
Compute_KContrast(evolution_[0].Lt, options_.kcontrast_percentille);
|
||||
|
||||
// Allocate memory for the flow and step images
|
||||
Mat Lflow = Mat::zeros(evolution_[0].Lt.rows, evolution_[0].Lt.cols, CV_32F);
|
||||
Mat Lstep = Mat::zeros(evolution_[0].Lt.rows, evolution_[0].Lt.cols, CV_32F);
|
||||
|
||||
// Now generate the rest of evolution levels
|
||||
for (size_t i = 1; i < evolution_.size(); i++)
|
||||
{
|
||||
evolution_[i - 1].Lt.copyTo(evolution_[i].Lt);
|
||||
gaussian_2D_convolution(evolution_[i - 1].Lt, evolution_[i].Lsmooth, 0, 0, options_.sderivatives);
|
||||
|
||||
// Compute the Gaussian derivatives Lx and Ly
|
||||
Scharr(evolution_[i].Lsmooth, evolution_[i].Lx, CV_32F, 1, 0, 1, 0, BORDER_DEFAULT);
|
||||
Scharr(evolution_[i].Lsmooth, evolution_[i].Ly, CV_32F, 0, 1, 1, 0, BORDER_DEFAULT);
|
||||
|
||||
// Compute the conductivity equation
|
||||
if (options_.diffusivity == KAZE::DIFF_PM_G1)
|
||||
pm_g1(evolution_[i].Lx, evolution_[i].Ly, Lflow, options_.kcontrast);
|
||||
else if (options_.diffusivity == KAZE::DIFF_PM_G2)
|
||||
pm_g2(evolution_[i].Lx, evolution_[i].Ly, Lflow, options_.kcontrast);
|
||||
else if (options_.diffusivity == KAZE::DIFF_WEICKERT)
|
||||
weickert_diffusivity(evolution_[i].Lx, evolution_[i].Ly, Lflow, options_.kcontrast);
|
||||
|
||||
// Perform FED n inner steps
|
||||
for (int j = 0; j < nsteps_[i - 1]; j++)
|
||||
nld_step_scalar(evolution_[i].Lt, Lflow, Lstep, tsteps_[i - 1][j]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the k contrast factor
|
||||
* @param img Input image
|
||||
* @param kpercentile Percentile of the gradient histogram
|
||||
*/
|
||||
void KAZEFeatures::Compute_KContrast(const Mat &img, const float &kpercentile)
|
||||
{
|
||||
options_.kcontrast = compute_k_percentile(img, kpercentile, options_.sderivatives, options_.kcontrast_bins, 0, 0);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the feature detector response for the nonlinear scale space
|
||||
* @note We use the Hessian determinant as feature detector
|
||||
*/
|
||||
void KAZEFeatures::Compute_Detector_Response(void)
|
||||
{
|
||||
float lxx = 0.0, lxy = 0.0, lyy = 0.0;
|
||||
|
||||
// Firstly compute the multiscale derivatives
|
||||
Compute_Multiscale_Derivatives();
|
||||
|
||||
for (size_t i = 0; i < evolution_.size(); i++)
|
||||
{
|
||||
for (int ix = 0; ix < options_.img_height; ix++)
|
||||
{
|
||||
for (int jx = 0; jx < options_.img_width; jx++)
|
||||
{
|
||||
lxx = *(evolution_[i].Lxx.ptr<float>(ix)+jx);
|
||||
lxy = *(evolution_[i].Lxy.ptr<float>(ix)+jx);
|
||||
lyy = *(evolution_[i].Lyy.ptr<float>(ix)+jx);
|
||||
*(evolution_[i].Ldet.ptr<float>(ix)+jx) = (lxx*lyy - lxy*lxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method selects interesting keypoints through the nonlinear scale space
|
||||
* @param kpts Vector of keypoints
|
||||
*/
|
||||
void KAZEFeatures::Feature_Detection(std::vector<KeyPoint>& kpts)
|
||||
{
|
||||
kpts.clear();
|
||||
Compute_Detector_Response();
|
||||
Determinant_Hessian(kpts);
|
||||
Do_Subpixel_Refinement(kpts);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
class MultiscaleDerivativesKAZEInvoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
explicit MultiscaleDerivativesKAZEInvoker(std::vector<TEvolution>& ev) : evolution_(&ev)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
std::vector<TEvolution>& evolution = *evolution_;
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
compute_scharr_derivatives(evolution[i].Lsmooth, evolution[i].Lx, 1, 0, evolution[i].sigma_size);
|
||||
compute_scharr_derivatives(evolution[i].Lsmooth, evolution[i].Ly, 0, 1, evolution[i].sigma_size);
|
||||
compute_scharr_derivatives(evolution[i].Lx, evolution[i].Lxx, 1, 0, evolution[i].sigma_size);
|
||||
compute_scharr_derivatives(evolution[i].Ly, evolution[i].Lyy, 0, 1, evolution[i].sigma_size);
|
||||
compute_scharr_derivatives(evolution[i].Lx, evolution[i].Lxy, 0, 1, evolution[i].sigma_size);
|
||||
|
||||
evolution[i].Lx = evolution[i].Lx*((evolution[i].sigma_size));
|
||||
evolution[i].Ly = evolution[i].Ly*((evolution[i].sigma_size));
|
||||
evolution[i].Lxx = evolution[i].Lxx*((evolution[i].sigma_size)*(evolution[i].sigma_size));
|
||||
evolution[i].Lxy = evolution[i].Lxy*((evolution[i].sigma_size)*(evolution[i].sigma_size));
|
||||
evolution[i].Lyy = evolution[i].Lyy*((evolution[i].sigma_size)*(evolution[i].sigma_size));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<TEvolution>* evolution_;
|
||||
};
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the multiscale derivatives for the nonlinear scale space
|
||||
*/
|
||||
void KAZEFeatures::Compute_Multiscale_Derivatives(void)
|
||||
{
|
||||
parallel_for_(Range(0, (int)evolution_.size()),
|
||||
MultiscaleDerivativesKAZEInvoker(evolution_));
|
||||
}
|
||||
|
||||
|
||||
/* ************************************************************************* */
|
||||
class FindExtremumKAZEInvoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
explicit FindExtremumKAZEInvoker(std::vector<TEvolution>& ev, std::vector<std::vector<KeyPoint> >& kpts_par,
|
||||
const KAZEOptions& options) : evolution_(&ev), kpts_par_(&kpts_par), options_(options)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
std::vector<TEvolution>& evolution = *evolution_;
|
||||
std::vector<std::vector<KeyPoint> >& kpts_par = *kpts_par_;
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
float value = 0.0;
|
||||
bool is_extremum = false;
|
||||
|
||||
for (int ix = 1; ix < options_.img_height - 1; ix++)
|
||||
{
|
||||
for (int jx = 1; jx < options_.img_width - 1; jx++)
|
||||
{
|
||||
is_extremum = false;
|
||||
value = *(evolution[i].Ldet.ptr<float>(ix)+jx);
|
||||
|
||||
// Filter the points with the detector threshold
|
||||
if (value > options_.dthreshold)
|
||||
{
|
||||
if (value >= *(evolution[i].Ldet.ptr<float>(ix)+jx - 1))
|
||||
{
|
||||
// First check on the same scale
|
||||
if (check_maximum_neighbourhood(evolution[i].Ldet, 1, value, ix, jx, 1))
|
||||
{
|
||||
// Now check on the lower scale
|
||||
if (check_maximum_neighbourhood(evolution[i - 1].Ldet, 1, value, ix, jx, 0))
|
||||
{
|
||||
// Now check on the upper scale
|
||||
if (check_maximum_neighbourhood(evolution[i + 1].Ldet, 1, value, ix, jx, 0))
|
||||
is_extremum = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the point of interest!!
|
||||
if (is_extremum)
|
||||
{
|
||||
KeyPoint point;
|
||||
point.pt.x = (float)jx;
|
||||
point.pt.y = (float)ix;
|
||||
point.response = fabs(value);
|
||||
point.size = evolution[i].esigma;
|
||||
point.octave = (int)evolution[i].octave;
|
||||
point.class_id = i;
|
||||
|
||||
// We use the angle field for the sublevel value
|
||||
// Then, we will replace this angle field with the main orientation
|
||||
point.angle = static_cast<float>(evolution[i].sublevel);
|
||||
kpts_par[i - 1].push_back(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<TEvolution>* evolution_;
|
||||
std::vector<std::vector<KeyPoint> >* kpts_par_;
|
||||
KAZEOptions options_;
|
||||
};
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method performs the detection of keypoints by using the normalized
|
||||
* score of the Hessian determinant through the nonlinear scale space
|
||||
* @param kpts Vector of keypoints
|
||||
* @note We compute features for each of the nonlinear scale space level in a different processing thread
|
||||
*/
|
||||
void KAZEFeatures::Determinant_Hessian(std::vector<KeyPoint>& kpts)
|
||||
{
|
||||
int level = 0;
|
||||
float smax = 3.0;
|
||||
int id_repeated = 0;
|
||||
int left_x = 0, right_x = 0, up_y = 0, down_y = 0;
|
||||
bool is_extremum = false, is_repeated = false, is_out = false;
|
||||
|
||||
// Delete the memory of the vector of keypoints vectors
|
||||
// In case we use the same kaze object for multiple images
|
||||
for (size_t i = 0; i < kpts_par_.size(); i++) {
|
||||
vector<KeyPoint>().swap(kpts_par_[i]);
|
||||
}
|
||||
kpts_par_.clear();
|
||||
vector<KeyPoint> aux;
|
||||
|
||||
// Allocate memory for the vector of vectors
|
||||
for (size_t i = 1; i < evolution_.size() - 1; i++) {
|
||||
kpts_par_.push_back(aux);
|
||||
}
|
||||
|
||||
parallel_for_(Range(1, (int)evolution_.size()-1),
|
||||
FindExtremumKAZEInvoker(evolution_, kpts_par_, options_));
|
||||
|
||||
// Now fill the vector of keypoints!!!
|
||||
for (int i = 0; i < (int)kpts_par_.size(); i++)
|
||||
{
|
||||
for (int j = 0; j < (int)kpts_par_[i].size(); j++)
|
||||
{
|
||||
level = i + 1;
|
||||
const TEvolution& evolution_level = evolution_[level];
|
||||
|
||||
is_extremum = true;
|
||||
is_repeated = false;
|
||||
is_out = false;
|
||||
|
||||
const KeyPoint& kpts_par_ij = kpts_par_[i][j];
|
||||
|
||||
// Check in case we have the same point as maxima in previous evolution levels
|
||||
for (int ik = 0; ik < (int)kpts.size(); ik++)
|
||||
{
|
||||
const KeyPoint& kpts_ik = kpts[ik];
|
||||
if (kpts_ik.class_id == level || kpts_ik.class_id == level + 1 || kpts_ik.class_id == level - 1) {
|
||||
Point2f diff = kpts_par_ij.pt - kpts_ik.pt;
|
||||
float dist = diff.dot(diff);
|
||||
|
||||
if (dist < evolution_level.sigma_size*evolution_level.sigma_size) {
|
||||
if (kpts_par_ij.response > kpts_ik.response) {
|
||||
id_repeated = ik;
|
||||
is_repeated = true;
|
||||
}
|
||||
else {
|
||||
is_extremum = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_extremum == true) {
|
||||
// Check that the point is under the image limits for the descriptor computation
|
||||
left_x = cvRound(kpts_par_ij.pt.x - smax*kpts_par_ij.size);
|
||||
right_x = cvRound(kpts_par_ij.pt.x + smax*kpts_par_ij.size);
|
||||
up_y = cvRound(kpts_par_ij.pt.y - smax*kpts_par_ij.size);
|
||||
down_y = cvRound(kpts_par_ij.pt.y + smax*kpts_par_ij.size);
|
||||
|
||||
if (left_x < 0 || right_x >= evolution_level.Ldet.cols ||
|
||||
up_y < 0 || down_y >= evolution_level.Ldet.rows) {
|
||||
is_out = true;
|
||||
}
|
||||
|
||||
if (is_out == false) {
|
||||
if (is_repeated == false) {
|
||||
kpts.push_back(kpts_par_ij);
|
||||
}
|
||||
else {
|
||||
kpts[id_repeated] = kpts_par_ij;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method performs subpixel refinement of the detected keypoints
|
||||
* @param kpts Vector of detected keypoints
|
||||
*/
|
||||
void KAZEFeatures::Do_Subpixel_Refinement(std::vector<KeyPoint> &kpts) {
|
||||
|
||||
int step = 1;
|
||||
int x = 0, y = 0;
|
||||
float Dx = 0.0, Dy = 0.0, Ds = 0.0, dsc = 0.0;
|
||||
float Dxx = 0.0, Dyy = 0.0, Dss = 0.0, Dxy = 0.0, Dxs = 0.0, Dys = 0.0;
|
||||
Mat A = Mat::zeros(3, 3, CV_32F);
|
||||
Mat b = Mat::zeros(3, 1, CV_32F);
|
||||
Mat dst = Mat::zeros(3, 1, CV_32F);
|
||||
|
||||
vector<KeyPoint> kpts_(kpts);
|
||||
|
||||
for (size_t i = 0; i < kpts_.size(); i++) {
|
||||
|
||||
x = static_cast<int>(kpts_[i].pt.x);
|
||||
y = static_cast<int>(kpts_[i].pt.y);
|
||||
|
||||
// Compute the gradient
|
||||
Dx = (1.0f / (2.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x + step)
|
||||
- *(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x - step));
|
||||
Dy = (1.0f / (2.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y + step) + x)
|
||||
- *(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y - step) + x));
|
||||
Ds = 0.5f*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr<float>(y)+x)
|
||||
- *(evolution_[kpts_[i].class_id - 1].Ldet.ptr<float>(y)+x));
|
||||
|
||||
// Compute the Hessian
|
||||
Dxx = (1.0f / (step*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x + step)
|
||||
+ *(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x - step)
|
||||
- 2.0f*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x)));
|
||||
|
||||
Dyy = (1.0f / (step*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y + step) + x)
|
||||
+ *(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y - step) + x)
|
||||
- 2.0f*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x)));
|
||||
|
||||
Dss = *(evolution_[kpts_[i].class_id + 1].Ldet.ptr<float>(y)+x)
|
||||
+ *(evolution_[kpts_[i].class_id - 1].Ldet.ptr<float>(y)+x)
|
||||
- 2.0f*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y)+x));
|
||||
|
||||
Dxy = (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y + step) + x + step)
|
||||
+ (*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y - step) + x - step)))
|
||||
- (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y - step) + x + step)
|
||||
+ (*(evolution_[kpts_[i].class_id].Ldet.ptr<float>(y + step) + x - step)));
|
||||
|
||||
Dxs = (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr<float>(y)+x + step)
|
||||
+ (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr<float>(y)+x - step)))
|
||||
- (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr<float>(y)+x - step)
|
||||
+ (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr<float>(y)+x + step)));
|
||||
|
||||
Dys = (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr<float>(y + step) + x)
|
||||
+ (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr<float>(y - step) + x)))
|
||||
- (1.0f / (4.0f*step))*(*(evolution_[kpts_[i].class_id + 1].Ldet.ptr<float>(y - step) + x)
|
||||
+ (*(evolution_[kpts_[i].class_id - 1].Ldet.ptr<float>(y + step) + x)));
|
||||
|
||||
// Solve the linear system
|
||||
*(A.ptr<float>(0)) = Dxx;
|
||||
*(A.ptr<float>(1) + 1) = Dyy;
|
||||
*(A.ptr<float>(2) + 2) = Dss;
|
||||
|
||||
*(A.ptr<float>(0) + 1) = *(A.ptr<float>(1)) = Dxy;
|
||||
*(A.ptr<float>(0) + 2) = *(A.ptr<float>(2)) = Dxs;
|
||||
*(A.ptr<float>(1) + 2) = *(A.ptr<float>(2) + 1) = Dys;
|
||||
|
||||
*(b.ptr<float>(0)) = -Dx;
|
||||
*(b.ptr<float>(1)) = -Dy;
|
||||
*(b.ptr<float>(2)) = -Ds;
|
||||
|
||||
solve(A, b, dst, DECOMP_LU);
|
||||
|
||||
if (fabs(*(dst.ptr<float>(0))) <= 1.0f && fabs(*(dst.ptr<float>(1))) <= 1.0f && fabs(*(dst.ptr<float>(2))) <= 1.0f) {
|
||||
kpts_[i].pt.x += *(dst.ptr<float>(0));
|
||||
kpts_[i].pt.y += *(dst.ptr<float>(1));
|
||||
dsc = kpts_[i].octave + (kpts_[i].angle + *(dst.ptr<float>(2))) / ((float)(options_.nsublevels));
|
||||
|
||||
// In OpenCV the size of a keypoint is the diameter!!
|
||||
kpts_[i].size = 2.0f*options_.soffset*pow((float)2.0f, dsc);
|
||||
kpts_[i].angle = 0.0;
|
||||
}
|
||||
// Set the points to be deleted after the for loop
|
||||
else {
|
||||
kpts_[i].response = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the vector of keypoints
|
||||
kpts.clear();
|
||||
|
||||
for (size_t i = 0; i < kpts_.size(); i++) {
|
||||
if (kpts_[i].response != -1) {
|
||||
kpts.push_back(kpts_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
class KAZE_Descriptor_Invoker : public ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
KAZE_Descriptor_Invoker(std::vector<KeyPoint> &kpts, Mat &desc, std::vector<TEvolution>& evolution, const KAZEOptions& options)
|
||||
: kpts_(&kpts)
|
||||
, desc_(&desc)
|
||||
, evolution_(&evolution)
|
||||
, options_(options)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~KAZE_Descriptor_Invoker()
|
||||
{
|
||||
}
|
||||
|
||||
void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
std::vector<KeyPoint> &kpts = *kpts_;
|
||||
Mat &desc = *desc_;
|
||||
std::vector<TEvolution> &evolution = *evolution_;
|
||||
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
kpts[i].angle = 0.0;
|
||||
if (options_.upright)
|
||||
{
|
||||
kpts[i].angle = 0.0;
|
||||
if (options_.extended)
|
||||
Get_KAZE_Upright_Descriptor_128(kpts[i], desc.ptr<float>((int)i));
|
||||
else
|
||||
Get_KAZE_Upright_Descriptor_64(kpts[i], desc.ptr<float>((int)i));
|
||||
}
|
||||
else
|
||||
{
|
||||
KAZEFeatures::Compute_Main_Orientation(kpts[i], evolution, options_);
|
||||
|
||||
if (options_.extended)
|
||||
Get_KAZE_Descriptor_128(kpts[i], desc.ptr<float>((int)i));
|
||||
else
|
||||
Get_KAZE_Descriptor_64(kpts[i], desc.ptr<float>((int)i));
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
void Get_KAZE_Upright_Descriptor_64(const KeyPoint& kpt, float* desc) const;
|
||||
void Get_KAZE_Descriptor_64(const KeyPoint& kpt, float* desc) const;
|
||||
void Get_KAZE_Upright_Descriptor_128(const KeyPoint& kpt, float* desc) const;
|
||||
void Get_KAZE_Descriptor_128(const KeyPoint& kpt, float *desc) const;
|
||||
|
||||
std::vector<KeyPoint> * kpts_;
|
||||
Mat * desc_;
|
||||
std::vector<TEvolution> * evolution_;
|
||||
KAZEOptions options_;
|
||||
};
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the set of descriptors through the nonlinear scale space
|
||||
* @param kpts Vector of keypoints
|
||||
* @param desc Matrix with the feature descriptors
|
||||
*/
|
||||
void KAZEFeatures::Feature_Description(std::vector<KeyPoint> &kpts, Mat &desc)
|
||||
{
|
||||
for(size_t i = 0; i < kpts.size(); i++)
|
||||
{
|
||||
CV_Assert(0 <= kpts[i].class_id && kpts[i].class_id < static_cast<int>(evolution_.size()));
|
||||
}
|
||||
|
||||
// Allocate memory for the matrix of descriptors
|
||||
if (options_.extended == true) {
|
||||
desc = Mat::zeros((int)kpts.size(), 128, CV_32FC1);
|
||||
}
|
||||
else {
|
||||
desc = Mat::zeros((int)kpts.size(), 64, CV_32FC1);
|
||||
}
|
||||
|
||||
parallel_for_(Range(0, (int)kpts.size()), KAZE_Descriptor_Invoker(kpts, desc, evolution_, options_));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the main orientation for a given keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @note The orientation is computed using a similar approach as described in the
|
||||
* original SURF method. See Bay et al., Speeded Up Robust Features, ECCV 2006
|
||||
*/
|
||||
void KAZEFeatures::Compute_Main_Orientation(KeyPoint &kpt, const std::vector<TEvolution>& evolution_, const KAZEOptions& options)
|
||||
{
|
||||
int ix = 0, iy = 0, idx = 0, s = 0, level = 0;
|
||||
float xf = 0.0, yf = 0.0, gweight = 0.0;
|
||||
vector<float> resX(109), resY(109), Ang(109);
|
||||
|
||||
// Variables for computing the dominant direction
|
||||
float sumX = 0.0, sumY = 0.0, max = 0.0, ang1 = 0.0, ang2 = 0.0;
|
||||
|
||||
// Get the information from the keypoint
|
||||
xf = kpt.pt.x;
|
||||
yf = kpt.pt.y;
|
||||
level = kpt.class_id;
|
||||
s = cvRound(kpt.size / 2.0f);
|
||||
|
||||
// Calculate derivatives responses for points within radius of 6*scale
|
||||
for (int i = -6; i <= 6; ++i) {
|
||||
for (int j = -6; j <= 6; ++j) {
|
||||
if (i*i + j*j < 36) {
|
||||
iy = cvRound(yf + j*s);
|
||||
ix = cvRound(xf + i*s);
|
||||
|
||||
if (iy >= 0 && iy < options.img_height && ix >= 0 && ix < options.img_width) {
|
||||
gweight = gaussian(iy - yf, ix - xf, 2.5f*s);
|
||||
resX[idx] = gweight*(*(evolution_[level].Lx.ptr<float>(iy)+ix));
|
||||
resY[idx] = gweight*(*(evolution_[level].Ly.ptr<float>(iy)+ix));
|
||||
}
|
||||
else {
|
||||
resX[idx] = 0.0;
|
||||
resY[idx] = 0.0;
|
||||
}
|
||||
|
||||
Ang[idx] = fastAtan2(resY[idx], resX[idx]) * (float)(CV_PI / 180.0f);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop slides pi/3 window around feature point
|
||||
for (ang1 = 0; ang1 < 2.0f*CV_PI; ang1 += 0.15f) {
|
||||
ang2 = (ang1 + (float)(CV_PI / 3.0) > (float)(2.0*CV_PI) ? ang1 - (float)(5.0*CV_PI / 3.0) : ang1 + (float)(CV_PI / 3.0));
|
||||
sumX = sumY = 0.f;
|
||||
|
||||
for (size_t k = 0; k < Ang.size(); ++k) {
|
||||
// Get angle from the x-axis of the sample point
|
||||
const float & ang = Ang[k];
|
||||
|
||||
// Determine whether the point is within the window
|
||||
if (ang1 < ang2 && ang1 < ang && ang < ang2) {
|
||||
sumX += resX[k];
|
||||
sumY += resY[k];
|
||||
}
|
||||
else if (ang2 < ang1 &&
|
||||
((ang > 0 && ang < ang2) || (ang > ang1 && ang < (float)(2.0*CV_PI)))) {
|
||||
sumX += resX[k];
|
||||
sumY += resY[k];
|
||||
}
|
||||
}
|
||||
|
||||
// if the vector produced from this window is longer than all
|
||||
// previous vectors then this forms the new dominant direction
|
||||
if (sumX*sumX + sumY*sumY > max) {
|
||||
// store largest orientation
|
||||
max = sumX*sumX + sumY*sumY;
|
||||
kpt.angle = fastAtan2(sumY, sumX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the upright descriptor (not rotation invariant) of
|
||||
* the provided keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
* @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired
|
||||
* from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching,
|
||||
* ECCV 2008
|
||||
*/
|
||||
void KAZE_Descriptor_Invoker::Get_KAZE_Upright_Descriptor_64(const KeyPoint &kpt, float *desc) const
|
||||
{
|
||||
float dx = 0.0, dy = 0.0, mdx = 0.0, mdy = 0.0, gauss_s1 = 0.0, gauss_s2 = 0.0;
|
||||
float rx = 0.0, ry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0;
|
||||
float sample_x = 0.0, sample_y = 0.0;
|
||||
int x1 = 0, y1 = 0, sample_step = 0, pattern_size = 0;
|
||||
int x2 = 0, y2 = 0, kx = 0, ky = 0, i = 0, j = 0, dcount = 0;
|
||||
float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0;
|
||||
int dsize = 0, scale = 0, level = 0;
|
||||
|
||||
std::vector<TEvolution>& evolution = *evolution_;
|
||||
|
||||
// Subregion centers for the 4x4 gaussian weighting
|
||||
float cx = -0.5f, cy = 0.5f;
|
||||
|
||||
// Set the descriptor size and the sample and pattern sizes
|
||||
dsize = 64;
|
||||
sample_step = 5;
|
||||
pattern_size = 12;
|
||||
|
||||
// Get the information from the keypoint
|
||||
yf = kpt.pt.y;
|
||||
xf = kpt.pt.x;
|
||||
scale = cvRound(kpt.size / 2.0f);
|
||||
level = kpt.class_id;
|
||||
|
||||
i = -8;
|
||||
|
||||
// Calculate descriptor for this interest point
|
||||
// Area of size 24 s x 24 s
|
||||
while (i < pattern_size) {
|
||||
j = -8;
|
||||
i = i - 4;
|
||||
|
||||
cx += 1.0f;
|
||||
cy = -0.5f;
|
||||
|
||||
while (j < pattern_size) {
|
||||
|
||||
dx = dy = mdx = mdy = 0.0;
|
||||
cy += 1.0f;
|
||||
j = j - 4;
|
||||
|
||||
ky = i + sample_step;
|
||||
kx = j + sample_step;
|
||||
|
||||
ys = yf + (ky*scale);
|
||||
xs = xf + (kx*scale);
|
||||
|
||||
for (int k = i; k < i + 9; k++) {
|
||||
for (int l = j; l < j + 9; l++) {
|
||||
|
||||
sample_y = k*scale + yf;
|
||||
sample_x = l*scale + xf;
|
||||
|
||||
//Get the gaussian weighted x and y responses
|
||||
gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale);
|
||||
|
||||
y1 = (int)(sample_y - 0.5f);
|
||||
x1 = (int)(sample_x - 0.5f);
|
||||
|
||||
checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height);
|
||||
|
||||
y2 = (int)(sample_y + 0.5f);
|
||||
x2 = (int)(sample_x + 0.5f);
|
||||
|
||||
checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height);
|
||||
|
||||
fx = sample_x - x1;
|
||||
fy = sample_y - y1;
|
||||
|
||||
res1 = *(evolution[level].Lx.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Lx.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Lx.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Lx.ptr<float>(y2)+x2);
|
||||
rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
res1 = *(evolution[level].Ly.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Ly.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Ly.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Ly.ptr<float>(y2)+x2);
|
||||
ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
rx = gauss_s1*rx;
|
||||
ry = gauss_s1*ry;
|
||||
|
||||
// Sum the derivatives to the cumulative descriptor
|
||||
dx += rx;
|
||||
dy += ry;
|
||||
mdx += fabs(rx);
|
||||
mdy += fabs(ry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the values to the descriptor vector
|
||||
gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f);
|
||||
|
||||
desc[dcount++] = dx*gauss_s2;
|
||||
desc[dcount++] = dy*gauss_s2;
|
||||
desc[dcount++] = mdx*gauss_s2;
|
||||
desc[dcount++] = mdy*gauss_s2;
|
||||
|
||||
len += (dx*dx + dy*dy + mdx*mdx + mdy*mdy)*gauss_s2*gauss_s2;
|
||||
|
||||
j += 9;
|
||||
}
|
||||
|
||||
i += 9;
|
||||
}
|
||||
|
||||
// convert to unit vector
|
||||
len = sqrt(len);
|
||||
|
||||
for (i = 0; i < dsize; i++) {
|
||||
desc[i] /= len;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the descriptor of the provided keypoint given the
|
||||
* main orientation of the keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
* @note Rectangular grid of 24 s x 24 s. Descriptor Length 64. The descriptor is inspired
|
||||
* from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching,
|
||||
* ECCV 2008
|
||||
*/
|
||||
void KAZE_Descriptor_Invoker::Get_KAZE_Descriptor_64(const KeyPoint &kpt, float *desc) const
|
||||
{
|
||||
float dx = 0.0, dy = 0.0, mdx = 0.0, mdy = 0.0, gauss_s1 = 0.0, gauss_s2 = 0.0;
|
||||
float rx = 0.0, ry = 0.0, rrx = 0.0, rry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0;
|
||||
float sample_x = 0.0, sample_y = 0.0, co = 0.0, si = 0.0, angle = 0.0;
|
||||
float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0;
|
||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0, sample_step = 0, pattern_size = 0;
|
||||
int kx = 0, ky = 0, i = 0, j = 0, dcount = 0;
|
||||
int dsize = 0, scale = 0, level = 0;
|
||||
|
||||
std::vector<TEvolution>& evolution = *evolution_;
|
||||
|
||||
// Subregion centers for the 4x4 gaussian weighting
|
||||
float cx = -0.5f, cy = 0.5f;
|
||||
|
||||
// Set the descriptor size and the sample and pattern sizes
|
||||
dsize = 64;
|
||||
sample_step = 5;
|
||||
pattern_size = 12;
|
||||
|
||||
// Get the information from the keypoint
|
||||
yf = kpt.pt.y;
|
||||
xf = kpt.pt.x;
|
||||
scale = cvRound(kpt.size / 2.0f);
|
||||
angle = kpt.angle * static_cast<float>(CV_PI / 180.f);
|
||||
level = kpt.class_id;
|
||||
co = cos(angle);
|
||||
si = sin(angle);
|
||||
|
||||
i = -8;
|
||||
|
||||
// Calculate descriptor for this interest point
|
||||
// Area of size 24 s x 24 s
|
||||
while (i < pattern_size) {
|
||||
|
||||
j = -8;
|
||||
i = i - 4;
|
||||
|
||||
cx += 1.0f;
|
||||
cy = -0.5f;
|
||||
|
||||
while (j < pattern_size) {
|
||||
|
||||
dx = dy = mdx = mdy = 0.0;
|
||||
cy += 1.0f;
|
||||
j = j - 4;
|
||||
|
||||
ky = i + sample_step;
|
||||
kx = j + sample_step;
|
||||
|
||||
xs = xf + (-kx*scale*si + ky*scale*co);
|
||||
ys = yf + (kx*scale*co + ky*scale*si);
|
||||
|
||||
for (int k = i; k < i + 9; ++k) {
|
||||
for (int l = j; l < j + 9; ++l) {
|
||||
|
||||
// Get coords of sample point on the rotated axis
|
||||
sample_y = yf + (l*scale*co + k*scale*si);
|
||||
sample_x = xf + (-l*scale*si + k*scale*co);
|
||||
|
||||
// Get the gaussian weighted x and y responses
|
||||
gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale);
|
||||
y1 = cvFloor(sample_y);
|
||||
x1 = cvFloor(sample_x);
|
||||
|
||||
checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height);
|
||||
|
||||
y2 = y1 + 1;
|
||||
x2 = x1 + 1;
|
||||
|
||||
checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height);
|
||||
|
||||
fx = sample_x - x1;
|
||||
fy = sample_y - y1;
|
||||
|
||||
res1 = *(evolution[level].Lx.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Lx.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Lx.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Lx.ptr<float>(y2)+x2);
|
||||
rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
res1 = *(evolution[level].Ly.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Ly.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Ly.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Ly.ptr<float>(y2)+x2);
|
||||
ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
// Get the x and y derivatives on the rotated axis
|
||||
rry = gauss_s1*(rx*co + ry*si);
|
||||
rrx = gauss_s1*(-rx*si + ry*co);
|
||||
|
||||
// Sum the derivatives to the cumulative descriptor
|
||||
dx += rrx;
|
||||
dy += rry;
|
||||
mdx += fabs(rrx);
|
||||
mdy += fabs(rry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the values to the descriptor vector
|
||||
gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f);
|
||||
desc[dcount++] = dx*gauss_s2;
|
||||
desc[dcount++] = dy*gauss_s2;
|
||||
desc[dcount++] = mdx*gauss_s2;
|
||||
desc[dcount++] = mdy*gauss_s2;
|
||||
len += (dx*dx + dy*dy + mdx*mdx + mdy*mdy)*gauss_s2*gauss_s2;
|
||||
j += 9;
|
||||
}
|
||||
i += 9;
|
||||
}
|
||||
|
||||
// convert to unit vector
|
||||
len = sqrt(len);
|
||||
|
||||
for (i = 0; i < dsize; i++) {
|
||||
desc[i] /= len;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the extended upright descriptor (not rotation invariant) of
|
||||
* the provided keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
* @note Rectangular grid of 24 s x 24 s. Descriptor Length 128. The descriptor is inspired
|
||||
* from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching,
|
||||
* ECCV 2008
|
||||
*/
|
||||
void KAZE_Descriptor_Invoker::Get_KAZE_Upright_Descriptor_128(const KeyPoint &kpt, float *desc) const
|
||||
{
|
||||
float gauss_s1 = 0.0, gauss_s2 = 0.0;
|
||||
float rx = 0.0, ry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0;
|
||||
float sample_x = 0.0, sample_y = 0.0;
|
||||
int x1 = 0, y1 = 0, sample_step = 0, pattern_size = 0;
|
||||
int x2 = 0, y2 = 0, kx = 0, ky = 0, i = 0, j = 0, dcount = 0;
|
||||
float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0;
|
||||
float dxp = 0.0, dyp = 0.0, mdxp = 0.0, mdyp = 0.0;
|
||||
float dxn = 0.0, dyn = 0.0, mdxn = 0.0, mdyn = 0.0;
|
||||
int dsize = 0, scale = 0, level = 0;
|
||||
|
||||
// Subregion centers for the 4x4 gaussian weighting
|
||||
float cx = -0.5f, cy = 0.5f;
|
||||
|
||||
std::vector<TEvolution>& evolution = *evolution_;
|
||||
|
||||
// Set the descriptor size and the sample and pattern sizes
|
||||
dsize = 128;
|
||||
sample_step = 5;
|
||||
pattern_size = 12;
|
||||
|
||||
// Get the information from the keypoint
|
||||
yf = kpt.pt.y;
|
||||
xf = kpt.pt.x;
|
||||
scale = cvRound(kpt.size / 2.0f);
|
||||
level = kpt.class_id;
|
||||
|
||||
i = -8;
|
||||
|
||||
// Calculate descriptor for this interest point
|
||||
// Area of size 24 s x 24 s
|
||||
while (i < pattern_size) {
|
||||
|
||||
j = -8;
|
||||
i = i - 4;
|
||||
|
||||
cx += 1.0f;
|
||||
cy = -0.5f;
|
||||
|
||||
while (j < pattern_size) {
|
||||
|
||||
dxp = dxn = mdxp = mdxn = 0.0;
|
||||
dyp = dyn = mdyp = mdyn = 0.0;
|
||||
|
||||
cy += 1.0f;
|
||||
j = j - 4;
|
||||
|
||||
ky = i + sample_step;
|
||||
kx = j + sample_step;
|
||||
|
||||
ys = yf + (ky*scale);
|
||||
xs = xf + (kx*scale);
|
||||
|
||||
for (int k = i; k < i + 9; k++) {
|
||||
for (int l = j; l < j + 9; l++) {
|
||||
|
||||
sample_y = k*scale + yf;
|
||||
sample_x = l*scale + xf;
|
||||
|
||||
//Get the gaussian weighted x and y responses
|
||||
gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale);
|
||||
|
||||
y1 = (int)(sample_y - 0.5f);
|
||||
x1 = (int)(sample_x - 0.5f);
|
||||
|
||||
checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height);
|
||||
|
||||
y2 = (int)(sample_y + 0.5f);
|
||||
x2 = (int)(sample_x + 0.5f);
|
||||
|
||||
checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height);
|
||||
|
||||
fx = sample_x - x1;
|
||||
fy = sample_y - y1;
|
||||
|
||||
res1 = *(evolution[level].Lx.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Lx.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Lx.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Lx.ptr<float>(y2)+x2);
|
||||
rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
res1 = *(evolution[level].Ly.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Ly.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Ly.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Ly.ptr<float>(y2)+x2);
|
||||
ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
rx = gauss_s1*rx;
|
||||
ry = gauss_s1*ry;
|
||||
|
||||
// Sum the derivatives to the cumulative descriptor
|
||||
if (ry >= 0.0) {
|
||||
dxp += rx;
|
||||
mdxp += fabs(rx);
|
||||
}
|
||||
else {
|
||||
dxn += rx;
|
||||
mdxn += fabs(rx);
|
||||
}
|
||||
|
||||
if (rx >= 0.0) {
|
||||
dyp += ry;
|
||||
mdyp += fabs(ry);
|
||||
}
|
||||
else {
|
||||
dyn += ry;
|
||||
mdyn += fabs(ry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the values to the descriptor vector
|
||||
gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f);
|
||||
|
||||
desc[dcount++] = dxp*gauss_s2;
|
||||
desc[dcount++] = dxn*gauss_s2;
|
||||
desc[dcount++] = mdxp*gauss_s2;
|
||||
desc[dcount++] = mdxn*gauss_s2;
|
||||
desc[dcount++] = dyp*gauss_s2;
|
||||
desc[dcount++] = dyn*gauss_s2;
|
||||
desc[dcount++] = mdyp*gauss_s2;
|
||||
desc[dcount++] = mdyn*gauss_s2;
|
||||
|
||||
// Store the current length^2 of the vector
|
||||
len += (dxp*dxp + dxn*dxn + mdxp*mdxp + mdxn*mdxn +
|
||||
dyp*dyp + dyn*dyn + mdyp*mdyp + mdyn*mdyn)*gauss_s2*gauss_s2;
|
||||
|
||||
j += 9;
|
||||
}
|
||||
|
||||
i += 9;
|
||||
}
|
||||
|
||||
// convert to unit vector
|
||||
len = sqrt(len);
|
||||
|
||||
for (i = 0; i < dsize; i++) {
|
||||
desc[i] /= len;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This method computes the extended G-SURF descriptor of the provided keypoint
|
||||
* given the main orientation of the keypoint
|
||||
* @param kpt Input keypoint
|
||||
* @param desc Descriptor vector
|
||||
* @note Rectangular grid of 24 s x 24 s. Descriptor Length 128. The descriptor is inspired
|
||||
* from Agrawal et al., CenSurE: Center Surround Extremas for Realtime Feature Detection and Matching,
|
||||
* ECCV 2008
|
||||
*/
|
||||
void KAZE_Descriptor_Invoker::Get_KAZE_Descriptor_128(const KeyPoint &kpt, float *desc) const
|
||||
{
|
||||
float gauss_s1 = 0.0, gauss_s2 = 0.0;
|
||||
float rx = 0.0, ry = 0.0, rrx = 0.0, rry = 0.0, len = 0.0, xf = 0.0, yf = 0.0, ys = 0.0, xs = 0.0;
|
||||
float sample_x = 0.0, sample_y = 0.0, co = 0.0, si = 0.0, angle = 0.0;
|
||||
float fx = 0.0, fy = 0.0, res1 = 0.0, res2 = 0.0, res3 = 0.0, res4 = 0.0;
|
||||
float dxp = 0.0, dyp = 0.0, mdxp = 0.0, mdyp = 0.0;
|
||||
float dxn = 0.0, dyn = 0.0, mdxn = 0.0, mdyn = 0.0;
|
||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0, sample_step = 0, pattern_size = 0;
|
||||
int kx = 0, ky = 0, i = 0, j = 0, dcount = 0;
|
||||
int dsize = 0, scale = 0, level = 0;
|
||||
|
||||
std::vector<TEvolution>& evolution = *evolution_;
|
||||
|
||||
// Subregion centers for the 4x4 gaussian weighting
|
||||
float cx = -0.5f, cy = 0.5f;
|
||||
|
||||
// Set the descriptor size and the sample and pattern sizes
|
||||
dsize = 128;
|
||||
sample_step = 5;
|
||||
pattern_size = 12;
|
||||
|
||||
// Get the information from the keypoint
|
||||
yf = kpt.pt.y;
|
||||
xf = kpt.pt.x;
|
||||
scale = cvRound(kpt.size / 2.0f);
|
||||
angle = kpt.angle * static_cast<float>(CV_PI / 180.f);
|
||||
level = kpt.class_id;
|
||||
co = cos(angle);
|
||||
si = sin(angle);
|
||||
|
||||
i = -8;
|
||||
|
||||
// Calculate descriptor for this interest point
|
||||
// Area of size 24 s x 24 s
|
||||
while (i < pattern_size) {
|
||||
|
||||
j = -8;
|
||||
i = i - 4;
|
||||
|
||||
cx += 1.0f;
|
||||
cy = -0.5f;
|
||||
|
||||
while (j < pattern_size) {
|
||||
|
||||
dxp = dxn = mdxp = mdxn = 0.0;
|
||||
dyp = dyn = mdyp = mdyn = 0.0;
|
||||
|
||||
cy += 1.0f;
|
||||
j = j - 4;
|
||||
|
||||
ky = i + sample_step;
|
||||
kx = j + sample_step;
|
||||
|
||||
xs = xf + (-kx*scale*si + ky*scale*co);
|
||||
ys = yf + (kx*scale*co + ky*scale*si);
|
||||
|
||||
for (int k = i; k < i + 9; ++k) {
|
||||
for (int l = j; l < j + 9; ++l) {
|
||||
|
||||
// Get coords of sample point on the rotated axis
|
||||
sample_y = yf + (l*scale*co + k*scale*si);
|
||||
sample_x = xf + (-l*scale*si + k*scale*co);
|
||||
|
||||
// Get the gaussian weighted x and y responses
|
||||
gauss_s1 = gaussian(xs - sample_x, ys - sample_y, 2.5f*scale);
|
||||
|
||||
y1 = cvFloor(sample_y);
|
||||
x1 = cvFloor(sample_x);
|
||||
|
||||
checkDescriptorLimits(x1, y1, options_.img_width, options_.img_height);
|
||||
|
||||
y2 = y1 + 1;
|
||||
x2 = x1 + 1;
|
||||
|
||||
checkDescriptorLimits(x2, y2, options_.img_width, options_.img_height);
|
||||
|
||||
fx = sample_x - x1;
|
||||
fy = sample_y - y1;
|
||||
|
||||
res1 = *(evolution[level].Lx.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Lx.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Lx.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Lx.ptr<float>(y2)+x2);
|
||||
rx = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
res1 = *(evolution[level].Ly.ptr<float>(y1)+x1);
|
||||
res2 = *(evolution[level].Ly.ptr<float>(y1)+x2);
|
||||
res3 = *(evolution[level].Ly.ptr<float>(y2)+x1);
|
||||
res4 = *(evolution[level].Ly.ptr<float>(y2)+x2);
|
||||
ry = (1.0f - fx)*(1.0f - fy)*res1 + fx*(1.0f - fy)*res2 + (1.0f - fx)*fy*res3 + fx*fy*res4;
|
||||
|
||||
// Get the x and y derivatives on the rotated axis
|
||||
rry = gauss_s1*(rx*co + ry*si);
|
||||
rrx = gauss_s1*(-rx*si + ry*co);
|
||||
|
||||
// Sum the derivatives to the cumulative descriptor
|
||||
if (rry >= 0.0) {
|
||||
dxp += rrx;
|
||||
mdxp += fabs(rrx);
|
||||
}
|
||||
else {
|
||||
dxn += rrx;
|
||||
mdxn += fabs(rrx);
|
||||
}
|
||||
|
||||
if (rrx >= 0.0) {
|
||||
dyp += rry;
|
||||
mdyp += fabs(rry);
|
||||
}
|
||||
else {
|
||||
dyn += rry;
|
||||
mdyn += fabs(rry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the values to the descriptor vector
|
||||
gauss_s2 = gaussian(cx - 2.0f, cy - 2.0f, 1.5f);
|
||||
|
||||
desc[dcount++] = dxp*gauss_s2;
|
||||
desc[dcount++] = dxn*gauss_s2;
|
||||
desc[dcount++] = mdxp*gauss_s2;
|
||||
desc[dcount++] = mdxn*gauss_s2;
|
||||
desc[dcount++] = dyp*gauss_s2;
|
||||
desc[dcount++] = dyn*gauss_s2;
|
||||
desc[dcount++] = mdyp*gauss_s2;
|
||||
desc[dcount++] = mdyn*gauss_s2;
|
||||
|
||||
// Store the current length^2 of the vector
|
||||
len += (dxp*dxp + dxn*dxn + mdxp*mdxp + mdxn*mdxn +
|
||||
dyp*dyp + dyn*dyn + mdyp*mdyp + mdyn*mdyn)*gauss_s2*gauss_s2;
|
||||
|
||||
j += 9;
|
||||
}
|
||||
|
||||
i += 9;
|
||||
}
|
||||
|
||||
// convert to unit vector
|
||||
len = sqrt(len);
|
||||
|
||||
for (i = 0; i < dsize; i++) {
|
||||
desc[i] /= len;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
|
||||
/**
|
||||
* @file KAZE.h
|
||||
* @brief Main program for detecting and computing descriptors in a nonlinear
|
||||
* scale space
|
||||
* @date Jan 21, 2012
|
||||
* @author Pablo F. Alcantarilla
|
||||
*/
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_KAZE_FEATURES_H__
|
||||
#define __OPENCV_FEATURES_2D_KAZE_FEATURES_H__
|
||||
|
||||
/* ************************************************************************* */
|
||||
// Includes
|
||||
#include "KAZEConfig.h"
|
||||
#include "nldiffusion_functions.h"
|
||||
#include "fed.h"
|
||||
#include "TEvolution.h"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/* ************************************************************************* */
|
||||
// KAZE Class Declaration
|
||||
class KAZEFeatures
|
||||
{
|
||||
private:
|
||||
|
||||
/// Parameters of the Nonlinear diffusion class
|
||||
KAZEOptions options_; ///< Configuration options for KAZE
|
||||
std::vector<TEvolution> evolution_; ///< Vector of nonlinear diffusion evolution
|
||||
|
||||
/// Vector of keypoint vectors for finding extrema in multiple threads
|
||||
std::vector<std::vector<cv::KeyPoint> > kpts_par_;
|
||||
|
||||
/// FED parameters
|
||||
int ncycles_; ///< Number of cycles
|
||||
bool reordering_; ///< Flag for reordering time steps
|
||||
std::vector<std::vector<float > > tsteps_; ///< Vector of FED dynamic time steps
|
||||
std::vector<int> nsteps_; ///< Vector of number of steps per cycle
|
||||
|
||||
public:
|
||||
|
||||
/// Constructor
|
||||
KAZEFeatures(KAZEOptions& options);
|
||||
|
||||
/// Public methods for KAZE interface
|
||||
void Allocate_Memory_Evolution(void);
|
||||
int Create_Nonlinear_Scale_Space(const cv::Mat& img);
|
||||
void Feature_Detection(std::vector<cv::KeyPoint>& kpts);
|
||||
void Feature_Description(std::vector<cv::KeyPoint>& kpts, cv::Mat& desc);
|
||||
static void Compute_Main_Orientation(cv::KeyPoint& kpt, const std::vector<TEvolution>& evolution_, const KAZEOptions& options);
|
||||
|
||||
/// Feature Detection Methods
|
||||
void Compute_KContrast(const cv::Mat& img, const float& kper);
|
||||
void Compute_Multiscale_Derivatives(void);
|
||||
void Compute_Detector_Response(void);
|
||||
void Determinant_Hessian(std::vector<cv::KeyPoint>& kpts);
|
||||
void Do_Subpixel_Refinement(std::vector<cv::KeyPoint>& kpts);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @file TEvolution.h
|
||||
* @brief Header file with the declaration of the TEvolution struct
|
||||
* @date Jun 02, 2014
|
||||
* @author Pablo F. Alcantarilla
|
||||
*/
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_TEVOLUTION_H__
|
||||
#define __OPENCV_FEATURES_2D_TEVOLUTION_H__
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/* ************************************************************************* */
|
||||
/// KAZE/A-KAZE nonlinear diffusion filtering evolution
|
||||
struct TEvolution
|
||||
{
|
||||
TEvolution() {
|
||||
etime = 0.0f;
|
||||
esigma = 0.0f;
|
||||
octave = 0;
|
||||
sublevel = 0;
|
||||
sigma_size = 0;
|
||||
}
|
||||
|
||||
Mat Lx, Ly; ///< First order spatial derivatives
|
||||
Mat Lxx, Lxy, Lyy; ///< Second order spatial derivatives
|
||||
Mat Lt; ///< Evolution image
|
||||
Mat Lsmooth; ///< Smoothed image
|
||||
Mat Ldet; ///< Detector response
|
||||
|
||||
float etime; ///< Evolution time
|
||||
float esigma; ///< Evolution sigma. For linear diffusion t = sigma^2 / 2
|
||||
int octave; ///< Image octave
|
||||
int sublevel; ///< Image sublevel in each octave
|
||||
int sigma_size; ///< Integer esigma. For computing the feature detector responses
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,192 +0,0 @@
|
||||
//=============================================================================
|
||||
//
|
||||
// fed.cpp
|
||||
// Authors: Pablo F. Alcantarilla (1), Jesus Nuevo (2)
|
||||
// Institutions: Georgia Institute of Technology (1)
|
||||
// TrueVision Solutions (2)
|
||||
// Date: 15/09/2013
|
||||
// Email: pablofdezalc@gmail.com
|
||||
//
|
||||
// AKAZE Features Copyright 2013, Pablo F. Alcantarilla, Jesus Nuevo
|
||||
// All Rights Reserved
|
||||
// See LICENSE for the license information
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* @file fed.cpp
|
||||
* @brief Functions for performing Fast Explicit Diffusion and building the
|
||||
* nonlinear scale space
|
||||
* @date Sep 15, 2013
|
||||
* @author Pablo F. Alcantarilla, Jesus Nuevo
|
||||
* @note This code is derived from FED/FJ library from Grewenig et al.,
|
||||
* The FED/FJ library allows solving more advanced problems
|
||||
* Please look at the following papers for more information about FED:
|
||||
* [1] S. Grewenig, J. Weickert, C. Schroers, A. Bruhn. Cyclic Schemes for
|
||||
* PDE-Based Image Analysis. Technical Report No. 327, Department of Mathematics,
|
||||
* Saarland University, Saarbrücken, Germany, March 2013
|
||||
* [2] S. Grewenig, J. Weickert, A. Bruhn. From box filtering to fast explicit diffusion.
|
||||
* DAGM, 2010
|
||||
*
|
||||
*/
|
||||
#include "../precomp.hpp"
|
||||
#include "fed.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
//*************************************************************************************
|
||||
//*************************************************************************************
|
||||
|
||||
/**
|
||||
* @brief This function allocates an array of the least number of time steps such
|
||||
* that a certain stopping time for the whole process can be obtained and fills
|
||||
* it with the respective FED time step sizes for one cycle
|
||||
* The function returns the number of time steps per cycle or 0 on failure
|
||||
* @param T Desired process stopping time
|
||||
* @param M Desired number of cycles
|
||||
* @param tau_max Stability limit for the explicit scheme
|
||||
* @param reordering Reordering flag
|
||||
* @param tau The vector with the dynamic step sizes
|
||||
*/
|
||||
int fed_tau_by_process_time(const float& T, const int& M, const float& tau_max,
|
||||
const bool& reordering, std::vector<float>& tau) {
|
||||
// All cycles have the same fraction of the stopping time
|
||||
return fed_tau_by_cycle_time(T/(float)M,tau_max,reordering,tau);
|
||||
}
|
||||
|
||||
//*************************************************************************************
|
||||
//*************************************************************************************
|
||||
|
||||
/**
|
||||
* @brief This function allocates an array of the least number of time steps such
|
||||
* that a certain stopping time for the whole process can be obtained and fills it
|
||||
* it with the respective FED time step sizes for one cycle
|
||||
* The function returns the number of time steps per cycle or 0 on failure
|
||||
* @param t Desired cycle stopping time
|
||||
* @param tau_max Stability limit for the explicit scheme
|
||||
* @param reordering Reordering flag
|
||||
* @param tau The vector with the dynamic step sizes
|
||||
*/
|
||||
int fed_tau_by_cycle_time(const float& t, const float& tau_max,
|
||||
const bool& reordering, std::vector<float> &tau) {
|
||||
int n = 0; // Number of time steps
|
||||
float scale = 0.0; // Ratio of t we search to maximal t
|
||||
|
||||
// Compute necessary number of time steps
|
||||
n = cvCeil(sqrtf(3.0f*t/tau_max+0.25f)-0.5f-1.0e-8f);
|
||||
scale = 3.0f*t/(tau_max*(float)(n*(n+1)));
|
||||
|
||||
// Call internal FED time step creation routine
|
||||
return fed_tau_internal(n,scale,tau_max,reordering,tau);
|
||||
}
|
||||
|
||||
//*************************************************************************************
|
||||
//*************************************************************************************
|
||||
|
||||
/**
|
||||
* @brief This function allocates an array of time steps and fills it with FED
|
||||
* time step sizes
|
||||
* The function returns the number of time steps per cycle or 0 on failure
|
||||
* @param n Number of internal steps
|
||||
* @param scale Ratio of t we search to maximal t
|
||||
* @param tau_max Stability limit for the explicit scheme
|
||||
* @param reordering Reordering flag
|
||||
* @param tau The vector with the dynamic step sizes
|
||||
*/
|
||||
int fed_tau_internal(const int& n, const float& scale, const float& tau_max,
|
||||
const bool& reordering, std::vector<float> &tau) {
|
||||
float c = 0.0, d = 0.0; // Time savers
|
||||
vector<float> tauh; // Helper vector for unsorted taus
|
||||
|
||||
if (n <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory for the time step size
|
||||
tau = vector<float>(n);
|
||||
|
||||
if (reordering) {
|
||||
tauh = vector<float>(n);
|
||||
}
|
||||
|
||||
// Compute time saver
|
||||
c = 1.0f / (4.0f * (float)n + 2.0f);
|
||||
d = scale * tau_max / 2.0f;
|
||||
|
||||
// Set up originally ordered tau vector
|
||||
for (int k = 0; k < n; ++k) {
|
||||
float h = cosf((float)CV_PI * (2.0f * (float)k + 1.0f) * c);
|
||||
|
||||
if (reordering) {
|
||||
tauh[k] = d / (h * h);
|
||||
}
|
||||
else {
|
||||
tau[k] = d / (h * h);
|
||||
}
|
||||
}
|
||||
|
||||
// Permute list of time steps according to chosen reordering function
|
||||
int kappa = 0, prime = 0;
|
||||
|
||||
if (reordering == true) {
|
||||
// Choose kappa cycle with k = n/2
|
||||
// This is a heuristic. We can use Leja ordering instead!!
|
||||
kappa = n / 2;
|
||||
|
||||
// Get modulus for permutation
|
||||
prime = n + 1;
|
||||
|
||||
while (!fed_is_prime_internal(prime)) {
|
||||
prime++;
|
||||
}
|
||||
|
||||
// Perform permutation
|
||||
for (int k = 0, l = 0; l < n; ++k, ++l) {
|
||||
int index = 0;
|
||||
while ((index = ((k+1)*kappa) % prime - 1) >= n) {
|
||||
k++;
|
||||
}
|
||||
|
||||
tau[l] = tauh[index];
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
//*************************************************************************************
|
||||
//*************************************************************************************
|
||||
|
||||
/**
|
||||
* @brief This function checks if a number is prime or not
|
||||
* @param number Number to check if it is prime or not
|
||||
* @return true if the number is prime
|
||||
*/
|
||||
bool fed_is_prime_internal(const int& number) {
|
||||
bool is_prime = false;
|
||||
|
||||
if (number <= 1) {
|
||||
return false;
|
||||
}
|
||||
else if (number == 1 || number == 2 || number == 3 || number == 5 || number == 7) {
|
||||
return true;
|
||||
}
|
||||
else if ((number % 2) == 0 || (number % 3) == 0 || (number % 5) == 0 || (number % 7) == 0) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
is_prime = true;
|
||||
int upperLimit = (int)sqrt(1.0f + number);
|
||||
int divisor = 11;
|
||||
|
||||
while (divisor <= upperLimit ) {
|
||||
if (number % divisor == 0)
|
||||
{
|
||||
is_prime = false;
|
||||
}
|
||||
|
||||
divisor +=2;
|
||||
}
|
||||
|
||||
return is_prime;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef __OPENCV_FEATURES_2D_FED_H__
|
||||
#define __OPENCV_FEATURES_2D_FED_H__
|
||||
|
||||
//******************************************************************************
|
||||
//******************************************************************************
|
||||
|
||||
// Includes
|
||||
#include <vector>
|
||||
|
||||
//*************************************************************************************
|
||||
//*************************************************************************************
|
||||
|
||||
// Declaration of functions
|
||||
int fed_tau_by_process_time(const float& T, const int& M, const float& tau_max,
|
||||
const bool& reordering, std::vector<float>& tau);
|
||||
int fed_tau_by_cycle_time(const float& t, const float& tau_max,
|
||||
const bool& reordering, std::vector<float> &tau) ;
|
||||
int fed_tau_internal(const int& n, const float& scale, const float& tau_max,
|
||||
const bool& reordering, std::vector<float> &tau);
|
||||
bool fed_is_prime_internal(const int& number);
|
||||
|
||||
//*************************************************************************************
|
||||
//*************************************************************************************
|
||||
|
||||
#endif // __OPENCV_FEATURES_2D_FED_H__
|
||||
@@ -1,542 +0,0 @@
|
||||
//=============================================================================
|
||||
//
|
||||
// nldiffusion_functions.cpp
|
||||
// Author: Pablo F. Alcantarilla
|
||||
// Institution: University d'Auvergne
|
||||
// Address: Clermont Ferrand, France
|
||||
// Date: 27/12/2011
|
||||
// Email: pablofdezalc@gmail.com
|
||||
//
|
||||
// KAZE Features Copyright 2012, Pablo F. Alcantarilla
|
||||
// All Rights Reserved
|
||||
// See LICENSE for the license information
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* @file nldiffusion_functions.cpp
|
||||
* @brief Functions for non-linear diffusion applications:
|
||||
* 2D Gaussian Derivatives
|
||||
* Perona and Malik conductivity equations
|
||||
* Perona and Malik evolution
|
||||
* @date Dec 27, 2011
|
||||
* @author Pablo F. Alcantarilla
|
||||
*/
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "nldiffusion_functions.h"
|
||||
#include <iostream>
|
||||
|
||||
// Namespaces
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
namespace cv
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function smoothes an image with a Gaussian kernel
|
||||
* @param src Input image
|
||||
* @param dst Output image
|
||||
* @param ksize_x Kernel size in X-direction (horizontal)
|
||||
* @param ksize_y Kernel size in Y-direction (vertical)
|
||||
* @param sigma Kernel standard deviation
|
||||
*/
|
||||
void gaussian_2D_convolution(const cv::Mat& src, cv::Mat& dst, int ksize_x, int ksize_y, float sigma) {
|
||||
|
||||
int ksize_x_ = 0, ksize_y_ = 0;
|
||||
|
||||
// Compute an appropriate kernel size according to the specified sigma
|
||||
if (sigma > ksize_x || sigma > ksize_y || ksize_x == 0 || ksize_y == 0) {
|
||||
ksize_x_ = cvCeil(2.0f*(1.0f + (sigma - 0.8f) / (0.3f)));
|
||||
ksize_y_ = ksize_x_;
|
||||
}
|
||||
|
||||
// The kernel size must be and odd number
|
||||
if ((ksize_x_ % 2) == 0) {
|
||||
ksize_x_ += 1;
|
||||
}
|
||||
|
||||
if ((ksize_y_ % 2) == 0) {
|
||||
ksize_y_ += 1;
|
||||
}
|
||||
|
||||
// Perform the Gaussian Smoothing with border replication
|
||||
GaussianBlur(src, dst, Size(ksize_x_, ksize_y_), sigma, sigma, BORDER_REPLICATE);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes image derivatives with Scharr kernel
|
||||
* @param src Input image
|
||||
* @param dst Output image
|
||||
* @param xorder Derivative order in X-direction (horizontal)
|
||||
* @param yorder Derivative order in Y-direction (vertical)
|
||||
* @note Scharr operator approximates better rotation invariance than
|
||||
* other stencils such as Sobel. See Weickert and Scharr,
|
||||
* A Scheme for Coherence-Enhancing Diffusion Filtering with Optimized Rotation Invariance,
|
||||
* Journal of Visual Communication and Image Representation 2002
|
||||
*/
|
||||
void image_derivatives_scharr(const cv::Mat& src, cv::Mat& dst, int xorder, int yorder) {
|
||||
Scharr(src, dst, CV_32F, xorder, yorder, 1.0, 0, BORDER_DEFAULT);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes the Perona and Malik conductivity coefficient g1
|
||||
* g1 = exp(-|dL|^2/k^2)
|
||||
* @param _Lx First order image derivative in X-direction (horizontal)
|
||||
* @param _Ly First order image derivative in Y-direction (vertical)
|
||||
* @param _dst Output image
|
||||
* @param k Contrast factor parameter
|
||||
*/
|
||||
void pm_g1(InputArray _Lx, InputArray _Ly, OutputArray _dst, float k) {
|
||||
_dst.create(_Lx.size(), _Lx.type());
|
||||
Mat Lx = _Lx.getMat();
|
||||
Mat Ly = _Ly.getMat();
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
Size sz = Lx.size();
|
||||
float inv_k = 1.0f / (k*k);
|
||||
for (int y = 0; y < sz.height; y++) {
|
||||
|
||||
const float* Lx_row = Lx.ptr<float>(y);
|
||||
const float* Ly_row = Ly.ptr<float>(y);
|
||||
float* dst_row = dst.ptr<float>(y);
|
||||
|
||||
for (int x = 0; x < sz.width; x++) {
|
||||
dst_row[x] = (-inv_k*(Lx_row[x]*Lx_row[x] + Ly_row[x]*Ly_row[x]));
|
||||
}
|
||||
}
|
||||
|
||||
exp(dst, dst);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes the Perona and Malik conductivity coefficient g2
|
||||
* g2 = 1 / (1 + dL^2 / k^2)
|
||||
* @param _Lx First order image derivative in X-direction (horizontal)
|
||||
* @param _Ly First order image derivative in Y-direction (vertical)
|
||||
* @param _dst Output image
|
||||
* @param k Contrast factor parameter
|
||||
*/
|
||||
void pm_g2(InputArray _Lx, InputArray _Ly, OutputArray _dst, float k) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
_dst.create(_Lx.size(), _Lx.type());
|
||||
Mat Lx = _Lx.getMat();
|
||||
Mat Ly = _Ly.getMat();
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
Size sz = Lx.size();
|
||||
dst.create(sz, Lx.type());
|
||||
float k2inv = 1.0f / (k * k);
|
||||
|
||||
for(int y = 0; y < sz.height; y++) {
|
||||
const float *Lx_row = Lx.ptr<float>(y);
|
||||
const float *Ly_row = Ly.ptr<float>(y);
|
||||
float* dst_row = dst.ptr<float>(y);
|
||||
for(int x = 0; x < sz.width; x++) {
|
||||
dst_row[x] = 1.0f / (1.0f + ((Lx_row[x] * Lx_row[x] + Ly_row[x] * Ly_row[x]) * k2inv));
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes Weickert conductivity coefficient gw
|
||||
* @param _Lx First order image derivative in X-direction (horizontal)
|
||||
* @param _Ly First order image derivative in Y-direction (vertical)
|
||||
* @param _dst Output image
|
||||
* @param k Contrast factor parameter
|
||||
* @note For more information check the following paper: J. Weickert
|
||||
* Applications of nonlinear diffusion in image processing and computer vision,
|
||||
* Proceedings of Algorithmy 2000
|
||||
*/
|
||||
void weickert_diffusivity(InputArray _Lx, InputArray _Ly, OutputArray _dst, float k) {
|
||||
_dst.create(_Lx.size(), _Lx.type());
|
||||
Mat Lx = _Lx.getMat();
|
||||
Mat Ly = _Ly.getMat();
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
Size sz = Lx.size();
|
||||
float inv_k = 1.0f / (k*k);
|
||||
for (int y = 0; y < sz.height; y++) {
|
||||
|
||||
const float* Lx_row = Lx.ptr<float>(y);
|
||||
const float* Ly_row = Ly.ptr<float>(y);
|
||||
float* dst_row = dst.ptr<float>(y);
|
||||
|
||||
for (int x = 0; x < sz.width; x++) {
|
||||
float dL = inv_k*(Lx_row[x]*Lx_row[x] + Ly_row[x]*Ly_row[x]);
|
||||
dst_row[x] = -3.315f/(dL*dL*dL*dL);
|
||||
}
|
||||
}
|
||||
|
||||
exp(dst, dst);
|
||||
dst = 1.0 - dst;
|
||||
}
|
||||
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes Charbonnier conductivity coefficient gc
|
||||
* gc = 1 / sqrt(1 + dL^2 / k^2)
|
||||
* @param _Lx First order image derivative in X-direction (horizontal)
|
||||
* @param _Ly First order image derivative in Y-direction (vertical)
|
||||
* @param _dst Output image
|
||||
* @param k Contrast factor parameter
|
||||
* @note For more information check the following paper: J. Weickert
|
||||
* Applications of nonlinear diffusion in image processing and computer vision,
|
||||
* Proceedings of Algorithmy 2000
|
||||
*/
|
||||
void charbonnier_diffusivity(InputArray _Lx, InputArray _Ly, OutputArray _dst, float k) {
|
||||
_dst.create(_Lx.size(), _Lx.type());
|
||||
Mat Lx = _Lx.getMat();
|
||||
Mat Ly = _Ly.getMat();
|
||||
Mat dst = _dst.getMat();
|
||||
|
||||
Size sz = Lx.size();
|
||||
float inv_k = 1.0f / (k*k);
|
||||
for (int y = 0; y < sz.height; y++) {
|
||||
|
||||
const float* Lx_row = Lx.ptr<float>(y);
|
||||
const float* Ly_row = Ly.ptr<float>(y);
|
||||
float* dst_row = dst.ptr<float>(y);
|
||||
|
||||
for (int x = 0; x < sz.width; x++) {
|
||||
float den = sqrt(1.0f+inv_k*(Lx_row[x]*Lx_row[x] + Ly_row[x]*Ly_row[x]));
|
||||
dst_row[x] = 1.0f / den;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes a good empirical value for the k contrast factor
|
||||
* given an input image, the percentile (0-1), the gradient scale and the number of
|
||||
* bins in the histogram
|
||||
* @param img Input image
|
||||
* @param perc Percentile of the image gradient histogram (0-1)
|
||||
* @param gscale Scale for computing the image gradient histogram
|
||||
* @param nbins Number of histogram bins
|
||||
* @param ksize_x Kernel size in X-direction (horizontal) for the Gaussian smoothing kernel
|
||||
* @param ksize_y Kernel size in Y-direction (vertical) for the Gaussian smoothing kernel
|
||||
* @return k contrast factor
|
||||
*/
|
||||
float compute_k_percentile(const cv::Mat& img, float perc, float gscale, int nbins, int ksize_x, int ksize_y) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
int nbin = 0, nelements = 0, nthreshold = 0, k = 0;
|
||||
float kperc = 0.0, modg = 0.0;
|
||||
float npoints = 0.0;
|
||||
float hmax = 0.0;
|
||||
|
||||
// Create the array for the histogram
|
||||
std::vector<int> hist(nbins, 0);
|
||||
|
||||
// Create the matrices
|
||||
Mat gaussian = Mat::zeros(img.rows, img.cols, CV_32F);
|
||||
Mat Lx = Mat::zeros(img.rows, img.cols, CV_32F);
|
||||
Mat Ly = Mat::zeros(img.rows, img.cols, CV_32F);
|
||||
|
||||
// Perform the Gaussian convolution
|
||||
gaussian_2D_convolution(img, gaussian, ksize_x, ksize_y, gscale);
|
||||
|
||||
// Compute the Gaussian derivatives Lx and Ly
|
||||
Scharr(gaussian, Lx, CV_32F, 1, 0, 1, 0, cv::BORDER_DEFAULT);
|
||||
Scharr(gaussian, Ly, CV_32F, 0, 1, 1, 0, cv::BORDER_DEFAULT);
|
||||
|
||||
// Skip the borders for computing the histogram
|
||||
for (int i = 1; i < gaussian.rows - 1; i++) {
|
||||
const float *lx = Lx.ptr<float>(i);
|
||||
const float *ly = Ly.ptr<float>(i);
|
||||
for (int j = 1; j < gaussian.cols - 1; j++) {
|
||||
modg = lx[j]*lx[j] + ly[j]*ly[j];
|
||||
|
||||
// Get the maximum
|
||||
if (modg > hmax) {
|
||||
hmax = modg;
|
||||
}
|
||||
}
|
||||
}
|
||||
hmax = sqrt(hmax);
|
||||
// Skip the borders for computing the histogram
|
||||
for (int i = 1; i < gaussian.rows - 1; i++) {
|
||||
const float *lx = Lx.ptr<float>(i);
|
||||
const float *ly = Ly.ptr<float>(i);
|
||||
for (int j = 1; j < gaussian.cols - 1; j++) {
|
||||
modg = lx[j]*lx[j] + ly[j]*ly[j];
|
||||
|
||||
// Find the correspondent bin
|
||||
if (modg != 0.0) {
|
||||
nbin = (int)floor(nbins*(sqrt(modg) / hmax));
|
||||
|
||||
if (nbin == nbins) {
|
||||
nbin--;
|
||||
}
|
||||
|
||||
hist[nbin]++;
|
||||
npoints++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now find the perc of the histogram percentile
|
||||
nthreshold = (int)(npoints*perc);
|
||||
|
||||
for (k = 0; nelements < nthreshold && k < nbins; k++) {
|
||||
nelements = nelements + hist[k];
|
||||
}
|
||||
|
||||
if (nelements < nthreshold) {
|
||||
kperc = 0.03f;
|
||||
}
|
||||
else {
|
||||
kperc = hmax*((float)(k) / (float)nbins);
|
||||
}
|
||||
|
||||
return kperc;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes Scharr image derivatives
|
||||
* @param src Input image
|
||||
* @param dst Output image
|
||||
* @param xorder Derivative order in X-direction (horizontal)
|
||||
* @param yorder Derivative order in Y-direction (vertical)
|
||||
* @param scale Scale factor for the derivative size
|
||||
*/
|
||||
void compute_scharr_derivatives(const cv::Mat& src, cv::Mat& dst, int xorder, int yorder, int scale) {
|
||||
Mat kx, ky;
|
||||
compute_derivative_kernels(kx, ky, xorder, yorder, scale);
|
||||
sepFilter2D(src, dst, CV_32F, kx, ky);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief Compute derivative kernels for sizes different than 3
|
||||
* @param _kx Horizontal kernel ues
|
||||
* @param _ky Vertical kernel values
|
||||
* @param dx Derivative order in X-direction (horizontal)
|
||||
* @param dy Derivative order in Y-direction (vertical)
|
||||
* @param scale Scale factor or derivative size
|
||||
*/
|
||||
void compute_derivative_kernels(cv::OutputArray _kx, cv::OutputArray _ky, int dx, int dy, int scale) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
int ksize = 3 + 2 * (scale - 1);
|
||||
|
||||
// The standard Scharr kernel
|
||||
if (scale == 1) {
|
||||
getDerivKernels(_kx, _ky, dx, dy, 0, true, CV_32F);
|
||||
return;
|
||||
}
|
||||
|
||||
_kx.create(ksize, 1, CV_32F, -1, true);
|
||||
_ky.create(ksize, 1, CV_32F, -1, true);
|
||||
Mat kx = _kx.getMat();
|
||||
Mat ky = _ky.getMat();
|
||||
std::vector<float> kerI;
|
||||
|
||||
float w = 10.0f / 3.0f;
|
||||
float norm = 1.0f / (2.0f*scale*(w + 2.0f));
|
||||
|
||||
for (int k = 0; k < 2; k++) {
|
||||
Mat* kernel = k == 0 ? &kx : &ky;
|
||||
int order = k == 0 ? dx : dy;
|
||||
kerI.assign(ksize, 0.0f);
|
||||
|
||||
if (order == 0) {
|
||||
kerI[0] = norm, kerI[ksize / 2] = w*norm, kerI[ksize - 1] = norm;
|
||||
}
|
||||
else if (order == 1) {
|
||||
kerI[0] = -1, kerI[ksize / 2] = 0, kerI[ksize - 1] = 1;
|
||||
}
|
||||
|
||||
Mat temp(kernel->rows, kernel->cols, CV_32F, &kerI[0]);
|
||||
temp.copyTo(*kernel);
|
||||
}
|
||||
}
|
||||
|
||||
class Nld_Step_Scalar_Invoker : public cv::ParallelLoopBody
|
||||
{
|
||||
public:
|
||||
Nld_Step_Scalar_Invoker(cv::Mat& Ld, const cv::Mat& c, cv::Mat& Lstep, float _stepsize)
|
||||
: _Ld(&Ld)
|
||||
, _c(&c)
|
||||
, _Lstep(&Lstep)
|
||||
, stepsize(_stepsize)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~Nld_Step_Scalar_Invoker()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void operator()(const cv::Range& range) const CV_OVERRIDE
|
||||
{
|
||||
cv::Mat& Ld = *_Ld;
|
||||
const cv::Mat& c = *_c;
|
||||
cv::Mat& Lstep = *_Lstep;
|
||||
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
const float *c_prev = c.ptr<float>(i - 1);
|
||||
const float *c_curr = c.ptr<float>(i);
|
||||
const float *c_next = c.ptr<float>(i + 1);
|
||||
const float *ld_prev = Ld.ptr<float>(i - 1);
|
||||
const float *ld_curr = Ld.ptr<float>(i);
|
||||
const float *ld_next = Ld.ptr<float>(i + 1);
|
||||
|
||||
float *dst = Lstep.ptr<float>(i);
|
||||
|
||||
for (int j = 1; j < Lstep.cols - 1; j++)
|
||||
{
|
||||
float xpos = (c_curr[j] + c_curr[j+1])*(ld_curr[j+1] - ld_curr[j]);
|
||||
float xneg = (c_curr[j-1] + c_curr[j]) *(ld_curr[j] - ld_curr[j-1]);
|
||||
float ypos = (c_curr[j] + c_next[j]) *(ld_next[j] - ld_curr[j]);
|
||||
float yneg = (c_prev[j] + c_curr[j]) *(ld_curr[j] - ld_prev[j]);
|
||||
dst[j] = 0.5f*stepsize*(xpos - xneg + ypos - yneg);
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
cv::Mat * _Ld;
|
||||
const cv::Mat * _c;
|
||||
cv::Mat * _Lstep;
|
||||
float stepsize;
|
||||
};
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function performs a scalar non-linear diffusion step
|
||||
* @param Ld Output image in the evolution
|
||||
* @param c Conductivity image
|
||||
* @param Lstep Previous image in the evolution
|
||||
* @param stepsize The step size in time units
|
||||
* @note Forward Euler Scheme 3x3 stencil
|
||||
* The function c is a scalar value that depends on the gradient norm
|
||||
* dL_by_ds = d(c dL_by_dx)_by_dx + d(c dL_by_dy)_by_dy
|
||||
*/
|
||||
void nld_step_scalar(cv::Mat& Ld, const cv::Mat& c, cv::Mat& Lstep, float stepsize) {
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
cv::parallel_for_(cv::Range(1, Lstep.rows - 1), Nld_Step_Scalar_Invoker(Ld, c, Lstep, stepsize), (double)Ld.total()/(1 << 16));
|
||||
|
||||
float xneg, xpos, yneg, ypos;
|
||||
float* dst = Lstep.ptr<float>(0);
|
||||
const float* cprv = NULL;
|
||||
const float* ccur = c.ptr<float>(0);
|
||||
const float* cnxt = c.ptr<float>(1);
|
||||
const float* ldprv = NULL;
|
||||
const float* ldcur = Ld.ptr<float>(0);
|
||||
const float* ldnxt = Ld.ptr<float>(1);
|
||||
for (int j = 1; j < Lstep.cols - 1; j++) {
|
||||
xpos = (ccur[j] + ccur[j+1]) * (ldcur[j+1] - ldcur[j]);
|
||||
xneg = (ccur[j-1] + ccur[j]) * (ldcur[j] - ldcur[j-1]);
|
||||
ypos = (ccur[j] + cnxt[j]) * (ldnxt[j] - ldcur[j]);
|
||||
dst[j] = 0.5f*stepsize*(xpos - xneg + ypos);
|
||||
}
|
||||
|
||||
dst = Lstep.ptr<float>(Lstep.rows - 1);
|
||||
ccur = c.ptr<float>(Lstep.rows - 1);
|
||||
cprv = c.ptr<float>(Lstep.rows - 2);
|
||||
ldcur = Ld.ptr<float>(Lstep.rows - 1);
|
||||
ldprv = Ld.ptr<float>(Lstep.rows - 2);
|
||||
|
||||
for (int j = 1; j < Lstep.cols - 1; j++) {
|
||||
xpos = (ccur[j] + ccur[j+1]) * (ldcur[j+1] - ldcur[j]);
|
||||
xneg = (ccur[j-1] + ccur[j]) * (ldcur[j] - ldcur[j-1]);
|
||||
yneg = (cprv[j] + ccur[j]) * (ldcur[j] - ldprv[j]);
|
||||
dst[j] = 0.5f*stepsize*(xpos - xneg - yneg);
|
||||
}
|
||||
|
||||
ccur = c.ptr<float>(1);
|
||||
ldcur = Ld.ptr<float>(1);
|
||||
cprv = c.ptr<float>(0);
|
||||
ldprv = Ld.ptr<float>(0);
|
||||
|
||||
int r0 = Lstep.cols - 1;
|
||||
int r1 = Lstep.cols - 2;
|
||||
|
||||
for (int i = 1; i < Lstep.rows - 1; i++) {
|
||||
cnxt = c.ptr<float>(i + 1);
|
||||
ldnxt = Ld.ptr<float>(i + 1);
|
||||
dst = Lstep.ptr<float>(i);
|
||||
|
||||
xpos = (ccur[0] + ccur[1]) * (ldcur[1] - ldcur[0]);
|
||||
ypos = (ccur[0] + cnxt[0]) * (ldnxt[0] - ldcur[0]);
|
||||
yneg = (cprv[0] + ccur[0]) * (ldcur[0] - ldprv[0]);
|
||||
dst[0] = 0.5f*stepsize*(xpos + ypos - yneg);
|
||||
|
||||
xneg = (ccur[r1] + ccur[r0]) * (ldcur[r0] - ldcur[r1]);
|
||||
ypos = (ccur[r0] + cnxt[r0]) * (ldnxt[r0] - ldcur[r0]);
|
||||
yneg = (cprv[r0] + ccur[r0]) * (ldcur[r0] - ldprv[r0]);
|
||||
dst[r0] = 0.5f*stepsize*(-xneg + ypos - yneg);
|
||||
|
||||
cprv = ccur;
|
||||
ccur = cnxt;
|
||||
ldprv = ldcur;
|
||||
ldcur = ldnxt;
|
||||
}
|
||||
Ld += Lstep;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function downsamples the input image using OpenCV resize
|
||||
* @param src Input image to be downsampled
|
||||
* @param dst Output image with half of the resolution of the input image
|
||||
*/
|
||||
void halfsample_image(const cv::Mat& src, cv::Mat& dst) {
|
||||
// Make sure the destination image is of the right size
|
||||
CV_Assert(src.cols / 2 == dst.cols);
|
||||
CV_Assert(src.rows / 2 == dst.rows);
|
||||
resize(src, dst, dst.size(), 0, 0, cv::INTER_AREA);
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function checks if a given pixel is a maximum in a local neighbourhood
|
||||
* @param img Input image where we will perform the maximum search
|
||||
* @param dsize Half size of the neighbourhood
|
||||
* @param value Response value at (x,y) position
|
||||
* @param row Image row coordinate
|
||||
* @param col Image column coordinate
|
||||
* @param same_img Flag to indicate if the image value at (x,y) is in the input image
|
||||
* @return 1->is maximum, 0->otherwise
|
||||
*/
|
||||
bool check_maximum_neighbourhood(const cv::Mat& img, int dsize, float value, int row, int col, bool same_img) {
|
||||
|
||||
bool response = true;
|
||||
|
||||
for (int i = row - dsize; i <= row + dsize; i++) {
|
||||
for (int j = col - dsize; j <= col + dsize; j++) {
|
||||
if (i >= 0 && i < img.rows && j >= 0 && j < img.cols) {
|
||||
if (same_img == true) {
|
||||
if (i != row || j != col) {
|
||||
if ((*(img.ptr<float>(i)+j)) > value) {
|
||||
response = false;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((*(img.ptr<float>(i)+j)) > value) {
|
||||
response = false;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @file nldiffusion_functions.h
|
||||
* @brief Functions for non-linear diffusion applications:
|
||||
* 2D Gaussian Derivatives
|
||||
* Perona and Malik conductivity equations
|
||||
* Perona and Malik evolution
|
||||
* @date Dec 27, 2011
|
||||
* @author Pablo F. Alcantarilla
|
||||
*/
|
||||
|
||||
#ifndef __OPENCV_FEATURES_2D_NLDIFFUSION_FUNCTIONS_H__
|
||||
#define __OPENCV_FEATURES_2D_NLDIFFUSION_FUNCTIONS_H__
|
||||
|
||||
/* ************************************************************************* */
|
||||
// Declaration of functions
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// Gaussian 2D convolution
|
||||
void gaussian_2D_convolution(const cv::Mat& src, cv::Mat& dst, int ksize_x, int ksize_y, float sigma);
|
||||
|
||||
// Diffusivity functions
|
||||
void pm_g1(InputArray Lx, InputArray Ly, OutputArray dst, float k);
|
||||
void pm_g2(InputArray Lx, InputArray Ly, OutputArray dst, float k);
|
||||
void weickert_diffusivity(InputArray Lx, InputArray Ly, OutputArray dst, float k);
|
||||
void charbonnier_diffusivity(InputArray Lx, InputArray Ly, OutputArray dst, float k);
|
||||
|
||||
float compute_k_percentile(const cv::Mat& img, float perc, float gscale, int nbins, int ksize_x, int ksize_y);
|
||||
|
||||
// Image derivatives
|
||||
void compute_scharr_derivatives(const cv::Mat& src, cv::Mat& dst, int xorder, int yorder, int scale);
|
||||
void compute_derivative_kernels(cv::OutputArray _kx, cv::OutputArray _ky, int dx, int dy, int scale);
|
||||
void image_derivatives_scharr(const cv::Mat& src, cv::Mat& dst, int xorder, int yorder);
|
||||
|
||||
// Nonlinear diffusion filtering scalar step
|
||||
void nld_step_scalar(cv::Mat& Ld, const cv::Mat& c, cv::Mat& Lstep, float stepsize);
|
||||
|
||||
// For non-maxima suppression
|
||||
bool check_maximum_neighbourhood(const cv::Mat& img, int dsize, float value, int row, int col, bool same_img);
|
||||
|
||||
// Image downsampling
|
||||
void halfsample_image(const cv::Mat& src, cv::Mat& dst);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef __OPENCV_FEATURES_2D_KAZE_UTILS_H__
|
||||
#define __OPENCV_FEATURES_2D_KAZE_UTILS_H__
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function computes the value of a 2D Gaussian function
|
||||
* @param x X Position
|
||||
* @param y Y Position
|
||||
* @param sigma Standard Deviation
|
||||
*/
|
||||
inline float gaussian(float x, float y, float sigma) {
|
||||
return expf(-(x*x + y*y) / (2.0f*sigma*sigma));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
/**
|
||||
* @brief This function checks descriptor limits
|
||||
* @param x X Position
|
||||
* @param y Y Position
|
||||
* @param width Image width
|
||||
* @param height Image height
|
||||
*/
|
||||
inline void checkDescriptorLimits(int &x, int &y, int width, int height) {
|
||||
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if (x > width - 1) {
|
||||
x = width - 1;
|
||||
}
|
||||
|
||||
if (y > height - 1) {
|
||||
y = height - 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,122 +0,0 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
|
||||
/**
|
||||
* @brief This function computes the Perona and Malik conductivity coefficient g2
|
||||
* g2 = 1 / (1 + dL^2 / k^2)
|
||||
* @param lx First order image derivative in X-direction (horizontal)
|
||||
* @param ly First order image derivative in Y-direction (vertical)
|
||||
* @param dst Output image
|
||||
* @param k Contrast factor parameter
|
||||
*/
|
||||
__kernel void
|
||||
AKAZE_pm_g2(__global const float* lx, __global const float* ly, __global float* dst,
|
||||
float k, int size)
|
||||
{
|
||||
int i = get_global_id(0);
|
||||
// OpenCV plays with dimensions so we need explicit check for this
|
||||
if (!(i < size))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float k2inv = 1.0f / (k * k);
|
||||
dst[i] = 1.0f / (1.0f + ((lx[i] * lx[i] + ly[i] * ly[i]) * k2inv));
|
||||
}
|
||||
|
||||
__kernel void
|
||||
AKAZE_nld_step_scalar(__global const float* lt, int lt_step, int lt_offset, int rows, int cols,
|
||||
__global const float* lf, __global float* dst, float step_size)
|
||||
{
|
||||
/* The labeling scheme for this five star stencil:
|
||||
[ a ]
|
||||
[ -1 c +1 ]
|
||||
[ b ]
|
||||
*/
|
||||
// column-first indexing
|
||||
int i = get_global_id(1);
|
||||
int j = get_global_id(0);
|
||||
|
||||
// OpenCV plays with dimensions so we need explicit check for this
|
||||
if (!(i < rows && j < cols))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get row indexes
|
||||
int a = (i - 1) * cols;
|
||||
int c = (i ) * cols;
|
||||
int b = (i + 1) * cols;
|
||||
// compute stencil
|
||||
float res = 0.0f;
|
||||
if (i == 0) // first rows
|
||||
{
|
||||
if (j == 0 || j == (cols - 1))
|
||||
{
|
||||
res = 0.0f;
|
||||
} else
|
||||
{
|
||||
res = (lf[c + j] + lf[c + j + 1])*(lt[c + j + 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[c + j - 1])*(lt[c + j - 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[b + j ])*(lt[b + j ] - lt[c + j]);
|
||||
}
|
||||
} else if (i == (rows - 1)) // last row
|
||||
{
|
||||
if (j == 0 || j == (cols - 1))
|
||||
{
|
||||
res = 0.0f;
|
||||
} else
|
||||
{
|
||||
res = (lf[c + j] + lf[c + j + 1])*(lt[c + j + 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[c + j - 1])*(lt[c + j - 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[a + j ])*(lt[a + j ] - lt[c + j]);
|
||||
}
|
||||
} else // inner rows
|
||||
{
|
||||
if (j == 0) // first column
|
||||
{
|
||||
res = (lf[c + 0] + lf[c + 1])*(lt[c + 1] - lt[c + 0]) +
|
||||
(lf[c + 0] + lf[b + 0])*(lt[b + 0] - lt[c + 0]) +
|
||||
(lf[c + 0] + lf[a + 0])*(lt[a + 0] - lt[c + 0]);
|
||||
} else if (j == (cols - 1)) // last column
|
||||
{
|
||||
res = (lf[c + j] + lf[c + j - 1])*(lt[c + j - 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[b + j ])*(lt[b + j ] - lt[c + j]) +
|
||||
(lf[c + j] + lf[a + j ])*(lt[a + j ] - lt[c + j]);
|
||||
} else // inner stencil
|
||||
{
|
||||
res = (lf[c + j] + lf[c + j + 1])*(lt[c + j + 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[c + j - 1])*(lt[c + j - 1] - lt[c + j]) +
|
||||
(lf[c + j] + lf[b + j ])*(lt[b + j ] - lt[c + j]) +
|
||||
(lf[c + j] + lf[a + j ])*(lt[a + j ] - lt[c + j]);
|
||||
}
|
||||
}
|
||||
|
||||
dst[c + j] = res * step_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute determinant from hessians
|
||||
* @details Compute Ldet by (Lxx.mul(Lyy) - Lxy.mul(Lxy)) * sigma
|
||||
*
|
||||
* @param lxx spatial derivates
|
||||
* @param lxy spatial derivates
|
||||
* @param lyy spatial derivates
|
||||
* @param dst output determinant
|
||||
* @param sigma determinant will be scaled by this sigma
|
||||
*/
|
||||
__kernel void
|
||||
AKAZE_compute_determinant(__global const float* lxx, __global const float* lxy, __global const float* lyy,
|
||||
__global float* dst, float sigma, int size)
|
||||
{
|
||||
int i = get_global_id(0);
|
||||
// OpenCV plays with dimensions so we need explicit check for this
|
||||
if (!(i < size))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dst[i] = (lxx[i] * lyy[i] - lxy[i] * lxy[i]) * sigma;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "../test_precomp.hpp"
|
||||
#include "cvconfig.h"
|
||||
#include "opencv2/ts/ocl_test.hpp"
|
||||
#include <functional>
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
|
||||
namespace opencv_test {
|
||||
namespace ocl {
|
||||
|
||||
#define TEST_IMAGES testing::Values(\
|
||||
"detectors_descriptors_evaluation/images_datasets/leuven/img1.png",\
|
||||
"../stitching/a3.png", \
|
||||
"../stitching/s2.jpg")
|
||||
|
||||
PARAM_TEST_CASE(Feature2DFixture, std::function<Ptr<Feature2D>()>, std::string)
|
||||
{
|
||||
std::string filename;
|
||||
Mat image, descriptors;
|
||||
vector<KeyPoint> keypoints;
|
||||
UMat uimage, udescriptors;
|
||||
vector<KeyPoint> ukeypoints;
|
||||
Ptr<Feature2D> feature;
|
||||
|
||||
virtual void SetUp()
|
||||
{
|
||||
feature = GET_PARAM(0)();
|
||||
filename = GET_PARAM(1);
|
||||
|
||||
image = readImage(filename);
|
||||
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
image.copyTo(uimage);
|
||||
|
||||
OCL_OFF(feature->detect(image, keypoints));
|
||||
OCL_ON(feature->detect(uimage, ukeypoints));
|
||||
// note: we use keypoints from CPU for GPU too, to test descriptors separately
|
||||
OCL_OFF(feature->compute(image, keypoints, descriptors));
|
||||
OCL_ON(feature->compute(uimage, keypoints, udescriptors));
|
||||
}
|
||||
};
|
||||
|
||||
OCL_TEST_P(Feature2DFixture, KeypointsSame)
|
||||
{
|
||||
EXPECT_EQ(keypoints.size(), ukeypoints.size());
|
||||
|
||||
for (size_t i = 0; i < keypoints.size(); ++i)
|
||||
{
|
||||
EXPECT_GE(KeyPoint::overlap(keypoints[i], ukeypoints[i]), 0.95);
|
||||
EXPECT_NEAR(keypoints[i].angle, ukeypoints[i].angle, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
OCL_TEST_P(Feature2DFixture, DescriptorsSame)
|
||||
{
|
||||
EXPECT_MAT_NEAR(descriptors, udescriptors, 0.001);
|
||||
}
|
||||
|
||||
OCL_INSTANTIATE_TEST_CASE_P(AKAZE, Feature2DFixture,
|
||||
testing::Combine(testing::Values([]() { return AKAZE::create(); }), TEST_IMAGES));
|
||||
|
||||
OCL_INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, Feature2DFixture,
|
||||
testing::Combine(testing::Values([]() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }), TEST_IMAGES));
|
||||
|
||||
}//ocl
|
||||
}//cvtest
|
||||
|
||||
#endif //HAVE_OPENCL
|
||||
@@ -1,138 +0,0 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class CV_AgastTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_AgastTest();
|
||||
~CV_AgastTest();
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
CV_AgastTest::CV_AgastTest() {}
|
||||
CV_AgastTest::~CV_AgastTest() {}
|
||||
|
||||
void CV_AgastTest::run( int )
|
||||
{
|
||||
for(int type=0; type <= 2; ++type) {
|
||||
Mat image1 = imread(string(ts->get_data_path()) + "inpaint/orig.png");
|
||||
Mat image2 = imread(string(ts->get_data_path()) + "cameracalibration/chess9.png");
|
||||
string xml = string(ts->get_data_path()) + format("agast/result%d.xml", type);
|
||||
|
||||
if (image1.empty() || image2.empty())
|
||||
{
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
|
||||
return;
|
||||
}
|
||||
|
||||
Mat gray1, gray2;
|
||||
cvtColor(image1, gray1, COLOR_BGR2GRAY);
|
||||
cvtColor(image2, gray2, COLOR_BGR2GRAY);
|
||||
|
||||
vector<KeyPoint> keypoints1;
|
||||
vector<KeyPoint> keypoints2;
|
||||
AGAST(gray1, keypoints1, 30, true, static_cast<AgastFeatureDetector::DetectorType>(type));
|
||||
AGAST(gray2, keypoints2, (type > 0 ? 30 : 20), true, static_cast<AgastFeatureDetector::DetectorType>(type));
|
||||
|
||||
for(size_t i = 0; i < keypoints1.size(); ++i)
|
||||
{
|
||||
const KeyPoint& kp = keypoints1[i];
|
||||
cv::circle(image1, kp.pt, cvRound(kp.size/2), Scalar(255, 0, 0));
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < keypoints2.size(); ++i)
|
||||
{
|
||||
const KeyPoint& kp = keypoints2[i];
|
||||
cv::circle(image2, kp.pt, cvRound(kp.size/2), Scalar(255, 0, 0));
|
||||
}
|
||||
|
||||
Mat kps1(1, (int)(keypoints1.size() * sizeof(KeyPoint)), CV_8U, &keypoints1[0]);
|
||||
Mat kps2(1, (int)(keypoints2.size() * sizeof(KeyPoint)), CV_8U, &keypoints2[0]);
|
||||
|
||||
FileStorage fs(xml, FileStorage::READ);
|
||||
if (!fs.isOpened())
|
||||
{
|
||||
fs.open(xml, FileStorage::WRITE);
|
||||
if (!fs.isOpened())
|
||||
{
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA);
|
||||
return;
|
||||
}
|
||||
fs << "exp_kps1" << kps1;
|
||||
fs << "exp_kps2" << kps2;
|
||||
fs.release();
|
||||
fs.open(xml, FileStorage::READ);
|
||||
if (!fs.isOpened())
|
||||
{
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Mat exp_kps1, exp_kps2;
|
||||
read( fs["exp_kps1"], exp_kps1, Mat() );
|
||||
read( fs["exp_kps2"], exp_kps2, Mat() );
|
||||
fs.release();
|
||||
|
||||
if ( exp_kps1.size != kps1.size || 0 != cvtest::norm(exp_kps1, kps1, NORM_L2) ||
|
||||
exp_kps2.size != kps2.size || 0 != cvtest::norm(exp_kps2, kps2, NORM_L2))
|
||||
{
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
/*cv::namedWindow("Img1"); cv::imshow("Img1", image1);
|
||||
cv::namedWindow("Img2"); cv::imshow("Img2", image2);
|
||||
cv::waitKey(0);*/
|
||||
}
|
||||
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
}
|
||||
|
||||
TEST(Features2d_AGAST, regression) { CV_AgastTest test; test.safe_run(); }
|
||||
|
||||
}} // namespace
|
||||
@@ -1,48 +0,0 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
TEST(Features2d_AKAZE, detect_and_compute_split)
|
||||
{
|
||||
Mat testImg(100, 100, CV_8U);
|
||||
RNG rng(101);
|
||||
rng.fill(testImg, RNG::UNIFORM, Scalar(0), Scalar(255), true);
|
||||
|
||||
Ptr<Feature2D> ext = AKAZE::create(AKAZE::DESCRIPTOR_MLDB, 0, 3, 0.001f, 1, 1, KAZE::DIFF_PM_G2);
|
||||
vector<KeyPoint> detAndCompKps;
|
||||
Mat desc;
|
||||
ext->detectAndCompute(testImg, noArray(), detAndCompKps, desc);
|
||||
|
||||
vector<KeyPoint> detKps;
|
||||
ext->detect(testImg, detKps);
|
||||
|
||||
ASSERT_EQ(detKps.size(), detAndCompKps.size());
|
||||
|
||||
for(size_t i = 0; i < detKps.size(); i++)
|
||||
ASSERT_EQ(detKps[i].hash(), detAndCompKps[i].hash());
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is here to guard propagation of NaNs that happens on this image. NaNs are guarded
|
||||
* by debug asserts in AKAZE, which should fire for you if you are lucky.
|
||||
*
|
||||
* This test also reveals problems with uninitialized memory that happens only on this image.
|
||||
* This is very hard to hit and depends a lot on particular allocator. Run this test in valgrind and check
|
||||
* for uninitialized values if you think you are hitting this problem again.
|
||||
*/
|
||||
TEST(Features2d_AKAZE, uninitialized_and_nans)
|
||||
{
|
||||
Mat b1 = imread(cvtest::TS::ptr()->get_data_path() + "../stitching/b1.png");
|
||||
ASSERT_FALSE(b1.empty());
|
||||
|
||||
vector<KeyPoint> keypoints;
|
||||
Mat desc;
|
||||
Ptr<Feature2D> akaze = AKAZE::create();
|
||||
akaze->detectAndCompute(b1, noArray(), keypoints, desc);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -1,108 +0,0 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class CV_BRISKTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_BRISKTest();
|
||||
~CV_BRISKTest();
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
CV_BRISKTest::CV_BRISKTest() {}
|
||||
CV_BRISKTest::~CV_BRISKTest() {}
|
||||
|
||||
void CV_BRISKTest::run( int )
|
||||
{
|
||||
Mat image1 = imread(string(ts->get_data_path()) + "inpaint/orig.png");
|
||||
Mat image2 = imread(string(ts->get_data_path()) + "cameracalibration/chess9.png");
|
||||
|
||||
if (image1.empty() || image2.empty())
|
||||
{
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
|
||||
return;
|
||||
}
|
||||
|
||||
Mat gray1, gray2;
|
||||
cvtColor(image1, gray1, COLOR_BGR2GRAY);
|
||||
cvtColor(image2, gray2, COLOR_BGR2GRAY);
|
||||
|
||||
Ptr<FeatureDetector> detector = BRISK::create();
|
||||
|
||||
// Check parameter get/set functions.
|
||||
BRISK* detectorTyped = dynamic_cast<BRISK*>(detector.get());
|
||||
ASSERT_NE(nullptr, detectorTyped);
|
||||
detectorTyped->setOctaves(3);
|
||||
detectorTyped->setThreshold(30);
|
||||
ASSERT_EQ(detectorTyped->getOctaves(), 3);
|
||||
ASSERT_EQ(detectorTyped->getThreshold(), 30);
|
||||
detectorTyped->setOctaves(4);
|
||||
detectorTyped->setThreshold(29);
|
||||
ASSERT_EQ(detectorTyped->getOctaves(), 4);
|
||||
ASSERT_EQ(detectorTyped->getThreshold(), 29);
|
||||
|
||||
vector<KeyPoint> keypoints1;
|
||||
vector<KeyPoint> keypoints2;
|
||||
detector->detect(image1, keypoints1);
|
||||
detector->detect(image2, keypoints2);
|
||||
|
||||
for(size_t i = 0; i < keypoints1.size(); ++i)
|
||||
{
|
||||
const KeyPoint& kp = keypoints1[i];
|
||||
ASSERT_NE(kp.angle, -1);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < keypoints2.size(); ++i)
|
||||
{
|
||||
const KeyPoint& kp = keypoints2[i];
|
||||
ASSERT_NE(kp.angle, -1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Features2d_BRISK, regression) { CV_BRISKTest test; test.safe_run(); }
|
||||
|
||||
}} // namespace
|
||||
@@ -20,17 +20,9 @@ const static std::string IMAGE_BIKES = "detectors_descriptors_evaluation/images_
|
||||
INSTANTIATE_TEST_CASE_P(SIFT, DescriptorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return SIFT::create(); }, []() { return SIFT::create(); }, 0.98f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BRISK, DescriptorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return BRISK::create(); }, []() { return BRISK::create(); }, 0.99f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ORB, DescriptorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return ORB::create(); }, []() { return ORB::create(); }, 0.99f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE, DescriptorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return AKAZE::create(); }, []() { return AKAZE::create(); }, 0.99f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DescriptorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }, []() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }, 0.99f));
|
||||
|
||||
/*
|
||||
* Descriptor's scale invariance check
|
||||
@@ -39,10 +31,4 @@ INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DescriptorRotationInvariance,
|
||||
INSTANTIATE_TEST_CASE_P(SIFT, DescriptorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return SIFT::create(0, 3, 0.09); }, []() { return SIFT::create(0, 3, 0.09); }, 0.78f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE, DescriptorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return AKAZE::create(); }, []() { return AKAZE::create(); }, 0.6f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DescriptorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }, []() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }, 0.55f));
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -25,14 +25,6 @@ TEST( Features2d_DescriptorExtractor_SIFT, regression )
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_DescriptorExtractor_BRISK, regression )
|
||||
{
|
||||
CV_DescriptorExtractorTest<Hamming> test( "descriptor-brisk",
|
||||
(CV_DescriptorExtractorTest<Hamming>::DistanceType)2.f,
|
||||
BRISK::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_DescriptorExtractor_ORB, regression )
|
||||
{
|
||||
// TODO adjust the parameters below
|
||||
@@ -46,31 +38,6 @@ TEST( Features2d_DescriptorExtractor_ORB, regression )
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_DescriptorExtractor_KAZE, regression )
|
||||
{
|
||||
CV_DescriptorExtractorTest< L2<float> > test( "descriptor-kaze", 0.03f,
|
||||
KAZE::create(),
|
||||
L2<float>(), KAZE::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_DescriptorExtractor_AKAZE, regression )
|
||||
{
|
||||
CV_DescriptorExtractorTest<Hamming> test( "descriptor-akaze",
|
||||
(CV_DescriptorExtractorTest<Hamming>::DistanceType)(486*0.05f),
|
||||
AKAZE::create(),
|
||||
Hamming(), AKAZE::create());
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_DescriptorExtractor_AKAZE_DESCRIPTOR_KAZE, regression )
|
||||
{
|
||||
CV_DescriptorExtractorTest< L2<float> > test( "descriptor-akaze-with-kaze-desc", 0.03f,
|
||||
AKAZE::create(AKAZE::DESCRIPTOR_KAZE),
|
||||
L2<float>(), AKAZE::create(AKAZE::DESCRIPTOR_KAZE));
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_DescriptorExtractor, batch_ORB )
|
||||
{
|
||||
string path = string(cvtest::TS::ptr()->get_data_path() + "detectors_descriptors_evaluation/images_datasets/graf");
|
||||
@@ -144,15 +111,7 @@ TEST_P(DescriptorImage, no_crash)
|
||||
glob(cvtest::TS::ptr()->get_data_path() + pattern, fnames, false);
|
||||
std::sort(fnames.begin(), fnames.end());
|
||||
|
||||
Ptr<AKAZE> akaze_mldb = AKAZE::create(AKAZE::DESCRIPTOR_MLDB);
|
||||
Ptr<AKAZE> akaze_mldb_upright = AKAZE::create(AKAZE::DESCRIPTOR_MLDB_UPRIGHT);
|
||||
Ptr<AKAZE> akaze_mldb_256 = AKAZE::create(AKAZE::DESCRIPTOR_MLDB, 256);
|
||||
Ptr<AKAZE> akaze_mldb_upright_256 = AKAZE::create(AKAZE::DESCRIPTOR_MLDB_UPRIGHT, 256);
|
||||
Ptr<AKAZE> akaze_kaze = AKAZE::create(AKAZE::DESCRIPTOR_KAZE);
|
||||
Ptr<AKAZE> akaze_kaze_upright = AKAZE::create(AKAZE::DESCRIPTOR_KAZE_UPRIGHT);
|
||||
Ptr<ORB> orb = ORB::create();
|
||||
Ptr<KAZE> kaze = KAZE::create();
|
||||
Ptr<BRISK> brisk = BRISK::create();
|
||||
size_t n = fnames.size();
|
||||
vector<KeyPoint> keypoints;
|
||||
Mat descriptors;
|
||||
@@ -183,15 +142,7 @@ TEST_P(DescriptorImage, no_crash)
|
||||
} \
|
||||
ASSERT_EQ(descriptors.rows, (int)keypoints.size());
|
||||
|
||||
TEST_DETECTOR("AKAZE:MLDB", akaze_mldb);
|
||||
TEST_DETECTOR("AKAZE:MLDB_UPRIGHT", akaze_mldb_upright);
|
||||
TEST_DETECTOR("AKAZE:MLDB_256", akaze_mldb_256);
|
||||
TEST_DETECTOR("AKAZE:MLDB_UPRIGHT_256", akaze_mldb_upright_256);
|
||||
TEST_DETECTOR("AKAZE:KAZE", akaze_kaze);
|
||||
TEST_DETECTOR("AKAZE:KAZE_UPRIGHT", akaze_kaze_upright);
|
||||
TEST_DETECTOR("KAZE", kaze);
|
||||
TEST_DETECTOR("ORB", orb);
|
||||
TEST_DETECTOR("BRISK", brisk);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,17 +20,9 @@ const static std::string IMAGE_BIKES = "detectors_descriptors_evaluation/images_
|
||||
INSTANTIATE_TEST_CASE_P(SIFT, DetectorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return SIFT::create(); }, 0.45f, 0.70f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BRISK, DetectorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return BRISK::create(); }, 0.45f, 0.76f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ORB, DetectorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return ORB::create(); }, 0.5f, 0.76f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE, DetectorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return AKAZE::create(); }, 0.5f, 0.71f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorRotationInvariance,
|
||||
Value(IMAGE_TSUKUBA, []() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }, 0.5f, 0.71f));
|
||||
|
||||
/*
|
||||
* Detector's scale invariance check
|
||||
@@ -39,19 +31,7 @@ INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorRotationInvariance,
|
||||
INSTANTIATE_TEST_CASE_P(SIFT, DetectorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return SIFT::create(0, 3, 0.09); }, 0.60f, 0.98f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BRISK, DetectorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return BRISK::create(); }, 0.08f, 0.49f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ORB, DetectorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return ORB::create(); }, 0.08f, 0.49f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(KAZE, DetectorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return KAZE::create(); }, 0.08f, 0.49f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE, DetectorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return AKAZE::create(); }, 0.08f, 0.49f));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AKAZE_DESCRIPTOR_KAZE, DetectorScaleInvariance,
|
||||
Value(IMAGE_BIKES, []() { return AKAZE::create(AKAZE::DESCRIPTOR_KAZE); }, 0.08f, 0.49f));
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -24,24 +24,12 @@ TEST( Features2d_Detector_SIFT, regression )
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_BRISK, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-brisk", BRISK::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_FAST, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-fast", FastFeatureDetector::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_AGAST, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-agast", AgastFeatureDetector::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_GFTT, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-gftt", GFTTDetector::create() );
|
||||
@@ -68,22 +56,4 @@ TEST( Features2d_Detector_ORB, regression )
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_KAZE, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-kaze", KAZE::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_AKAZE, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-akaze", AKAZE::create() );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST( Features2d_Detector_AKAZE_DESCRIPTOR_KAZE, regression )
|
||||
{
|
||||
CV_FeatureDetectorTest test( "detector-akaze-with-kaze-desc", AKAZE::create(AKAZE::DESCRIPTOR_KAZE) );
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -115,25 +115,12 @@ protected:
|
||||
|
||||
|
||||
// Registration of tests
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_BRISK, validation)
|
||||
{
|
||||
CV_FeatureDetectorKeypointsTest test(BRISK::create());
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_FAST, validation)
|
||||
{
|
||||
CV_FeatureDetectorKeypointsTest test(FastFeatureDetector::create());
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_AGAST, validation)
|
||||
{
|
||||
CV_FeatureDetectorKeypointsTest test(AgastFeatureDetector::create());
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_HARRIS, validation)
|
||||
{
|
||||
|
||||
@@ -161,21 +148,6 @@ TEST(Features2d_Detector_Keypoints_ORB, validation)
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_KAZE, validation)
|
||||
{
|
||||
CV_FeatureDetectorKeypointsTest test(KAZE::create());
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_AKAZE, validation)
|
||||
{
|
||||
CV_FeatureDetectorKeypointsTest test_kaze(AKAZE::create(AKAZE::DESCRIPTOR_KAZE));
|
||||
test_kaze.safe_run();
|
||||
|
||||
CV_FeatureDetectorKeypointsTest test_mldb(AKAZE::create(AKAZE::DESCRIPTOR_MLDB));
|
||||
test_mldb.safe_run();
|
||||
}
|
||||
|
||||
TEST(Features2d_Detector_Keypoints_SIFT, validation)
|
||||
{
|
||||
CV_FeatureDetectorKeypointsTest test(SIFT::create());
|
||||
|
||||
@@ -35,29 +35,13 @@ QUnit.test('Detectors', function(assert) {
|
||||
assert.equal(kp.size(), 7, 'MSER');
|
||||
*/
|
||||
|
||||
let brisk = new cv.BRISK();
|
||||
brisk.detect(image, kp);
|
||||
assert.equal(kp.size(), 191, 'BRISK');
|
||||
|
||||
let ffd = new cv.FastFeatureDetector();
|
||||
ffd.detect(image, kp);
|
||||
assert.equal(kp.size(), 12, 'FastFeatureDetector');
|
||||
|
||||
let afd = new cv.AgastFeatureDetector();
|
||||
afd.detect(image, kp);
|
||||
assert.equal(kp.size(), 67, 'AgastFeatureDetector');
|
||||
|
||||
let gftt = new cv.GFTTDetector();
|
||||
gftt.detect(image, kp);
|
||||
assert.equal(kp.size(), 168, 'GFTTDetector');
|
||||
|
||||
let kaze = new cv.KAZE();
|
||||
kaze.detect(image, kp);
|
||||
assert.equal(kp.size(), 159, 'KAZE');
|
||||
|
||||
let akaze = new cv.AKAZE();
|
||||
akaze.detect(image, kp);
|
||||
assert.equal(kp.size(), 53, 'AKAZE');
|
||||
});
|
||||
|
||||
QUnit.test('SimpleBlobDetector', function(assert) {
|
||||
|
||||
@@ -12,16 +12,16 @@ class algorithm_rw_test(NewOpenCVTests):
|
||||
os.close(fd)
|
||||
|
||||
# some arbitrary non-default parameters
|
||||
gold = cv.AKAZE_create(descriptor_size=1, descriptor_channels=2, nOctaves=3, threshold=4.0)
|
||||
gold.write(cv.FileStorage(fname, cv.FILE_STORAGE_WRITE), "AKAZE")
|
||||
gold = cv.ORB_create(nfeatures=200, scaleFactor=1.3, nlevels=5, edgeThreshold=28)
|
||||
gold.write(cv.FileStorage(fname, cv.FILE_STORAGE_WRITE), "ORB")
|
||||
|
||||
fs = cv.FileStorage(fname, cv.FILE_STORAGE_READ)
|
||||
algorithm = cv.AKAZE_create()
|
||||
algorithm.read(fs.getNode("AKAZE"))
|
||||
algorithm = cv.ORB_create()
|
||||
algorithm.read(fs.getNode("ORB"))
|
||||
|
||||
self.assertEqual(algorithm.getDescriptorSize(), 1)
|
||||
self.assertEqual(algorithm.getDescriptorChannels(), 2)
|
||||
self.assertEqual(algorithm.getNOctaves(), 3)
|
||||
self.assertEqual(algorithm.getThreshold(), 4.0)
|
||||
self.assertEqual(algorithm.getMaxFeatures(), 200)
|
||||
self.assertAlmostEqual(algorithm.getScaleFactor(), 1.3, places=6)
|
||||
self.assertEqual(algorithm.getNLevels(), 5)
|
||||
self.assertEqual(algorithm.getEdgeThreshold(), 28)
|
||||
|
||||
os.remove(fname)
|
||||
|
||||
@@ -20,9 +20,9 @@ namespace ocl {
|
||||
typedef TestBaseWithParam<string> stitch;
|
||||
|
||||
#if defined(HAVE_OPENCV_XFEATURES2D) && defined(OPENCV_ENABLE_NONFREE)
|
||||
#define TEST_DETECTORS testing::Values("surf", "orb", "akaze")
|
||||
#define TEST_DETECTORS testing::Values("surf", "sift", "orb", "akaze")
|
||||
#else
|
||||
#define TEST_DETECTORS testing::Values("orb", "akaze")
|
||||
#define TEST_DETECTORS testing::Values("orb", "sift")
|
||||
#endif
|
||||
|
||||
OCL_PERF_TEST_P(stitch, a123, TEST_DETECTORS)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
#include "opencv2/xfeatures2d/nonfree.hpp"
|
||||
#include "opencv2/xfeatures2d.hpp"
|
||||
#endif
|
||||
|
||||
namespace cv
|
||||
@@ -15,12 +16,16 @@ static inline Ptr<Feature2D> getFeatureFinder(const std::string& name)
|
||||
{
|
||||
if (name == "orb")
|
||||
return ORB::create();
|
||||
else if (name == "sift")
|
||||
return SIFT::create();
|
||||
#if defined(HAVE_OPENCV_XFEATURES2D) && defined(OPENCV_ENABLE_NONFREE)
|
||||
else if (name == "surf")
|
||||
return xfeatures2d::SURF::create();
|
||||
#endif
|
||||
#if defined(HAVE_OPENCV_XFEATURES2D)
|
||||
else if (name == "akaze")
|
||||
return AKAZE::create();
|
||||
return xfeatures2d::AKAZE::create();
|
||||
#endif
|
||||
else
|
||||
return Ptr<Feature2D>();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ typedef TestBaseWithParam<tuple<string, int>> stitchExposureCompMultiFeed;
|
||||
#if defined(HAVE_OPENCV_XFEATURES2D) && defined(OPENCV_ENABLE_NONFREE)
|
||||
#define TEST_DETECTORS testing::Values("surf", "orb", "akaze")
|
||||
#else
|
||||
#define TEST_DETECTORS testing::Values("orb", "akaze")
|
||||
#define TEST_DETECTORS testing::Values("orb")
|
||||
#endif
|
||||
#define TEST_EXP_COMP_BS testing::Values(32, 16, 12, 10, 8)
|
||||
#define TEST_EXP_COMP_NR_FEED testing::Values(1, 2, 3, 4, 5)
|
||||
|
||||
@@ -152,16 +152,12 @@ dnn = {'dnn_Net': ['setInput', 'forward', 'setPreferableBackend','getUnconnected
|
||||
'readNetFromONNX', 'readNetFromTFLite', 'readNet', 'blobFromImage']}
|
||||
|
||||
features2d = {'Feature2D': ['detect', 'compute', 'detectAndCompute', 'descriptorSize', 'descriptorType', 'defaultNorm', 'empty', 'getDefaultName'],
|
||||
'BRISK': ['create', 'getDefaultName'],
|
||||
'ORB': ['create', 'setMaxFeatures', 'setScaleFactor', 'setNLevels', 'setEdgeThreshold', 'setFastThreshold', 'setFirstLevel', 'setWTA_K', 'setScoreType', 'setPatchSize', 'getFastThreshold', 'getDefaultName'],
|
||||
'MSER': ['create', 'detectRegions', 'setDelta', 'getDelta', 'setMinArea', 'getMinArea', 'setMaxArea', 'getMaxArea', 'setPass2Only', 'getPass2Only', 'getDefaultName'],
|
||||
'FastFeatureDetector': ['create', 'setThreshold', 'getThreshold', 'setNonmaxSuppression', 'getNonmaxSuppression', 'setType', 'getType', 'getDefaultName'],
|
||||
'AgastFeatureDetector': ['create', 'setThreshold', 'getThreshold', 'setNonmaxSuppression', 'getNonmaxSuppression', 'setType', 'getType', 'getDefaultName'],
|
||||
'GFTTDetector': ['create', 'setMaxFeatures', 'getMaxFeatures', 'setQualityLevel', 'getQualityLevel', 'setMinDistance', 'getMinDistance', 'setBlockSize', 'getBlockSize', 'setHarrisDetector', 'getHarrisDetector', 'setK', 'getK', 'getDefaultName'],
|
||||
'SimpleBlobDetector': ['create', 'setParams', 'getParams', 'getDefaultName'],
|
||||
'SimpleBlobDetector_Params': [],
|
||||
'KAZE': ['create', 'setExtended', 'getExtended', 'setUpright', 'getUpright', 'setThreshold', 'getThreshold', 'setNOctaves', 'getNOctaves', 'setNOctaveLayers', 'getNOctaveLayers', 'setDiffusivity', 'getDiffusivity', 'getDefaultName'],
|
||||
'AKAZE': ['create', 'setDescriptorType', 'getDescriptorType', 'setDescriptorSize', 'getDescriptorSize', 'setDescriptorChannels', 'getDescriptorChannels', 'setThreshold', 'getThreshold', 'setNOctaves', 'getNOctaves', 'setNOctaveLayers', 'getNOctaveLayers', 'setDiffusivity', 'getDiffusivity', 'getDefaultName'],
|
||||
'DescriptorMatcher': ['add', 'clear', 'empty', 'isMaskSupported', 'train', 'match', 'knnMatch', 'radiusMatch', 'clone', 'create'],
|
||||
'BFMatcher': ['isMaskSupported', 'create'],
|
||||
'': ['drawKeypoints', 'drawMatches', 'drawMatchesKnn']}
|
||||
|
||||
+10
-2
@@ -5,6 +5,9 @@
|
||||
#include <opencv2/3d.hpp>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
#include "opencv2/xfeatures2d.hpp"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
@@ -33,7 +36,7 @@ int main(int argc, char** argv)
|
||||
vector<String> fileName;
|
||||
cv::CommandLineParser parser(argc, argv,
|
||||
"{help h ||}"
|
||||
"{feature|brisk|}"
|
||||
"{feature|orb|}"
|
||||
"{flann||}"
|
||||
"{maxlines|50|}"
|
||||
"{image1|aero1.jpg|}{image2|aero3.jpg|}");
|
||||
@@ -88,11 +91,16 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else if (feature == "brisk")
|
||||
{
|
||||
backend = BRISK::create();
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
backend = xfeatures2d::BRISK::create();
|
||||
if (useFlann)
|
||||
matcher = makePtr<FlannBasedMatcher>(makePtr<flann::LshIndexParams>(6, 12, 1));
|
||||
else
|
||||
matcher = DescriptorMatcher::create("BruteForce-Hamming");
|
||||
#else
|
||||
cout << "OpenCV is built without opencv_contrib modules. BRISK algorithm is not available!" << std::endl;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -424,14 +424,22 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
else if (features_type == "akaze")
|
||||
{
|
||||
finder = AKAZE::create();
|
||||
}
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
finder = xfeatures2d::AKAZE::create();
|
||||
#else
|
||||
cout << "OpenCV is built without opencv_contrib modules. AKAZE algorithm is not available!" << std::endl;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
else if (features_type == "surf")
|
||||
{
|
||||
#if defined(HAVE_OPENCV_XFEATURES2D) && defined(HAVE_OPENCV_NONFREE)
|
||||
finder = xfeatures2d::SURF::create();
|
||||
}
|
||||
#else
|
||||
cout << "OpenCV is built without NONFREE modules. SURF algorithm is not available!" << std::endl;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
else if (features_type == "sift")
|
||||
{
|
||||
finder = SIFT::create();
|
||||
|
||||
@@ -308,18 +308,39 @@ void createFeatures(const std::string &featureName, int numKeypoints, cv::Ptr<cv
|
||||
}
|
||||
else if (featureName == "KAZE")
|
||||
{
|
||||
detector = cv::KAZE::create();
|
||||
descriptor = cv::KAZE::create();
|
||||
#if defined (HAVE_OPENCV_XFEATURES2D)
|
||||
detector = cv::xfeatures2d::KAZE::create();
|
||||
descriptor = cv::xfeatures2d::KAZE::create();
|
||||
#else
|
||||
std::cout << "xfeatures2d module is not available." << std::endl;
|
||||
std::cout << "Default to ORB." << std::endl;
|
||||
detector = cv::ORB::create(numKeypoints);
|
||||
descriptor = cv::ORB::create(numKeypoints);
|
||||
#endif
|
||||
}
|
||||
else if (featureName == "AKAZE")
|
||||
{
|
||||
detector = cv::AKAZE::create();
|
||||
descriptor = cv::AKAZE::create();
|
||||
#if defined (HAVE_OPENCV_XFEATURES2D)
|
||||
detector = cv::xfeatures2d::AKAZE::create();
|
||||
descriptor = cv::xfeatures2d::AKAZE::create();
|
||||
#else
|
||||
std::cout << "xfeatures2d module is not available." << std::endl;
|
||||
std::cout << "Default to ORB." << std::endl;
|
||||
detector = cv::ORB::create(numKeypoints);
|
||||
descriptor = cv::ORB::create(numKeypoints);
|
||||
#endif
|
||||
}
|
||||
else if (featureName == "BRISK")
|
||||
{
|
||||
detector = cv::BRISK::create();
|
||||
descriptor = cv::BRISK::create();
|
||||
#if defined (HAVE_OPENCV_XFEATURES2D)
|
||||
detector = cv::xfeatures2d::BRISK::create();
|
||||
descriptor = cv::xfeatures2d::BRISK::create();
|
||||
#else
|
||||
std::cout << "xfeatures2d module is not available." << std::endl;
|
||||
std::cout << "Default to ORB." << std::endl;
|
||||
detector = cv::ORB::create(numKeypoints);
|
||||
descriptor = cv::ORB::create(numKeypoints);
|
||||
#endif
|
||||
}
|
||||
else if (featureName == "SIFT")
|
||||
{
|
||||
@@ -341,7 +362,7 @@ void createFeatures(const std::string &featureName, int numKeypoints, cv::Ptr<cv
|
||||
else if (featureName == "BINBOOST")
|
||||
{
|
||||
#if defined (HAVE_OPENCV_XFEATURES2D)
|
||||
detector = cv::KAZE::create();
|
||||
detector = cv::xfeatures2d::KAZE::create();
|
||||
descriptor = cv::xfeatures2d::BoostDesc::create();
|
||||
#else
|
||||
std::cout << "xfeatures2d module is not available." << std::endl;
|
||||
@@ -353,7 +374,7 @@ void createFeatures(const std::string &featureName, int numKeypoints, cv::Ptr<cv
|
||||
else if (featureName == "VGG")
|
||||
{
|
||||
#if defined (HAVE_OPENCV_XFEATURES2D)
|
||||
detector = cv::KAZE::create();
|
||||
detector = cv::xfeatures2d::KAZE::create();
|
||||
descriptor = cv::xfeatures2d::VGG::create();
|
||||
#else
|
||||
std::cout << "xfeatures2d module is not available." << std::endl;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <iostream>
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
#include <opencv2/features2d.hpp>
|
||||
#include "opencv2/xfeatures2d.hpp"
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
@@ -28,7 +30,7 @@ int main(int argc, char* argv[])
|
||||
vector<KeyPoint> kpts1, kpts2;
|
||||
Mat desc1, desc2;
|
||||
|
||||
Ptr<AKAZE> akaze = AKAZE::create();
|
||||
Ptr<xfeatures2d::AKAZE> akaze = xfeatures2d::AKAZE::create();
|
||||
akaze->detectAndCompute(img1, noArray(), kpts1, desc1);
|
||||
akaze->detectAndCompute(img2, noArray(), kpts2, desc2);
|
||||
//! [AKAZE]
|
||||
@@ -96,3 +98,10 @@ int main(int argc, char* argv[])
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int main()
|
||||
{
|
||||
std::cout << "This tutorial code needs the xfeatures2d contrib module to be run." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,11 +1,14 @@
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
#include <opencv2/features2d.hpp>
|
||||
#include "opencv2/xfeatures2d.hpp"
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/3d.hpp>
|
||||
#include <opencv2/highgui.hpp> //for imshow
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "stats.h" // Stats structure definition
|
||||
#include "utils.h" // Drawing and printing functions
|
||||
@@ -148,7 +151,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
Stats stats, akaze_stats, orb_stats;
|
||||
Ptr<AKAZE> akaze = AKAZE::create();
|
||||
Ptr<xfeatures2d::AKAZE> akaze = xfeatures2d::AKAZE::create();
|
||||
akaze->setThreshold(akaze_thresh);
|
||||
Ptr<ORB> orb = ORB::create();
|
||||
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
|
||||
@@ -211,3 +214,10 @@ int main(int argc, char **argv)
|
||||
printStatistics("ORB", orb_stats);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int main()
|
||||
{
|
||||
std::cout << "This tutorial code needs the xfeatures2d contrib module to be run." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,115 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "opencv2/opencv_modules.hpp"
|
||||
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/features2d.hpp>
|
||||
#include <opencv2/xfeatures2d.hpp>
|
||||
#include <opencv2/imgcodecs.hpp>
|
||||
#include <vector>
|
||||
|
||||
// If you find this code useful, please add a reference to the following paper in your work:
|
||||
// Gil Levi and Tal Hassner, "LATCH: Learned Arrangements of Three Patch Codes", arXiv preprint arXiv:1501.03719, 15 Jan. 2015
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
|
||||
const float inlier_threshold = 2.5f; // Distance threshold to identify inliers
|
||||
const float nn_match_ratio = 0.8f; // Nearest neighbor matching ratio
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
CommandLineParser parser(argc, argv,
|
||||
"{@img1 | graf1.png | input image 1}"
|
||||
"{@img2 | graf3.png | input image 2}"
|
||||
"{@homography | H1to3p.xml | homography matrix}");
|
||||
Mat img1 = imread( samples::findFile( parser.get<String>("@img1") ), IMREAD_GRAYSCALE);
|
||||
Mat img2 = imread( samples::findFile( parser.get<String>("@img2") ), IMREAD_GRAYSCALE);
|
||||
|
||||
Mat homography;
|
||||
FileStorage fs( samples::findFile( parser.get<String>("@homography") ), FileStorage::READ);
|
||||
fs.getFirstTopLevelNode() >> homography;
|
||||
|
||||
vector<KeyPoint> kpts1, kpts2;
|
||||
Mat desc1, desc2;
|
||||
|
||||
Ptr<cv::ORB> orb_detector = cv::ORB::create(10000);
|
||||
|
||||
Ptr<xfeatures2d::LATCH> latch = xfeatures2d::LATCH::create();
|
||||
|
||||
|
||||
orb_detector->detect(img1, kpts1);
|
||||
latch->compute(img1, kpts1, desc1);
|
||||
|
||||
orb_detector->detect(img2, kpts2);
|
||||
latch->compute(img2, kpts2, desc2);
|
||||
|
||||
BFMatcher matcher(NORM_HAMMING);
|
||||
vector< vector<DMatch> > nn_matches;
|
||||
matcher.knnMatch(desc1, desc2, nn_matches, 2);
|
||||
|
||||
vector<KeyPoint> matched1, matched2, inliers1, inliers2;
|
||||
vector<DMatch> good_matches;
|
||||
for (size_t i = 0; i < nn_matches.size(); i++) {
|
||||
DMatch first = nn_matches[i][0];
|
||||
float dist1 = nn_matches[i][0].distance;
|
||||
float dist2 = nn_matches[i][1].distance;
|
||||
|
||||
if (dist1 < nn_match_ratio * dist2) {
|
||||
matched1.push_back(kpts1[first.queryIdx]);
|
||||
matched2.push_back(kpts2[first.trainIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < matched1.size(); i++) {
|
||||
Mat col = Mat::ones(3, 1, CV_64F);
|
||||
col.at<double>(0) = matched1[i].pt.x;
|
||||
col.at<double>(1) = matched1[i].pt.y;
|
||||
|
||||
col = homography * col;
|
||||
col /= col.at<double>(2);
|
||||
double dist = sqrt(pow(col.at<double>(0) - matched2[i].pt.x, 2) +
|
||||
pow(col.at<double>(1) - matched2[i].pt.y, 2));
|
||||
|
||||
if (dist < inlier_threshold) {
|
||||
int new_i = static_cast<int>(inliers1.size());
|
||||
inliers1.push_back(matched1[i]);
|
||||
inliers2.push_back(matched2[i]);
|
||||
good_matches.push_back(DMatch(new_i, new_i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
Mat res;
|
||||
drawMatches(img1, inliers1, img2, inliers2, good_matches, res);
|
||||
imwrite("latch_result.png", res);
|
||||
|
||||
|
||||
double inlier_ratio = inliers1.size() * 1.0 / matched1.size();
|
||||
cout << "LATCH Matching Results" << endl;
|
||||
cout << "*******************************" << endl;
|
||||
cout << "# Keypoints 1: \t" << kpts1.size() << endl;
|
||||
cout << "# Keypoints 2: \t" << kpts2.size() << endl;
|
||||
cout << "# Matches: \t" << matched1.size() << endl;
|
||||
cout << "# Inliers: \t" << inliers1.size() << endl;
|
||||
cout << "# Inliers Ratio: \t" << inlier_ratio << endl;
|
||||
cout << endl;
|
||||
|
||||
imshow("result", res);
|
||||
waitKey();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cerr << "OpenCV was built without xfeatures2d module" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,96 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "opencv2/opencv_modules.hpp"
|
||||
|
||||
#ifdef HAVE_OPENCV_XFEATURES2D
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/features2d.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/cudafeatures2d.hpp"
|
||||
#include "opencv2/xfeatures2d/cuda.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace cv;
|
||||
using namespace cv::cuda;
|
||||
|
||||
static void help()
|
||||
{
|
||||
cout << "\nThis program demonstrates using SURF_CUDA features detector, descriptor extractor and BruteForceMatcher_CUDA" << endl;
|
||||
cout << "\nUsage:\n\tsurf_keypoint_matcher --left <image1> --right <image2>" << endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 5)
|
||||
{
|
||||
help();
|
||||
return -1;
|
||||
}
|
||||
|
||||
GpuMat img1, img2;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (string(argv[i]) == "--left")
|
||||
{
|
||||
img1.upload(imread(argv[++i], IMREAD_GRAYSCALE));
|
||||
CV_Assert(!img1.empty());
|
||||
}
|
||||
else if (string(argv[i]) == "--right")
|
||||
{
|
||||
img2.upload(imread(argv[++i], IMREAD_GRAYSCALE));
|
||||
CV_Assert(!img2.empty());
|
||||
}
|
||||
else if (string(argv[i]) == "--help")
|
||||
{
|
||||
help();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
cv::cuda::printShortCudaDeviceInfo(cv::cuda::getDevice());
|
||||
|
||||
SURF_CUDA surf;
|
||||
|
||||
// detecting keypoints & computing descriptors
|
||||
GpuMat keypoints1GPU, keypoints2GPU;
|
||||
GpuMat descriptors1GPU, descriptors2GPU;
|
||||
surf(img1, GpuMat(), keypoints1GPU, descriptors1GPU);
|
||||
surf(img2, GpuMat(), keypoints2GPU, descriptors2GPU);
|
||||
|
||||
cout << "FOUND " << keypoints1GPU.cols << " keypoints on first image" << endl;
|
||||
cout << "FOUND " << keypoints2GPU.cols << " keypoints on second image" << endl;
|
||||
|
||||
// matching descriptors
|
||||
Ptr<cv::cuda::DescriptorMatcher> matcher = cv::cuda::DescriptorMatcher::createBFMatcher(surf.defaultNorm());
|
||||
vector<DMatch> matches;
|
||||
matcher->match(descriptors1GPU, descriptors2GPU, matches);
|
||||
|
||||
// downloading results
|
||||
vector<KeyPoint> keypoints1, keypoints2;
|
||||
vector<float> descriptors1, descriptors2;
|
||||
surf.downloadKeypoints(keypoints1GPU, keypoints1);
|
||||
surf.downloadKeypoints(keypoints2GPU, keypoints2);
|
||||
surf.downloadDescriptors(descriptors1GPU, descriptors1);
|
||||
surf.downloadDescriptors(descriptors2GPU, descriptors2);
|
||||
|
||||
// drawing the results
|
||||
Mat img_matches;
|
||||
drawMatches(Mat(img1), keypoints1, Mat(img2), keypoints2, matches, img_matches);
|
||||
|
||||
namedWindow("matches", 0);
|
||||
imshow("matches", img_matches);
|
||||
waitKey(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cerr << "OpenCV was built without xfeatures2d module" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -15,7 +15,7 @@ import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfDMatch;
|
||||
import org.opencv.core.MatOfKeyPoint;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.features2d.AKAZE;
|
||||
import org.opencv.xfeatures2d.AKAZE;
|
||||
import org.opencv.features2d.DescriptorMatcher;
|
||||
import org.opencv.features2d.Features2d;
|
||||
import org.opencv.highgui.HighGui;
|
||||
|
||||
@@ -40,11 +40,13 @@ try:
|
||||
except AttributeError:
|
||||
print("SIFT not available")
|
||||
try:
|
||||
FEATURES_FIND_CHOICES['brisk'] = cv.BRISK_create
|
||||
cv.xfeatures2d_BRISK.create() # check if the function can be called
|
||||
FEATURES_FIND_CHOICES['brisk'] = cv.xfeatures2d_BRISK.create
|
||||
except AttributeError:
|
||||
print("BRISK not available")
|
||||
try:
|
||||
FEATURES_FIND_CHOICES['akaze'] = cv.AKAZE_create
|
||||
cv.xfeatures2d_AKAZE.create() # check if the function can be called
|
||||
FEATURES_FIND_CHOICES['akaze'] = cv.xfeatures2d_AKAZE.create
|
||||
except AttributeError:
|
||||
print("AKAZE not available")
|
||||
|
||||
@@ -276,7 +278,6 @@ def get_compensator(args):
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
img_names = args.img_names
|
||||
print(img_names)
|
||||
work_megapix = args.work_megapix
|
||||
seam_megapix = args.seam_megapix
|
||||
compose_megapix = args.compose_megapix
|
||||
|
||||
@@ -22,7 +22,7 @@ homography = fs.getFirstTopLevelNode().mat()
|
||||
## [load]
|
||||
|
||||
## [AKAZE]
|
||||
akaze = cv.AKAZE_create()
|
||||
akaze = cv.xfeatures2d.AKAZE_create()
|
||||
kpts1, desc1 = akaze.detectAndCompute(img1, None)
|
||||
kpts2, desc2 = akaze.detectAndCompute(img2, None)
|
||||
## [AKAZE]
|
||||
|
||||
Reference in New Issue
Block a user