Skip to content

Commit cf2d0e8

Browse files
authored
Merge pull request #167 from alicevision/dev/vlfeat-like-descriptor
Adding a VLFeat-compliant feature descriptor
2 parents 7d59a27 + b8aca36 commit cf2d0e8

File tree

10 files changed

+309
-14
lines changed

10 files changed

+309
-14
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
## [1.0.0] - YYYY-MM-DD
1919

20+
## 2025
21+
22+
- Added vlFeat's descriptor extraction method as an option [PR](https://github.com/alicevision/popsift/pull/167)
23+
2024
## 2024
2125

2226
- CMake: CUDA as first-order language, different CC selection

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ add_library(popsift
2424
popsift/s_desc_grid.cu popsift/s_desc_grid.h
2525
popsift/s_desc_igrid.cu popsift/s_desc_igrid.h
2626
popsift/s_desc_notile.cu popsift/s_desc_notile.h
27+
popsift/s_desc_vlfeat.cu popsift/s_desc_vlfeat.h
2728
popsift/s_desc_norm_rs.h
2829
popsift/s_desc_norm_l2.h
2930
popsift/s_desc_normalize.h

src/application/main.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@ static void parseargs(int argc, char** argv, popsift::Config& config, string& in
7373
// "Choice of span (1-sided) for Gauss filters. Default is VLFeat-like computation depending on sigma. "
7474
// "Options are: vlfeat, relative, relative-all, opencv, fixed9, fixed15"
7575
("desc-mode", value<std::string>()->notifier([&](const std::string& s) { config.setDescMode(s); }),
76-
"Choice of descriptor extraction modes:\n"
77-
"loop, iloop, grid, igrid, notile\n"
78-
"Default is loop\n"
79-
"loop is OpenCV-like horizontal scanning, computing only valid points, grid extracts only useful points but rounds them, iloop uses linear texture and rotated gradiant fetching. igrid is grid with linear interpolation. notile is like igrid but avoids redundant gradiant fetching.")
76+
popsift::Config::getDescModeUsage())
8077
("popsift-mode", bool_switch()->notifier([&](bool b) { if(b) config.setMode(popsift::Config::PopSift); }),
8178
"During the initial upscale, shift pixels by 1. In extrema refinement, steps up to 0.6, do not reject points when reaching max iterations, "
8279
"first contrast threshold is .8 * peak thresh. Shift feature coords octave 0 back to original pos.")

src/application/match.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,7 @@ static void parseargs(int argc, char** argv, popsift::Config& config, string& lF
7171
( "gauss-mode", value<std::string>()->notifier([&](const std::string& s) { config.setGaussMode(s); }),
7272
popsift::Config::getGaussModeUsage() )
7373
("desc-mode", value<std::string>()->notifier([&](const std::string& s) { config.setDescMode(s); }),
74-
"Choice of descriptor extraction modes:\n"
75-
"loop, iloop, grid, igrid, notile\n"
76-
"Default is loop\n"
77-
"loop is OpenCV-like horizontal scanning, computing only valid points, grid extracts only useful points but rounds them, iloop uses linear texture and rotated gradiant fetching. igrid is grid with linear interpolation. notile is like igrid but avoids redundant gradiant fetching.")
74+
popsift::Config::getDescModeUsage() )
7875
("popsift-mode", bool_switch()->notifier([&](bool b) { if(b) config.setMode(popsift::Config::PopSift); }),
7976
"During the initial upscale, shift pixels by 1. In extrema refinement, steps up to 0.6, do not reject points when reaching max iterations, "
8077
"first contrast threshold is .8 * peak thresh. Shift feature coords octave 0 back to original pos.")

src/popsift/s_desc_vlfeat.cu

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright 2016-2017, Simula Research Laboratory
3+
* 2018-2020, University of Oslo
4+
*
5+
* This Source Code Form is subject to the terms of the Mozilla Public
6+
* License, v. 2.0. If a copy of the MPL was not distributed with this
7+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
8+
*/
9+
#include "popsift/sift_config.h"
10+
11+
#include "common/assist.h"
12+
#include "common/debug_macros.h"
13+
#include "common/vec_macros.h"
14+
#include "s_desc_vlfeat.h"
15+
#include "s_gradiant.h"
16+
#include "sift_constants.h"
17+
#include "sift_pyramid.h"
18+
19+
#include <cstdio>
20+
21+
using namespace popsift;
22+
23+
__device__ static inline
24+
void ext_desc_vlfeat_sub( const float ang,
25+
const Extremum* ext,
26+
float* __restrict__ features,
27+
cudaTextureObject_t layer_tex,
28+
const int width,
29+
const int height )
30+
{
31+
const float x = ext->xpos;
32+
const float y = ext->ypos;
33+
const int level = ext->lpos; // old_level;
34+
const float sig = ext->sigma;
35+
const float SBP = fabsf(DESC_MAGNIFY * sig);
36+
37+
if( SBP == 0 ) {
38+
return;
39+
}
40+
41+
float cos_t;
42+
float sin_t;
43+
__sincosf( ang, &sin_t, &cos_t );
44+
45+
const float csbp = cos_t * SBP;
46+
const float ssbp = sin_t * SBP;
47+
const float crsbp = cos_t / SBP;
48+
const float srsbp = sin_t / SBP;
49+
50+
// We have 4x4*16 bins.
51+
// There centers have the offsets -1.5, -0.5, 0.5, 1.5 from the
52+
// keypoint. The points that support them stretch from -2 to 2
53+
const float2 maxdist = make_float2( -2.0f, -2.0f );
54+
55+
// We rotate the corner of the maximum range by the keypoint orientation.
56+
// const float ptx = csbp * maxdist - ssbp * maxdist;
57+
// const float pty = csbp * maxdist + ssbp * maxdist;
58+
const float ptx = fabsf( ::fmaf( csbp, maxdist.x, -ssbp * maxdist.y ) );
59+
const float pty = fabsf( ::fmaf( csbp, maxdist.y, ssbp * maxdist.x ) );
60+
61+
const float bsz = 2.0f * ( fabsf(csbp) + fabsf(ssbp) );
62+
63+
const int xmin = max(1, (int)floorf(x - ptx - bsz));
64+
const int ymin = max(1, (int)floorf(y - pty - bsz));
65+
const int xmax = min(width - 2, (int)floorf(x + ptx + bsz));
66+
const int ymax = min(height - 2, (int)floorf(y + pty + bsz));
67+
68+
__shared__ float dpt[128];
69+
70+
for( int i=threadIdx.x; i<128; i+=blockDim.x )
71+
{
72+
dpt[i] = 0.0f;
73+
}
74+
75+
__syncthreads();
76+
77+
for( int pix_y = ymin; pix_y <= ymax; pix_y += 1 )
78+
{
79+
for( int base_x = xmin; base_x <= xmax; base_x += 32 )
80+
{
81+
float mod;
82+
float th;
83+
84+
get_gradiant32( mod, th, base_x, pix_y, layer_tex, level );
85+
86+
mod /= 2.0f; // Our mod is double that of vlfeat. Huh.
87+
88+
th -= ang;
89+
while( th > M_PI2 ) th -= M_PI2;
90+
while( th < 0.0f ) th += M_PI2;
91+
__syncthreads();
92+
93+
const int pix_x = base_x + threadIdx.x;
94+
95+
if( ( pix_y <= ymax ) && ( pix_x <= xmax ) )
96+
{
97+
// d : distance from keypoint
98+
const float2 d = make_float2( pix_x - x, pix_y - y );
99+
100+
// n : normalized distance from keypoint
101+
const float2 n = make_float2( ::fmaf( crsbp, d.x, srsbp * d.y ),
102+
::fmaf( crsbp, d.y, -srsbp * d.x ) );
103+
104+
const float ww = __expf( -scalbnf(n.x*n.x + n.y*n.y, -3));
105+
106+
const float nt = 8.0f * th / M_PI2;
107+
108+
// neighbouring tile on the lower side: -2, -1, 0 or 1
109+
// (must use floorf because casting rounds towards zero
110+
const int3 t0 = make_int3( (int)floorf(n.x - 0.5f),
111+
(int)floorf(n.y - 0.5f),
112+
(int)nt );
113+
const float wgt_x = - ( n.x - ( t0.x + 0.5f ) );
114+
const float wgt_y = - ( n.y - ( t0.y + 0.5f ) );
115+
const float wgt_t = - ( nt - t0.z );
116+
117+
for( int tx=0; tx<2; tx++ )
118+
{
119+
for( int ty=0; ty<2; ty++ )
120+
{
121+
for( int tt=0; tt<2; tt++ )
122+
{
123+
if( ( t0.y + ty >= -2 ) &&
124+
( t0.y + ty < 2 ) &&
125+
( t0.x + tx >= -2 ) &&
126+
( t0.x + tx < 2 ) )
127+
{
128+
float i_wgt_x = ( tx == 0 ) ? 1.0f + wgt_x : wgt_x;
129+
float i_wgt_y = ( ty == 0 ) ? 1.0f + wgt_y : wgt_y;
130+
float i_wgt_t = ( tt == 0 ) ? 1.0f + wgt_t : wgt_t;
131+
132+
i_wgt_x = fabsf( i_wgt_x );
133+
i_wgt_y = fabsf( i_wgt_y );
134+
i_wgt_t = fabsf( i_wgt_t );
135+
136+
const float val = ww
137+
* mod
138+
* i_wgt_x
139+
* i_wgt_y
140+
* i_wgt_t;
141+
142+
const int offset = 80
143+
+ ( t0.y + ty ) * 32
144+
+ ( t0.x + tx ) * 8
145+
+ ( t0.z + tt ) % 8;
146+
147+
atomicAdd( &dpt[offset], val );
148+
}
149+
}
150+
}
151+
}
152+
}
153+
__syncthreads();
154+
}
155+
}
156+
157+
for( int i=threadIdx.x; i<128; i+=blockDim.x )
158+
{
159+
features[i] = dpt[i];
160+
}
161+
}
162+
163+
__global__ void ext_desc_vlfeat( int octave, cudaTextureObject_t layer_tex, int w, int h)
164+
{
165+
const int o_offset = dct.ori_ps[octave] + blockIdx.x;
166+
Descriptor* desc = &dbuf.desc [o_offset];
167+
const int ext_idx = dobuf.feat_to_ext_map[o_offset];
168+
Extremum* ext = dobuf.extrema + ext_idx;
169+
170+
const int ext_base = ext->idx_ori;
171+
const int ori_num = o_offset - ext_base;
172+
const float ang = ext->orientation[ori_num];
173+
174+
ext_desc_vlfeat_sub( ang,
175+
ext,
176+
desc->features,
177+
layer_tex,
178+
w,
179+
h );
180+
}
181+
182+
namespace popsift
183+
{
184+
185+
bool start_ext_desc_vlfeat( const int octave, Octave& oct_obj )
186+
{
187+
dim3 block;
188+
dim3 grid;
189+
grid.x = hct.ori_ct[octave];
190+
grid.y = 1;
191+
grid.z = 1;
192+
193+
if( grid.x == 0 ) return false;
194+
195+
block.x = 32;
196+
block.y = 1;
197+
block.z = 1;
198+
199+
size_t shared_size = 4 * 128 * sizeof(float);
200+
201+
ext_desc_vlfeat
202+
<<<grid,block,shared_size,oct_obj.getStream()>>>
203+
( octave,
204+
oct_obj.getDataTexPoint( ),
205+
oct_obj.getWidth(),
206+
oct_obj.getHeight() );
207+
208+
POP_SYNC_CHK;
209+
210+
return true;
211+
}
212+
213+
}; // namespace popsift

src/popsift/s_desc_vlfeat.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2016-2017, Simula Research Laboratory
3+
* 2018-2020, University of Oslo
4+
*
5+
* This Source Code Form is subject to the terms of the Mozilla Public
6+
* License, v. 2.0. If a copy of the MPL was not distributed with this
7+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
8+
*/
9+
#pragma once
10+
#include "sift_octave.h"
11+
#include "sift_pyramid.h"
12+
13+
namespace popsift
14+
{
15+
16+
bool start_ext_desc_vlfeat( const int octave, Octave& oct_obj );
17+
18+
}; // namespace popsift
19+

src/popsift/s_gradiant.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,37 @@ void get_gradiant( float& grad,
6868
theta = atan2f(dy, dx);
6969
}
7070

71+
/* A version of get_gradiant that works for a (32,1,1) threadblock
72+
* and pulls data to shared memory before computing. Data is pulled
73+
* less frequently, meaning that we do not rely on the L1 cache.
74+
*/
75+
__device__ static inline
76+
void get_gradiant32( float& grad,
77+
float& theta,
78+
const int x,
79+
const int y,
80+
cudaTextureObject_t layer,
81+
const int level )
82+
{
83+
const int idx = threadIdx.x;
84+
85+
__shared__ float x_array[34];
86+
87+
for( int i=idx; i<34; i += blockDim.x )
88+
{
89+
x_array[i] = readTex( layer, x+i-1.0f, y, level );
90+
}
91+
__syncthreads();
92+
93+
const float dx = x_array[idx+2] - x_array[idx];
94+
95+
const float dy = readTex( layer, x+idx, y+1.0f, level )
96+
- readTex( layer, x+idx, y-1.0f, level );
97+
98+
grad = hypotf( dx, dy ); // __fsqrt_rz(dx*dx + dy*dy);
99+
theta = atan2f(dy, dx);
100+
}
101+
71102
__device__ static inline
72103
void get_gradiant( float& grad,
73104
float& theta,

src/popsift/sift_conf.cu

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ void Config::setDescMode( const std::string& text )
7272
setDescMode( Config::IGrid );
7373
else if( text == "notile" )
7474
setDescMode( Config::NoTile );
75+
else if( text == "vlfeat" )
76+
setDescMode( Config::VLFeat_Desc );
7577
else
7678
POP_FATAL( "specified descriptor extraction mode must be one of loop, grid or igrid" );
7779
}
@@ -81,6 +83,25 @@ void Config::setDescMode( Config::DescMode m )
8183
_desc_mode = m;
8284
}
8385

