MRPT  2.0.4
CFeatureLines.cpp
Go to the documentation of this file.
1 /* +---------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2017, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in https://www.mrpt.org/License |
8  +---------------------------------------------------------------------------+
9  */
10 
11 #include "vision-precomp.h" // Precompiled headers
12 
13 #include <mrpt/core/exceptions.h>
15 #include <mrpt/vision/utils.h>
16 
17 // Universal include for all versions of OpenCV
18 #include <mrpt/3rdparty/do_opencv_includes.h>
19 
20 using namespace mrpt::vision;
21 using namespace std;
22 
23 #if MRPT_HAS_OPENCV
24 
26  const cv::Mat& canny_image, const std::vector<cv::Vec2f> lines,
27  std::vector<cv::Vec4i>& segments, size_t threshold)
28 {
29  // Some variables to change the coordinate system from polar to cartesian
30  double rho, theta;
31  double cosTheta, sinTheta;
32  double m, c, cMax;
33 
34  // Variables to define the two extreme points on the line, clipped on the
35  // window The constraint is x <= xF
36  int x, y, xF, yF;
37 
38  segments.clear();
39 
40  // For each line
41  for (size_t n(0); n < lines.size(); ++n)
42  {
43  // OpenCV implements the Hough accumulator with theta from 0 to PI, thus
44  // rho could be negative We want to always have rho >= 0
45  if (lines[n][0] < 0)
46  {
47  rho = -lines[n][0];
48  theta = lines[n][1] - CV_PI;
49  }
50  else
51  {
52  rho = lines[n][0];
53  theta = lines[n][1];
54  }
55 
56  if (rho == 0 && theta == 0)
57  { // That case appeared at least once, so a test is needed
58  continue;
59  }
60 
61  // Since the step for theta in cv::HoughLines should not be too small,
62  // theta should exactly be 0 (and the line vertical) when this condition
63  // is true
64  if (fabs(theta) < 0.00001)
65  {
66  x = xF = lround(rho);
67  y = 0;
68  yF = canny_image.rows - 1;
69  }
70  else
71  {
72  // Get the (m, c) slope and y-intercept from (rho, theta), so that y
73  // = mx + c <=> (x, y) is on the line
74  cosTheta = cos(theta);
75  sinTheta = sin(theta);
76  m = -cosTheta / sinTheta; // We are certain that sinTheta != 0
77  c = rho * (sinTheta - m * cosTheta);
78 
79  // Get the two extreme points (x, y) and (xF, xF) for the line
80  // inside the window, using (m, c)
81  if (c >= 0)
82  {
83  if (c < canny_image.rows)
84  {
85  // (x, y) is at the left of the window
86  x = 0;
87  y = static_cast<int>(c);
88  }
89  else
90  {
91  // (x, y) is at the bottom of the window
92  y = canny_image.rows - 1;
93  x = static_cast<int>((y - c) / m);
94  }
95  }
96  else
97  {
98  // (x, y) is at the top of the window
99  x = static_cast<int>(-c / m);
100  y = 0;
101  }
102  // Define the intersection with the right side of the window
103  cMax = m * (canny_image.cols - 1) + c;
104  if (cMax >= 0)
105  {
106  if (cMax < canny_image.rows)
107  {
108  // (xF, yF) is at the right of the window
109  xF = canny_image.cols - 1;
110  yF = static_cast<int>(cMax);
111  }
112  else
113  {
114  // (xF, yF) is at the bottom of the window
115  yF = canny_image.rows - 1;
116  xF = static_cast<int>((yF - c) / m);
117  }
118  }
119  else
120  {
121  // (xF, yF) is at the top of the window
122  xF = static_cast<int>(-c / m);
123  yF = 0;
124  }
125  }
126 
127  // Going through the line using the Bresenham algorithm
128  // dx1, dx2, dy1 and dy2 are increments that allow to be successful for
129  // each of the 8 octants (possible directions while iterating)
130  bool onSegment = false;
131  int memory;
132  int memoryX = 0, memoryY = 0;
133  int xPrev = 0, yPrev = 0;
134  size_t nbPixels = 0;
135 
136  int w = xF - x;
137  int h = yF - y;
138  int dx1, dy1, dx2, dy2 = 0;
139 
140  int longest, shortest;
141  int numerator;
142 
143  if (w < 0)
144  {
145  longest = -w;
146  dx1 = -1;
147  dx2 = -1;
148  }
149  else
150  {
151  longest = w;
152  dx1 = 1;
153  dx2 = 1;
154  }
155 
156  if (h < 0)
157  {
158  shortest = -h;
159  dy1 = -1;
160  }
161  else
162  {
163  shortest = h;
164  dy1 = 1;
165  }
166 
167  // We want to know whether the direction is more horizontal or vertical,
168  // and set the increments accordingly
169  if (longest <= shortest)
170  {
171  memory = longest;
172  longest = shortest;
173  shortest = memory;
174  dx2 = 0;
175  if (h < 0)
176  {
177  dy2 = -1;
178  }
179  else
180  {
181  dy2 = 1;
182  }
183  }
184 
185  numerator = longest / 2;
186 
187  // cout << n << " numerator " << numerator << endl;
188  for (int i(0); i <= longest; ++i)
189  {
190  // For each pixel, we don't want to use a classic "plot", but to
191  // look into canny_image for a black or white pixel
192  if (onSegment)
193  {
194  if (canny_image.at<char>(y, x) == 0 || i == longest)
195  {
196  // We are leaving a segment
197  onSegment = false;
198  if (nbPixels >= threshold)
199  {
200  segments.emplace_back(memoryX, memoryY, xPrev, yPrev);
201  // cout << "new segment " << segments.back() << endl;
202  }
203  }
204  else
205  {
206  // We are still on a segment
207  ++nbPixels;
208  }
209  }
210  else if (canny_image.at<char>(y, x) != 0)
211  {
212  // We are entering a segment, and keep this first position in
213  // (memoryX, memoryY)
214  onSegment = true;
215  nbPixels = 0;
216  memoryX = x;
217  memoryY = y;
218  }
219 
220  // xPrev and yPrev are used when leaving a segment, to keep in
221  // memory the last pixel on it
222  xPrev = x;
223  yPrev = y;
224 
225  // Next pixel using the condition of the Bresenham algorithm
226  numerator += shortest;
227  if (numerator >= longest)
228  {
229  numerator -= longest;
230  x += dx1;
231  y += dy1;
232  }
233  else
234  {
235  x += dx2;
236  y += dy2;
237  }
238  }
239  }
240 }
241 #endif
242 
244  const mrpt::img::CImage& img_in,
245  std::vector<std::array<int, 4>>& segments_out, size_t threshold,
246  const bool display)
247 {
248 #if MRPT_HAS_OPENCV
249 
250  using namespace cv;
251  using namespace std;
252  const Mat image = img_in.asCvMat<Mat>(mrpt::img::SHALLOW_COPY);
253 
254  // Canny edge detector
255  cv::Mat canny_img;
256  int lowThreshold = 150;
257  // int const max_lowThreshold = 100;
258  int ratio = 3;
259  int kernel_size = 3;
260  cv::Canny(
261  image, canny_img, lowThreshold, lowThreshold * ratio,
262  kernel_size); // 250, 600 // CAUTION: Both thresholds depend on the
263  // input image, they might be a bit hard to set because
264  // they depend on the strength of the gradients
265  // cv::namedWindow("Canny detector", cv::WINDOW_AUTOSIZE);
266  // cv::imshow("Canny detector", canny_img);
267  // cv::waitKey(0);
268  // cv::destroyWindow("Canny detector");
269 
270  // Get lines through the Hough transform
271 
272  // CvMat matCannyStub, *matCannyImg = (CvMat*)canny_img.getAs<IplImage>();
273  // CV_CALL(matCannyImg = cvGetMat(canny_img.getAs<IplImage>(),
274  // &matCannyStub));
275 
276  std::vector<cv::Vec2f> lines;
277  cv::HoughLines(
278  canny_img, lines, 1, CV_PI / 180.0,
279  threshold); // CAUTION: The last parameter depends on the input image,
280  // it's the smallest number of pixels to consider a line in
281  // the accumulator
282  // double minLineLength=50, maxLineGap=5;
283  // cv::HoughLinesP(canny_img, lines, 1, CV_PI / 180.0, threshold,
284  // minLineLength, maxLineGap); // CAUTION: The last parameter depends on
285  // the input image, it's the smallest number of pixels to consider a line
286  // in the accumulator
287  // std::cout << lines.size() << " lines detected" << std::endl;
288 
289  // Possible dilatation of the canny detector
290  // Useful when the lines are thin and not perfectly straight
291  cv::Mat filteredCanny;
292  // Choose whether filtering the Canny or not, for thin and non-perfect edges
293  /*filteredCanny = canny_img.clone();*/
294  cv::dilate(canny_img, filteredCanny, cv::Mat());
295  // cv::namedWindow("Filtered Canny detector");
296  // cv::imshow("Filtered Canny detector", filteredCanny);
297  // cv::waitKey(0);
298  // cv::destroyWindow("Filtered Canny detector");
299 
300  // Extracting segments (pairs of points) from the filtered Canny detector
301  // And using the line parameters from lines
302  std::vector<cv::Vec4i> segments;
303  extractLines_CannyHough(filteredCanny, lines, segments, threshold);
304 
305  // Force the segments to have a predefined order (e1y <= e2y)
306  for (auto line = begin(segments); line != end(segments); ++line)
307  if ((*line)[1] == (*line)[3])
308  {
309  if ((*line)[0] > (*line)[1])
310  *line =
311  cv::Vec4i((*line)[2], (*line)[3], (*line)[0], (*line)[1]);
312  }
313  else if ((*line)[1] < (*line)[3])
314  *line = cv::Vec4i((*line)[2], (*line)[3], (*line)[0], (*line)[1]);
315 
316  // convert cv->STL:
317  segments_out.resize(segments.size());
318  for (size_t i = 0; i < segments.size(); i++)
319  for (size_t k = 0; k < segments_out[i].size(); k++)
320  segments_out[i][k] = segments[i][k];
321 
322  // Display 2D segments
323  if (display)
324  {
325  cv::Mat image_lines;
326  image.convertTo(image_lines, CV_8UC1, 1.0 / 2);
327  for (auto line = begin(segments); line != end(segments); ++line)
328  {
329  cv::line(
330  image_lines, cv::Point((*line)[0], (*line)[1]),
331  cv::Point((*line)[2], (*line)[3]), cv::Scalar(255, 0, 255), 1);
332  cv::circle(
333  image_lines, cv::Point((*line)[0], (*line)[1]), 3,
334  cv::Scalar(255, 0, 255), 3);
335  cv::putText(
336  image_lines, string(to_string(distance(begin(segments), line))),
337  cv::Point(
338  ((*line)[0] + (*line)[2]) / 2,
339  ((*line)[1] + (*line)[3]) / 2),
340  0, 1.2, cv::Scalar(200, 0, 0), 3);
341  }
342  cv::imshow("lines", image_lines);
343  cv::moveWindow("lines", 20, 100 + 700);
344  cv::waitKey(0);
345  }
346 #else
347  THROW_EXCEPTION("Requires building MRPT with OpenCV support");
348 #endif
349 }
Shallow copy: the copied object is a reference to the original one.
Definition: img/CImage.h:75
double Scalar
Definition: KmUtils.h:43
std::string to_string(T v)
Just like std::to_string(), but with an overloaded version for std::string arguments.
Definition: format.h:36
#define THROW_EXCEPTION(msg)
Definition: exceptions.h:67
#define CV_PI
STL namespace.
Definition: img/CImage.h:23
void asCvMat(cv::Mat &out_img, copy_type_t copy_type) const
Makes a shallow or deep copy of this image into the provided cv::Mat.
Definition: CImage.cpp:220
Classes for computer vision, detectors, features, etc.
Definition: CDifodo.h:17
const_iterator end() const
Definition: ts_hash_map.h:246
const_iterator begin() const
Definition: ts_hash_map.h:240
void extractLines(const mrpt::img::CImage &image, std::vector< std::array< int, 4 >> &segments, size_t threshold, const bool display=false)
static void extractLines_CannyHough(const cv::Mat &canny_image, const std::vector< cv::Vec2f > lines, std::vector< cv::Vec4i > &segments, size_t threshold)
double distance(const TPoint2D &p1, const TPoint2D &p2)
Gets the distance between two points in a 2D space.
Definition: geometry.cpp:1807
A class for storing images as grayscale or RGB bitmaps.
Definition: img/CImage.h:148



Page generated by Doxygen 1.8.14 for MRPT 2.0.4 Git: 33de1d0ad Sat Jun 20 11:02:42 2020 +0200 at sáb jun 20 17:35:17 CEST 2020