-
Notifications
You must be signed in to change notification settings - Fork 0
/
demo_lib_sift.cpp
1222 lines (926 loc) · 39.2 KB
/
demo_lib_sift.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Authors: Unknown. Please, if you are the author of this file, or if you
// know who are the authors of this file, let us know, so we can give the
// adequate credits and/or get the adequate authorizations.
// WARNING:
// This file implements an algorithm possibly linked to the patent
//
// David Lowe "Method and apparatus for identifying scale invariant
// features in an image and use of same for locating an object in an
// image", U.S. Patent 6,711,293.
//
// This file is made available for the exclusive aim of serving as
// scientific tool to verify of the soundness and
// completeness of the algorithm description. Compilation,
// execution and redistribution of this file may violate exclusive
// patents rights in certain countries.
// The situation being different for every country and changing
// over time, it is your responsibility to determine which patent
// rights restrictions apply to you before you compile, use,
// modify, or redistribute this file. A patent lawyer is qualified
// to make this determination.
// If and only if they don't conflict with any patent terms, you
// can benefit from the following license terms attached to this
// file.
//
// This program is provided for scientific and educational only:
// you can use and/or modify it for these purposes, but you are
// not allowed to redistribute this work or derivative works in
// source or executable form. A license must be obtained from the
// patent right holders for any other use.
#include "demo_lib_sift.h"
#define DEBUG 0
#define ABS(x) (((x) > 0) ? (x) : (-(x)))
void default_sift_parameters(siftPar &par)
{
par.OctaveMax=100000;
par.DoubleImSize = 0;
par.order = 3;
par.InitSigma = 1.6;
par.BorderDist = 5;
par.Scales = 3;
par.PeakThresh = 255.0 * 0.04 / 3.0;
par.EdgeThresh = 0.06;
par.EdgeThresh1 = 0.08;
par.OriBins = 36;
par.OriSigma = 1.5;
par.OriHistThresh = 0.8;
par.MaxIndexVal = 0.2;
par.MagFactor = 3;
par.IndexSigma = 1.0;
par.IgnoreGradSign = 0;
// par.MatchRatio = 0.6;
// par.MatchRatio = 0.75; // Guoshen Yu. Since l1 distance is used for matching instead of l2, a larger threshold is needed.
par.MatchRatio = 0.73; // Guoshen Yu. Since l1 distance is used for matching instead of l2, a larger threshold is needed.
par.MatchXradius = 1000000.0f;
par.MatchYradius = 1000000.0f;
par.noncorrectlylocalized = 0;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// SIFT Keypoint detection
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void OctaveKeypoints(flimage & image, float octSize, keypointslist& keys,siftPar &par);
void FindMaxMin( flimage* dogs, flimage* blur, float octSize ,keypointslist& keys,siftPar &par);
bool LocalMax(float val, flimage& dog, int y0, int x0);
bool LocalMin(float val, flimage& dog, int y0, int x0);
bool LocalMaxMin(float val, const flimage& dog, int y0, int x0);
int NotOnEdge( flimage& dog, int r, int c, float octSize,siftPar &par);
float FitQuadratic(float offset[3], flimage* dogs, int s, int r, int c);
void InterpKeyPoint(
flimage* dogs, int s, int r, int c,
const flimage& grad, const flimage& ori, flimage& map,
float octSize, keypointslist& keys, int movesRemain,siftPar &par);
void AssignOriHist(
const flimage& grad, const flimage& ori, float octSize,
float octScale, float octRow, float octCol, keypointslist& keys,siftPar &par);
void SmoothHistogram(
float* hist, int bins);
float InterpPeak(
float a, float b, float c);
void MakeKeypoint(
const flimage& grad, const flimage& ori, float octSize, float octScale,
float octRow, float octCol, float angle, keypointslist& keys,siftPar &par);
void MakeKeypointSample(
keypoint& key, const flimage& grad, const flimage& ori,
float scale, float row, float col,siftPar &par);
void NormalizeVec(
float* vec);
void KeySampleVec(
keypoint& key, const flimage& grad, const flimage& ori,
float scale, float row, float col,siftPar &par);
void KeySample(
float index[IndexSize][IndexSize][OriSize], keypoint& key,
const flimage& grad, const flimage& ori,
float scale, float row, float col,siftPar &par);
void AddSample(
float index[IndexSize][IndexSize][OriSize], keypoint& key,
const flimage& grad, const flimage& orim,
float r, float c, float rpos, float cpos, float rx, float cx,siftPar &par);
void PlaceInIndex(
float index[IndexSize][IndexSize][OriSize],
float mag, float ori, float rx, float cx,siftPar &par);
void compute_sift_keypoints(float *input, keypointslist& keypoints, int width, int height, siftPar &par)
{
flimage image;
/// Make zoom of image if necessary
float octSize = 1.0;
if (par.DoubleImSize){
//printf("... compute_sift_keypoints :: applying zoom\n");
// image.create(2*width, 2*height);
// apply_zoom(input,image.getPlane(),2.0,par.order,width,height);
// octSize *= 0.5;
printf("Doulbe image size not allowed. Guoshen Yu\n");
exit(-1);
} else
{
image.create(width,height,input);
}
// printf("Using initial Dog value: %f\n", par.PeakThresh);
// printf("Double image size: %d\n", par.DoubleImSize);
// printf("Interpolation order: %d\n", par.order);
/// Apply initial smoothing to input image to raise its smoothing to par.InitSigma.
/// We assume image from camera has smoothing of sigma = 0.5, which becomes sigma = 1.0 if image has been doubled.
/// increase = sqrt(Init^2 - Current^2)
float curSigma;
if (par.DoubleImSize) curSigma = 1.0; else curSigma = 0.5;
if (par.InitSigma > curSigma ) {
if (DEBUG) printf("Convolving initial image to achieve std: %f \n", par.InitSigma);
float sigma = (float) sqrt((double)(par.InitSigma * par.InitSigma - curSigma * curSigma));
gaussian_convolution( image.getPlane(), image.getPlane(), image.nwidth(), image.nheight(), sigma);
}
/// Convolve by par.InitSigma at each step inside OctaveKeypoints by steps of
/// Subsample of factor 2 while reasonable image size
/// Keep reducing image by factors of 2 until one dimension is
/// smaller than minimum size at which a feature could be detected.
int minsize = 2 * par.BorderDist + 2;
int OctaveCounter = 0;
//printf("... compute_sift_keypoints :: maximum number of scales : %d\n", par.OctaveMax);
while (image.nwidth() > minsize && image.nheight() > minsize && OctaveCounter < par.OctaveMax) {
if (DEBUG) printf("Calling OctaveKeypoints \n");
OctaveKeypoints(image, octSize, keypoints,par);
// image is blurred inside OctaveKeypoints and therefore can be sampled
flimage aux( (int)((float) image.nwidth() / 2.0f) , (int)((float) image.nheight() / 2.0f));
if (DEBUG) printf("Sampling initial image \n");
sample(image.getPlane(), aux.getPlane(), 2.0f, image.nwidth(), image.nheight());
image = aux;
octSize *= 2.0;
OctaveCounter++;
}
/* printf("sift:: %d keypoints\n", keypoints.size());
printf("sift:: plus non correctly localized: %d \n", par.noncorrectlylocalized);*/
}
/////////////////////////////////////////////////
/// EXTREMA DETECTION IN ONE SCALE-SPACE OCTAVE:
/////////////////////////////////////////////////
/// par.Scales determine how many steps we perform to pass from one scale to the next one: sigI --> 2*sigI
/// At each step we pass from sigma_0 --> sigma0 * (1 + R)
/// At the last step sigI * (1 + R)^par.Scales = 2 * sigI
/// (1+R) = 2 ^(1 / par.Scales) it is called sigmaRatio
/// It seems that blur[par.Scales+1] is compared in two succesive iterations
void OctaveKeypoints(flimage & image, float octSize, keypointslist& keys,siftPar &par)
{
// Guoshen Yu, 2010.09.21, Windows version
// flimage blur[par.Scales+3], dogs[par.Scales+2];
int size_blur = par.Scales+3;
int size_dogs = par.Scales+2;
flimage *blur = new flimage[size_blur];
flimage *dogs = new flimage[size_dogs];
float sigmaRatio = (float) pow(2.0, 1.0 / (double) par.Scales);
/* Build array, blur, holding par.Scales+3 blurred versions of the image. */
blur[0] = flimage(image); /* First level is input to this routine. */
float prevSigma = par.InitSigma; /* Input image has par.InitSigma smoothing. */
/* Form each level by adding incremental blur from previous level.
Increase in blur is from prevSigma to prevSigma * sigmaRatio, so
increase^2 = (prevSigma * sigmaRatio)^2 - prevSigma^2
*/
for (int i = 1; i < par.Scales + 3; i++) {
if (DEBUG) printf("Convolving scale: %d \n", i);
blur[i] = flimage(blur[i-1]);
float increase = prevSigma*(float)sqrt((double)(sigmaRatio*sigmaRatio-1.0));
gaussian_convolution( blur[i].getPlane(), blur[i].getPlane(), blur[i].nwidth(), blur[i].nheight(), increase);
prevSigma *= sigmaRatio;
}
/* Compute an array, dogs, of difference-of-Gaussian images by
subtracting each image from its next blurred version. */
for (int i = 0; i < par.Scales + 2; i++) {
dogs[i] = flimage(blur[i]);
/// dogs[i] = dogs[i] - blur[i+1]
combine(dogs[i].getPlane(),1.0f, blur[i+1].getPlane(),-1.0f, dogs[i].getPlane(), dogs[i].nwidth() * dogs[i].nheight());
}
// Image with exact blur to be subsampled is blur[scales]
image = blur[par.Scales];
/* Scale-space extrema detection in this octave */
if (DEBUG) printf("Looking for local maxima \n");
FindMaxMin(dogs, blur, octSize, keys,par);
// Guoshen Yu, 2010.09.22, Windows version
delete [] blur;
delete [] dogs;
}
/////////////////////////////////////////////////
///Find the local maxima and minima of the DOG images in scale space. Return the keypoints for these locations.
/////////////////////////////////////////////////
/// For each point at each scale we decide if it is a local maxima:
/// - |dogs(x,y,s)| > 0.8 * par.PeakThresh
/// - Local maximum or minimum in s-1,s,s+1
/// - NotonEdge: ratio of the two principle curvatures of the DOG function at this point be below a threshold.
/// blur[par.Scales+1] is not used in order to look for extrema
/// while these could be computed using avalaible blur and dogs
void FindMaxMin(
flimage* dogs, flimage* blur,
float octSize, keypointslist& keys,siftPar &par)
{
int width = dogs[0].nwidth(), height = dogs[0].nheight();
/* Create an image map in which locations that have a keypoint are
marked with value 1.0, to prevent two keypoints being located at
same position. This may seem an inefficient data structure, but
does not add significant overhead.
*/
flimage map(width,height,0.0f);
flimage grad(width,height,0.0f);
flimage ori(width,height,0.0f);
/* Search through each scale, leaving 1 scale below and 1 above.
There are par.Scales+2 dog images.
*/
for (int s = 1; s < par.Scales+1; s++) {
if (DEBUG) printf("************************scale: %d\n", s);
//getchar();
/* For each intermediate image, compute gradient and orientation
images to be used for keypoint description. */
compute_gradient_orientation(blur[s].getPlane(), grad.getPlane(), ori.getPlane(), blur[s].nwidth(), blur[s].nheight());
/* Only find peaks at least par.BorderDist samples from image border, as
peaks centered close to the border will lack stability. */
assert(par.BorderDist >= 2);
float val;
int partialcounter = 0;
for (int r = par.BorderDist; r < height - par.BorderDist; r++)
for (int c = par.BorderDist; c < width - par.BorderDist; c++) {
/* Pixel value at (c,r) position. */
val = dogs[s](c,r);
/* DOG magnitude must be above 0.8 * par.PeakThresh threshold
(precise threshold check will be done once peak
interpolation is performed). Then check whether this
point is a peak in 3x3 region at each level, and is not
on an elongated edge.
*/
if (fabs(val) > 0.8 * par.PeakThresh)
{
/*
// If local maxima
if (LocalMax(val, dogs[s-1], r, c,par) && LocalMax(val, dogs[s], r, c, par) && LocalMax(val, dogs[s+1], r, c,par) && NotOnEdge(dogs[s], r, c, octSize,par))
{
if (DEBUG) printf("Maximum Keypoint found (%d,%d,%d) val: %f\n",s,r,c,val);
InterpKeyPoint(
dogs, s, r, c, grad, ori,
map, octSize, keys, 5,par);
} else if (LocalMin(val, dogs[s-1], r, c,par) && LocalMin(val, dogs[s], r, c,par) && LocalMin(val, dogs[s+1], r, c,par) && NotOnEdge(dogs[s], r, c, octSize,par))
{
if (DEBUG) printf("Minimum Keypoint found (%d,%d,%d) val: %f\n",s,r,c,val);
InterpKeyPoint(
dogs, s, r, c, grad, ori,
map, octSize, keys, 5,par);
}
*/
if (LocalMaxMin(val, dogs[s-1], r, c) && LocalMaxMin(val, dogs[s], r, c) && LocalMaxMin(val, dogs[s+1], r, c) && NotOnEdge(dogs[s], r, c, octSize,par))
{
partialcounter++;
if (DEBUG) printf("%d: (%d,%d,%d) val: %f\n",partialcounter, s,r,c,val);
InterpKeyPoint(
dogs, s, r, c, grad, ori,
map, octSize, keys, 5,par);
//getchar();
}
}
}
}
}
//bool LocalMax(float val, flimage& dog, int y0, int x0, siftPar &par)
bool LocalMax(float val, flimage& dog, int y0, int x0)
{
for (int x = x0 - 1; x <= x0 + 1; x++)
for (int y = y0 - 1; y <= y0 + 1; y++){
//printf("%f \t", dog(x,y));
if (dog(x,y) > val) return 0;
}
return 1;
}
bool LocalMin(float val, flimage& dog, int y0, int x0)
{
for (int x = x0 - 1; x <= x0 + 1; x++)
for (int y = y0 - 1; y <= y0 + 1; y++){
//printf("%f \t", dog(x,y));
if (dog(x,y) < val) return 0;
}
return 1;
}
/* Return TRUE iff val is a local maximum (positive value) or
minimum (negative value) compared to the 3x3 neighbourhood that
is centered at (row,col).
*/
bool LocalMaxMin(float val, const flimage& dog, int y0, int x0)
{
// For efficiency, use separate cases for maxima or minima, and
// return as soon as possible
if (val > 0.0) {
for (int x = x0 - 1; x <= x0 + 1; x++)
for (int y = y0 - 1; y <= y0 + 1; y++){
if (dog(x,y) > val) return false;
}
} else {
for (int x = x0 - 1; x <= x0 + 1; x++)
for (int y = y0 - 1; y <= y0 + 1; y++){
if (dog(x,y) < val) return false;
}
}
return true;
}
/* Returns FALSE if this point on the DOG function lies on an edge.
This test is done early because it is very efficient and eliminates
many points. It requires that the ratio of the two principle
curvatures of the DOG function at this point be below a threshold.
Edge threshold is higher on the first scale where SNR is small in
order to reduce the number of unstable keypoints.
*/
int NotOnEdge(flimage& dog, int r, int c, float octSize,siftPar &par)
{
/* Compute 2x2 Hessian values from pixel differences. */
float H00 = dog(c,r-1) - 2.0 * dog(c,r) + dog(c,r+1), /* AMIR: div by ? */
H11 = dog(c-1,r) - 2.0 * dog(c,r) + dog(c+1,r),
H01 = ( (dog(c+1,r+1) - dog(c-1,r+1)) - (dog(c+1,r-1) - dog(c-1,r-1)) ) / 4.0;
/* Compute determinant and trace of the Hessian. */
float det = H00 * H11 - H01 * H01, /// Det H = \prod l_i
trace = H00 + H11; /// tr H = \sum l_i
/// As we do not desire edges but only corners we demand l_max / l_min less than a threshold
/// In practice if A = k B, A*B = k B^2
/// (A + B)^2 = (k+1)^2 * B^2
/// k B^2 > t * (k+1)^2 * B^2 sii k / (k+1)^2 > t
/// This is a decreasing function for k > 1 and value 0.3 at k=1.
/// Setting t = 0.08, means k<=10
/* To detect an edge response, we require the ratio of smallest
to largest principle curvatures of the DOG function
(eigenvalues of the Hessian) to be below a threshold. For
efficiency, we use Harris' idea of requiring the determinant to
be above par.EdgeThresh times the squared trace, as for eigenvalues
A and B, det = AB, trace = A+B. So if A = 10B, then det = 10B**2,
and trace**2 = (11B)**2 = 121B**2, so par.EdgeThresh = 10/121 =
0.08 to require ratio of eigenvalues less than 10.
*/
if (octSize <= 1)
return (det > par.EdgeThresh1 * trace * trace);
else
return (det > par.EdgeThresh * trace * trace);
}
/* Create a keypoint at a peak near scale space location (s,r,c), where
s is scale (index of DOGs image), and (r,c) is (row, col) location.
Add to the list of keys with any new keys added.
*/
void InterpKeyPoint(
flimage* dogs, int s, int r, int c,
const flimage& grad, const flimage& ori, flimage& map,
float octSize, keypointslist& keys, int movesRemain,siftPar &par)
{
/* Fit quadratic to determine offset and peak value. */
float offset[3];
float peakval = FitQuadratic(offset, dogs, s, r, c);
if (DEBUG) printf("peakval: %f, of[0]: %f of[1]: %f of[2]: %f\n", peakval, offset[0], offset[1], offset[2]);
/* Move to an adjacent (row,col) location if quadratic interpolation
is larger than 0.6 units in some direction (we use 0.6 instead of
0.5 to avoid jumping back and forth near boundary). We do not
perform move to adjacent scales, as it is seldom useful and we
do not have easy access to adjacent scale structures. The
movesRemain counter allows only a fixed number of moves to
prevent possibility of infinite loops.
*/
int newr = r, newc = c;
if (offset[1] > 0.6 && r < dogs[0].nheight() - 3)
newr++;
else if (offset[1] < -0.6 && r > 3)
newr--;
if (offset[2] > 0.6 && c < dogs[0].nwidth() - 3)
newc++;
else if (offset[2] < -0.6 && c > 3)
newc--;
if (movesRemain > 0 && (newr != r || newc != c)) {
InterpKeyPoint(
dogs, s, newr, newc, grad, ori, map,
octSize, keys,movesRemain - 1,par);
return;
}
/* Do not create a keypoint if interpolation still remains far
outside expected limits, or if magnitude of peak value is below
threshold (i.e., contrast is too low). */
if ( fabs(offset[0]) > 1.5 || fabs(offset[1]) > 1.5 ||
fabs(offset[2]) > 1.5 || fabs(peakval) < par.PeakThresh)
{
if (DEBUG) printf("Point not well localized by FitQuadratic\n");
par.noncorrectlylocalized++;
return;
}
/* Check that no keypoint has been created at this location (to avoid
duplicates). Otherwise, mark this map location.
*/
if (map(c,r) > 0.0) return;
map(c,r) = 1.0;
/* The scale relative to this octave is given by octScale. The scale
units are in terms of sigma for the smallest of the Gaussians in the
DOG used to identify that scale.
*/
// Guoshen Yu, 2010.09.21 Windows version
// float octScale = par.InitSigma * pow(2.0, (s + offset[0]) / (float) par.Scales);
float octScale = par.InitSigma * pow(2.0, (s + offset[0]) / (double) par.Scales);
/// always use histogram of orientations
//if (UseHistogramOri)
AssignOriHist(
grad, ori, octSize, octScale,
r + offset[1], c + offset[2], keys, par);
//else
// AssignOriAvg(
// grad, ori, octSize, octScale,
// r + offset[1], c + offset[2], keys);
}
/* Apply the method developed by Matthew Brown (see BMVC 02 paper) to
fit a 3D quadratic function through the DOG function values around
the location (s,r,c), i.e., (scale,row,col), at which a peak has
been detected. Return the interpolated peak position as a vector
in "offset", which gives offset from position (s,r,c). The
returned value is the interpolated DOG magnitude at this peak.
*/
float FitQuadratic(float offset[3], flimage* dogs, int s, int r, int c)
{
float g[3];
flimage *dog0, *dog1, *dog2;
int i;
//s = 1; r = 128; c = 128;
float ** H = allocate_float_matrix(3, 3);
/* Select the dog images at peak scale, dog1, as well as the scale
below, dog0, and scale above, dog2. */
dog0 = &dogs[s-1];
dog1 = &dogs[s];
dog2 = &dogs[s+1];
/* Fill in the values of the gradient from pixel differences. */
g[0] = ((*dog2)(c,r) - (*dog0)(c,r)) / 2.0;
g[1] = ((*dog1)(c,r+1) - (*dog1)(c,r-1)) / 2.0;
g[2] = ((*dog1)(c+1,r) - (*dog1)(c-1,r)) / 2.0;
/* Fill in the values of the Hessian from pixel differences. */
H[0][0] = (*dog0)(c,r) - 2.0 * (*dog1)(c,r) + (*dog2)(c,r);
H[1][1] = (*dog1)(c,r-1) - 2.0 * (*dog1)(c,r) + (*dog1)(c,r+1);
H[2][2] = (*dog1)(c-1,r) - 2.0 * (*dog1)(c,r) + (*dog1)(c+1,r);
H[0][1] = H[1][0] = ( ((*dog2)(c,r+1) - (*dog2)(c,r-1)) -
((*dog0)(c,r+1) - (*dog0)(c,r-1)) ) / 4.0;
H[0][2] = H[2][0] = ( ((*dog2)(c+1,r) - (*dog2)(c-1,r)) -
((*dog0)(c+1,r) - (*dog0)(c-1,r)) ) / 4.0;
H[1][2] = H[2][1] = ( ((*dog1)(c+1,r+1) - (*dog1)(c-1,r+1)) -
((*dog1)(c+1,r-1) - (*dog1)(c-1,r-1)) ) / 4.0;
/* Solve the 3x3 linear sytem, Hx = -g. Result, x, gives peak offset.
Note that SolveLinearSystem destroys contents of H. */
offset[0] = - g[0];
offset[1] = - g[1];
offset[2] = - g[2];
// for(i=0; i < 3; i++){
//
// for(j=0; j < 3; j++) printf("%f ", H[i][j]);
// printf("\n");
// }
// printf("\n");
//
// for(i=0; i < 3; i++) printf("%f ", offset[i]);
// printf("\n");
float solution[3];
lusolve(H, solution, offset,3);
// printf("\n");
// for(i=0; i < 3; i++) printf("%f ", solution[i]);
// printf("\n");
desallocate_float_matrix(H,3,3);
delete[] H; /*memcheck*/
/* Also return value of DOG at peak location using initial value plus
0.5 times linear interpolation with gradient to peak position
(this is correct for a quadratic approximation).
*/
for(i=0; i < 3; i++) offset[i] = solution[i];
return ((*dog1)(c,r) + 0.5 * (solution[0]*g[0]+solution[1]*g[1]+solution[2]*g[2]));
}
/// - Compute histogram of orientation in a neighborhood weighted by gradient and distance to center
/// - Look for local (3-neighborhood) maximum with valuer larger or equal than par.OriHistThresh * maxval
/* Assign an orientation to this keypoint. This is done by creating a
Gaussian weighted histogram of the gradient directions in the
region. The histogram is smoothed and the largest peak selected.
The results are in the range of -PI to PI.
*/
void AssignOriHist(
const flimage& grad, const flimage& ori, float octSize,
float octScale, float octRow, float octCol,keypointslist& keys,siftPar &par)
{
int bin, prev, next;
// Guoshen Yu, 2010.09.21 Windows version
// float hist[par.OriBins], distsq, dif, gval, weight, angle, interp;
float distsq, dif, gval, weight, angle, interp;
int tmp_size = par.OriBins;
float *hist = new float[tmp_size];
float radius2, sigma2;
int row = (int) (octRow+0.5),
col = (int) (octCol+0.5),
rows = grad.nheight(),
cols = grad.nwidth();
for (int i = 0; i < par.OriBins; i++) hist[i] = 0.0;
/* Look at pixels within 3 sigma around the point and sum their
Gaussian weighted gradient magnitudes into the histogram. */
float sigma = par.OriSigma * octScale;
int radius = (int) (sigma * 3.0);
int rmin = MAX(0,row-radius);
int cmin = MAX(0,col-radius);
int rmax = MIN(row+radius,rows-2);
int cmax = MIN(col+radius,cols-2);
radius2 = (float)(radius * radius);
sigma2 = 2.0*sigma*sigma;
for (int r = rmin; r <= rmax; r++) {
for (int c = cmin; c <= cmax; c++) {
gval = grad(c,r);
dif = (r - octRow); distsq = dif*dif;
dif = (c - octCol); distsq += dif*dif;
if (gval > 0.0 && distsq < radius2 + 0.5) {
weight = exp(- distsq / sigma2);
/* Ori is in range of -PI to PI. */
angle = ori(c,r);
bin = (int) (par.OriBins * (angle + PI + 0.001) / (2.0 * PI));
assert(bin >= 0 && bin <= par.OriBins);
bin = MIN(bin, par.OriBins - 1);
hist[bin] += weight * gval;
}
}
}
/* Apply smoothing 6 times for accurate Gaussian approximation. */
for (int i = 0; i < 6; i++)
SmoothHistogram(hist, par.OriBins);
/* Find maximum value in histogram. */
float maxval = 0.0;
for (int i = 0; i < par.OriBins; i++)
if (hist[i] > maxval) maxval = hist[i];
/* Look for each local peak in histogram. If value is within
par.OriHistThresh of maximum value, then generate a keypoint. */
for (int i = 0; i < par.OriBins; i++) {
prev = (i == 0 ? par.OriBins - 1 : i - 1);
next = (i == par.OriBins - 1 ? 0 : i + 1);
if ( hist[i] > hist[prev] && hist[i] > hist[next] &&
hist[i] >= par.OriHistThresh * maxval ) {
/* Use parabolic fit to interpolate peak location from 3 samples.
Set angle in range -PI to PI. */
interp = InterpPeak(hist[prev], hist[i], hist[next]);
angle = 2.0 * PI * (i + 0.5 + interp) / par.OriBins - PI;
assert(angle >= -PI && angle <= PI);
if (DEBUG) printf("angle selected: %f \t location: (%f,%f)\n", angle, octRow, octCol);
;
/* Create a keypoint with this orientation. */
MakeKeypoint(
grad, ori, octSize, octScale,
octRow, octCol, angle, keys,par);
}
}
// Guoshen Yu, 2010.09.22, Windows version
delete [] hist;
}
/* Smooth a histogram by using a [1/3 1/3 1/3] kernel. Assume the histogram
is connected in a circular buffer.
*/
void SmoothHistogram(float* hist, int bins)
{
float prev, temp;
prev = hist[bins - 1];
for (int i = 0; i < bins; i++) {
temp = hist[i];
hist[i] = ( prev + hist[i] + hist[(i + 1 == bins) ? 0 : i + 1] ) / 3.0;
prev = temp;
}
}
/* Return a number in the range [-0.5, 0.5] that represents the
location of the peak of a parabola passing through the 3 evenly
spaced samples. The center value is assumed to be greater than or
equal to the other values if positive, or less than if negative.
*/
float InterpPeak(float a, float b, float c)
{
if (b < 0.0) {
a = -a; b = -b; c = -c;
}
assert(b >= a && b >= c);
return 0.5 * (a - c) / (a - 2.0 * b + c);
}
/* Joan Pau: Add a new keypoint to a vector of keypoints
Create a new keypoint and return list of keypoints with new one added.
*/
void MakeKeypoint(
const flimage& grad, const flimage& ori, float octSize, float octScale,
float octRow, float octCol, float angle, keypointslist& keys,siftPar &par)
{
keypoint newkeypoint;
newkeypoint.x = octSize * octCol; /*x coordinate */
newkeypoint.y = octSize * octRow; /*y coordinate */
newkeypoint.scale = octSize * octScale; /* scale */
newkeypoint.angle = angle; /* orientation */
MakeKeypointSample(newkeypoint,grad,ori,octScale,octRow,octCol,par);
keys.push_back(newkeypoint);
}
/* Use the parameters of this keypoint to sample the gradient images
at a set of locations within a circular region around the keypoint.
The (scale,row,col) values are relative to current octave sampling.
The resulting vector is stored in the key.
*/
void MakeKeypointSample(
keypoint& key, const flimage& grad, const flimage& ori,
float scale, float row, float col,siftPar &par)
{
/* Produce sample vector. */
KeySampleVec(key, grad, ori, scale, row, col,par);
/* Normalize vector. This should provide illumination invariance
for planar lambertian surfaces (except for saturation effects).
Normalization also improves nearest-neighbor metric by
increasing relative distance for vectors with few features.
It is also useful to implement a distance threshold and to
allow conversion to integer format.
*/
NormalizeVec(key.vec);
/* Now that normalization has been done, threshold elements of
index vector to decrease emphasis on large gradient magnitudes.
Admittedly, the large magnitude values will have affected the
normalization, and therefore the threshold, so this is of
limited value.
*/
bool changed = false;
for (int i = 0; i < VecLength; i++)
if (key.vec[i] > par.MaxIndexVal) {
key.vec[i] = par.MaxIndexVal;
changed = true;
}
if (changed) NormalizeVec(key.vec);
/* Convert float vector to integer. Assume largest value in normalized
vector is likely to be less than 0.5. */
/// QUESTION: why is the vector quantized to integer
int intval;
for (int i = 0; i < VecLength; i++) {
intval = (int)(512.0 * key.vec[i]);
key.vec[i] = (int) MIN(255, intval);
}
}
/* Normalize length of vec to 1.0.
*/
void NormalizeVec(float* vec)
{
float val, fac;
float sqlen = 0.0;
for (int i = 0; i < VecLength; i++) {
val = vec[i];
sqlen += val * val;
}
fac = 1.0 / sqrt(sqlen);
for (int i = 0; i < VecLength; i++)
vec[i] *= fac;
}
/* Create a 3D index array into which gradient values are accumulated.
After filling array, copy values back into vec.
*/
void KeySampleVec(
keypoint& key, const flimage& grad, const flimage& ori,
float scale, float row, float col,siftPar &par)
{
float index[IndexSize][IndexSize][OriSize];
/* Initialize index array. */
for (int i = 0; i < IndexSize; i++)
for (int j = 0; j < IndexSize; j++)
for (int k = 0; k < OriSize; k++)
index[i][j][k] = 0.0;
KeySample(index, key, grad, ori, scale, row, col, par);
/* Unwrap the 3D index values into 1D vec. */
int v = 0;
for (int i = 0; i < IndexSize; i++)
for (int j = 0; j < IndexSize; j++)
for (int k = 0; k < OriSize; k++)
key.vec[v++] = index[i][j][k];
}
/* Add features to vec obtained from sampling the grad and ori images
for a particular scale. Location of key is (scale,row,col) with respect
to images at this scale. We examine each pixel within a circular
region containing the keypoint, and distribute the gradient for that
pixel into the appropriate bins of the index array.
*/
void KeySample(
float index[IndexSize][IndexSize][OriSize], keypoint& key,
const flimage& grad, const flimage& ori, float scale, float row, float col,siftPar &par)
{
float rpos, cpos, rx, cx;
int irow = (int) (row + 0.5),
icol = (int) (col + 0.5);
float sine = (float) sin(key.angle),
cosine = (float) cos(key.angle);
/* The spacing of index samples in terms of pixels at this scale. */
float spacing = scale * par.MagFactor;
/* Radius of index sample region must extend to diagonal corner of
index patch plus half sample for interpolation. */
float radius = 1.414 * spacing * (IndexSize + 1) / 2.0;
int iradius = (int) (radius + 0.5);
/* Examine all points from the gradient image that could lie within the
index square. */
for (int i = -iradius; i <= iradius; i++) {
for (int j = -iradius; j <= iradius; j++) {
/* Rotate sample offset to make it relative to key orientation.
Uses (row,col) instead of (x,y) coords. Also, make subpixel
correction as later image offset must be an integer. Divide
by spacing to put in index units.
*/
/* Guoshen Yu, inverse the rotation */
rpos = ((cosine * i - sine * j) - (row - irow)) / spacing;
cpos = ((sine * i + cosine * j) - (col - icol)) / spacing;
/*
rpos = ((cosine * i + sine * j) - (row - irow)) / spacing;
cpos = ((- sine * i + cosine * j) - (col - icol)) / spacing;*/
/* Compute location of sample in terms of real-valued index array
coordinates. Subtract 0.5 so that rx of 1.0 means to put full
weight on index[1] (e.g., when rpos is 0 and IndexSize is 3. */
rx = rpos + IndexSize / 2.0 - 0.5;
cx = cpos + IndexSize / 2.0 - 0.5;
/* Test whether this sample falls within boundary of index patch. */
if ( rx > -1.0 && rx < (float) IndexSize &&
cx > -1.0 && cx < (float) IndexSize )
AddSample(
index, key, grad, ori,
irow + i, icol + j, rpos, cpos, rx, cx,par);
}
}
}
/* Given a sample from the image gradient, place it in the index array.
*/
void AddSample(
float index[IndexSize][IndexSize][OriSize], keypoint& key,
const flimage& grad, const flimage& orim,
float r, float c, float rpos, float cpos, float rx, float cx,siftPar &par)
{
/* Clip at image boundaries. */
if (r < 0 || r >= grad.nheight() || c < 0 || c >= grad.nwidth())
return;
/* Compute Gaussian weight for sample, as function of radial distance
from center. Sigma is relative to half-width of index. */
float sigma = par.IndexSigma * 0.5 * IndexSize,
weight = exp(- (rpos * rpos + cpos * cpos) / (2.0 * sigma * sigma)),
// mag = weight * grad(c,r);
mag = weight * grad((int)c,(int)r); // Guoshen Yu, explicitely cast to int to avoid warning
/* Subtract keypoint orientation to give ori relative to keypoint. */
// float ori = orim(c,r) - key.angle;
float ori = orim((int)c,(int)r) - key.angle; // Guoshen Yu, explicitely cast to int to avoid warning
/* Put orientation in range [0, 2*PI]. If sign of gradient is to
be ignored, then put in range [0, PI]. */
if (par.IgnoreGradSign) {
while (ori > PI ) ori -= PI;
while (ori < 0.0) ori += PI;
} else {
while (ori > 2.0*PI) ori -= 2.0*PI;
while (ori < 0.0 ) ori += 2.0*PI;
}
PlaceInIndex(index, mag, ori, rx, cx,par);
}
/* Increment the appropriate locations in the index to incorporate
this image sample. The location of the sample in the index is (rx,cx).