/
Project2.cpp
executable file
·1349 lines (1117 loc) · 42.8 KB
/
Project2.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
#define _USE_MATH_DEFINES
#include "mainwindow.h"
#include "math.h"
#include "ui_mainwindow.h"
#include <QtGui>
#include "Matrix.h"
#include <time.h>
/*******************************************************************************
Draw a red cross on top of detected Harris corners.
interestPts: array of interest points (x, y)
numInterestPts: number of interest points
imageDisplay: image used for drawing
*******************************************************************************/
void MainWindow::DrawInterestPoints(CIntPt *interestPts, int numInterestPts, QImage &imageDisplay)
{
int i;
int r, c, rd, cd;
int imageWidth = imageDisplay.width();
int imageHeight = imageDisplay.height();
for(i=0;i<numInterestPts;i++)
{
c = (int) interestPts[i].m_X;
r = (int) interestPts[i].m_Y;
for(rd=-2;rd<=2;rd++)
if(r+rd >= 0 && r+rd < imageHeight && c >= 0 && c < imageWidth)
imageDisplay.setPixel(c, r + rd, qRgb(255, 0, 0));
for(cd=-2;cd<=2;cd++)
if(r >= 0 && r < imageHeight && c + cd >= 0 && c + cd < imageWidth)
imageDisplay.setPixel(c + cd, r, qRgb(255, 0, 0));
}
}
/*******************************************************************************
Compute simple 8d descriptors for each interest point (corner).
image: input image
interestPts: array of interest points
numInterestPts: number of interest points
If the descriptor cannot be computed, i.e. it's too close to the boundary of
the image, its descriptor length will be set to 0.
*******************************************************************************/
void MainWindow::ComputeDescriptors(QImage image, CIntPt *interestPts, int numInterestPts)
{
if(!numInterestPts)
{
return;
}
int r, c, cd, rd, i;
int imageWidth = image.width();
int imageHeight = image.height();
double *buffer = new double [imageWidth * imageHeight];
QRgb pixel;
// Descriptor parameters
double sigma = 2.0;
int rad = 4;
// Compute descriptors from green channel
for(r=0;r<imageHeight;r++)
for(c=0;c<imageWidth;c++)
{
pixel = image.pixel(c, r);
buffer[r*imageWidth + c] = (double) qGreen(pixel);
}
// Blur
SeparableGaussianBlurImage(buffer, imageWidth, imageHeight, sigma);
// Compute the descriptor from the difference between the point sampled at its center
// and eight points sampled around it.
for(i=0;i<numInterestPts;i++)
{
int c = (int) interestPts[i].m_X;
int r = (int) interestPts[i].m_Y;
// If any of the sampled points falls outside of the image, reject it
if(c >= rad && c < imageWidth - rad && r >= rad && r < imageHeight - rad)
{
double centerValue = buffer[(r)*imageWidth + c];
int j = 0;
for(rd=-1;rd<=1;rd++)
for(cd=-1;cd<=1;cd++)
if(rd != 0 || cd != 0)
{
interestPts[i].m_Desc[j] = buffer[(r + rd*rad)*imageWidth + c + cd*rad] - centerValue;
j++;
}
interestPts[i].m_DescSize = DESC_SIZE;
}
else
{
interestPts[i].m_DescSize = 0;
}
}
delete [] buffer;
}
/*******************************************************************************
Draw a green line between matches in the two images (i.e., visualizes the potential homography).
matches: matching points
numMatches: number of matching points
image1Display: image to draw matches
image2Display: image to draw matches
*******************************************************************************/
void MainWindow::DrawMatches(CMatches *matches, int numMatches, QImage &image1Display, QImage &image2Display)
{
int i;
// Show matches on image
QPainter painter;
painter.begin(&image1Display);
QColor green(0, 250, 0);
QColor red(250, 0, 0);
for(i=0;i<numMatches;i++)
{
painter.setPen(green);
painter.drawLine((int) matches[i].m_X1, (int) matches[i].m_Y1, (int) matches[i].m_X2, (int) matches[i].m_Y2);
painter.setPen(red);
painter.drawEllipse((int) matches[i].m_X1-1, (int) matches[i].m_Y1-1, 3, 3);
}
QPainter painter2;
painter2.begin(&image2Display);
painter2.setPen(green);
for(i=0;i<numMatches;i++)
{
painter2.setPen(green);
painter2.drawLine((int) matches[i].m_X1, (int) matches[i].m_Y1, (int) matches[i].m_X2, (int) matches[i].m_Y2);
painter2.setPen(red);
painter2.drawEllipse((int) matches[i].m_X2-1, (int) matches[i].m_Y2-1, 3, 3);
}
}
/*******************************************************************************
Given a set of matches, compute the "best fitting" homography.
matches: matching points
numMatches: number of matching points
h: returned homography
isForward: direction of the projection (true = image1 -> image2, false = image2 -> image1)
*******************************************************************************/
bool MainWindow::ComputeHomography(CMatches *matches, int numMatches, double h[3][3], bool isForward)
{
int error;
int nEq=numMatches*2;
dmat M=newdmat(0,nEq,0,7,&error);
dmat a=newdmat(0,7,0,0,&error);
dmat b=newdmat(0,nEq,0,0,&error);
double x0, y0, x1, y1;
for (int i=0;i<nEq/2;i++)
{
if(isForward == false)
{
x0 = matches[i].m_X1;
y0 = matches[i].m_Y1;
x1 = matches[i].m_X2;
y1 = matches[i].m_Y2;
}
else
{
x0 = matches[i].m_X2;
y0 = matches[i].m_Y2;
x1 = matches[i].m_X1;
y1 = matches[i].m_Y1;
}
//Eq 1 for corrpoint
M.el[i*2][0]=x1;
M.el[i*2][1]=y1;
M.el[i*2][2]=1;
M.el[i*2][3]=0;
M.el[i*2][4]=0;
M.el[i*2][5]=0;
M.el[i*2][6]=(x1*x0*-1);
M.el[i*2][7]=(y1*x0*-1);
b.el[i*2][0]=x0;
//Eq 2 for corrpoint
M.el[i*2+1][0]=0;
M.el[i*2+1][1]=0;
M.el[i*2+1][2]=0;
M.el[i*2+1][3]=x1;
M.el[i*2+1][4]=y1;
M.el[i*2+1][5]=1;
M.el[i*2+1][6]=(x1*y0*-1);
M.el[i*2+1][7]=(y1*y0*-1);
b.el[i*2+1][0]=y0;
}
int ret=solve_system (M,a,b);
if (ret!=0)
{
freemat(M);
freemat(a);
freemat(b);
return false;
}
else
{
h[0][0]= a.el[0][0];
h[0][1]= a.el[1][0];
h[0][2]= a.el[2][0];
h[1][0]= a.el[3][0];
h[1][1]= a.el[4][0];
h[1][2]= a.el[5][0];
h[2][0]= a.el[6][0];
h[2][1]= a.el[7][0];
h[2][2]= 1;
}
freemat(M);
freemat(a);
freemat(b);
return true;
}
/*******************************************************************************
A little helper function to clamp a value between a min and a max.
value: value to clamp
min: range minimum
max: range maximum
*******************************************************************************/
double clamp(double value, double min, double max)
{
if (value < min)
{
value = min;
}
if (value > max)
{
value = max;
}
return value;
}
/*******************************************************************************
Get the L1-norm distance between two interest points.
interestPoint1: first point
interestPoint2: second point
(Order doesn't matter.)
*******************************************************************************/
double getDistance(CIntPt interestPoint1, CIntPt interestPoint2)
{
double total = 0.0;
for(int i = 0; i < interestPoint1.m_DescSize; i++)
{
total += (interestPoint1.m_Desc[i] - interestPoint2.m_Desc[i]) * (interestPoint1.m_Desc[i] - interestPoint2.m_Desc[i]);
}
return sqrt(total);
}
/*******************************************************************************
Take a buffer pre-padded with empty borders of thickness "radius",
and fills these borders with zeroes.
buffer[]: pre-padded image buffer
radius: thickness of border in pixels
bufferWidth: total width of buffer in pixels
bufferHeight: total height of buffer in pixels
*******************************************************************************/
void zeroBorders(double buffer[], int radius, int bufferWidth, int bufferHeight)
{
int r, c;
// Pad left edge with zeros
for(r = 0; r < bufferHeight; r++)
{
for(c = 0; c < radius; c++)
{
buffer[r * bufferWidth + c] = 0.0;
}
}
// Pad right edge with zeros
for(r = 0; r < bufferHeight; r++)
{
for(c = bufferWidth - radius; c < bufferWidth; c++)
{
buffer[r * bufferWidth + c] = 0.0;
}
}
// Pad top edge with zeros
for(r = 0; r < radius; r++)
{
for(c = radius; c < bufferWidth - radius; c++)
{
buffer[r * bufferWidth + c] = 0.0;
}
}
// Pad bottom edge with zeros
for(r = bufferHeight - radius; r < bufferHeight; r++)
{
for(c = radius; c < bufferWidth - radius; c++)
{
buffer[r * bufferWidth + c] = 0.0;
}
}
}
/*******************************************************************************
Cull out any points that aren't local maxima in a (neighborhoodSize x neighborhoodSize) neighborhood
image[]: input image
imageWidth: width of image in pixels
imageHeight: height of image in pixels
neighborhoodSize: size of search neighborhood
*******************************************************************************/
void localMaxima(double image[], int imageWidth, int imageHeight, int neighborhoodSize)
{
int neighborhoodRadius = (neighborhoodSize - 1) / 2;
// Create a buffer with borders
int bufferWidth = imageWidth + neighborhoodRadius * 2;
int bufferHeight = imageHeight + neighborhoodRadius * 2;
double *buffer = new double [bufferWidth * bufferHeight];
// Copy image into the center of the buffer
for(int r = 0; r < imageHeight; r++)
{
for(int c = 0; c < imageWidth; c++)
{
buffer[(r + neighborhoodRadius) * bufferWidth + (c + neighborhoodRadius)] = image[r * imageWidth + c];
}
}
// Zero out the border
zeroBorders(buffer, neighborhoodRadius, imageWidth, imageHeight);
// For each pixel in the buffer (not counting the edges)
for(int r = neighborhoodRadius; r < bufferHeight - neighborhoodRadius; r++)
{
for(int c = neighborhoodRadius; c < bufferWidth - neighborhoodRadius; c++)
{
// Compare to every pixel in the neighborhood (except itself)
for(int windowRow = r - neighborhoodRadius; windowRow < r + neighborhoodRadius; windowRow++)
{
for(int windowCol = c - neighborhoodRadius; windowCol < c + neighborhoodRadius; windowCol++)
{
// If we're on our own pixel, ignore
if((windowRow == r)&&(windowCol == c))
{
// Do nothing
}
else
{
// If the intensity of the middle pixel is less than the neighborhood pixel
// to which it's being compared, make the middle pixel not count
if(buffer[r * bufferWidth + c] <= buffer[windowRow * bufferWidth + windowCol])
{
image[(r - neighborhoodRadius) * imageWidth + (c - neighborhoodRadius)] = 0.0;
}
}
}
}
}
}
delete [] buffer;
}
/*******************************************************************************
A test function to draw any floating-point image.
imageToDraw[]: image to draw
imageToDrawWidth: width of image to draw in pixels
imageToDrawHeight: height of image to draw in pixels
imageDisplay: display to which to draw the image
*******************************************************************************/
void testDraw(double imageToDraw[], int imageToDrawWidth, int imageToDrawHeight, QImage &imageDisplay)
{
if(imageDisplay.width() > imageToDrawWidth)
{
return;
}
if(imageDisplay.height() > imageToDrawHeight)
{
return;
}
// Get max for scaling
double max = 0.0;
for(int r = 0; r < imageDisplay.height(); r++)
{
for(int c = 0; c < imageDisplay.width(); c++)
{
if((imageToDraw[r * imageToDrawWidth + c]) > max)
{
max = imageToDraw[r * imageToDrawWidth + c];
}
}
}
double factor = 255.0 / max;
for(int r = 0; r < imageDisplay.height(); r++)
{
for(int c = 0; c < imageDisplay.width(); c++)
{
imageDisplay.setPixel(c, r, qRgb((int) imageToDraw[r * imageToDrawWidth + c] * factor, (int) imageToDraw[r * imageToDrawWidth + c] * factor, (int) imageToDraw[r * imageToDrawWidth + c] * factor));
}
}
}
/*******************************************************************************
Take an image and a kernel to convolve it with, and return the convolved image
(This should work for 1-D kernels as well, allowing it to be used as part of
a separable filter.) Note that this function assumes that any image passed
in has its edges pre-padded, if necessary. Padding is left intact.
imageToConvolve[]: buffer containing image to convolve
imageWidth: width of image in pixels
imageHeight: height of image in pixels
kernel[]: buffer containing convolution kernel
kernelWidth: width of kernel in pixels
kernelHeight: height of kernel in pixels
verticalPadding: height of padding used (padding is necessary so that the
convolution kernel doesn't run off the edge)
horizontalPadding: width of padding used
clampTo255: whether or not to clamp intensity values 0-255
add128: whether or not to add 128 to each intensity value (in case an operation subtracted from it)
*******************************************************************************/
void convolve(double imageToConvolve[], int imageWidth, int imageHeight, double kernel[], int kernelWidth, int kernelHeight, int verticalPadding, int horizontalPadding, bool clampTo255, bool add128)
{
int imageRow, imageCol, kernelRow, kernelCol;
// Figure out the kernel radii from the dimensions.
int vKernelRadius = (kernelHeight - 1) / 2;
int hKernelRadius = (kernelWidth - 1) / 2;
// Make a copy of the image to convolve for modifications
double * imageCopy = new double [imageWidth * imageHeight];
for(imageRow = 0; imageRow < imageHeight; imageRow++)
{
for(imageCol = 0; imageCol < imageWidth; imageCol++)
{
imageCopy[imageRow * imageWidth + imageCol] = imageToConvolve[imageRow * imageWidth + imageCol];
}
}
// For each pixel in the image (except the edges)
for(imageRow = verticalPadding; imageRow < imageHeight - verticalPadding; imageRow++)
{
for(imageCol = horizontalPadding; imageCol < imageWidth - horizontalPadding; imageCol++)
{
double newPixel = 0.0;
// Convolve the kernel at each pixel
for(kernelRow = 0; kernelRow < kernelHeight; kernelRow++)
{
for(kernelCol = 0; kernelCol < kernelWidth; kernelCol++)
{
double pixel;
// Get the pixel value (adjust coordinates by horizontalPadding
// and verticalPadding to account for edges, which aren't being used)
pixel = imageToConvolve[(imageRow - vKernelRadius + kernelRow) * imageWidth + imageCol - hKernelRadius + kernelCol];
// Get the value of the kernel weight
double weight = kernel[kernelRow * kernelWidth + kernelCol];
newPixel += weight * pixel;
}
}
if(add128)
{
newPixel += 128.0;
}
if(clampTo255)
{
// Clamp pixel value
newPixel = clamp(newPixel, 0.0, 255.0);
}
// Store convolved pixel in the image
imageCopy[imageRow * imageWidth + imageCol] = newPixel;
}
}
// Now, copy the changed copy back
for(imageRow = 0; imageRow < imageHeight; imageRow++)
{
for(imageCol = 0; imageCol < imageWidth; imageCol++)
{
imageToConvolve[imageRow * imageWidth + imageCol] = imageCopy[imageRow * imageWidth + imageCol];
}
}
delete [] imageCopy;
}
/*******************************************************************************
Blur a single channel floating point image with Gaussian weights.
image: input and output image
imageWidth: width of image in pixels
imageHeight: height of image in pixels
sigma: standard deviation of Gaussian
*******************************************************************************/
void MainWindow::SeparableGaussianBlurImage(double *image, int imageWidth, int imageHeight, double sigma)
{
// If the sigma (standard deviation) is < 1, don't blur.
// (Fractional sigmas result in unbalanced kernels due to
// the Gaussian function exploding a bit.)
if (sigma < 1)
{
return;
}
// With a kernel that using a radius of sigma * 3, 99.7% of values are accounted for.
int radius = floor(sigma * 3 + 0.5);
// Set size for convolution kernel.
int size = radius * 2 + 1;
// Create a buffer with edges the thickness of radius
int bufferWidth = imageWidth + radius * 2;
int bufferHeight = imageHeight + radius * 2;
double *buffer = new double [bufferWidth * bufferHeight];
// Copy image into the center of the buffer
for(int r = 0; r < imageHeight; r++)
{
for(int c = 0; c < imageWidth; c++)
{
buffer[(r + radius) * bufferWidth + (c + radius)] = image[r * imageWidth + c];
}
}
// Zero out the border
zeroBorders(buffer, radius, bufferWidth, bufferHeight);
// Compute two kernels to convolve with the image.
double *hKernel = new double [size];
double *vKernel = new double [size];
double e = M_E;
double pi = M_PI;
// Construct kernels for horizontal and vertical blur
for (int i = 0; i < size; i++)
{
// x distance is the center point (equivalent to the radius value)
// minus the x value of the current kernel pixel
int x = radius - i;
// Plug and chug into the Gaussian filter 1D formula
hKernel[i] = (1.0 / (sqrt(2.0 * pi) * sigma)) * pow(e, -((double)x * (double)x) / (2 * sigma * sigma));
vKernel[i] = hKernel[i];
}
// Apply horizontal blur
convolve(buffer, bufferWidth, bufferHeight, hKernel, size, 1, radius, radius, false, false);
// Apply vertical blur
convolve(buffer, bufferWidth, bufferHeight, vKernel, 1, size, radius, radius, false, false);
// Copy buffer back into (normal-sized) image
for(int r = 0; r < imageHeight; r++)
{
for(int c = 0; c < imageWidth; c++)
{
image[r * imageWidth + c] = buffer[(r + radius) * bufferWidth + (c + radius)];
}
}
delete [] hKernel;
delete [] vKernel;
delete [] buffer;
}
/*******************************************************************************
Detect Harris corners.
image: input image
sigma: standard deviation of Gaussian used to blur corner detector
thres: Threshold for detecting corners
interestPts: returned interest points
numInterestPts: number of interest points returned
imageDisplay: image returned to display (for debugging)
*******************************************************************************/
void MainWindow::HarrisCornerDetector(QImage image, double sigma, double thres, CIntPt **interestPts, int &numInterestPts, QImage &imageDisplay)
{
int r, c;
double harrisScaleFactor = 19.0;
int imageWidth = image.width();
int imageHeight = image.height();
if((!imageWidth)||(!imageHeight))
{
return;
}
double *buffer = new double [imageWidth * imageHeight];
QRgb pixel;
numInterestPts = 0;
// We'll compute the corner response using just the green channel
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
pixel = image.pixel(c, r);
buffer[r * imageWidth + c] = (double) qGreen(pixel);
}
}
// Define a small window for examining the image. This window will be used
// to compute the covariance matrix and Harris response at each point.
// Since we'll be computing a Gaussian sum of the values in the window,
// let's size it so the radius is sigma * 3 (99.7% of values are accounted for).
// Let's expand the buffer so that we can calculate the derivatives
// for the original buffer data, centered in the expanded buffer.
int expandedBufferWidth = imageWidth + 2;
int expandedBufferHeight = imageHeight + 2;
double *expandedBuffer = new double [expandedBufferWidth * expandedBufferHeight];
zeroBorders(expandedBuffer, 1, expandedBufferWidth, expandedBufferHeight);
// Now, fill in our expanded buffer with the contents of the first buffer (centered).
for(r = 1; r < imageHeight + 1; r++)
{
for(c = 1; c < imageWidth + 1; c++)
{
expandedBuffer[r * (expandedBufferWidth) + c] = buffer[(r - 1) * imageWidth + (c - 1)];
}
}
// 1st-derivative kernel to convolve with the image
double *firstDKernel = new double [3];
firstDKernel[0] = -1.0;
firstDKernel[1] = 0.0;
firstDKernel[2] = 1.0;
// Set up image copies for the x and y derivatives, as well as the arrays
// for which we'll use them
double * x_deriv = new double [expandedBufferWidth * expandedBufferHeight];
double * y_deriv = new double [expandedBufferWidth * expandedBufferHeight];
double * y_deriv_squared = new double [imageWidth * imageHeight];
double * x_deriv_squared = new double [imageWidth * imageHeight];
double * xy_deriv = new double [imageWidth * imageHeight];
// Copy expanded buffer into the x- and y-derivative buffers
for(r = 0; r < expandedBufferHeight; r++)
{
for(c = 0; c < expandedBufferWidth; c++)
{
x_deriv[r * expandedBufferWidth + c] = expandedBuffer[r * expandedBufferWidth + c];
y_deriv[r * expandedBufferWidth + c] = expandedBuffer[r * expandedBufferWidth + c];
}
}
// Calculate the x-derivative of the buffer image
convolve(x_deriv, expandedBufferWidth, expandedBufferHeight, firstDKernel, 3, 1, 1, 1, false, false);
// Calculate the y-derivative of the buffer image
convolve(y_deriv, expandedBufferWidth, expandedBufferHeight, firstDKernel, 1, 3, 1, 1, false, false);
// Calculate the x-derivative squared of the buffer image
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
x_deriv_squared[r * imageWidth + c] = x_deriv[(r + 1) * expandedBufferWidth + (c + 1)] * x_deriv[(r + 1) * expandedBufferWidth + (c + 1)];
}
}
// Now blur it
SeparableGaussianBlurImage(x_deriv_squared, imageWidth, imageHeight, sigma);
// Calculate the y-derivative squared of the buffer image
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
y_deriv_squared[r * imageWidth + c] = y_deriv[(r + 1) * expandedBufferWidth + (c + 1)] * y_deriv[(r + 1) * expandedBufferWidth + (c + 1)];
}
}
// Now blur it
SeparableGaussianBlurImage(y_deriv_squared, imageWidth, imageHeight, sigma);
// Calculate the x-derivative * y-derivative of the buffer image
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
xy_deriv[r * imageWidth + c] = x_deriv[(r + 1) * expandedBufferWidth + (c + 1)] * y_deriv[(r + 1) * expandedBufferWidth + (c + 1)];
}
}
// Now blur it
SeparableGaussianBlurImage(xy_deriv, imageWidth, imageHeight, sigma);
// Set up a Harris response buffer to hold the Harris response for each pixel
double *harrisResponseBuffer = new double [imageWidth * imageHeight];
double dx_squared, dy_squared, dxdy, determinant, trace;
// For each pixel in the image
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
// Get our dx^2 value
dx_squared = x_deriv_squared[r * imageWidth + c];
// Get our dy^2 value
dy_squared = y_deriv_squared[r * imageWidth + c];
// Get our dxdy value
dxdy = xy_deriv[r * imageWidth + c];
// Assuming a matrix like this:
//
// dx^2 dxdy
// [ ]
// dxdy dy^2
//
// Find the determinant of the matrix
determinant = dx_squared * dy_squared - dxdy * dxdy;
// Find the trace of the matrix
trace = dx_squared + dy_squared;
// Finally, plug the determinant/trace for this point into the Harris response buffer
if(trace == 0)
{
harrisResponseBuffer[r * imageWidth + c] = 0.0;
}
else
{
harrisResponseBuffer[r * imageWidth + c] = abs(determinant / trace) / harrisScaleFactor;
}
}
}
double min = 0.0;
double max = 0.0;
// Now that we've populated the Harris response buffer, let's cull out responses below the threshold
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
if((harrisResponseBuffer[r * imageWidth + c] != 0)&&(harrisResponseBuffer[r * imageWidth + c] < min))
{
min = harrisResponseBuffer[r * imageWidth + c];
}
if(harrisResponseBuffer[r * imageWidth + c] > max)
{
max = harrisResponseBuffer[r * imageWidth + c];
}
if(harrisResponseBuffer[r * imageWidth + c] < thres)
{
harrisResponseBuffer[r * imageWidth + c] = 0.0;
}
}
}
// Cull out any points in the Harris response that aren't local peaks in their 3x3 neighborhood
localMaxima(harrisResponseBuffer, imageWidth, imageHeight, 5);
// Count interest points in Harris response
numInterestPts = 0;
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
if(harrisResponseBuffer[r * imageWidth + c] > 0.0)
{
numInterestPts++;
}
}
}
// Allocate an array in which store our interest points
*interestPts = new CIntPt[numInterestPts];
// Store our interest points
int i = 0;
for(r = 0; r < imageHeight; r++)
{
for(c = 0; c < imageWidth; c++)
{
if(harrisResponseBuffer[r * imageWidth + c] > 0.0)
{
(*interestPts)[i].m_X = c;
(*interestPts)[i].m_Y = r;
i++;
}
}
}
// Once you are done finding the interest points, display them on the image
DrawInterestPoints(*interestPts, numInterestPts, imageDisplay);
delete [] buffer;
delete [] firstDKernel;
delete [] harrisResponseBuffer;
delete [] x_deriv;
delete [] y_deriv;
delete [] x_deriv_squared;
delete [] y_deriv_squared;
delete [] xy_deriv;
delete [] expandedBuffer;
}
/*******************************************************************************
Find matching interest points between images.
image1: first input image
interestPts1: interest points corresponding to image 1
numInterestPts1: number of interest points in image 1
image2: second input image
interestPts2: interest points corresponding to image 2
numInterestPts2: number of interest points in image 2
matches: set of matching points to be returned
numMatches: number of matching points returned
image1Display: image used to display matches
image2Display: image used to display matches
*******************************************************************************/
void MainWindow::MatchInterestPoints(QImage image1, CIntPt *interestPts1, int numInterestPts1,
QImage image2, CIntPt *interestPts2, int numInterestPts2,
CMatches **matches, int &numMatches, QImage &image1Display, QImage &image2Display)
{
numMatches = numInterestPts1;
// Compute the descriptors for each interest point.
// You can access the descriptor for each interest point using interestPts1[i].m_Desc[j].
// If interestPts1[i].m_DescSize = 0, it was not able to compute a descriptor for that point
ComputeDescriptors(image1, interestPts1, numInterestPts1);
ComputeDescriptors(image2, interestPts2, numInterestPts2);
// The position of the interest point in image 1 is (m_X1, m_Y1)
// The position of the interest point in image 2 is (m_X2, m_Y2)
*matches = new CMatches[numMatches];
double minL2distance;
int matchNo = 0;
int attemptNo = 0;
int rejectNo = 0;
for(int image1pt = 0; image1pt < numInterestPts1; image1pt++)
{
// If the current point doesn't have a descriptor, skip it
if(interestPts1[image1pt].m_DescSize == 0)
{
rejectNo++;
continue;
}
for(int image2pt = 0; image2pt < numInterestPts2; image2pt++)
{
// If the current point doesn't have a descriptor, skip it
if(interestPts2[image2pt].m_DescSize == 0)
{
continue;
}
// If it's the first match attempted, assume it's right and get the distance
if(attemptNo == 0)
{
(*matches)[matchNo].m_X1 = interestPts1[image1pt].m_X;
(*matches)[matchNo].m_Y1 = interestPts1[image1pt].m_Y;
(*matches)[matchNo].m_X2 = interestPts2[image2pt].m_X;
(*matches)[matchNo].m_Y2 = interestPts2[image2pt].m_Y;
minL2distance = getDistance(interestPts1[image1pt], interestPts2[image2pt]);
attemptNo++;
}
else
{
// If the distance is shorter, this is a better match
if(getDistance(interestPts1[image1pt], interestPts2[image2pt]) < minL2distance)
{
(*matches)[matchNo].m_X1 = interestPts1[image1pt].m_X;
(*matches)[matchNo].m_Y1 = interestPts1[image1pt].m_Y;
(*matches)[matchNo].m_X2 = interestPts2[image2pt].m_X;
(*matches)[matchNo].m_Y2 = interestPts2[image2pt].m_Y;
minL2distance = getDistance(interestPts1[image1pt], interestPts2[image2pt]);
attemptNo++;
}
}
}
attemptNo = 0;
matchNo++;
}
numMatches = numMatches - rejectNo;
// Draw the matches
DrawMatches(*matches, numMatches, image1Display, image2Display);
//delete [] (*matches);
}
/*******************************************************************************
Project a point (x1, y1) using the homography transformation h.
(x1, y1): input point
(x2, y2): returned point
h: input homography used to project point
*******************************************************************************/
void Project(double x1, double y1, double &x2, double &y2, double h[3][3])
{
// Get the product [u v w] of the following matrix multiply:
//
// a b c x u
//
// [ d e f ] * [ y ] = [ v ]
//
// g h 1 1 w
//
double u, v, w;
u = h[0][0] * x1 + h[0][1] * y1 + h[0][2] * 1;
v = h[1][0] * x1 + h[1][1] * y1 + h[1][2] * 1;
w = h[2][0] * x1 + h[2][1] * y1 + h[2][2] * 1;
x2 = u / w;
y2 = v / w;
}
/*******************************************************************************
Count the number of inliers given a homography. This is a helper function for RANSAC.
h: input homography used to project points (image1 -> image2)
matches: array of matching points
numMatches: number of matches in the array
inlierThreshold: maximum distance between points that are considered to be inliers
Returns the total number of inliers.
*******************************************************************************/
int MainWindow::ComputeInlierCount(double h[3][3], CMatches *matches, int numMatches, double inlierThreshold)
{
double projected_x, projected_y, distance;
int numInliers = 0;
// Project the first point in each match using the homography given,