OSSIM - Open Source Software Image Map  Version 1.9.0 (20180803)
ossimDescriptorSource.cpp
Go to the documentation of this file.
1 //**************************************************************************************************
2 //
3 // OSSIM Open Source Geospatial Data Processing Library
4 // See top level LICENSE.txt file for license information
5 //
6 //**************************************************************************************************
7 
9 #include "AtpOpenCV.h"
10 #include "atp/AtpCommon.h"
11 #include "AtpGenerator.h"
13 
14 #include <opencv2/xfeatures2d.hpp>
15 
16 namespace ATP
17 {
18 static const int DEFAULT_CMP_PATCH_SIZING_FACTOR = 2;
19 static const double DEFAULT_NOMINAL_POSITION_ERROR = 50; // meters
20 
22 {
23  // PRIVATE, SHOULD NEVER BE CALLED. WOULD NEED TO CALL INITIALIZE
24 }
25 
27 : AtpTileSource(inputSources)
28 {
29  initialize();
30 }
31 
33 : AtpTileSource(generator)
34 {
35  // PRIVATE, SHOULD NEVER BE CALLED. WOULD NEED TO CALL INITIALIZE
36 }
37 
39 {
40 }
41 
43 {
44  // Base class initializes ref and cmp chain datat members and associated IVTs. Subsequent call
45  // to base class setViewGeom() will initialize the associated IVTs:
47 
48  ossimDpt gsd = m_generator->getRefIVT()->getOutputMetersPerPixel();
49  double nominalGsd = (gsd.x + gsd.y)/2.0;
50 
51  // Fetch the CE90 of both images to determine cmp image search patch size:
52  ossimSensorModel* ref_sm = dynamic_cast<ossimSensorModel*>(
53  m_generator->getRefIVT()->getImageGeometry()->getProjection());
54  ossimSensorModel* cmp_sm = dynamic_cast<ossimSensorModel*>(
55  m_generator->getCmpIVT()->getImageGeometry()->getProjection());
56  if (ref_sm && cmp_sm)
57  {
58  double ref_ce = ref_sm->getNominalPosError();
59  double cmp_ce = cmp_sm->getNominalPosError();
60  double total_error = ref_ce + cmp_ce; // meters -- being conservative here and not doing rss
61  m_cmpPatchInflation = 2.0*total_error/nominalGsd;
62  }
63  else
64  {
65  m_cmpPatchInflation = 2.0*DEFAULT_NOMINAL_POSITION_ERROR/nominalGsd;
66  }
67 }
68 
70  ossim_uint32 resLevel)
71 {
72  static const char* MODULE = "ossimDescriptorSource::getTile() ";
73  AtpConfig& config = AtpConfig::instance();
74 
75  if (config.diagnosticLevel(2))
76  CINFO<<"\n\n"<< MODULE << " -- tileRect: "<<tileRect<<endl;
77  m_tiePoints.clear();
78  if (!m_tile)
79  allocate();
80 
81  // The tile rect (in view space) is referenced by the tiepoint filtering code:
82  m_tile->setImageRectangle(tileRect);
83 
84  // Make sure there are at least two images as input.
85  if(getNumberOfInputs() < 2)
86  {
87  CINFO << MODULE << " ERROR: wrong number of inputs " << getNumberOfInputs() << " when expecting at least 2 \n";
88  return 0;
89  }
90 
91  // The tileRect passed in is in a common map-projected view space. Need to transform the rect
92  // into image space for both ref and cmp images:
93  ossimIrect cmpViewRect (tileRect);
95  ossimIrect refRect (m_generator->getRefIVT()->getViewToImageBounds(tileRect));
96  ossimIrect cmpRect (m_generator->getCmpIVT()->getViewToImageBounds(cmpViewRect));
97 
98  // Retrieve both the ref and cmp image data
99  ossimRefPtr<ossimImageData> ref_tile = m_generator->getRefChain()->getTile(refRect, resLevel);
100  if (ref_tile->getDataObjectStatus() == OSSIM_EMPTY)
101  {
102  CWARN << MODULE << " ERROR: could not get REF tile with rect " << refRect << "\n";
103  return m_tile;
104  }
105  if (config.diagnosticLevel(5))
106  ref_tile->write("REF_TILE.RAS");
107 
108 
109  ossimRefPtr<ossimImageData> cmp_tile = m_generator->getCmpChain()->getTile(cmpRect, resLevel);
110  if (cmp_tile->getDataObjectStatus() == OSSIM_EMPTY)
111  {
112  CWARN << MODULE << " ERROR: could not get CMP tile with rect " << cmpRect << "\n";
113  return m_tile;
114  }
115  if (config.diagnosticLevel(5))
116  cmp_tile->write("CMP_TILE.RAS");
117 
118  //m_tile = ref_tile;
119 
120  // Convert both into OpenCV Mat objects using the Ipl image.
121  IplImage* refImage = convertToIpl(ref_tile.get());
122  IplImage* cmpImage = convertToIpl(cmp_tile.get());
123  cv::Mat queryImg = cv::cvarrToMat(refImage);
124  cv::Mat trainImg = cv::cvarrToMat(cmpImage);
125 
126  // Get the KeyPoints using appropriate descriptor.
127  vector<cv::KeyPoint> kpA;
128  vector<cv::KeyPoint> kpB;
129  cv::Mat desA;
130  cv::Mat desB;
131 
132  ossimString descriptorType (config.getParameter("descriptor").asString());
133  descriptorType.upcase();
134 
135  // Search for features and compute descriptors for all:
136  cv::Ptr<cv::Feature2D> detector;
137  if(descriptorType == "AKAZE")
138  detector = cv::AKAZE::create();
139  else if(descriptorType == "KAZE")
140  detector = cv::KAZE::create();
141  else if(descriptorType == "ORB")
142  detector = cv::ORB::create();
143  else if(descriptorType == "SURF")
144  detector = cv::xfeatures2d::SURF::create();
145  else if(descriptorType == "SIFT")
146  detector = cv::xfeatures2d::SIFT::create();
147  else
148  {
149  CWARN << MODULE << " WARNING: No such descriptor as " << descriptorType << "\n";
150  return m_tile;
151  }
152 
153 #if 1
154  detector->detectAndCompute(queryImg, cv::noArray(), kpA, desA);
155  detector->detectAndCompute(trainImg, cv::noArray(), kpB, desB);
156 
157 #else
158  // Separate detect and compute to limit the number of features for which descriptors are
159  // computed... Perform detection:
160  detector->detect(queryImg, kpA);
161  detector->detect(trainImg, kpB);
162 
163  //CINFO<<"\n -- Ref tileRect: "<<refRect<<endl;
164  //CINFO<<"\n -- Cmp tileRect: "<<cmpRect<<endl;
165  CINFO << "DETECTIONS: "<<kpA.size() << ", "<<kpB.size() << endl;//TODO REMOVE
166 
167  // Limit strongest detections to max number of features per tile on query image:
168  unsigned int maxNumFeatures = config.getParameter("numFeaturesPerTile").asUint();
169  //CINFO << "\n numFeatures= "<<maxNumFeatures << endl;
170  if (kpA.size() > maxNumFeatures)
171  {
172  sort(kpA.begin(), kpA.end(), sortFunc);
173  kpA.resize(maxNumFeatures);
174  }
175 
176  //if (kpB.size() > maxNumFeatures)
177  //{
178  // sort(kpB.begin(), kpB.end(), sortFunc);
179  // kpB.resize(maxNumFeatures);
180  //}
181  //CINFO << "\nTRIM:\n kpA.size = "<<kpA.size() << endl;
182  //CINFO << " kpb.size = "<<kpA.size() << endl;
183 
184  // Now perform descriptor computations on remaining features:
185  detector->compute(queryImg, kpA, desA);
186  detector->compute(trainImg, kpB, desB);
187  //CINFO << "\nCOMPUTE:\n desA.size = "<<desA.size() << endl;
188  //CINFO << " desB.size = "<<desB.size() << endl;
189 
190 #endif
191 
192  if (config.diagnosticLevel(5))
193  {
194  CINFO << "\nDETECT:\n kpA.size = " << kpA.size() << endl;
195  CINFO << " kpb.size = " << kpA.size() << endl;
196  CINFO << "\nCOMPUTE:\n desA.size = " << desA.size() << endl;
197  CINFO << " desB.size = " << desB.size() << endl;
198  }
199 
200  // Get the DPoints using the appropriate matcher.
201  std::vector< std::vector<cv::DMatch> > matches;
202  if(!(desA.empty() || desB.empty()))
203  {
204  std::string matcherType = config.getParameter("matcher").asString();
205  uint k = config.getParameter("maxNumMatchesPerFeature").asUint();
206  if (config.paramExists("k")) // support legacy keywords
207  k = config.getParameter("k").asUint();
208  if (kpA.size() < k)
209  k = kpA.size();
210  transform(matcherType.begin(), matcherType.end(), matcherType.begin(),::toupper);
211 
212  shared_ptr<cv::DescriptorMatcher> matcher;
213  if(matcherType == "FLANN")
214  {
215  if(desA.type()!=CV_32F)
216  desA.convertTo(desA, CV_32F);
217  if(desB.type()!=CV_32F)
218  desB.convertTo(desB, CV_32F);
219 
220  matcher.reset(new cv::FlannBasedMatcher);
221  }
222  else if(matcherType == "BF")
223  {
224  if(desA.type()!=CV_8U)
225  desA.convertTo(desA, CV_8U);
226  if(desB.type()!=CV_8U)
227  desB.convertTo(desB, CV_8U);
228 
229  int normType;
230  std::string ErrorFunction = config.getParameter("normType").asString();
231  if(ErrorFunction == "NORM_L1"){
232  normType = cv::NORM_L1;
233  } else if(ErrorFunction == "NORM_L2") {
234  normType = cv::NORM_L2;
235  } else if(ErrorFunction == "NORM_HAMMING") {
236  normType = cv::NORM_HAMMING;
237  } else {
238  std::string descriptorUsed = config.getParameter("descriptor").asString();
239  if(descriptorUsed == "SURF" || descriptorUsed == "SIFT"){
240  normType = cv::NORM_L2;
241  } else {
242  normType = cv::NORM_HAMMING;
243  }
244  }
245 
246  matcher.reset(new cv::BFMatcher(normType, true));
247  }
248  else
249  {
250  CWARN << MODULE << " WARNING: No such matcher as " << matcherType << "\n";
251  return m_tile;
252  }
253 
254  matcher->knnMatch(desA, desB, matches, k);
255  }
256 
257  float minDistance = INT_MAX;
258  float maxDistance = 0;
259 
260  // Find the highest distance to compute the relative confidence of each match
261  for (auto &featureMatches : matches)
262  {
263  for (auto &match : featureMatches)
264  {
265  if (minDistance >match.distance)
266  minDistance = match.distance;
267  if (maxDistance < match.distance)
268  maxDistance = match.distance;
269  }
270  }
271 
272  // Experimental: force min distance to be the perfect match case = 0:
273  minDistance = 0.0;
274  float delta = maxDistance - minDistance;
275 
276  // Sort the matches in order of strength (i.e., confidence) using stl map:
277  multimap<double, shared_ptr<AutoTiePoint> > tpMap;
278 
279  // Convert the openCV match points to something Atp could understand.
280  string sid(""); // Leave blank to have it auto-assigned by CorrelationTiePoint constructor
281 
282  for (auto &featureMatches : matches)
283  {
284  if (featureMatches.empty())
285  continue;
286 
287  shared_ptr<AutoTiePoint> atp (new AutoTiePoint(m_generator, sid));
288  cv::KeyPoint cv_A = kpA[(featureMatches[0]).queryIdx];
289  cv::KeyPoint cv_B;
290 
291  ossimDpt refImgPt (refRect.ul().x + cv_A.pt.x, refRect.ul().y + cv_A.pt.y);
292  atp->setRefImagePt(refImgPt);
293 
294  // Create the match points
295  double strength = 0;
296  for (auto &match : featureMatches)
297  {
298  cv_B = kpB[(match).trainIdx];
299  ossimDpt cmpImgPt (cmpRect.ul().x + cv_B.pt.x, cmpRect.ul().y + cv_B.pt.y);
300  double strength_j = 1.0 - (match.distance-minDistance)/delta;
301  if (strength_j > strength)
302  strength = strength_j;
303  atp->addImageMatch(cmpImgPt, strength_j);
304  }
305 
306  // Insert into sorted map using one's complement as key since in ascending order:
307  tpMap.insert(pair<double, shared_ptr<AutoTiePoint> >(1.0-strength, atp));
308  sid.clear();
309  }
310 
311  if (config.diagnosticLevel(2))
312  {
313  CINFO<<" Detections R,C: "<<kpA.size() << ", "<<kpB.size() << endl;//TODO REMOVE
314  CINFO<<" Matches (Before filtering): "<<matches.size()<<endl;
315 
316  }
317 
318  // Now skim off the best matches and copy them to the list being returned:
319  unsigned int N = config.getParameter("numFeaturesPerTile").asUint();
320  unsigned int n = 0;
321  auto tp_iter = tpMap.begin();
322  while ((tp_iter != tpMap.end()) && (n < N))
323  {
324  m_tiePoints.push_back(tp_iter->second);
325  tp_iter++;
326  n++;
327  }
328 
329  if (config.diagnosticLevel(2))
330  CINFO<<" After capping to max num features ("<<N<<"), num TPs in tile = "<<n<<endl;
331 
332  filterPoints();
333  return m_tile;
334 }
335 
336 }
virtual const double & getNominalPosError() const
Returns the estimated Absolute horizontal position error (CE90) of the sensor model.
JsonParam & getParameter(const char *paramName)
Returns a parameter (might be a null parameter if paramName not found in the configuration.
Definition: JsonConfig.cpp:377
static ossimString upcase(const ossimString &aString)
Definition: ossimString.cpp:34
virtual void setImageRectangle(const ossimIrect &rect)
Base class for all automatic tiepoints.
Definition: AutoTiePoint.h:30
struct ATP::ossimDescriptorSource::SortFunc sortFunc
std::shared_ptr< AtpGenerator > m_generator
Definition: AtpTileSource.h:85
double y
Definition: ossimDpt.h:165
unsigned int asUint() const
Definition: JsonConfig.cpp:264
virtual ossimDataObjectStatus getDataObjectStatus() const
Base class for tile sources performing auto tie point extraction.
Definition: AtpTileSource.h:26
bool paramExists(const char *paramName) const
Definition: JsonConfig.cpp:395
const ossimIrect & expand(const ossimIpt &padding)
Definition: ossimIrect.cpp:308
virtual bool write(const ossimFilename &f) const
Writes tile to stream.
std::string asString() const
Definition: JsonConfig.cpp:285
virtual void initialize()
std::vector< ossimRefPtr< ossimConnectableObject > > ConnectableObjectList
os2<< "> n<< " > nendobj n
Base class for OSSIM-based ATP generators.
Definition: AtpGenerator.h:33
unsigned int ossim_uint32
THESE FUNCTIONS REQUIRE OPENCV.
ossimRefPtr< ossimImageData > m_tile
Definition: AtpTileSource.h:87
bool diagnosticLevel(unsigned int level) const
Convenience method returns TRUE if the currently set diagnostic level is <= level.
Definition: JsonConfig.cpp:461
IplImage * convertToIpl(const ossimImageData *data)
Converts an ossimImageData pointer to an IplImage for use in OpenCV.
Definition: AtpOpenCV.cpp:25
virtual ossim_uint32 getNumberOfInputs() const
Returns the number of input objects.
double x
Definition: ossimDpt.h:164
static AtpConfig & instance()
Singleton implementation.
Definition: AtpConfig.cpp:20
#define CWARN
virtual void allocate()
#define CINFO
virtual ossimRefPtr< ossimImageData > getTile(const ossimIrect &origin, ossim_uint32 rLevel=0)
Derived classes implement their particular tiepoint matching algorithms for the requested tile...
Singleton class maintaining parameters affecting the automatic tie point generation.
Definition: AtpConfig.h:24