86+
const char* Config::getDescModeUsage( )
87+
{
88+
return "Choice of descriptor extraction modes:\n"
89+
"loop, iloop, grid, igrid, notile, vlfeat\n"
90+
"Default is loop\n"
91+
"loop is OpenCV-like horizontal scanning, sampling every pixel in a radius around the "
92+
"centers or the 16 tiles arond the keypoint. Each sampled point contributes to two "
93+
"histogram bins."
94+
"iloop is like loop but samples all constant 1-pixel distances from the keypoint, "
95+
"using the CUDA texture engine for interpolation. "
96+
"grid is like loop but works on rotated, normalized tiles, relying on CUDA 2D cache "
97+
"to replace the manual data aligment idea of loop. "
98+
"igrid iloop and grid. "
99+
"notile is like igrid but handles all 16 tiles at once.\n"
100+
"vlfeat is VLFeat-like horizontal scanning, sampling every pixel in a radius around "
101+
"keypoint itself, using the 16 tile centers only for weighting. Every sampled point "
102+
"contributes to up to eight histogram bins.";
103+
}
104+
84105
void Config::setGaussMode( const std::string& m )
85106
{
86107
if( m == "vlfeat" )

src/popsift/sift_conf.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,20 @@ struct Config
8484
*/
8585
enum DescMode
8686
{
87-
/// scan horizontal, extract valid points
87+
/// scan horizontal, extract valid points - weight goes into 2 histogram bins
8888
Loop,
89-
/// scan horizontal, extract valid points, interpolate with tex engine
89+
/// loop-compatible; scan horizontal, extract valid points, interpolate with tex engine
9090
ILoop,
91-
/// scan in rotated mode, round pixel address
91+
/// loop-compatible; scan in rotated mode, round pixel address
9292
Grid,
93-
/// scan in rotated mode, interpolate with tex engine
93+
/// loop-compatible; scan in rotated mode, interpolate with tex engine
9494
IGrid,
95-
/// variant of IGrid, no duplicate gradient fetching
96-
NoTile
95+
/// loop-compatible; variant of IGrid, no duplicate gradient fetching
96+
NoTile,
97+
/** extraction code according to VLFeat, similar to loop, weight goes into
98+
* up to 8 histogram bins
99+
*/
100+
VLFeat_Desc
97101
};
98102

99103
/**
@@ -182,6 +186,11 @@ struct Config
182186
*/
183187
void setDescMode( DescMode mode = Loop );
184188

189+
/**
190+
* @brief Helper functions for the main program's usage string.
191+
*/
192+
static const char* getDescModeUsage( );
193+
185194
// void setGaussGroup( int groupsize );
186195
// int getGaussGroup( ) const;
187196

0 commit comments

Comments
 (0)