1 : /******************************************************************************
2 : * Project: GDAL
3 : * Purpose: Correlator - GDALSimpleSURF and GDALFeaturePoint classes.
4 : * Author: Andrew Migal, migal.drew@gmail.com
5 : *
6 : ******************************************************************************
7 : * Copyright (c) 2012, Andrew Migal
8 : *
9 : * Permission is hereby granted, free of charge, to any person obtaining a
10 : * copy of this software and associated documentation files (the "Software"),
11 : * to deal in the Software without restriction, including without limitation
12 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 : * and/or sell copies of the Software, and to permit persons to whom the
14 : * Software is furnished to do so, subject to the following conditions:
15 : *
16 : * The above copyright notice and this permission notice shall be included
17 : * in all copies or substantial portions of the Software.
18 : *
19 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 : * DEALINGS IN THE SOFTWARE.
26 : ****************************************************************************/
27 :
28 : #include "gdal_simplesurf.h"
29 :
30 : CPL_CVSID("$Id");
31 :
32 : /************************************************************************/
33 : /* ==================================================================== */
34 : /* GDALFeaturePoint */
35 : /* ==================================================================== */
36 : /************************************************************************/
37 :
38 0 : GDALFeaturePoint::GDALFeaturePoint()
39 : {
40 0 : nX = -1;
41 0 : nY = -1;
42 0 : nScale = -1;
43 0 : nRadius = -1;
44 0 : nSign = -1;
45 :
46 0 : padfDescriptor = new double[DESC_SIZE];
47 0 : }
48 :
49 0 : GDALFeaturePoint::GDALFeaturePoint(const GDALFeaturePoint& fp)
50 : {
51 0 : nX = fp.nX;
52 0 : nY = fp.nY;
53 0 : nScale = fp.nScale;
54 0 : nRadius = fp.nRadius;
55 0 : nSign = fp.nSign;
56 :
57 0 : padfDescriptor = new double[DESC_SIZE];
58 0 : for (int i = 0; i < DESC_SIZE; i++)
59 0 : padfDescriptor[i] = fp.padfDescriptor[i];
60 0 : }
61 :
62 0 : GDALFeaturePoint::GDALFeaturePoint(int nX, int nY,
63 0 : int nScale, int nRadius, int nSign)
64 : {
65 0 : this->nX = nX;
66 0 : this->nY = nY;
67 0 : this->nScale = nScale;
68 0 : this->nRadius = nRadius;
69 0 : this->nSign = nSign;
70 :
71 0 : this->padfDescriptor = new double[DESC_SIZE];
72 0 : }
73 :
74 0 : GDALFeaturePoint& GDALFeaturePoint::operator=(const GDALFeaturePoint& point)
75 : {
76 0 : if (this != &point)
77 : {
78 0 : nX = point.nX;
79 0 : nY = point.nY;
80 0 : nScale = point.nScale;
81 0 : nRadius = point.nRadius;
82 0 : nSign = point.nSign;
83 :
84 : //Free memory
85 0 : delete[] padfDescriptor;
86 :
87 : //Copy descriptor values
88 0 : padfDescriptor = new double[DESC_SIZE];
89 0 : for (int i = 0; i < DESC_SIZE; i++)
90 0 : padfDescriptor[i] = point.padfDescriptor[i];
91 : }
92 :
93 0 : return *this;
94 : }
95 :
96 0 : int GDALFeaturePoint::GetX() { return nX; }
97 0 : void GDALFeaturePoint::SetX(int nX) { this->nX = nX; }
98 :
99 0 : int GDALFeaturePoint::GetY() { return nY; }
100 0 : void GDALFeaturePoint::SetY(int nY) { this->nY = nY; }
101 :
102 0 : int GDALFeaturePoint::GetScale() { return nScale; }
103 0 : void GDALFeaturePoint::SetScale(int nScale) { this->nScale = nScale; }
104 :
105 0 : int GDALFeaturePoint::GetRadius() { return nRadius; }
106 0 : void GDALFeaturePoint::SetRadius(int nRadius) { this->nRadius = nRadius; }
107 :
108 0 : int GDALFeaturePoint::GetSign() { return nSign; }
109 0 : void GDALFeaturePoint::SetSign(int nSign) { this->nSign = nSign; }
110 :
111 0 : double& GDALFeaturePoint::operator [] (int nIndex)
112 : {
113 0 : if (nIndex < 0 || nIndex >= DESC_SIZE)
114 : {
115 : CPLError(CE_Failure, CPLE_AppDefined,
116 0 : "Descriptor index is out of range");
117 : }
118 :
119 0 : return padfDescriptor[nIndex];
120 : }
121 :
122 0 : GDALFeaturePoint::~GDALFeaturePoint() {
123 0 : delete[] padfDescriptor;
124 0 : }
125 :
126 : /************************************************************************/
127 : /* ==================================================================== */
128 : /* GDALSimpleSurf */
129 : /* ==================================================================== */
130 : /************************************************************************/
131 :
132 0 : GDALSimpleSURF::GDALSimpleSURF(int nOctaveStart, int nOctaveEnd)
133 : {
134 0 : this->octaveStart = nOctaveStart;
135 0 : this->octaveEnd = nOctaveEnd;
136 :
137 : // Initialize Octave map with custom range
138 0 : poOctMap = new GDALOctaveMap(octaveStart, octaveEnd);
139 0 : }
140 :
141 0 : CPLErr GDALSimpleSURF::ConvertRGBToLuminosity(
142 : GDALRasterBand *red, GDALRasterBand *green, GDALRasterBand *blue,
143 : int nXSize, int nYSize, double **padfImg, int nHeight, int nWidth)
144 : {
145 0 : const double forRed = 0.21;
146 0 : const double forGreen = 0.72;
147 0 : const double forBlue = 0.07;
148 :
149 0 : if (red == NULL || green == NULL || blue == NULL)
150 : {
151 : CPLError(CE_Failure, CPLE_AppDefined,
152 0 : "Raster bands are not specified");
153 0 : return CE_Failure;
154 : }
155 :
156 0 : if (nXSize > red->GetXSize() || nYSize > red->GetYSize())
157 : {
158 : CPLError(CE_Failure, CPLE_AppDefined,
159 0 : "Red band has less size than has been requested");
160 0 : return CE_Failure;
161 : }
162 :
163 0 : if (padfImg == NULL)
164 : {
165 0 : CPLError(CE_Failure, CPLE_AppDefined, "Buffer isn't specified");
166 0 : return CE_Failure;
167 : }
168 :
169 0 : GDALDataType eRedType = red->GetRasterDataType();
170 0 : GDALDataType eGreenType = green->GetRasterDataType();
171 0 : GDALDataType eBlueType = blue->GetRasterDataType();
172 :
173 0 : int dataRedSize = GDALGetDataTypeSize(eRedType) / 8;
174 0 : int dataGreenSize = GDALGetDataTypeSize(eGreenType) / 8;
175 0 : int dataBlueSize = GDALGetDataTypeSize(eBlueType) / 8;
176 :
177 0 : void *paRedLayer = CPLMalloc(dataRedSize * nWidth * nHeight);
178 0 : void *paGreenLayer = CPLMalloc(dataGreenSize * nWidth * nHeight);
179 0 : void *paBlueLayer = CPLMalloc(dataBlueSize * nWidth * nHeight);
180 :
181 0 : red->RasterIO(GF_Read, 0, 0, nXSize, nYSize, paRedLayer, nWidth, nHeight, eRedType, 0, 0);
182 0 : green->RasterIO(GF_Read, 0, 0, nXSize, nYSize, paGreenLayer, nWidth, nHeight, eGreenType, 0, 0);
183 0 : blue->RasterIO(GF_Read, 0, 0, nXSize, nYSize, paBlueLayer, nWidth, nHeight, eBlueType, 0, 0);
184 :
185 0 : double maxValue = 255.0;
186 0 : for (int row = 0; row < nHeight; row++)
187 0 : for (int col = 0; col < nWidth; col++)
188 : {
189 : // Get RGB values
190 0 : double dfRedVal = SRCVAL(paRedLayer, eRedType,
191 : nWidth * row + col * dataRedSize);
192 0 : double dfGreenVal = SRCVAL(paGreenLayer, eGreenType,
193 : nWidth * row + col * dataGreenSize);
194 0 : double dfBlueVal = SRCVAL(paBlueLayer, eBlueType,
195 : nWidth * row + col * dataBlueSize);
196 : // Compute luminosity value
197 0 : padfImg[row][col] = (
198 : dfRedVal * forRed +
199 : dfGreenVal * forGreen +
200 0 : dfBlueVal * forBlue) / maxValue;
201 : }
202 :
203 0 : CPLFree(paRedLayer);
204 0 : CPLFree(paGreenLayer);
205 0 : CPLFree(paBlueLayer);
206 :
207 0 : return CE_None;
208 : }
209 :
210 : std::vector<GDALFeaturePoint>*
211 0 : GDALSimpleSURF::ExtractFeaturePoints(GDALIntegralImage *poImg,
212 : double dfThreshold)
213 : {
214 : std::vector<GDALFeaturePoint>* poCollection =
215 0 : new std::vector<GDALFeaturePoint>();
216 :
217 : //Calc Hessian values for layers
218 0 : poOctMap->ComputeMap(poImg);
219 :
220 : //Search for exremum points
221 0 : for (int oct = octaveStart; oct <= octaveEnd; oct++)
222 : {
223 0 : for (int k = 0; k < GDALOctaveMap::INTERVALS - 2; k++)
224 : {
225 0 : GDALOctaveLayer *bot = poOctMap->pMap[oct - 1][k];
226 0 : GDALOctaveLayer *mid = poOctMap->pMap[oct - 1][k + 1];
227 0 : GDALOctaveLayer *top = poOctMap->pMap[oct - 1][k + 2];
228 :
229 0 : for (int i = 0; i < mid->height; i++)
230 : {
231 0 : for (int j = 0; j < mid->width; j++)
232 : {
233 0 : if (poOctMap->PointIsExtremum(i, j, bot, mid, top, dfThreshold))
234 : {
235 : GDALFeaturePoint oFP(j, i, mid->scale,
236 0 : mid->radius, mid->signs[i][j]);
237 0 : SetDescriptor(&oFP, poImg);
238 0 : poCollection->push_back(oFP);
239 : }
240 : }
241 : }
242 : }
243 : }
244 :
245 0 : return poCollection;
246 : }
247 :
248 :
249 0 : double GDALSimpleSURF::GetEuclideanDistance(
250 : GDALFeaturePoint &firstPoint, GDALFeaturePoint &secondPoint)
251 : {
252 0 : double sum = 0;
253 :
254 0 : for (int i = 0; i < GDALFeaturePoint::DESC_SIZE; i++)
255 0 : sum += (firstPoint[i] - secondPoint[i]) * (firstPoint[i] - secondPoint[i]);
256 :
257 0 : return sqrt(sum);
258 : }
259 :
260 0 : void GDALSimpleSURF::NormalizeDistances(std::list<MatchedPointPairInfo> *poList)
261 : {
262 0 : double max = 0;
263 :
264 0 : std::list<MatchedPointPairInfo>::iterator i;
265 0 : for (i = poList->begin(); i != poList->end(); i++)
266 0 : if ((*i).euclideanDist > max)
267 0 : max = (*i).euclideanDist;
268 :
269 0 : if (max != 0)
270 : {
271 0 : for (i = poList->begin(); i != poList->end(); i++)
272 0 : (*i).euclideanDist /= max;
273 : }
274 0 : }
275 :
276 0 : void GDALSimpleSURF::SetDescriptor(
277 : GDALFeaturePoint *poPoint, GDALIntegralImage *poImg)
278 : {
279 : // Affects to the descriptor area
280 0 : const int haarScale = 20;
281 :
282 : // Side of the Haar wavelet
283 0 : int haarFilterSize = 2 * poPoint->GetScale();
284 :
285 : // Length of the side of the descriptor area
286 0 : int descSide = haarScale * poPoint->GetScale();
287 :
288 : // Side of the quadrant in 4x4 grid
289 0 : int quadStep = descSide / 4;
290 :
291 : // Side of the sub-quadrant in 5x5 regular grid of quadrant
292 0 : int subQuadStep = quadStep / 5;
293 :
294 0 : int leftTop_row = poPoint->GetY() - (descSide / 2);
295 0 : int leftTop_col = poPoint->GetX() - (descSide / 2);
296 :
297 0 : int count = 0;
298 :
299 0 : for (int r = leftTop_row; r < leftTop_row + descSide; r += quadStep)
300 0 : for (int c = leftTop_col; c < leftTop_col + descSide; c += quadStep)
301 : {
302 0 : double dx = 0;
303 0 : double dy = 0;
304 0 : double abs_dx = 0;
305 0 : double abs_dy = 0;
306 :
307 0 : for (int sub_r = r; sub_r < r + quadStep; sub_r += subQuadStep)
308 0 : for (int sub_c = c; sub_c < c + quadStep; sub_c += subQuadStep)
309 : {
310 : // Approximate center of sub quadrant
311 0 : int cntr_r = sub_r + subQuadStep / 2;
312 0 : int cntr_c = sub_c + subQuadStep / 2;
313 :
314 : // Left top point for Haar wavelet computation
315 0 : int cur_r = cntr_r - haarFilterSize / 2;
316 0 : int cur_c = cntr_c - haarFilterSize / 2;
317 :
318 : // Gradients
319 0 : double cur_dx = poImg->HaarWavelet_X(cur_r, cur_c, haarFilterSize);
320 0 : double cur_dy = poImg->HaarWavelet_Y(cur_r, cur_c, haarFilterSize);
321 :
322 0 : dx += cur_dx;
323 0 : dy += cur_dy;
324 0 : abs_dx += fabs(cur_dx);
325 0 : abs_dy += fabs(cur_dy);
326 : }
327 :
328 : // Fills point's descriptor
329 0 : (*poPoint)[count++] = dx;
330 0 : (*poPoint)[count++] = dy;
331 0 : (*poPoint)[count++] = abs_dx;
332 0 : (*poPoint)[count++] = abs_dy;
333 : }
334 0 : }
335 :
336 : /**
337 : * Find corresponding points (equal points in two collections).
338 : *
339 : * @param poMatched Resulting collection for matched points
340 : * @param poFirstCollection Points on the first image
341 : * @param poSecondCollection Points on the second image
342 : * @param dfThreshold Value from 0 to 1. Threshold affects to number of
343 : * matched points. If threshold is higher, amount of corresponding
344 : * points is larger, and vice versa
345 : *
346 : * @note Typical threshold's value is 0,1. BUT it's a very approximate guide.
347 : * It can be 0,001 or even 1. This threshold provides direct adjustment
348 : * of point matching.
349 : * NOTICE that if threshold is lower, matches are more robust and correct, but number of
350 : * matched points is smaller. Therefore if algorithm performs many false
351 : * detections and produces bad results, reduce threshold.
352 : * Otherwise, if algorithm finds nothing, increase threshold.
353 : *
354 : * @return CE_None or CE_Failure if error occurs.
355 : */
356 :
357 0 : CPLErr GDALSimpleSURF::MatchFeaturePoints(
358 : std::vector<GDALFeaturePoint*> *poMatchPairs,
359 : std::vector<GDALFeaturePoint> *poFirstCollect,
360 : std::vector<GDALFeaturePoint> *poSecondCollect,
361 : double dfThreshold)
362 : {
363 : /* -------------------------------------------------------------------- */
364 : /* Validate parameters. */
365 : /* -------------------------------------------------------------------- */
366 0 : if (poMatchPairs == NULL)
367 : {
368 : CPLError(CE_Failure, CPLE_AppDefined,
369 0 : "Matched points colection isn't specified");
370 0 : return CE_Failure;
371 : }
372 :
373 0 : if (poFirstCollect == NULL || poSecondCollect == NULL)
374 : {
375 : CPLError(CE_Failure, CPLE_AppDefined,
376 0 : "Feature point collections are not specified");
377 0 : return CE_Failure;
378 : }
379 :
380 : /* ==================================================================== */
381 : /* Matching algorithm. */
382 : /* ==================================================================== */
383 : // Affects to false matching pruning
384 0 : const double ratioThreshold = 0.8;
385 :
386 0 : int len_1 = poFirstCollect->size();
387 0 : int len_2 = poSecondCollect->size();
388 :
389 0 : int minLength = (len_1 < len_2) ? len_1 : len_2;
390 :
391 : // Temporary pointers. Used to swap collections
392 : std::vector<GDALFeaturePoint> *p_1;
393 : std::vector<GDALFeaturePoint> *p_2;
394 :
395 0 : bool isSwap = false;
396 :
397 : // Assign p_1 - collection with minimal number of points
398 0 : if (minLength == len_2)
399 : {
400 0 : p_1 = poSecondCollect;
401 0 : p_2 = poFirstCollect;
402 :
403 0 : int tmp = 0;
404 0 : tmp = len_1;
405 0 : len_1 = len_2;
406 0 : len_2 = tmp;
407 0 : isSwap = true;
408 : }
409 : else
410 : {
411 : // Assignment 'as is'
412 0 : p_1 = poFirstCollect;
413 0 : p_2 = poSecondCollect;
414 0 : isSwap = false;
415 : }
416 :
417 : // Stores matched point indexes and
418 : // their euclidean distances
419 : std::list<MatchedPointPairInfo> *poPairInfoList =
420 0 : new std::list<MatchedPointPairInfo>();
421 :
422 : // Flags that points in the 2nd collection are matched or not
423 0 : bool *alreadyMatched = new bool[len_2];
424 0 : for (int i = 0; i < len_2; i++)
425 0 : alreadyMatched[i] = false;
426 :
427 0 : for (int i = 0; i < len_1; i++)
428 : {
429 : // Distance to the nearest point
430 0 : double bestDist = -1;
431 : // Index of the nearest point in p_2 collection
432 0 : int bestIndex = -1;
433 :
434 : // Distance to the 2nd nearest point
435 0 : double bestDist_2 = -1;
436 :
437 : // Find the nearest and 2nd nearest points
438 0 : for (int j = 0; j < len_2; j++)
439 0 : if (!alreadyMatched[j])
440 0 : if (p_1->at(i).GetSign() ==
441 : p_2->at(j).GetSign())
442 : {
443 : // Get distance between two feature points
444 : double curDist = GetEuclideanDistance(
445 0 : p_1->at(i), p_2->at(j));
446 :
447 0 : if (bestDist == -1)
448 : {
449 0 : bestDist = curDist;
450 0 : bestIndex = j;
451 : }
452 : else
453 : {
454 0 : if (curDist < bestDist)
455 : {
456 0 : bestDist = curDist;
457 0 : bestIndex = j;
458 : }
459 : }
460 :
461 : // Findes the 2nd nearest point
462 0 : if (bestDist_2 < 0)
463 0 : bestDist_2 = curDist;
464 : else
465 0 : if (curDist > bestDist && curDist < bestDist_2)
466 0 : bestDist_2 = curDist;
467 : }
468 : /* -------------------------------------------------------------------- */
469 : /* False matching pruning. */
470 : /* If ratio bestDist to bestDist_2 greater than 0.8 => */
471 : /* consider as false detection. */
472 : /* Otherwise, add points as matched pair. */
473 : /*----------------------------------------------------------------------*/
474 0 : if (bestDist_2 > 0 && bestDist >= 0)
475 0 : if (bestDist / bestDist_2 < ratioThreshold)
476 : {
477 0 : MatchedPointPairInfo info(i, bestIndex, bestDist);
478 0 : poPairInfoList->push_back(info);
479 0 : alreadyMatched[bestIndex] = true;
480 : }
481 : }
482 :
483 :
484 : /* -------------------------------------------------------------------- */
485 : /* Pruning based on the provided threshold */
486 : /* -------------------------------------------------------------------- */
487 :
488 0 : NormalizeDistances(poPairInfoList);
489 :
490 0 : std::list<MatchedPointPairInfo>::const_iterator iter;
491 0 : for (iter = poPairInfoList->begin(); iter != poPairInfoList->end(); iter++)
492 : {
493 0 : if ((*iter).euclideanDist <= dfThreshold)
494 : {
495 0 : int i_1 = (*iter).ind_1;
496 0 : int i_2 = (*iter).ind_2;
497 :
498 : // Add copies into MatchedCollection
499 0 : if(!isSwap)
500 : {
501 0 : poMatchPairs->push_back( &(p_1->at(i_1)) );
502 0 : poMatchPairs->push_back( &(p_2->at(i_2)) );
503 : }
504 : else
505 : {
506 0 : poMatchPairs->push_back( &(p_2->at(i_2)) );
507 0 : poMatchPairs->push_back( &(p_1->at(i_1)) );
508 : }
509 : }
510 : }
511 :
512 : // Clean up
513 0 : delete[] alreadyMatched;
514 :
515 0 : return CE_None;
516 : }
517 :
518 0 : GDALSimpleSURF::~GDALSimpleSURF()
519 : {
520 0 : delete poOctMap;
521 0 : }
522 :
|