1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Planar surface detection tool.
33  *
34  * Authors:
35  * Romain Tallonneau
36  *
37  *****************************************************************************/
38 
39 #include <visp3/vision/vpPlanarObjectDetector.h>
40 
41 #if (VISP_HAVE_OPENCV_VERSION >= 0x020000) &&                                                                          \
42     (VISP_HAVE_OPENCV_VERSION < 0x030000) // Require opencv >= 2.0.0 and < 3.0.0
43 
44 #include <visp3/core/vpColor.h>
45 #include <visp3/core/vpDisplay.h>
46 #include <visp3/core/vpException.h>
47 #include <visp3/core/vpImageConvert.h>
48 #include <visp3/core/vpImagePoint.h>
49 #include <visp3/core/vpImageTools.h>
50 
51 #include <iostream>
52 #include <vector>
53 
54 /*!
55 
56   Basic constructor
57 
58 */
vpPlanarObjectDetector()59 vpPlanarObjectDetector::vpPlanarObjectDetector()
60   : fern(), homography(), H(), dst_corners(), isCorrect(false), ref_corners(), modelROI(), currentImagePoints(),
61     refImagePoints(), minNbMatching(10)
62 {
63 }
64 
65 /*!
66 
67   Basic constructor, load data from a file.
68 
69   \param _dataFile : the name of the file.
70   \param _objectName : the name of the object to load.
71 
72 */
vpPlanarObjectDetector(const std::string & _dataFile,const std::string & _objectName)73 vpPlanarObjectDetector::vpPlanarObjectDetector(const std::string &_dataFile, const std::string &_objectName)
74   : fern(), homography(), H(), dst_corners(), isCorrect(false), ref_corners(), modelROI(), currentImagePoints(),
75     refImagePoints(), minNbMatching(10)
76 {
77   load(_dataFile, _objectName);
78 }
79 
80 /*!
81   Initialise stuff. For the moment does nothing.
82 */
init()83 void vpPlanarObjectDetector::init() {}
84 
85 /*!
86 
87   Basic destructor
88 
89 */
~vpPlanarObjectDetector()90 vpPlanarObjectDetector::~vpPlanarObjectDetector() {}
91 
92 /*!
93   Compute the rectangular ROI from at least 4 points and set the region of
94   interest on the current image.
95 
96   \param ip : the list of image point.
97   \param nbpt : the number of point.
98 */
computeRoi(vpImagePoint * ip,unsigned int nbpt)99 void vpPlanarObjectDetector::computeRoi(vpImagePoint *ip, unsigned int nbpt)
100 {
101   if (nbpt < 3) {
102     throw vpException(vpException::badValue, "Not enough point to compute the region of interest.");
103   }
104 
105   std::vector<vpImagePoint> ptsx(nbpt);
106   std::vector<vpImagePoint> ptsy(nbpt);
107   for (unsigned int i = 0; i < nbpt; i++) {
108     ptsx[i] = ptsy[i] = ip[i];
109   }
110 
111   for (unsigned int i = 0; i < nbpt; i++) {
112     for (unsigned int j = 0; j < nbpt - 1; j++) {
113       if (ptsx[j].get_j() > ptsx[j + 1].get_j()) {
114         double tmp = ptsx[j + 1].get_j();
115         ptsx[j + 1].set_j(ptsx[j].get_j());
116         ptsx[j].set_j(tmp);
117       }
118     }
119   }
120   for (unsigned int i = 0; i < nbpt; i++) {
121     for (unsigned int j = 0; j < nbpt - 1; j++) {
122       if (ptsy[j].get_i() > ptsy[j + 1].get_i()) {
123         double tmp = ptsy[j + 1].get_i();
124         ptsy[j + 1].set_i(ptsy[j].get_i());
125         ptsy[j].set_i(tmp);
126       }
127     }
128   }
129 }
130 
131 /*!
132   Train the classifier from the entire image (it is therefore assumed that the
133   image is planar).
134 
135   Depending on the parameters, the training can take up to several minutes.
136 
137   \param _I : The image use to train the classifier.
138 
139   \return The number of reference points.
140 */
buildReference(const vpImage<unsigned char> & _I)141 unsigned int vpPlanarObjectDetector::buildReference(const vpImage<unsigned char> &_I)
142 {
143   modelROI.x = 0;
144   modelROI.y = 0;
145   modelROI.width = (int)_I.getWidth();
146   modelROI.height = (int)_I.getHeight();
147 
148   initialiseRefCorners(modelROI);
149 
150   return fern.buildReference(_I);
151 }
152 
153 /*!
154   Train the classifier on a region of the entire image. The region is a
155   rectangle defined by its top left corner, its height and its width. The
156   parameters of this rectangle must be given in pixel. It also includes the
157   training of the fern classifier.
158 
159   \param _I : The image use to train the classifier.
160   \param _iP : The top left corner of the rectangle defining the region of
161   interest (ROI). \param _height : The height of the ROI. \param _width : The
162   width of the ROI.
163 
164   \return the number of reference points
165 */
buildReference(const vpImage<unsigned char> & _I,const vpImagePoint & _iP,unsigned int _height,unsigned int _width)166 unsigned int vpPlanarObjectDetector::buildReference(const vpImage<unsigned char> &_I, const vpImagePoint &_iP,
167                                                     unsigned int _height, unsigned int _width)
168 {
169   unsigned int res = fern.buildReference(_I, _iP, _height, _width);
170   modelROI.x = (int)_iP.get_u();
171   modelROI.y = (int)_iP.get_v();
172   modelROI.width = (int)_width;
173   modelROI.height = (int)_height;
174 
175   initialiseRefCorners(modelROI);
176 
177   return res;
178 }
179 
180 /*!
181   Train the classifier on a region of the entire image. The region is a
182   rectangle. The parameters of this rectangle must be given in pixel. It also
183   includes the training of the fern classifier.
184 
185   \param _I : The image use to train the classifier.
186   \param _rectangle : The rectangle defining the region of interest (ROI).
187 
188   \return The number of reference points.
189 */
buildReference(const vpImage<unsigned char> & _I,const vpRect & _rectangle)190 unsigned int vpPlanarObjectDetector::buildReference(const vpImage<unsigned char> &_I, const vpRect &_rectangle)
191 {
192   unsigned int res = fern.buildReference(_I, _rectangle);
193 
194   vpImagePoint iP = _rectangle.getTopLeft();
195 
196   modelROI.x = (int)iP.get_u();
197   modelROI.y = (int)iP.get_v();
198   modelROI.width = (int)_rectangle.getWidth();
199   modelROI.height = (int)_rectangle.getHeight();
200 
201   initialiseRefCorners(modelROI);
202 
203   return res;
204 }
205 
206 /*!
207   Compute the points of interest in the current image and try to recognise
208   them using the trained classifier. The matched pairs can be found with the
209   getMatchedPointByRef function. The homography between the two planar
210   surfaces is also computed.
211 
212   \param I : The gray scaled image where the points are computed.
213 
214   \return True if the surface has been found.
215 */
matchPoint(const vpImage<unsigned char> & I)216 bool vpPlanarObjectDetector::matchPoint(const vpImage<unsigned char> &I)
217 {
218   fern.matchPoint(I);
219 
220   /* compute homography */
221   std::vector<cv::Point2f> refPts = fern.getRefPt();
222   std::vector<cv::Point2f> curPts = fern.getCurPt();
223 
224   for (unsigned int i = 0; i < refPts.size(); ++i) {
225     refPts[i].x += modelROI.x;
226     refPts[i].y += modelROI.y;
227   }
228   for (unsigned int i = 0; i < curPts.size(); ++i) {
229     curPts[i].x += modelROI.x;
230     curPts[i].y += modelROI.y;
231   }
232 
233   if (curPts.size() < 4) {
234     for (unsigned int i = 0; i < 3; i += 1) {
235       for (unsigned int j = 0; j < 3; j += 1) {
236         if (i == j) {
237           homography[i][j] = 1;
238         } else {
239           homography[i][j] = 0;
240         }
241       }
242     }
243     return false;
244   }
245 
246   /* part of code from OpenCV planarObjectDetector */
247   std::vector<unsigned char> mask;
248   H = cv::findHomography(cv::Mat(refPts), cv::Mat(curPts), mask, cv::RANSAC, 10);
249 
250   if (H.data) {
251     const cv::Mat_<double> &H_tmp = H;
252     dst_corners.resize(4);
253     for (unsigned int i = 0; i < 4; i++) {
254       cv::Point2f pt = ref_corners[i];
255 
256       double w = 1. / (H_tmp(2, 0) * pt.x + H_tmp(2, 1) * pt.y + H_tmp(2, 2));
257       dst_corners[i] = cv::Point2f((float)((H_tmp(0, 0) * pt.x + H_tmp(0, 1) * pt.y + H_tmp(0, 2)) * w),
258                                    (float)((H_tmp(1, 0) * pt.x + H_tmp(1, 1) * pt.y + H_tmp(1, 2)) * w));
259     }
260 
261     double *ptr = (double *)H_tmp.data;
262     for (unsigned int i = 0; i < 9; i++) {
263       this->homography[(unsigned int)(i / 3)][i % 3] = *(ptr++);
264     }
265     isCorrect = true;
266   } else {
267     isCorrect = false;
268   }
269 
270   currentImagePoints.resize(0);
271   refImagePoints.resize(0);
272   for (unsigned int i = 0; i < mask.size(); i += 1) {
273     if (mask[i] != 0) {
274       vpImagePoint ip;
275       ip.set_i(curPts[i].y);
276       ip.set_j(curPts[i].x);
277       currentImagePoints.push_back(ip);
278       ip.set_i(refPts[i].y);
279       ip.set_j(refPts[i].x);
280       refImagePoints.push_back(ip);
281     }
282   }
283 
284   if (currentImagePoints.size() < minNbMatching) {
285     isCorrect = false;
286   }
287 
288   return isCorrect;
289 }
290 
291 /*!
292   Compute the points of interest in the specified region of the current image
293   and try to recognise them using the trained classifier. The matched pairs
294   can be found with the getMatchedPointByRef function. The homography between
295   the two planar surfaces is also computed.
296 
297   \param I : The gray scaled image where the points are computed.
298   \param iP : the top left corner of the rectangle defining the ROI
299   \param height : the height of the ROI
300   \param width : the width of the ROI
301 
302   \return true if the surface has been found.
303 */
matchPoint(const vpImage<unsigned char> & I,const vpImagePoint & iP,unsigned int height,unsigned int width)304 bool vpPlanarObjectDetector::matchPoint(const vpImage<unsigned char> &I, const vpImagePoint &iP,
305                                         unsigned int height, unsigned int width)
306 {
307   if ((iP.get_i() + height) >= I.getHeight() || (iP.get_j() + width) >= I.getWidth()) {
308     vpTRACE("Bad size for the subimage");
309     throw(vpException(vpImageException::notInTheImage, "Bad size for the subimage"));
310   }
311 
312   vpImage<unsigned char> subImage;
313 
314   vpImageTools::crop(I, (unsigned int)iP.get_i(), (unsigned int)iP.get_j(), height, width, subImage);
315 
316   return this->matchPoint(subImage);
317 }
318 
319 /*!
320   Compute the points of interest in the specified region of the current image
321   and try to recognise them using the trained classifier. The matched pairs
322   can be found with the getMatchedPointByRef function. The homography between
323   the two planar surfaces is also computed.
324 
325   \param I : The gray scaled image where the points are computed.
326   \param rectangle : The rectangle defining the ROI.
327 
328   \return True if the surface has been found.
329 */
matchPoint(const vpImage<unsigned char> & I,const vpRect & rectangle)330 bool vpPlanarObjectDetector::matchPoint(const vpImage<unsigned char> &I, const vpRect &rectangle)
331 {
332   vpImagePoint iP;
333   iP.set_i(rectangle.getTop());
334   iP.set_j(rectangle.getLeft());
335   return (this->matchPoint(I, iP, (unsigned int)rectangle.getHeight(), (unsigned int)rectangle.getWidth()));
336 }
337 
338 /*!
339   Display the result of the matching. The plane is displayed in red and the
340   points of interests detected in the image are shown by a red dot surrounded
341   by a green circle (the radius of the circle depends on the octave at which
342   it has been detected).
343 
344   \param I : The gray scaled image for the display.
345   \param displayKpts : The flag to display keypoints in addition to the
346   surface.
347 */
display(vpImage<unsigned char> & I,bool displayKpts)348 void vpPlanarObjectDetector::display(vpImage<unsigned char> &I, bool displayKpts)
349 {
350   for (unsigned int i = 0; i < dst_corners.size(); i++) {
351     vpImagePoint ip1(dst_corners[i].y - modelROI.y, dst_corners[i].x - modelROI.x);
352     vpImagePoint ip2(dst_corners[(i + 1) % dst_corners.size()].y - modelROI.y,
353                      dst_corners[(i + 1) % dst_corners.size()].x - modelROI.x);
354     vpDisplay::displayLine(I, ip1, ip2, vpColor::red);
355   }
356 
357   if (displayKpts) {
358     for (unsigned int i = 0; i < currentImagePoints.size(); ++i) {
359       vpImagePoint ip(currentImagePoints[i].get_i() - modelROI.y, currentImagePoints[i].get_j() - modelROI.x);
360       vpDisplay::displayCross(I, ip, 5, vpColor::red);
361     }
362   }
363 }
364 
365 /*!
366   This function displays the matched reference points and the matched
367   points computed in the current image. The reference points are
368   displayed in the image Ireference and the matched points coming from
369   the current image are displayed in the image Icurrent. It is
370   possible to set Ireference and Icurrent with the same image when
371   calling the method.
372 
373   \param Iref : The image where the matched reference points are
374   displayed.
375 
376   \param Icurrent : The image where the matched points computed in the
377   current image are displayed.
378 
379   \param displayKpts : The flag to display keypoints in addition to the
380   surface.
381 */
display(vpImage<unsigned char> & Iref,vpImage<unsigned char> & Icurrent,bool displayKpts)382 void vpPlanarObjectDetector::display(vpImage<unsigned char> &Iref, vpImage<unsigned char> &Icurrent, bool displayKpts)
383 {
384   display(Icurrent, displayKpts);
385 
386   if (displayKpts) {
387     for (unsigned int i = 0; i < refImagePoints.size(); ++i) {
388       vpDisplay::displayCross(Iref, refImagePoints[i], 5, vpColor::green);
389     }
390   }
391 }
392 
393 /*!
394   \brief Load the Fern classifier.
395 
396   Load and initialize the Fern classifier and load the 3D and 2D keypoints. It
397   can take up to sevral seconds.
398 
399   \param dataFilename : The name of the data filename (very large text file).
400   It can have any file extension.
401   \param objName : The name of the object.
402 */
load(const std::string & dataFilename,const std::string & objName)403 void vpPlanarObjectDetector::load(const std::string &dataFilename, const std::string &objName)
404 {
405   fern.load(dataFilename, objName);
406   modelROI = fern.getModelROI();
407   initialiseRefCorners(modelROI);
408 }
409 
410 /*!
411   \brief Record the Ferns classifier in the text file.
412 
413   \param objectName : The name of the object to store in the data file.
414   \param dataFile : The name of the data filename (very large text file).
415   It can have any file extension.
416 */
recordDetector(const std::string & objectName,const std::string & dataFile)417 void vpPlanarObjectDetector::recordDetector(const std::string &objectName, const std::string &dataFile)
418 {
419   fern.record(objectName, dataFile);
420 }
421 
422 /*!
423   Return the last positions of the detected corners.
424 
425   \return The vectors of corners' postions.
426 */
getDetectedCorners() const427 std::vector<vpImagePoint> vpPlanarObjectDetector::getDetectedCorners() const
428 {
429   vpImagePoint ip;
430   std::vector<vpImagePoint> corners;
431   corners.clear();
432   for (unsigned int i = 0; i < dst_corners.size(); i++) {
433     ip.set_uv(dst_corners[i].x, dst_corners[i].y);
434     corners.push_back(ip);
435   }
436 
437   return corners;
438 }
439 
440 /*!
441   Initialise the internal reference corners from the rectangle.
442 
443   \param _modelROI : The rectangle defining the region of interest.
444 */
initialiseRefCorners(const cv::Rect & _modelROI)445 void vpPlanarObjectDetector::initialiseRefCorners(const cv::Rect &_modelROI)
446 {
447   cv::Point2f ip;
448 
449   ip.y = (float)_modelROI.y;
450   ip.x = (float)_modelROI.x;
451   ref_corners.push_back(ip);
452 
453   ip.y = (float)(_modelROI.y + _modelROI.height);
454   ip.x = (float)_modelROI.x;
455   ref_corners.push_back(ip);
456 
457   ip.y = (float)(_modelROI.y + _modelROI.height);
458   ip.x = (float)(_modelROI.x + _modelROI.width);
459   ref_corners.push_back(ip);
460 
461   ip.y = (float)_modelROI.y;
462   ip.x = (float)(_modelROI.x + _modelROI.width);
463   ref_corners.push_back(ip);
464 }
465 
getReferencePoint(unsigned int _i,vpImagePoint & _imPoint)466 void vpPlanarObjectDetector::getReferencePoint(unsigned int _i, vpImagePoint &_imPoint)
467 {
468   if (_i >= refImagePoints.size()) {
469     throw vpException(vpException::fatalError, "index out of bound in getMatchedPoints.");
470   }
471   _imPoint = refImagePoints[_i];
472 }
473 
getMatchedPoints(const unsigned int _index,vpImagePoint & _referencePoint,vpImagePoint & _currentPoint)474 void vpPlanarObjectDetector::getMatchedPoints(const unsigned int _index, vpImagePoint &_referencePoint,
475                                               vpImagePoint &_currentPoint)
476 {
477   //  fern.getMatchedPoints(_index, _referencePoint, _currentPoint);
478   if (_index >= currentImagePoints.size()) {
479     throw vpException(vpException::fatalError, "index out of bound in getMatchedPoints.");
480   }
481 
482   _referencePoint = refImagePoints[_index];
483   _currentPoint = currentImagePoints[_index];
484 }
485 
486 #elif !defined(VISP_BUILD_SHARED_LIBS)
487 // Work arround to avoid warning:
488 // libvisp_vision.a(vpPlanarObjectDetector.cpp.o) has no symbols
dummy_vpPlanarObjectDetector()489 void dummy_vpPlanarObjectDetector(){};
490 #endif
491