OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
SBDBlobExtractor.cpp
Go to the documentation of this file.
1 
11 // Copyright 2015 Sensics, Inc.
12 //
13 // Licensed under the Apache License, Version 2.0 (the "License");
14 // you may not use this file except in compliance with the License.
15 // You may obtain a copy of the License at
16 //
17 // http://www.apache.org/licenses/LICENSE-2.0
18 //
19 // Unless required by applicable law or agreed to in writing, software
20 // distributed under the License is distributed on an "AS IS" BASIS,
21 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 // See the License for the specific language governing permissions and
23 // limitations under the License.
24 
25 // Internal Includes
26 #include "SBDBlobExtractor.h"
27 #include "BlobExtractor.h"
28 #include "cvUtils.h"
29 
30 // Library/third-party includes
31 #include <boost/assert.hpp>
32 #include <opencv2/features2d/features2d.hpp>
33 #include <opencv2/imgproc/imgproc.hpp>
34 
35 // Standard includes
36 // - none
37 
38 #include <iostream>
39 
40 #define OSVR_NEW_BLOB_CODE
41 
42 namespace osvr {
43 namespace vbtracker {
44 
45 #if 0
46  class KeypointDetailer {
49  public:
50  using ContourType = std::vector<cv::Point2i>;
51  LedMeasurementVec
52  augmentKeypoints(cv::Mat const &grayImage,
53  std::vector<cv::KeyPoint> const &foundKeyPoints) {
54 
55  LedMeasurementVec ret;
56  cv::Mat greyCopy = grayImage.clone();
57 
60  m_floodFillMask =
61  cv::Mat::zeros(grayImage.rows + 2, grayImage.cols + 2, CV_8UC1);
62  m_floodFillMask.row(0) = 1;
63  m_floodFillMask.row(m_floodFillMask.rows - 1) = 1;
64  m_floodFillMask.col(0) = 1;
65  m_floodFillMask.col(m_floodFillMask.cols - 1) = 1;
66  m_origBounds = cv::Rect(1, 1, m_floodFillMask.cols - 2,
67  m_floodFillMask.rows - 2);
68  for (auto const &keypoint : foundKeyPoints) {
69  ret.push_back(augmentKeypoint(greyCopy, keypoint));
70  }
71 
72  return ret;
73  }
74 
75  cv::Mat getDebugImage() { return m_floodFillMask(m_origBounds); }
76 
77  private:
78  LedMeasurement augmentKeypoint(cv::Mat grayImage,
79  cv::KeyPoint origKeypoint) {
80  // Saving this now before we monkey with m_floodFillMask
81  cv::bitwise_not(m_floodFillMask, m_scratchNotMask);
82 
83  const int loDiff = 2;
84  const int upDiff = 2;
85  auto m_area = cv::floodFill(
86  grayImage, m_floodFillMask, origKeypoint.pt, 255,
87  &m_filledBounds, loDiff, upDiff,
88  CV_FLOODFILL_MASK_ONLY | (/* connectivity 4 or 8 */ 4) |
89  (/* value to write in to mask */ 255 << 8));
90  // Now m_floodFillMask contains the mask with both our point
91  // and all other points so far. We need to split them by ANDing with
92  // the NOT of the old flood-fill mask we saved earlier.
93  cv::bitwise_and(m_scratchNotMask, m_floodFillMask,
94  m_perPointResults);
95  // OK, now we have the results for just this point in per-point
96  // results
97 
99  auto ret = LedMeasurement{origKeypoint, grayImage.size()};
100  if (m_area <= 0) {
101  // strange...
102  return ret;
103  }
104  m_binarySubImage = m_perPointResults(m_filledBounds);
105  computeContour();
106  m_moments = haveContour() ? cv::moments(m_contour, false)
107  : cv::moments(m_binarySubImage, true);
108 
109 // the first two we are using from the keypoint description, the latter we can't
110 // always get (contour-based)
111 #if 0
112  ret.diameter = diameter();
114  ret.area = area();
115  if (haveContour()) {
116  ret.circularity = circularity();
117  }
118 #endif
119  ret.knowBoundingBox = true;
120  ret.boundingBox =
121  cv::Size2f(m_filledBounds.width, m_filledBounds.height);
122  return ret;
123  }
124 
126  void computeContour() {
127 
128  m_haveContour = false;
129  return;
130  // Nudge out one pixel each direction if we can.
131  auto expandedBounds = m_filledBounds;
132  if (expandedBounds.x > 0) {
133  expandedBounds.x--;
134  expandedBounds.width++;
135  }
136  if (expandedBounds.y > 0) {
137  expandedBounds.y--;
138  expandedBounds.height++;
139  }
140  if (expandedBounds.br().x < m_perPointResults.cols) {
141  expandedBounds.width++;
142  }
143  if (expandedBounds.br().y < m_perPointResults.rows) {
144  expandedBounds.height++;
145  }
146 
147  cv::Mat input = m_perPointResults(expandedBounds).clone();
148  std::vector<ContourType> contours;
149  cv::findContours(input, contours, CV_RETR_EXTERNAL,
150  CV_CHAIN_APPROX_NONE,
151  cv::Point(-1, -1) + expandedBounds.tl());
152 #if 0
153  if (contours.size() != 1) {
154  std::cout << "Weird, we have " << contours.size()
155  << " contours!" << std::endl;
156  }
157 #endif
158  if (!contours.empty()) {
159  m_contour = cv::Mat(contours[0], true);
160  m_haveContour = true;
161  } else {
162  m_haveContour = false;
163  }
164  }
165 
166  bool haveContour() { return m_haveContour; }
167 
168  double area() const { return static_cast<double>(m_area); }
169 
171  double diameter() const { return 2 * std::sqrt(area() / CV_PI); }
172 
174  double perimeter() const { return cv::arcLength(m_contour, true); }
175 
177  double circularity() const {
178  auto perim = perimeter();
179  return 4 * CV_PI * area() / (perim * perim);
180  }
181  cv::Rect m_origBounds;
182  cv::Mat m_scratchNotMask;
183  cv::Mat m_floodFillMask;
186  int m_area;
187  cv::Mat m_perPointResults;
188  cv::Mat m_binarySubImage;
189  cv::Rect m_filledBounds;
190  bool m_haveContour = false;
191  cv::Mat m_contour;
192  cv::Moments m_moments;
194  };
195 #endif
196 
198  : m_algo(Algo::SimpleBlobDetector), m_params(blobParams) {
199 
200  auto &p = m_params;
202  m_sbdParams.minDistBetweenBlobs = p.minDistBetweenBlobs;
203 
204  m_sbdParams.minArea = p.minArea; // How small can the blobs be?
205 
206  // Look for bright blobs: there is a bug in this code
207  m_sbdParams.filterByColor = false;
208  // m_sbdParams.blobColor = static_cast<uchar>(255);
209 
210  m_sbdParams.filterByInertia =
211  false; // Do we test for non-elongated blobs?
212  // m_sbdParams.minInertiaRatio = 0.5;
213  // m_sbdParams.maxInertiaRatio = 1.0;
214 
215  m_sbdParams.filterByCircularity =
216  p.filterByCircularity; // Test for circularity?
217  m_sbdParams.minCircularity =
218  p.minCircularity; // default is 0.8, but the edge of the
219  // case can make the blobs "weird-shaped"
220 
221  m_sbdParams.filterByConvexity =
222  p.filterByConvexity; // Test for convexity?
223  m_sbdParams.minConvexity = p.minConvexity;
224  }
225 
227  EdgeHoleParams const &extParams)
228  : m_algo(Algo::EdgeHoleExtractor), m_params(blobParams),
229  m_extractor(extParams) {}
230 
233  }
234 
235  LedMeasurementVec const &
236  SBDBlobExtractor::extractBlobs(cv::Mat const &grayImage) {
237  m_latestMeasurements.clear();
238  m_lastGrayImage = grayImage.clone();
239  m_debugThresholdImageDirty = true;
240  m_debugBlobImageDirty = true;
241  getKeypoints(grayImage);
242 
243 #if 0
244  // This code uses the Keypoint Detailer, but is slightly unreliable.
245 
248  cv::Mat thresholded;
249  cv::threshold(grayImage, thresholded, m_sbdParams.minThreshold, 255,
250  cv::THRESH_BINARY);
251 
252  m_latestMeasurements =
253  m_keypointDetailer->augmentKeypoints(thresholded, m_keyPoints);
254 #endif
255  cv::Size sz = grayImage.size();
256  if (Algo::SimpleBlobDetector == m_algo) {
259  m_latestMeasurements.resize(m_keyPoints.size());
260  std::transform(begin(m_keyPoints), end(m_keyPoints),
261  begin(m_latestMeasurements),
262  [sz](cv::KeyPoint const &kp) {
263  return LedMeasurement{kp, sz};
264  });
265  }
266  return m_latestMeasurements;
267  }
268 
269  void SBDBlobExtractor::getKeypoints(cv::Mat const &grayImage) {
270  if (Algo::EdgeHoleExtractor == m_algo) {
271  m_latestMeasurements = m_extractor(m_lastGrayImage, m_params);
272 
273  } else if (Algo::SimpleBlobDetector == m_algo) {
274  m_keyPoints.clear();
275  //================================================================
276  // Tracking the points
277 
278  // Construct a blob detector and find the blobs in the image.
279  auto &p = m_params;
280  auto rangeInfo = ImageRangeInfo(grayImage);
281  if (rangeInfo.maxVal < p.absoluteMinThreshold) {
283  return;
284  }
285  auto thresholdInfo = ImageThresholdInfo(rangeInfo, p);
286 
287  // 0.3 LERP between min and max as the min threshold, but
288  // don't let really dim frames confuse us.
289  m_sbdParams.minThreshold =
290  static_cast<float>(thresholdInfo.minThreshold);
291  m_sbdParams.maxThreshold =
292  static_cast<float>(thresholdInfo.maxThreshold);
293  m_sbdParams.thresholdStep =
294  static_cast<float>(thresholdInfo.thresholdStep);
295 
301 #if CV_MAJOR_VERSION == 2
302  cv::Ptr<cv::SimpleBlobDetector> detector =
303  new cv::SimpleBlobDetector(m_sbdParams);
304 #elif CV_MAJOR_VERSION == 3
305  auto detector = cv::SimpleBlobDetector::create(m_sbdParams);
306 #else
307 #error "Unrecognized OpenCV version!"
308 #endif
309  detector->detect(grayImage, m_keyPoints);
310 
311  // @todo: Consider computing the center of mass of a dilated
312  // bounding
313  // rectangle around each keypoint to produce a more precise subpixel
314  // localization of each LED. The moments() function may be helpful
315  // with this.
316 
317  // @todo: Estimate the summed brightness of each blob so that we can
318  // detect when they are getting brighter and dimmer. Pass this as
319  // the brightness parameter to the Led class when adding a new one
320  // or augmenting with a new frame.
321  }
322  }
323 
324  cv::Mat SBDBlobExtractor::generateDebugThresholdImage() const {
325  if (Algo::EdgeHoleExtractor == m_algo) {
326  return m_extractor.getEdgeDetectedImage().clone();
327  }
328  BOOST_ASSERT(Algo::SimpleBlobDetector == m_algo);
329  // Fake the thresholded image to give an idea of what the
330  // blob detector is doing.
331  auto getCurrentThresh = [&](int i) {
332  return i * m_sbdParams.thresholdStep + m_sbdParams.minThreshold;
333  };
334  cv::Mat ret;
335  cv::Mat temp;
336  cv::threshold(m_lastGrayImage, ret, m_sbdParams.minThreshold, 255,
337  CV_THRESH_BINARY);
338  cv::Mat tempOut;
339  for (int i = 1; getCurrentThresh(i) < m_sbdParams.maxThreshold; ++i) {
340  auto currentThresh = getCurrentThresh(i);
341  cv::threshold(m_lastGrayImage, temp, currentThresh, currentThresh,
342  CV_THRESH_BINARY);
343  cv::addWeighted(ret, 0.5, temp, 0.5, 0, tempOut);
344  ret = tempOut;
345  }
346 
347  return ret;
348  }
350  if (m_debugThresholdImageDirty) {
351  m_debugThresholdImage = generateDebugThresholdImage();
352  m_debugThresholdImageDirty = false;
353  }
354  return m_debugThresholdImage;
355  }
356 
357  cv::Mat SBDBlobExtractor::generateDebugBlobImage() const {
358  cv::Mat ret;
359  if (Algo::EdgeHoleExtractor == m_algo) {
360  // Draw outlines and centers of detected LEDs in blue.
361  ret = drawSingleColoredContours(m_lastGrayImage,
362  m_extractor.getContours(),
363  cv::Scalar(255, 0, 0));
364 
365  } else if (Algo::SimpleBlobDetector == m_algo) {
366  cv::Mat tempColor;
367  cv::cvtColor(m_lastGrayImage, tempColor, CV_GRAY2BGR);
368  // Draw detected blobs as blue circles.
369  cv::drawKeypoints(tempColor, m_keyPoints, ret,
370  cv::Scalar(255, 0, 0),
371  cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
372  }
373  return ret;
374  }
375  cv::Mat const &SBDBlobExtractor::getDebugBlobImage() {
376  if (m_debugBlobImageDirty) {
377  m_debugBlobImage = generateDebugBlobImage();
378  m_debugBlobImageDirty = false;
379  }
380  return m_debugBlobImage;
381  }
382 #if 0
383  cv::Mat const &SBDBlobExtractor::getDebugExtraImage() {
384 
385  m_extraImage = m_keypointDetailer->getDebugImage().clone();
386  return m_extraImage;
387  }
388 #endif
389 
391  BlobParams const &blobParams)
392  : m_params(blobParams) {
393 
394  auto &p = m_params;
396  m_sbdParams.minDistBetweenBlobs = p.minDistBetweenBlobs;
397 
398  m_sbdParams.minArea = p.minArea; // How small can the blobs be?
399 
400  // Look for bright blobs: there is a bug in this code
401  m_sbdParams.filterByColor = false;
402  // m_sbdParams.blobColor = static_cast<uchar>(255);
403 
404  m_sbdParams.filterByInertia =
405  false; // Do we test for non-elongated blobs?
406  // m_sbdParams.minInertiaRatio = 0.5;
407  // m_sbdParams.maxInertiaRatio = 1.0;
408 
409  m_sbdParams.filterByCircularity =
410  p.filterByCircularity; // Test for circularity?
411  m_sbdParams.minCircularity =
412  p.minCircularity; // default is 0.8, but the edge of the
413  // case can make the blobs "weird-shaped"
414 
415  m_sbdParams.filterByConvexity =
416  p.filterByConvexity; // Test for convexity?
417  m_sbdParams.minConvexity = p.minConvexity;
418  }
419  cv::Mat SBDGenericBlobExtractor::generateDebugThresholdImage_() const {
420  // Fake the thresholded image to give an idea of what the
421  // blob detector is doing.
422  auto getCurrentThresh = [&](int i) {
423  return i * m_sbdParams.thresholdStep + m_sbdParams.minThreshold;
424  };
425  cv::Mat ret;
426  cv::Mat temp;
427  cv::threshold(getLatestGrayImage(), ret, m_sbdParams.minThreshold, 255,
428  CV_THRESH_BINARY);
429  cv::Mat tempOut;
430  for (int i = 1; getCurrentThresh(i) < m_sbdParams.maxThreshold; ++i) {
431  auto currentThresh = getCurrentThresh(i);
432  cv::threshold(getLatestGrayImage(), temp, currentThresh,
433  currentThresh, CV_THRESH_BINARY);
434  cv::addWeighted(ret, 0.5, temp, 0.5, 0, tempOut);
435  ret = tempOut;
436  }
437 
438  return ret;
439  }
440 
441  cv::Mat SBDGenericBlobExtractor::generateDebugBlobImage_() const {
442  cv::Mat ret;
443  cv::Mat tempColor;
444  cv::cvtColor(getLatestGrayImage(), tempColor, CV_GRAY2BGR);
445  // Draw detected blobs as blue circles.
446  cv::drawKeypoints(tempColor, m_keyPoints, ret, cv::Scalar(255, 0, 0),
447  cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
448  return ret;
449  }
450 
452  getKeypoints(getLatestGrayImage());
453  cv::Size sz = getLatestGrayImage().size();
456  LedMeasurementVec ret;
457  ret.resize(m_keyPoints.size());
458  std::transform(begin(m_keyPoints), end(m_keyPoints), begin(ret),
459  [sz](cv::KeyPoint const &kp) {
460  return LedMeasurement{kp, sz};
461  });
462  return ret;
463  }
464  void SBDGenericBlobExtractor::getKeypoints(cv::Mat const &grayImage) {
465  m_keyPoints.clear();
466  //================================================================
467  // Tracking the points
468 
469  // Construct a blob detector and find the blobs in the image.
470  auto &p = m_params;
471  auto rangeInfo = ImageRangeInfo(grayImage);
472  if (rangeInfo.maxVal < p.absoluteMinThreshold) {
474  return;
475  }
476  auto thresholdInfo = ImageThresholdInfo(rangeInfo, p);
477 
478  // 0.3 LERP between min and max as the min threshold, but
479  // don't let really dim frames confuse us.
480  m_sbdParams.minThreshold =
481  static_cast<float>(thresholdInfo.minThreshold);
482  m_sbdParams.maxThreshold =
483  static_cast<float>(thresholdInfo.maxThreshold);
484  m_sbdParams.thresholdStep =
485  static_cast<float>(thresholdInfo.thresholdStep);
486 
492 #if CV_MAJOR_VERSION == 2
493  cv::Ptr<cv::SimpleBlobDetector> detector =
494  new cv::SimpleBlobDetector(m_sbdParams);
495 #elif CV_MAJOR_VERSION == 3
496  auto detector = cv::SimpleBlobDetector::create(m_sbdParams);
497 #else
498 #error "Unrecognized OpenCV version!"
499 #endif
500  detector->detect(grayImage, m_keyPoints);
501 
502  // @todo: Consider computing the center of mass of a dilated
503  // bounding
504  // rectangle around each keypoint to produce a more precise subpixel
505  // localization of each LED. The moments() function may be helpful
506  // with this.
507 
508  // @todo: Estimate the summed brightness of each blob so that we can
509  // detect when they are getting brighter and dimmer. Pass this as
510  // the brightness parameter to the Led class when adding a new one
511  // or augmenting with a new frame.
512  }
513 
514  BlobExtractorPtr makeBlobExtractor(BlobParams const &blobParams) {
515  auto extractor = std::make_shared<SBDGenericBlobExtractor>(blobParams);
516  return extractor;
517  }
518 } // namespace vbtracker
519 } // namespace osvr
SBDBlobExtractor(BlobParams const &blobParams)
Construct with just BlobParams: uses simple blob detector.
t_< detail::transform_< List, Fun >> transform
Definition: Transform.h:54
LedMeasurementVec const & extractBlobs(cv::Mat const &grayImage)
LedMeasurementVec extractBlobs_() override
SBDGenericBlobExtractor(BlobParams const &blobParams)
Header.
Blob detection configuration parameters.
Definition: BlobParams.h:40
double Scalar
Common scalar type.