1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html
4
5 // This code is also subject to the license terms in the LICENSE_WillowGarage.md file found in this module's directory
6
7 #include "precomp.hpp"
8
9 namespace cv
10 {
11 namespace linemod
12 {
13
14 // struct Feature
15
16 /**
17 * \brief Get the label [0,8) of the single bit set in quantized.
18 */
getLabel(int quantized)19 static inline int getLabel(int quantized)
20 {
21 switch (quantized)
22 {
23 case 1: return 0;
24 case 2: return 1;
25 case 4: return 2;
26 case 8: return 3;
27 case 16: return 4;
28 case 32: return 5;
29 case 64: return 6;
30 case 128: return 7;
31 default:
32 CV_Error(Error::StsBadArg, "Invalid value of quantized parameter");
33 }
34 }
35
read(const FileNode & fn)36 void Feature::read(const FileNode& fn)
37 {
38 FileNodeIterator fni = fn.begin();
39 fni >> x >> y >> label;
40 }
41
write(FileStorage & fs) const42 void Feature::write(FileStorage& fs) const
43 {
44 fs << "[:" << x << y << label << "]";
45 }
46
47 // struct Template
48
49 /**
50 * \brief Crop a set of overlapping templates from different modalities.
51 *
52 * \param[in,out] templates Set of templates representing the same object view.
53 *
54 * \return The bounding box of all the templates in original image coordinates.
55 */
cropTemplates(std::vector<Template> & templates)56 static Rect cropTemplates(std::vector<Template>& templates)
57 {
58 int min_x = std::numeric_limits<int>::max();
59 int min_y = std::numeric_limits<int>::max();
60 int max_x = std::numeric_limits<int>::min();
61 int max_y = std::numeric_limits<int>::min();
62
63 // First pass: find min/max feature x,y over all pyramid levels and modalities
64 for (int i = 0; i < (int)templates.size(); ++i)
65 {
66 Template& templ = templates[i];
67
68 for (int j = 0; j < (int)templ.features.size(); ++j)
69 {
70 int x = templ.features[j].x << templ.pyramid_level;
71 int y = templ.features[j].y << templ.pyramid_level;
72 min_x = std::min(min_x, x);
73 min_y = std::min(min_y, y);
74 max_x = std::max(max_x, x);
75 max_y = std::max(max_y, y);
76 }
77 }
78
79 /// @todo Why require even min_x, min_y?
80 if (min_x % 2 == 1) --min_x;
81 if (min_y % 2 == 1) --min_y;
82
83 // Second pass: set width/height and shift all feature positions
84 for (int i = 0; i < (int)templates.size(); ++i)
85 {
86 Template& templ = templates[i];
87 templ.width = (max_x - min_x) >> templ.pyramid_level;
88 templ.height = (max_y - min_y) >> templ.pyramid_level;
89 int offset_x = min_x >> templ.pyramid_level;
90 int offset_y = min_y >> templ.pyramid_level;
91
92 for (int j = 0; j < (int)templ.features.size(); ++j)
93 {
94 templ.features[j].x -= offset_x;
95 templ.features[j].y -= offset_y;
96 }
97 }
98
99 return Rect(min_x, min_y, max_x - min_x, max_y - min_y);
100 }
101
read(const FileNode & fn)102 void Template::read(const FileNode& fn)
103 {
104 width = fn["width"];
105 height = fn["height"];
106 pyramid_level = fn["pyramid_level"];
107
108 FileNode features_fn = fn["features"];
109 features.resize(features_fn.size());
110 FileNodeIterator it = features_fn.begin(), it_end = features_fn.end();
111 for (int i = 0; it != it_end; ++it, ++i)
112 {
113 features[i].read(*it);
114 }
115 }
116
write(FileStorage & fs) const117 void Template::write(FileStorage& fs) const
118 {
119 fs << "width" << width;
120 fs << "height" << height;
121 fs << "pyramid_level" << pyramid_level;
122
123 fs << "features" << "[";
124 for (int i = 0; i < (int)features.size(); ++i)
125 {
126 features[i].write(fs);
127 }
128 fs << "]"; // features
129 }
130
131 /****************************************************************************************\
132 * Modality interfaces *
133 \****************************************************************************************/
134
selectScatteredFeatures(const std::vector<Candidate> & candidates,std::vector<Feature> & features,size_t num_features,float distance)135 void QuantizedPyramid::selectScatteredFeatures(const std::vector<Candidate>& candidates,
136 std::vector<Feature>& features,
137 size_t num_features, float distance)
138 {
139 features.clear();
140 float distance_sq = distance * distance;
141 int i = 0;
142 while (features.size() < num_features)
143 {
144 Candidate c = candidates[i];
145
146 // Add if sufficient distance away from any previously chosen feature
147 bool keep = true;
148 for (int j = 0; (j < (int)features.size()) && keep; ++j)
149 {
150 Feature f = features[j];
151 keep = (c.f.x - f.x)*(c.f.x - f.x) + (c.f.y - f.y)*(c.f.y - f.y) >= distance_sq;
152 }
153 if (keep)
154 features.push_back(c.f);
155
156 if (++i == (int)candidates.size())
157 {
158 // Start back at beginning, and relax required distance
159 i = 0;
160 distance -= 1.0f;
161 distance_sq = distance * distance;
162 }
163 }
164 }
165
create(const String & modality_type)166 Ptr<Modality> Modality::create(const String& modality_type)
167 {
168 if (modality_type == "ColorGradient")
169 return makePtr<ColorGradient>();
170 else if (modality_type == "DepthNormal")
171 return makePtr<DepthNormal>();
172 else
173 return Ptr<Modality>();
174 }
175
create(const FileNode & fn)176 Ptr<Modality> Modality::create(const FileNode& fn)
177 {
178 String type = fn["type"];
179 Ptr<Modality> modality = create(type);
180 modality->read(fn);
181 return modality;
182 }
183
colormap(const Mat & quantized,Mat & dst)184 void colormap(const Mat& quantized, Mat& dst)
185 {
186 std::vector<Vec3b> lut(8);
187 lut[0] = Vec3b( 0, 0, 255);
188 lut[1] = Vec3b( 0, 170, 255);
189 lut[2] = Vec3b( 0, 255, 170);
190 lut[3] = Vec3b( 0, 255, 0);
191 lut[4] = Vec3b(170, 255, 0);
192 lut[5] = Vec3b(255, 170, 0);
193 lut[6] = Vec3b(255, 0, 0);
194 lut[7] = Vec3b(255, 0, 170);
195
196 dst = Mat::zeros(quantized.size(), CV_8UC3);
197 for (int r = 0; r < dst.rows; ++r)
198 {
199 const uchar* quant_r = quantized.ptr(r);
200 Vec3b* dst_r = dst.ptr<Vec3b>(r);
201 for (int c = 0; c < dst.cols; ++c)
202 {
203 uchar q = quant_r[c];
204 if (q)
205 dst_r[c] = lut[getLabel(q)];
206 }
207 }
208 }
209
drawFeatures(InputOutputArray img,const std::vector<Template> & templates,const Point2i & tl,int size)210 void drawFeatures(InputOutputArray img, const std::vector<Template>& templates, const Point2i& tl, int size)
211 {
212 #ifdef HAVE_OPENCV_IMGPROC
213 static Scalar colors[] = {{0, 0, 255}, {0, 255, 0}};
214 static int markers[] = {MARKER_SQUARE, MARKER_DIAMOND};
215
216 int modality = 0;
217 for(const Template& t : templates)
218 {
219 if(t.pyramid_level != 0) continue;
220
221 for(const Feature& f : t.features)
222 {
223 drawMarker(img, tl + Point(f.x, f.y), colors[int(modality != 0)], markers[int(modality != 0)], size);
224 }
225
226 modality++;
227 }
228 #else
229 CV_Assert(false, "functionality needs imgproc module");
230 #endif
231 }
232
233 /****************************************************************************************\
234 * Color gradient modality *
235 \****************************************************************************************/
236
237 // Forward declaration
238 void hysteresisGradient(Mat& magnitude, Mat& angle,
239 Mat& ap_tmp, float threshold);
240
241 /**
242 * \brief Compute quantized orientation image from color image.
243 *
244 * Implements section 2.2 "Computing the Gradient Orientations."
245 *
246 * \param[in] src The source 8-bit, 3-channel image.
247 * \param[out] magnitude Destination floating-point array of squared magnitudes.
248 * \param[out] angle Destination 8-bit array of orientations. Each bit
249 * represents one bin of the orientation space.
250 * \param threshold Magnitude threshold. Keep only gradients whose norms are
251 * larger than this.
252 */
quantizedOrientations(const Mat & src,Mat & magnitude,Mat & angle,float threshold)253 static void quantizedOrientations(const Mat& src, Mat& magnitude,
254 Mat& angle, float threshold)
255 {
256 magnitude.create(src.size(), CV_32F);
257
258 // Allocate temporary buffers
259 Size size = src.size();
260 Mat sobel_3dx; // per-channel horizontal derivative
261 Mat sobel_3dy; // per-channel vertical derivative
262 Mat sobel_dx(size, CV_32F); // maximum horizontal derivative
263 Mat sobel_dy(size, CV_32F); // maximum vertical derivative
264 Mat sobel_ag; // final gradient orientation (unquantized)
265 Mat smoothed;
266
267 // Compute horizontal and vertical image derivatives on all color channels separately
268 static const int KERNEL_SIZE = 7;
269 // For some reason cvSmooth/cv::GaussianBlur, cvSobel/cv::Sobel have different defaults for border handling...
270 GaussianBlur(src, smoothed, Size(KERNEL_SIZE, KERNEL_SIZE), 0, 0, BORDER_REPLICATE);
271 Sobel(smoothed, sobel_3dx, CV_16S, 1, 0, 3, 1.0, 0.0, BORDER_REPLICATE);
272 Sobel(smoothed, sobel_3dy, CV_16S, 0, 1, 3, 1.0, 0.0, BORDER_REPLICATE);
273
274 short * ptrx = (short *)sobel_3dx.data;
275 short * ptry = (short *)sobel_3dy.data;
276 float * ptr0x = (float *)sobel_dx.data;
277 float * ptr0y = (float *)sobel_dy.data;
278 float * ptrmg = (float *)magnitude.data;
279
280 const int length1 = static_cast<int>(sobel_3dx.step1());
281 const int length2 = static_cast<int>(sobel_3dy.step1());
282 const int length3 = static_cast<int>(sobel_dx.step1());
283 const int length4 = static_cast<int>(sobel_dy.step1());
284 const int length5 = static_cast<int>(magnitude.step1());
285 const int length0 = sobel_3dy.cols * 3;
286
287 for (int r = 0; r < sobel_3dy.rows; ++r)
288 {
289 int ind = 0;
290
291 for (int i = 0; i < length0; i += 3)
292 {
293 // Use the gradient orientation of the channel whose magnitude is largest
294 int mag1 = ptrx[i+0] * ptrx[i + 0] + ptry[i + 0] * ptry[i + 0];
295 int mag2 = ptrx[i+1] * ptrx[i + 1] + ptry[i + 1] * ptry[i + 1];
296 int mag3 = ptrx[i+2] * ptrx[i + 2] + ptry[i + 2] * ptry[i + 2];
297
298 if (mag1 >= mag2 && mag1 >= mag3)
299 {
300 ptr0x[ind] = ptrx[i];
301 ptr0y[ind] = ptry[i];
302 ptrmg[ind] = (float)mag1;
303 }
304 else if (mag2 >= mag1 && mag2 >= mag3)
305 {
306 ptr0x[ind] = ptrx[i + 1];
307 ptr0y[ind] = ptry[i + 1];
308 ptrmg[ind] = (float)mag2;
309 }
310 else
311 {
312 ptr0x[ind] = ptrx[i + 2];
313 ptr0y[ind] = ptry[i + 2];
314 ptrmg[ind] = (float)mag3;
315 }
316 ++ind;
317 }
318 ptrx += length1;
319 ptry += length2;
320 ptr0x += length3;
321 ptr0y += length4;
322 ptrmg += length5;
323 }
324
325 // Calculate the final gradient orientations
326 phase(sobel_dx, sobel_dy, sobel_ag, true);
327 hysteresisGradient(magnitude, angle, sobel_ag, threshold * threshold);
328 }
329
hysteresisGradient(Mat & magnitude,Mat & quantized_angle,Mat & angle,float threshold)330 void hysteresisGradient(Mat& magnitude, Mat& quantized_angle,
331 Mat& angle, float threshold)
332 {
333 // Quantize 360 degree range of orientations into 16 buckets
334 // Note that [0, 11.25), [348.75, 360) both get mapped in the end to label 0,
335 // for stability of horizontal and vertical features.
336 Mat_<unsigned char> quantized_unfiltered;
337 angle.convertTo(quantized_unfiltered, CV_8U, 16.0 / 360.0);
338
339 // Zero out top and bottom rows
340 /// @todo is this necessary, or even correct?
341 memset(quantized_unfiltered.ptr(), 0, quantized_unfiltered.cols);
342 memset(quantized_unfiltered.ptr(quantized_unfiltered.rows - 1), 0, quantized_unfiltered.cols);
343 // Zero out first and last columns
344 for (int r = 0; r < quantized_unfiltered.rows; ++r)
345 {
346 quantized_unfiltered(r, 0) = 0;
347 quantized_unfiltered(r, quantized_unfiltered.cols - 1) = 0;
348 }
349
350 // Mask 16 buckets into 8 quantized orientations
351 for (int r = 1; r < angle.rows - 1; ++r)
352 {
353 uchar* quant_r = quantized_unfiltered.ptr<uchar>(r);
354 for (int c = 1; c < angle.cols - 1; ++c)
355 {
356 quant_r[c] &= 7;
357 }
358 }
359
360 // Filter the raw quantized image. Only accept pixels where the magnitude is above some
361 // threshold, and there is local agreement on the quantization.
362 quantized_angle = Mat::zeros(angle.size(), CV_8U);
363 for (int r = 1; r < angle.rows - 1; ++r)
364 {
365 float* mag_r = magnitude.ptr<float>(r);
366
367 for (int c = 1; c < angle.cols - 1; ++c)
368 {
369 if (mag_r[c] > threshold)
370 {
371 // Compute histogram of quantized bins in 3x3 patch around pixel
372 int histogram[8] = {0, 0, 0, 0, 0, 0, 0, 0};
373
374 uchar* patch3x3_row = &quantized_unfiltered(r-1, c-1);
375 histogram[patch3x3_row[0]]++;
376 histogram[patch3x3_row[1]]++;
377 histogram[patch3x3_row[2]]++;
378
379 patch3x3_row += quantized_unfiltered.step1();
380 histogram[patch3x3_row[0]]++;
381 histogram[patch3x3_row[1]]++;
382 histogram[patch3x3_row[2]]++;
383
384 patch3x3_row += quantized_unfiltered.step1();
385 histogram[patch3x3_row[0]]++;
386 histogram[patch3x3_row[1]]++;
387 histogram[patch3x3_row[2]]++;
388
389 // Find bin with the most votes from the patch
390 int max_votes = 0;
391 int index = -1;
392 for (int i = 0; i < 8; ++i)
393 {
394 if (max_votes < histogram[i])
395 {
396 index = i;
397 max_votes = histogram[i];
398 }
399 }
400
401 // Only accept the quantization if majority of pixels in the patch agree
402 static const int NEIGHBOR_THRESHOLD = 5;
403 if (max_votes >= NEIGHBOR_THRESHOLD)
404 quantized_angle.at<uchar>(r, c) = uchar(1 << index);
405 }
406 }
407 }
408 }
409
410 class ColorGradientPyramid : public QuantizedPyramid
411 {
412 public:
413 ColorGradientPyramid(const Mat& src, const Mat& mask,
414 float weak_threshold, size_t num_features,
415 float strong_threshold);
416
417 virtual void quantize(Mat& dst) const CV_OVERRIDE;
418
419 virtual bool extractTemplate(Template& templ) const CV_OVERRIDE;
420
421 virtual void pyrDown() CV_OVERRIDE;
422
423 protected:
424 /// Recalculate angle and magnitude images
425 void update();
426
427 Mat src;
428 Mat mask;
429
430 int pyramid_level;
431 Mat angle;
432 Mat magnitude;
433
434 float weak_threshold;
435 size_t num_features;
436 float strong_threshold;
437 };
438
ColorGradientPyramid(const Mat & _src,const Mat & _mask,float _weak_threshold,size_t _num_features,float _strong_threshold)439 ColorGradientPyramid::ColorGradientPyramid(const Mat& _src, const Mat& _mask,
440 float _weak_threshold, size_t _num_features,
441 float _strong_threshold)
442 : src(_src),
443 mask(_mask),
444 pyramid_level(0),
445 weak_threshold(_weak_threshold),
446 num_features(_num_features),
447 strong_threshold(_strong_threshold)
448 {
449 update();
450 }
451
update()452 void ColorGradientPyramid::update()
453 {
454 quantizedOrientations(src, magnitude, angle, weak_threshold);
455 }
456
pyrDown()457 void ColorGradientPyramid::pyrDown()
458 {
459 // Some parameters need to be adjusted
460 num_features /= 2; /// @todo Why not 4?
461 ++pyramid_level;
462
463 // Downsample the current inputs
464 Size size(src.cols / 2, src.rows / 2);
465 Mat next_src;
466 cv::pyrDown(src, next_src, size);
467 src = next_src;
468 if (!mask.empty())
469 {
470 Mat next_mask;
471 resize(mask, next_mask, size, 0.0, 0.0, INTER_NEAREST);
472 mask = next_mask;
473 }
474
475 update();
476 }
477
quantize(Mat & dst) const478 void ColorGradientPyramid::quantize(Mat& dst) const
479 {
480 dst = Mat::zeros(angle.size(), CV_8U);
481 angle.copyTo(dst, mask);
482 }
483
extractTemplate(Template & templ) const484 bool ColorGradientPyramid::extractTemplate(Template& templ) const
485 {
486 // Want features on the border to distinguish from background
487 Mat local_mask;
488 if (!mask.empty())
489 {
490 erode(mask, local_mask, Mat(), Point(-1,-1), 1, BORDER_REPLICATE);
491 subtract(mask, local_mask, local_mask);
492 }
493
494 // Create sorted list of all pixels with magnitude greater than a threshold
495 std::vector<Candidate> candidates;
496 bool no_mask = local_mask.empty();
497 float threshold_sq = strong_threshold*strong_threshold;
498 for (int r = 0; r < magnitude.rows; ++r)
499 {
500 const uchar* angle_r = angle.ptr<uchar>(r);
501 const float* magnitude_r = magnitude.ptr<float>(r);
502 const uchar* mask_r = no_mask ? NULL : local_mask.ptr<uchar>(r);
503
504 for (int c = 0; c < magnitude.cols; ++c)
505 {
506 if (no_mask || mask_r[c])
507 {
508 uchar quantized = angle_r[c];
509 if (quantized > 0)
510 {
511 float score = magnitude_r[c];
512 if (score > threshold_sq)
513 {
514 candidates.push_back(Candidate(c, r, getLabel(quantized), score));
515 }
516 }
517 }
518 }
519 }
520 // We require a certain number of features
521 if (candidates.size() < num_features)
522 return false;
523 // NOTE: Stable sort to agree with old code, which used std::list::sort()
524 std::stable_sort(candidates.begin(), candidates.end());
525
526 // Use heuristic based on surplus of candidates in narrow outline for initial distance threshold
527 float distance = static_cast<float>(candidates.size() / num_features + 1);
528 selectScatteredFeatures(candidates, templ.features, num_features, distance);
529
530 // Size determined externally, needs to match templates for other modalities
531 templ.width = -1;
532 templ.height = -1;
533 templ.pyramid_level = pyramid_level;
534
535 return true;
536 }
537
ColorGradient()538 ColorGradient::ColorGradient()
539 : weak_threshold(10.0f),
540 num_features(63),
541 strong_threshold(55.0f)
542 {
543 }
544
ColorGradient(float _weak_threshold,size_t _num_features,float _strong_threshold)545 ColorGradient::ColorGradient(float _weak_threshold, size_t _num_features, float _strong_threshold)
546 : weak_threshold(_weak_threshold),
547 num_features(_num_features),
548 strong_threshold(_strong_threshold)
549 {
550 }
551
create(float weak_threshold,size_t num_features,float strong_threshold)552 Ptr<ColorGradient> ColorGradient::create(float weak_threshold, size_t num_features, float strong_threshold)
553 {
554 return makePtr<ColorGradient>(weak_threshold, num_features, strong_threshold);
555 }
556
557 static const char CG_NAME[] = "ColorGradient";
558
name() const559 String ColorGradient::name() const
560 {
561 return CG_NAME;
562 }
563
processImpl(const Mat & src,const Mat & mask) const564 Ptr<QuantizedPyramid> ColorGradient::processImpl(const Mat& src,
565 const Mat& mask) const
566 {
567 return makePtr<ColorGradientPyramid>(src, mask, weak_threshold, num_features, strong_threshold);
568 }
569
read(const FileNode & fn)570 void ColorGradient::read(const FileNode& fn)
571 {
572 String type = fn["type"];
573 CV_Assert(type == CG_NAME);
574
575 weak_threshold = fn["weak_threshold"];
576 num_features = int(fn["num_features"]);
577 strong_threshold = fn["strong_threshold"];
578 }
579
write(FileStorage & fs) const580 void ColorGradient::write(FileStorage& fs) const
581 {
582 fs << "type" << CG_NAME;
583 fs << "weak_threshold" << weak_threshold;
584 fs << "num_features" << int(num_features);
585 fs << "strong_threshold" << strong_threshold;
586 }
587
588 /****************************************************************************************\
589 * Depth normal modality *
590 \****************************************************************************************/
591
592 // Contains GRANULARITY and NORMAL_LUT
593 #include "normal_lut.i"
594
accumBilateral(long delta,long i,long j,long * A,long * b,int threshold)595 static void accumBilateral(long delta, long i, long j, long * A, long * b, int threshold)
596 {
597 long f = std::abs(delta) < threshold ? 1 : 0;
598
599 const long fi = f * i;
600 const long fj = f * j;
601
602 A[0] += fi * i;
603 A[1] += fi * j;
604 A[3] += fj * j;
605 b[0] += fi * delta;
606 b[1] += fj * delta;
607 }
608
609 /**
610 * \brief Compute quantized normal image from depth image.
611 *
612 * Implements section 2.6 "Extension to Dense Depth Sensors."
613 *
614 * \param[in] src The source 16-bit depth image (in mm).
615 * \param[out] dst The destination 8-bit image. Each bit represents one bin of
616 * the view cone.
617 * \param distance_threshold Ignore pixels beyond this distance.
618 * \param difference_threshold When computing normals, ignore contributions of pixels whose
619 * depth difference with the central pixel is above this threshold.
620 *
621 * \todo Should also need camera model, or at least focal lengths? Replace distance_threshold with mask?
622 */
quantizedNormals(const Mat & src,Mat & dst,int distance_threshold,int difference_threshold)623 static void quantizedNormals(const Mat& src, Mat& dst, int distance_threshold,
624 int difference_threshold)
625 {
626 dst = Mat::zeros(src.size(), CV_8U);
627
628 const unsigned short * lp_depth = src.ptr<ushort>();
629 unsigned char * lp_normals = dst.ptr<uchar>();
630
631 const int l_W = src.cols;
632 const int l_H = src.rows;
633
634 const int l_r = 5; // used to be 7
635 const int l_offset0 = -l_r - l_r * l_W;
636 const int l_offset1 = 0 - l_r * l_W;
637 const int l_offset2 = +l_r - l_r * l_W;
638 const int l_offset3 = -l_r;
639 const int l_offset4 = +l_r;
640 const int l_offset5 = -l_r + l_r * l_W;
641 const int l_offset6 = 0 + l_r * l_W;
642 const int l_offset7 = +l_r + l_r * l_W;
643
644 const int l_offsetx = GRANULARITY / 2;
645 const int l_offsety = GRANULARITY / 2;
646
647 for (int l_y = l_r; l_y < l_H - l_r - 1; ++l_y)
648 {
649 const unsigned short * lp_line = lp_depth + (l_y * l_W + l_r);
650 unsigned char * lp_norm = lp_normals + (l_y * l_W + l_r);
651
652 for (int l_x = l_r; l_x < l_W - l_r - 1; ++l_x)
653 {
654 long l_d = lp_line[0];
655
656 if (l_d < distance_threshold)
657 {
658 // accum
659 long l_A[4]; l_A[0] = l_A[1] = l_A[2] = l_A[3] = 0;
660 long l_b[2]; l_b[0] = l_b[1] = 0;
661 accumBilateral(lp_line[l_offset0] - l_d, -l_r, -l_r, l_A, l_b, difference_threshold);
662 accumBilateral(lp_line[l_offset1] - l_d, 0, -l_r, l_A, l_b, difference_threshold);
663 accumBilateral(lp_line[l_offset2] - l_d, +l_r, -l_r, l_A, l_b, difference_threshold);
664 accumBilateral(lp_line[l_offset3] - l_d, -l_r, 0, l_A, l_b, difference_threshold);
665 accumBilateral(lp_line[l_offset4] - l_d, +l_r, 0, l_A, l_b, difference_threshold);
666 accumBilateral(lp_line[l_offset5] - l_d, -l_r, +l_r, l_A, l_b, difference_threshold);
667 accumBilateral(lp_line[l_offset6] - l_d, 0, +l_r, l_A, l_b, difference_threshold);
668 accumBilateral(lp_line[l_offset7] - l_d, +l_r, +l_r, l_A, l_b, difference_threshold);
669
670 // solve
671 long l_det = l_A[0] * l_A[3] - l_A[1] * l_A[1];
672 long l_ddx = l_A[3] * l_b[0] - l_A[1] * l_b[1];
673 long l_ddy = -l_A[1] * l_b[0] + l_A[0] * l_b[1];
674
675 /// @todo Magic number 1150 is focal length? This is something like
676 /// f in SXGA mode, but in VGA is more like 530.
677 float l_nx = static_cast<float>(1150 * l_ddx);
678 float l_ny = static_cast<float>(1150 * l_ddy);
679 float l_nz = static_cast<float>(-l_det * l_d);
680
681 float l_sqrt = sqrtf(l_nx * l_nx + l_ny * l_ny + l_nz * l_nz);
682
683 if (l_sqrt > 0)
684 {
685 float l_norminv = 1.0f / (l_sqrt);
686
687 l_nx *= l_norminv;
688 l_ny *= l_norminv;
689 l_nz *= l_norminv;
690
691 //*lp_norm = fabs(l_nz)*255;
692
693 int l_val1 = static_cast<int>(l_nx * l_offsetx + l_offsetx);
694 int l_val2 = static_cast<int>(l_ny * l_offsety + l_offsety);
695 int l_val3 = static_cast<int>(l_nz * GRANULARITY + GRANULARITY);
696
697 *lp_norm = NORMAL_LUT[l_val3][l_val2][l_val1];
698 }
699 else
700 {
701 *lp_norm = 0; // Discard shadows from depth sensor
702 }
703 }
704 else
705 {
706 *lp_norm = 0; //out of depth
707 }
708 ++lp_line;
709 ++lp_norm;
710 }
711 }
712 medianBlur(dst, dst, 5);
713 }
714
715 class DepthNormalPyramid : public QuantizedPyramid
716 {
717 public:
718 DepthNormalPyramid(const Mat& src, const Mat& mask,
719 int distance_threshold, int difference_threshold, size_t num_features,
720 int extract_threshold);
721
722 virtual void quantize(Mat& dst) const CV_OVERRIDE;
723
724 virtual bool extractTemplate(Template& templ) const CV_OVERRIDE;
725
726 virtual void pyrDown() CV_OVERRIDE;
727
728 protected:
729 Mat mask;
730
731 int pyramid_level;
732 Mat normal;
733
734 size_t num_features;
735 int extract_threshold;
736 };
737
DepthNormalPyramid(const Mat & src,const Mat & _mask,int distance_threshold,int difference_threshold,size_t _num_features,int _extract_threshold)738 DepthNormalPyramid::DepthNormalPyramid(const Mat& src, const Mat& _mask,
739 int distance_threshold, int difference_threshold, size_t _num_features,
740 int _extract_threshold)
741 : mask(_mask),
742 pyramid_level(0),
743 num_features(_num_features),
744 extract_threshold(_extract_threshold)
745 {
746 quantizedNormals(src, normal, distance_threshold, difference_threshold);
747 }
748
pyrDown()749 void DepthNormalPyramid::pyrDown()
750 {
751 // Some parameters need to be adjusted
752 num_features /= 2; /// @todo Why not 4?
753 extract_threshold /= 2;
754 ++pyramid_level;
755
756 // In this case, NN-downsample the quantized image
757 Mat next_normal;
758 Size size(normal.cols / 2, normal.rows / 2);
759 resize(normal, next_normal, size, 0.0, 0.0, INTER_NEAREST);
760 normal = next_normal;
761 if (!mask.empty())
762 {
763 Mat next_mask;
764 resize(mask, next_mask, size, 0.0, 0.0, INTER_NEAREST);
765 mask = next_mask;
766 }
767 }
768
quantize(Mat & dst) const769 void DepthNormalPyramid::quantize(Mat& dst) const
770 {
771 dst = Mat::zeros(normal.size(), CV_8U);
772 normal.copyTo(dst, mask);
773 }
774
extractTemplate(Template & templ) const775 bool DepthNormalPyramid::extractTemplate(Template& templ) const
776 {
777 // Features right on the object border are unreliable
778 Mat local_mask;
779 if (!mask.empty())
780 {
781 erode(mask, local_mask, Mat(), Point(-1,-1), 2, BORDER_REPLICATE);
782 }
783
784 // Compute distance transform for each individual quantized orientation
785 Mat temp = Mat::zeros(normal.size(), CV_8U);
786 Mat distances[8];
787 for (int i = 0; i < 8; ++i)
788 {
789 temp.setTo(1 << i, local_mask);
790 bitwise_and(temp, normal, temp);
791 // temp is now non-zero at pixels in the mask with quantized orientation i
792 distanceTransform(temp, distances[i], DIST_C, 3);
793 }
794
795 // Count how many features taken for each label
796 int label_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0};
797
798 // Create sorted list of candidate features
799 std::vector<Candidate> candidates;
800 bool no_mask = local_mask.empty();
801 for (int r = 0; r < normal.rows; ++r)
802 {
803 const uchar* normal_r = normal.ptr<uchar>(r);
804 const uchar* mask_r = no_mask ? NULL : local_mask.ptr<uchar>(r);
805
806 for (int c = 0; c < normal.cols; ++c)
807 {
808 if (no_mask || mask_r[c])
809 {
810 uchar quantized = normal_r[c];
811
812 if (quantized != 0 && quantized != 255) // background and shadow
813 {
814 int label = getLabel(quantized);
815
816 // Accept if distance to a pixel belonging to a different label is greater than
817 // some threshold. IOW, ideal feature is in the center of a large homogeneous
818 // region.
819 float score = distances[label].at<float>(r, c);
820 if (score >= extract_threshold)
821 {
822 candidates.push_back( Candidate(c, r, label, score) );
823 ++label_counts[label];
824 }
825 }
826 }
827 }
828 }
829 // We require a certain number of features
830 if (candidates.size() < num_features)
831 return false;
832
833 // Prefer large distances, but also want to collect features over all 8 labels.
834 // So penalize labels with lots of candidates.
835 for (size_t i = 0; i < candidates.size(); ++i)
836 {
837 Candidate& c = candidates[i];
838 c.score /= (float)label_counts[c.f.label];
839 }
840 std::stable_sort(candidates.begin(), candidates.end());
841
842 // Use heuristic based on object area for initial distance threshold
843 float area = no_mask ? (float)normal.total() : (float)countNonZero(local_mask);
844 float distance = sqrtf(area) / sqrtf((float)num_features) + 1.5f;
845 selectScatteredFeatures(candidates, templ.features, num_features, distance);
846
847 // Size determined externally, needs to match templates for other modalities
848 templ.width = -1;
849 templ.height = -1;
850 templ.pyramid_level = pyramid_level;
851
852 return true;
853 }
854
DepthNormal()855 DepthNormal::DepthNormal()
856 : distance_threshold(2000),
857 difference_threshold(50),
858 num_features(63),
859 extract_threshold(2)
860 {
861 }
862
DepthNormal(int _distance_threshold,int _difference_threshold,size_t _num_features,int _extract_threshold)863 DepthNormal::DepthNormal(int _distance_threshold, int _difference_threshold, size_t _num_features,
864 int _extract_threshold)
865 : distance_threshold(_distance_threshold),
866 difference_threshold(_difference_threshold),
867 num_features(_num_features),
868 extract_threshold(_extract_threshold)
869 {
870 }
871
create(int distance_threshold,int difference_threshold,size_t num_features,int extract_threshold)872 Ptr<DepthNormal> DepthNormal::create(int distance_threshold, int difference_threshold, size_t num_features,
873 int extract_threshold)
874 {
875 return makePtr<DepthNormal>(distance_threshold, difference_threshold, num_features, extract_threshold);
876 }
877
878 static const char DN_NAME[] = "DepthNormal";
879
name() const880 String DepthNormal::name() const
881 {
882 return DN_NAME;
883 }
884
processImpl(const Mat & src,const Mat & mask) const885 Ptr<QuantizedPyramid> DepthNormal::processImpl(const Mat& src,
886 const Mat& mask) const
887 {
888 return makePtr<DepthNormalPyramid>(src, mask, distance_threshold, difference_threshold,
889 num_features, extract_threshold);
890 }
891
read(const FileNode & fn)892 void DepthNormal::read(const FileNode& fn)
893 {
894 String type = fn["type"];
895 CV_Assert(type == DN_NAME);
896
897 distance_threshold = fn["distance_threshold"];
898 difference_threshold = fn["difference_threshold"];
899 num_features = int(fn["num_features"]);
900 extract_threshold = fn["extract_threshold"];
901 }
902
write(FileStorage & fs) const903 void DepthNormal::write(FileStorage& fs) const
904 {
905 fs << "type" << DN_NAME;
906 fs << "distance_threshold" << distance_threshold;
907 fs << "difference_threshold" << difference_threshold;
908 fs << "num_features" << int(num_features);
909 fs << "extract_threshold" << extract_threshold;
910 }
911
912 /****************************************************************************************\
913 * Response maps *
914 \****************************************************************************************/
915
orUnaligned8u(const uchar * src,const int src_stride,uchar * dst,const int dst_stride,const int width,const int height)916 static void orUnaligned8u(const uchar * src, const int src_stride,
917 uchar * dst, const int dst_stride,
918 const int width, const int height)
919 {
920 #if CV_SSE2
921 volatile bool haveSSE2 = checkHardwareSupport(CPU_SSE2);
922 #if CV_SSE3
923 volatile bool haveSSE3 = checkHardwareSupport(CPU_SSE3);
924 #endif
925 bool src_aligned = reinterpret_cast<unsigned long long>(src) % 16 == 0;
926 #endif
927
928 for (int r = 0; r < height; ++r)
929 {
930 int c = 0;
931
932 #if CV_SSE2
933 // Use aligned loads if possible
934 if (haveSSE2 && src_aligned)
935 {
936 for ( ; c < width - 15; c += 16)
937 {
938 const __m128i* src_ptr = reinterpret_cast<const __m128i*>(src + c);
939 __m128i* dst_ptr = reinterpret_cast<__m128i*>(dst + c);
940 *dst_ptr = _mm_or_si128(*dst_ptr, *src_ptr);
941 }
942 }
943 #if CV_SSE3
944 // Use LDDQU for fast unaligned load
945 else if (haveSSE3)
946 {
947 for ( ; c < width - 15; c += 16)
948 {
949 __m128i val = _mm_lddqu_si128(reinterpret_cast<const __m128i*>(src + c));
950 __m128i* dst_ptr = reinterpret_cast<__m128i*>(dst + c);
951 *dst_ptr = _mm_or_si128(*dst_ptr, val);
952 }
953 }
954 #endif
955 // Fall back to MOVDQU
956 else if (haveSSE2)
957 {
958 for ( ; c < width - 15; c += 16)
959 {
960 __m128i val = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src + c));
961 __m128i* dst_ptr = reinterpret_cast<__m128i*>(dst + c);
962 *dst_ptr = _mm_or_si128(*dst_ptr, val);
963 }
964 }
965 #endif
966 for ( ; c < width; ++c)
967 dst[c] |= src[c];
968
969 // Advance to next row
970 src += src_stride;
971 dst += dst_stride;
972 }
973 }
974
975 /**
976 * \brief Spread binary labels in a quantized image.
977 *
978 * Implements section 2.3 "Spreading the Orientations."
979 *
980 * \param[in] src The source 8-bit quantized image.
981 * \param[out] dst Destination 8-bit spread image.
982 * \param T Sampling step. Spread labels T/2 pixels in each direction.
983 */
spread(const Mat & src,Mat & dst,int T)984 static void spread(const Mat& src, Mat& dst, int T)
985 {
986 // Allocate and zero-initialize spread (OR'ed) image
987 dst = Mat::zeros(src.size(), CV_8U);
988
989 // Fill in spread gradient image (section 2.3)
990 for (int r = 0; r < T; ++r)
991 {
992 int height = src.rows - r;
993 for (int c = 0; c < T; ++c)
994 {
995 orUnaligned8u(&src.at<unsigned char>(r, c), static_cast<int>(src.step1()), dst.ptr(),
996 static_cast<int>(dst.step1()), src.cols - c, height);
997 }
998 }
999 }
1000
1001 // Auto-generated by create_similarity_lut.py
1002 CV_DECL_ALIGNED(16) static const unsigned char SIMILARITY_LUT[256] = {0, 4, 3, 4, 2, 4, 3, 4, 1, 4, 3, 4, 2, 4, 3, 4, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 4, 4, 3, 3, 4, 4, 2, 3, 4, 4, 3, 3, 4, 4, 0, 1, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 0, 2, 1, 2, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 0, 3, 2, 3, 1, 3, 2, 3, 0, 3, 2, 3, 1, 3, 2, 3, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 0, 4, 3, 4, 2, 4, 3, 4, 1, 4, 3, 4, 2, 4, 3, 4, 0, 1, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, 4, 4, 3, 3, 4, 4, 2, 3, 4, 4, 3, 3, 4, 4, 0, 2, 1, 2, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 2, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 0, 3, 2, 3, 1, 3, 2, 3, 0, 3, 2, 3, 1, 3, 2, 3, 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4};
1003
1004 /**
1005 * \brief Precompute response maps for a spread quantized image.
1006 *
1007 * Implements section 2.4 "Precomputing Response Maps."
1008 *
1009 * \param[in] src The source 8-bit spread quantized image.
1010 * \param[out] response_maps Vector of 8 response maps, one for each bit label.
1011 */
computeResponseMaps(const Mat & src,std::vector<Mat> & response_maps)1012 static void computeResponseMaps(const Mat& src, std::vector<Mat>& response_maps)
1013 {
1014 CV_Assert((src.rows * src.cols) % 16 == 0);
1015
1016 // Allocate response maps
1017 response_maps.resize(8);
1018 for (int i = 0; i < 8; ++i)
1019 response_maps[i].create(src.size(), CV_8U);
1020
1021 Mat lsb4(src.size(), CV_8U);
1022 Mat msb4(src.size(), CV_8U);
1023
1024 for (int r = 0; r < src.rows; ++r)
1025 {
1026 const uchar* src_r = src.ptr(r);
1027 uchar* lsb4_r = lsb4.ptr(r);
1028 uchar* msb4_r = msb4.ptr(r);
1029
1030 for (int c = 0; c < src.cols; ++c)
1031 {
1032 // Least significant 4 bits of spread image pixel
1033 lsb4_r[c] = src_r[c] & 15;
1034 // Most significant 4 bits, right-shifted to be in [0, 16)
1035 msb4_r[c] = (src_r[c] & 240) >> 4;
1036 }
1037 }
1038
1039 #if CV_SSSE3
1040 volatile bool haveSSSE3 = checkHardwareSupport(CV_CPU_SSSE3);
1041 if (haveSSSE3)
1042 {
1043 const __m128i* lut = reinterpret_cast<const __m128i*>(SIMILARITY_LUT);
1044 for (int ori = 0; ori < 8; ++ori)
1045 {
1046 __m128i* map_data = response_maps[ori].ptr<__m128i>();
1047 __m128i* lsb4_data = lsb4.ptr<__m128i>();
1048 __m128i* msb4_data = msb4.ptr<__m128i>();
1049
1050 // Precompute the 2D response map S_i (section 2.4)
1051 for (int i = 0; i < (src.rows * src.cols) / 16; ++i)
1052 {
1053 // Using SSE shuffle for table lookup on 4 orientations at a time
1054 // The most/least significant 4 bits are used as the LUT index
1055 __m128i res1 = _mm_shuffle_epi8(lut[2*ori + 0], lsb4_data[i]);
1056 __m128i res2 = _mm_shuffle_epi8(lut[2*ori + 1], msb4_data[i]);
1057
1058 // Combine the results into a single similarity score
1059 map_data[i] = _mm_max_epu8(res1, res2);
1060 }
1061 }
1062 }
1063 else
1064 #endif
1065 {
1066 // For each of the 8 quantized orientations...
1067 for (int ori = 0; ori < 8; ++ori)
1068 {
1069 uchar* map_data = response_maps[ori].ptr<uchar>();
1070 uchar* lsb4_data = lsb4.ptr<uchar>();
1071 uchar* msb4_data = msb4.ptr<uchar>();
1072 const uchar* lut_low = SIMILARITY_LUT + 32*ori;
1073 const uchar* lut_hi = lut_low + 16;
1074
1075 for (int i = 0; i < src.rows * src.cols; ++i)
1076 {
1077 map_data[i] = std::max(lut_low[ lsb4_data[i] ], lut_hi[ msb4_data[i] ]);
1078 }
1079 }
1080 }
1081 }
1082
1083 /**
1084 * \brief Convert a response map to fast linearized ordering.
1085 *
1086 * Implements section 2.5 "Linearizing the Memory for Parallelization."
1087 *
1088 * \param[in] response_map The 2D response map, an 8-bit image.
1089 * \param[out] linearized The response map in linearized order. It has T*T rows,
1090 * each of which is a linear memory of length (W/T)*(H/T).
1091 * \param T Sampling step.
1092 */
linearize(const Mat & response_map,Mat & linearized,int T)1093 static void linearize(const Mat& response_map, Mat& linearized, int T)
1094 {
1095 CV_Assert(response_map.rows % T == 0);
1096 CV_Assert(response_map.cols % T == 0);
1097
1098 // linearized has T^2 rows, where each row is a linear memory
1099 int mem_width = response_map.cols / T;
1100 int mem_height = response_map.rows / T;
1101 linearized.create(T*T, mem_width * mem_height, CV_8U);
1102
1103 // Outer two for loops iterate over top-left T^2 starting pixels
1104 int index = 0;
1105 for (int r_start = 0; r_start < T; ++r_start)
1106 {
1107 for (int c_start = 0; c_start < T; ++c_start)
1108 {
1109 uchar* memory = linearized.ptr(index);
1110 ++index;
1111
1112 // Inner two loops copy every T-th pixel into the linear memory
1113 for (int r = r_start; r < response_map.rows; r += T)
1114 {
1115 const uchar* response_data = response_map.ptr(r);
1116 for (int c = c_start; c < response_map.cols; c += T)
1117 *memory++ = response_data[c];
1118 }
1119 }
1120 }
1121 }
1122
1123 /****************************************************************************************\
1124 * Linearized similarities *
1125 \****************************************************************************************/
1126
accessLinearMemory(const std::vector<Mat> & linear_memories,const Feature & f,int T,int W)1127 static const unsigned char* accessLinearMemory(const std::vector<Mat>& linear_memories,
1128 const Feature& f, int T, int W)
1129 {
1130 // Retrieve the TxT grid of linear memories associated with the feature label
1131 const Mat& memory_grid = linear_memories[f.label];
1132 CV_DbgAssert(memory_grid.rows == T*T);
1133 CV_DbgAssert(f.x >= 0);
1134 CV_DbgAssert(f.y >= 0);
1135 // The LM we want is at (x%T, y%T) in the TxT grid (stored as the rows of memory_grid)
1136 int grid_x = f.x % T;
1137 int grid_y = f.y % T;
1138 int grid_index = grid_y * T + grid_x;
1139 CV_DbgAssert(grid_index >= 0);
1140 CV_DbgAssert(grid_index < memory_grid.rows);
1141 const unsigned char* memory = memory_grid.ptr(grid_index);
1142 // Within the LM, the feature is at (x/T, y/T). W is the "width" of the LM, the
1143 // input image width decimated by T.
1144 int lm_x = f.x / T;
1145 int lm_y = f.y / T;
1146 int lm_index = lm_y * W + lm_x;
1147 CV_DbgAssert(lm_index >= 0);
1148 CV_DbgAssert(lm_index < memory_grid.cols);
1149 return memory + lm_index;
1150 }
1151
1152 /**
1153 * \brief Compute similarity measure for a given template at each sampled image location.
1154 *
1155 * Uses linear memories to compute the similarity measure as described in Fig. 7.
1156 *
1157 * \param[in] linear_memories Vector of 8 linear memories, one for each label.
1158 * \param[in] templ Template to match against.
1159 * \param[out] dst Destination 8-bit similarity image of size (W/T, H/T).
1160 * \param size Size (W, H) of the original input image.
1161 * \param T Sampling step.
1162 */
similarity(const std::vector<Mat> & linear_memories,const Template & templ,Mat & dst,Size size,int T)1163 static void similarity(const std::vector<Mat>& linear_memories, const Template& templ,
1164 Mat& dst, Size size, int T)
1165 {
1166 // 63 features or less is a special case because the max similarity per-feature is 4.
1167 // 255/4 = 63, so up to that many we can add up similarities in 8 bits without worrying
1168 // about overflow. Therefore here we use _mm_add_epi8 as the workhorse, whereas a more
1169 // general function would use _mm_add_epi16.
1170 CV_Assert(templ.features.size() <= 63);
1171 /// @todo Handle more than 255/MAX_RESPONSE features!!
1172
1173 // Decimate input image size by factor of T
1174 int W = size.width / T;
1175 int H = size.height / T;
1176
1177 // Feature dimensions, decimated by factor T and rounded up
1178 int wf = (templ.width - 1) / T + 1;
1179 int hf = (templ.height - 1) / T + 1;
1180
1181 // Span is the range over which we can shift the template around the input image
1182 int span_x = W - wf;
1183 int span_y = H - hf;
1184
1185 // Compute number of contiguous (in memory) pixels to check when sliding feature over
1186 // image. This allows template to wrap around left/right border incorrectly, so any
1187 // wrapped template matches must be filtered out!
1188 int template_positions = span_y * W + span_x + 1; // why add 1?
1189 //int template_positions = (span_y - 1) * W + span_x; // More correct?
1190
1191 /// @todo In old code, dst is buffer of size m_U. Could make it something like
1192 /// (span_x)x(span_y) instead?
1193 dst = Mat::zeros(H, W, CV_8U);
1194 uchar* dst_ptr = dst.ptr<uchar>();
1195
1196 #if CV_SSE2
1197 volatile bool haveSSE2 = checkHardwareSupport(CV_CPU_SSE2);
1198 #if CV_SSE3
1199 volatile bool haveSSE3 = checkHardwareSupport(CV_CPU_SSE3);
1200 #endif
1201 #endif
1202
1203 // Compute the similarity measure for this template by accumulating the contribution of
1204 // each feature
1205 for (int i = 0; i < (int)templ.features.size(); ++i)
1206 {
1207 // Add the linear memory at the appropriate offset computed from the location of
1208 // the feature in the template
1209 Feature f = templ.features[i];
1210 // Discard feature if out of bounds
1211 /// @todo Shouldn't actually see x or y < 0 here?
1212 if (f.x < 0 || f.x >= size.width || f.y < 0 || f.y >= size.height)
1213 continue;
1214 const uchar* lm_ptr = accessLinearMemory(linear_memories, f, T, W);
1215
1216 // Now we do an aligned/unaligned add of dst_ptr and lm_ptr with template_positions elements
1217 int j = 0;
1218 // Process responses 16 at a time if vectorization possible
1219 #if CV_SSE2
1220 #if CV_SSE3
1221 if (haveSSE3)
1222 {
1223 // LDDQU may be more efficient than MOVDQU for unaligned load of next 16 responses
1224 for ( ; j < template_positions - 15; j += 16)
1225 {
1226 __m128i responses = _mm_lddqu_si128(reinterpret_cast<const __m128i*>(lm_ptr + j));
1227 __m128i* dst_ptr_sse = reinterpret_cast<__m128i*>(dst_ptr + j);
1228 *dst_ptr_sse = _mm_add_epi8(*dst_ptr_sse, responses);
1229 }
1230 }
1231 else
1232 #endif
1233 if (haveSSE2)
1234 {
1235 // Fall back to MOVDQU
1236 for ( ; j < template_positions - 15; j += 16)
1237 {
1238 __m128i responses = _mm_loadu_si128(reinterpret_cast<const __m128i*>(lm_ptr + j));
1239 __m128i* dst_ptr_sse = reinterpret_cast<__m128i*>(dst_ptr + j);
1240 *dst_ptr_sse = _mm_add_epi8(*dst_ptr_sse, responses);
1241 }
1242 }
1243 #endif
1244 for ( ; j < template_positions; ++j)
1245 dst_ptr[j] = uchar(dst_ptr[j] + lm_ptr[j]);
1246 }
1247 }
1248
1249 /**
1250 * \brief Compute similarity measure for a given template in a local region.
1251 *
1252 * \param[in] linear_memories Vector of 8 linear memories, one for each label.
1253 * \param[in] templ Template to match against.
1254 * \param[out] dst Destination 8-bit similarity image, 16x16.
1255 * \param size Size (W, H) of the original input image.
1256 * \param T Sampling step.
1257 * \param center Center of the local region.
1258 */
similarityLocal(const std::vector<Mat> & linear_memories,const Template & templ,Mat & dst,Size size,int T,Point center)1259 static void similarityLocal(const std::vector<Mat>& linear_memories, const Template& templ,
1260 Mat& dst, Size size, int T, Point center)
1261 {
1262 // Similar to whole-image similarity() above. This version takes a position 'center'
1263 // and computes the energy in the 16x16 patch centered on it.
1264 CV_Assert(templ.features.size() <= 63);
1265
1266 // Compute the similarity map in a 16x16 patch around center
1267 int W = size.width / T;
1268 dst = Mat::zeros(16, 16, CV_8U);
1269
1270 // Offset each feature point by the requested center. Further adjust to (-8,-8) from the
1271 // center to get the top-left corner of the 16x16 patch.
1272 // NOTE: We make the offsets multiples of T to agree with results of the original code.
1273 int offset_x = (center.x / T - 8) * T;
1274 int offset_y = (center.y / T - 8) * T;
1275
1276 #if CV_SSE2
1277 volatile bool haveSSE2 = checkHardwareSupport(CV_CPU_SSE2);
1278 #if CV_SSE3
1279 volatile bool haveSSE3 = checkHardwareSupport(CV_CPU_SSE3);
1280 #endif
1281 __m128i* dst_ptr_sse = dst.ptr<__m128i>();
1282 #endif
1283
1284 for (int i = 0; i < (int)templ.features.size(); ++i)
1285 {
1286 Feature f = templ.features[i];
1287 f.x += offset_x;
1288 f.y += offset_y;
1289 // Discard feature if out of bounds, possibly due to applying the offset
1290 if (f.x < 0 || f.y < 0 || f.x >= size.width || f.y >= size.height)
1291 continue;
1292
1293 const uchar* lm_ptr = accessLinearMemory(linear_memories, f, T, W);
1294
1295 // Process whole row at a time if vectorization possible
1296 #if CV_SSE2
1297 #if CV_SSE3
1298 if (haveSSE3)
1299 {
1300 // LDDQU may be more efficient than MOVDQU for unaligned load of 16 responses from current row
1301 for (int row = 0; row < 16; ++row)
1302 {
1303 __m128i aligned = _mm_lddqu_si128(reinterpret_cast<const __m128i*>(lm_ptr));
1304 dst_ptr_sse[row] = _mm_add_epi8(dst_ptr_sse[row], aligned);
1305 lm_ptr += W; // Step to next row
1306 }
1307 }
1308 else
1309 #endif
1310 if (haveSSE2)
1311 {
1312 // Fall back to MOVDQU
1313 for (int row = 0; row < 16; ++row)
1314 {
1315 __m128i aligned = _mm_loadu_si128(reinterpret_cast<const __m128i*>(lm_ptr));
1316 dst_ptr_sse[row] = _mm_add_epi8(dst_ptr_sse[row], aligned);
1317 lm_ptr += W; // Step to next row
1318 }
1319 }
1320 else
1321 #endif
1322 {
1323 uchar* dst_ptr = dst.ptr<uchar>();
1324 for (int row = 0; row < 16; ++row)
1325 {
1326 for (int col = 0; col < 16; ++col)
1327 dst_ptr[col] = uchar(dst_ptr[col] + lm_ptr[col]);
1328 dst_ptr += 16;
1329 lm_ptr += W;
1330 }
1331 }
1332 }
1333 }
1334
addUnaligned8u16u(const uchar * src1,const uchar * src2,ushort * res,int length)1335 static void addUnaligned8u16u(const uchar * src1, const uchar * src2, ushort * res, int length)
1336 {
1337 const uchar * end = src1 + length;
1338
1339 while (src1 != end)
1340 {
1341 *res = *src1 + *src2;
1342
1343 ++src1;
1344 ++src2;
1345 ++res;
1346 }
1347 }
1348
1349 /**
1350 * \brief Accumulate one or more 8-bit similarity images.
1351 *
1352 * \param[in] similarities Source 8-bit similarity images.
1353 * \param[out] dst Destination 16-bit similarity image.
1354 */
addSimilarities(const std::vector<Mat> & similarities,Mat & dst)1355 static void addSimilarities(const std::vector<Mat>& similarities, Mat& dst)
1356 {
1357 if (similarities.size() == 1)
1358 {
1359 similarities[0].convertTo(dst, CV_16U);
1360 }
1361 else
1362 {
1363 // NOTE: add() seems to be rather slow in the 8U + 8U -> 16U case
1364 dst.create(similarities[0].size(), CV_16U);
1365 addUnaligned8u16u(similarities[0].ptr(), similarities[1].ptr(), dst.ptr<ushort>(), static_cast<int>(dst.total()));
1366
1367 /// @todo Optimize 16u + 8u -> 16u when more than 2 modalities
1368 for (size_t i = 2; i < similarities.size(); ++i)
1369 add(dst, similarities[i], dst, noArray(), CV_16U);
1370 }
1371 }
1372
1373 /****************************************************************************************\
1374 * High-level Detector API *
1375 \****************************************************************************************/
1376
Detector()1377 Detector::Detector()
1378 {
1379 }
1380
Detector(const std::vector<Ptr<Modality>> & _modalities,const std::vector<int> & T_pyramid)1381 Detector::Detector(const std::vector< Ptr<Modality> >& _modalities,
1382 const std::vector<int>& T_pyramid)
1383 : modalities(_modalities),
1384 pyramid_levels(static_cast<int>(T_pyramid.size())),
1385 T_at_level(T_pyramid)
1386 {
1387 }
1388
match(const std::vector<Mat> & sources,float threshold,std::vector<Match> & matches,const std::vector<String> & class_ids,OutputArrayOfArrays quantized_images,const std::vector<Mat> & masks) const1389 void Detector::match(const std::vector<Mat>& sources, float threshold, std::vector<Match>& matches,
1390 const std::vector<String>& class_ids, OutputArrayOfArrays quantized_images,
1391 const std::vector<Mat>& masks) const
1392 {
1393 matches.clear();
1394 if (quantized_images.needed())
1395 quantized_images.create(1, static_cast<int>(pyramid_levels * modalities.size()), CV_8U);
1396
1397 CV_Assert(sources.size() == modalities.size());
1398 // Initialize each modality with our sources
1399 std::vector< Ptr<QuantizedPyramid> > quantizers;
1400 for (int i = 0; i < (int)modalities.size(); ++i){
1401 Mat mask, source;
1402 source = sources[i];
1403 if(!masks.empty()){
1404 CV_Assert(masks.size() == modalities.size());
1405 mask = masks[i];
1406 }
1407 CV_Assert(mask.empty() || mask.size() == source.size());
1408 quantizers.push_back(modalities[i]->process(source, mask));
1409 }
1410 // pyramid level -> modality -> quantization
1411 LinearMemoryPyramid lm_pyramid(pyramid_levels,
1412 std::vector<LinearMemories>(modalities.size(), LinearMemories(8)));
1413
1414 // For each pyramid level, precompute linear memories for each modality
1415 std::vector<Size> sizes;
1416 for (int l = 0; l < pyramid_levels; ++l)
1417 {
1418 int T = T_at_level[l];
1419 std::vector<LinearMemories>& lm_level = lm_pyramid[l];
1420
1421 if (l > 0)
1422 {
1423 for (int i = 0; i < (int)quantizers.size(); ++i)
1424 quantizers[i]->pyrDown();
1425 }
1426
1427 Mat quantized, spread_quantized;
1428 std::vector<Mat> response_maps;
1429 for (int i = 0; i < (int)quantizers.size(); ++i)
1430 {
1431 quantizers[i]->quantize(quantized);
1432 spread(quantized, spread_quantized, T);
1433 computeResponseMaps(spread_quantized, response_maps);
1434
1435 LinearMemories& memories = lm_level[i];
1436 for (int j = 0; j < 8; ++j)
1437 linearize(response_maps[j], memories[j], T);
1438
1439 if (quantized_images.needed()) //use copyTo here to side step reference semantics.
1440 quantized.copyTo(quantized_images.getMatRef(static_cast<int>(l*quantizers.size() + i)));
1441 }
1442
1443 sizes.push_back(quantized.size());
1444 }
1445
1446 if (class_ids.empty())
1447 {
1448 // Match all templates
1449 TemplatesMap::const_iterator it = class_templates.begin(), itend = class_templates.end();
1450 for ( ; it != itend; ++it)
1451 matchClass(lm_pyramid, sizes, threshold, matches, it->first, it->second);
1452 }
1453 else
1454 {
1455 // Match only templates for the requested class IDs
1456 for (int i = 0; i < (int)class_ids.size(); ++i)
1457 {
1458 TemplatesMap::const_iterator it = class_templates.find(class_ids[i]);
1459 if (it != class_templates.end())
1460 matchClass(lm_pyramid, sizes, threshold, matches, it->first, it->second);
1461 }
1462 }
1463
1464 // Sort matches by similarity, and prune any duplicates introduced by pyramid refinement
1465 std::sort(matches.begin(), matches.end());
1466 std::vector<Match>::iterator new_end = std::unique(matches.begin(), matches.end());
1467 matches.erase(new_end, matches.end());
1468 }
1469
1470 // Used to filter out weak matches
1471 struct MatchPredicate
1472 {
MatchPredicatecv::linemod::MatchPredicate1473 MatchPredicate(float _threshold) : threshold(_threshold) {}
operator ()cv::linemod::MatchPredicate1474 bool operator() (const Match& m) { return m.similarity < threshold; }
1475 float threshold;
1476 };
1477
matchClass(const LinearMemoryPyramid & lm_pyramid,const std::vector<Size> & sizes,float threshold,std::vector<Match> & matches,const String & class_id,const std::vector<TemplatePyramid> & template_pyramids) const1478 void Detector::matchClass(const LinearMemoryPyramid& lm_pyramid,
1479 const std::vector<Size>& sizes,
1480 float threshold, std::vector<Match>& matches,
1481 const String& class_id,
1482 const std::vector<TemplatePyramid>& template_pyramids) const
1483 {
1484 // For each template...
1485 for (size_t template_id = 0; template_id < template_pyramids.size(); ++template_id)
1486 {
1487 const TemplatePyramid& tp = template_pyramids[template_id];
1488
1489 // First match over the whole image at the lowest pyramid level
1490 /// @todo Factor this out into separate function
1491 const std::vector<LinearMemories>& lowest_lm = lm_pyramid.back();
1492
1493 // Compute similarity maps for each modality at lowest pyramid level
1494 std::vector<Mat> similarities(modalities.size());
1495 int lowest_start = static_cast<int>(tp.size() - modalities.size());
1496 int lowest_T = T_at_level.back();
1497 int num_features = 0;
1498 for (int i = 0; i < (int)modalities.size(); ++i)
1499 {
1500 const Template& templ = tp[lowest_start + i];
1501 num_features += static_cast<int>(templ.features.size());
1502 similarity(lowest_lm[i], templ, similarities[i], sizes.back(), lowest_T);
1503 }
1504
1505 // Combine into overall similarity
1506 /// @todo Support weighting the modalities
1507 Mat total_similarity;
1508 addSimilarities(similarities, total_similarity);
1509
1510 // Convert user-friendly percentage to raw similarity threshold. The percentage
1511 // threshold scales from half the max response (what you would expect from applying
1512 // the template to a completely random image) to the max response.
1513 // NOTE: This assumes max per-feature response is 4, so we scale between [2*nf, 4*nf].
1514 int raw_threshold = static_cast<int>(2*num_features + (threshold / 100.f) * (2*num_features) + 0.5f);
1515
1516 // Find initial matches
1517 std::vector<Match> candidates;
1518 for (int r = 0; r < total_similarity.rows; ++r)
1519 {
1520 ushort* row = total_similarity.ptr<ushort>(r);
1521 for (int c = 0; c < total_similarity.cols; ++c)
1522 {
1523 int raw_score = row[c];
1524 if (raw_score > raw_threshold)
1525 {
1526 int offset = lowest_T / 2 + (lowest_T % 2 - 1);
1527 int x = c * lowest_T + offset;
1528 int y = r * lowest_T + offset;
1529 float score =(raw_score * 100.f) / (4 * num_features) + 0.5f;
1530 candidates.push_back(Match(x, y, score, class_id, static_cast<int>(template_id)));
1531 }
1532 }
1533 }
1534
1535 // Locally refine each match by marching up the pyramid
1536 for (int l = pyramid_levels - 2; l >= 0; --l)
1537 {
1538 const std::vector<LinearMemories>& lms = lm_pyramid[l];
1539 int T = T_at_level[l];
1540 int start = static_cast<int>(l * modalities.size());
1541 Size size = sizes[l];
1542 int border = 8 * T;
1543 int offset = T / 2 + (T % 2 - 1);
1544 int max_x = size.width - tp[start].width - border;
1545 int max_y = size.height - tp[start].height - border;
1546
1547 std::vector<Mat> similarities2(modalities.size());
1548 Mat total_similarity2;
1549 for (int m = 0; m < (int)candidates.size(); ++m)
1550 {
1551 Match& match2 = candidates[m];
1552 int x = match2.x * 2 + 1; /// @todo Support other pyramid distance
1553 int y = match2.y * 2 + 1;
1554
1555 // Require 8 (reduced) row/cols to the up/left
1556 x = std::max(x, border);
1557 y = std::max(y, border);
1558
1559 // Require 8 (reduced) row/cols to the down/left, plus the template size
1560 x = std::min(x, max_x);
1561 y = std::min(y, max_y);
1562
1563 // Compute local similarity maps for each modality
1564 int numFeatures = 0;
1565 for (int i = 0; i < (int)modalities.size(); ++i)
1566 {
1567 const Template& templ = tp[start + i];
1568 numFeatures += static_cast<int>(templ.features.size());
1569 similarityLocal(lms[i], templ, similarities2[i], size, T, Point(x, y));
1570 }
1571 addSimilarities(similarities2, total_similarity2);
1572
1573 // Find best local adjustment
1574 int best_score = 0;
1575 int best_r = -1, best_c = -1;
1576 for (int r = 0; r < total_similarity2.rows; ++r)
1577 {
1578 ushort* row = total_similarity2.ptr<ushort>(r);
1579 for (int c = 0; c < total_similarity2.cols; ++c)
1580 {
1581 int score = row[c];
1582 if (score > best_score)
1583 {
1584 best_score = score;
1585 best_r = r;
1586 best_c = c;
1587 }
1588 }
1589 }
1590 // Update current match
1591 match2.x = (x / T - 8 + best_c) * T + offset;
1592 match2.y = (y / T - 8 + best_r) * T + offset;
1593 match2.similarity = (best_score * 100.f) / (4 * numFeatures);
1594 }
1595
1596 // Filter out any matches that drop below the similarity threshold
1597 std::vector<Match>::iterator new_end = std::remove_if(candidates.begin(), candidates.end(),
1598 MatchPredicate(threshold));
1599 candidates.erase(new_end, candidates.end());
1600 }
1601
1602 matches.insert(matches.end(), candidates.begin(), candidates.end());
1603 }
1604 }
1605
addTemplate(const std::vector<Mat> & sources,const String & class_id,const Mat & object_mask,Rect * bounding_box)1606 int Detector::addTemplate(const std::vector<Mat>& sources, const String& class_id,
1607 const Mat& object_mask, Rect* bounding_box)
1608 {
1609 int num_modalities = static_cast<int>(modalities.size());
1610 std::vector<TemplatePyramid>& template_pyramids = class_templates[class_id];
1611 int template_id = static_cast<int>(template_pyramids.size());
1612
1613 TemplatePyramid tp;
1614 tp.resize(num_modalities * pyramid_levels);
1615
1616 // For each modality...
1617 for (int i = 0; i < num_modalities; ++i)
1618 {
1619 // Extract a template at each pyramid level
1620 Ptr<QuantizedPyramid> qp = modalities[i]->process(sources[i], object_mask);
1621 for (int l = 0; l < pyramid_levels; ++l)
1622 {
1623 /// @todo Could do mask subsampling here instead of in pyrDown()
1624 if (l > 0)
1625 qp->pyrDown();
1626
1627 bool success = qp->extractTemplate(tp[l*num_modalities + i]);
1628 if (!success)
1629 return -1;
1630 }
1631 }
1632
1633 Rect bb = cropTemplates(tp);
1634 if (bounding_box)
1635 *bounding_box = bb;
1636
1637 /// @todo Can probably avoid a copy of tp here with swap
1638 template_pyramids.push_back(tp);
1639 return template_id;
1640 }
1641
addSyntheticTemplate(const std::vector<Template> & templates,const String & class_id)1642 int Detector::addSyntheticTemplate(const std::vector<Template>& templates, const String& class_id)
1643 {
1644 std::vector<TemplatePyramid>& template_pyramids = class_templates[class_id];
1645 int template_id = static_cast<int>(template_pyramids.size());
1646 template_pyramids.push_back(templates);
1647 return template_id;
1648 }
1649
getTemplates(const String & class_id,int template_id) const1650 const std::vector<Template>& Detector::getTemplates(const String& class_id, int template_id) const
1651 {
1652 TemplatesMap::const_iterator i = class_templates.find(class_id);
1653 CV_Assert(i != class_templates.end());
1654 CV_Assert(i->second.size() > size_t(template_id));
1655 return i->second[template_id];
1656 }
1657
numTemplates() const1658 int Detector::numTemplates() const
1659 {
1660 int ret = 0;
1661 TemplatesMap::const_iterator i = class_templates.begin(), iend = class_templates.end();
1662 for ( ; i != iend; ++i)
1663 ret += static_cast<int>(i->second.size());
1664 return ret;
1665 }
1666
numTemplates(const String & class_id) const1667 int Detector::numTemplates(const String& class_id) const
1668 {
1669 TemplatesMap::const_iterator i = class_templates.find(class_id);
1670 if (i == class_templates.end())
1671 return 0;
1672 return static_cast<int>(i->second.size());
1673 }
1674
classIds() const1675 std::vector<String> Detector::classIds() const
1676 {
1677 std::vector<String> ids;
1678 TemplatesMap::const_iterator i = class_templates.begin(), iend = class_templates.end();
1679 for ( ; i != iend; ++i)
1680 {
1681 ids.push_back(i->first);
1682 }
1683
1684 return ids;
1685 }
1686
read(const FileNode & fn)1687 void Detector::read(const FileNode& fn)
1688 {
1689 class_templates.clear();
1690 pyramid_levels = fn["pyramid_levels"];
1691 fn["T"] >> T_at_level;
1692
1693 modalities.clear();
1694 FileNode modalities_fn = fn["modalities"];
1695 FileNodeIterator it = modalities_fn.begin(), it_end = modalities_fn.end();
1696 for ( ; it != it_end; ++it)
1697 {
1698 modalities.push_back(Modality::create(*it));
1699 }
1700 }
1701
write(FileStorage & fs) const1702 void Detector::write(FileStorage& fs) const
1703 {
1704 fs << "pyramid_levels" << pyramid_levels;
1705 fs << "T" << T_at_level;
1706
1707 fs << "modalities" << "[";
1708 for (int i = 0; i < (int)modalities.size(); ++i)
1709 {
1710 fs << "{";
1711 modalities[i]->write(fs);
1712 fs << "}";
1713 }
1714 fs << "]"; // modalities
1715 }
1716
readClass(const FileNode & fn,const String & class_id_override)1717 String Detector::readClass(const FileNode& fn, const String &class_id_override)
1718 {
1719 // Verify compatible with Detector settings
1720 FileNode mod_fn = fn["modalities"];
1721 CV_Assert(mod_fn.size() == modalities.size());
1722 FileNodeIterator mod_it = mod_fn.begin(), mod_it_end = mod_fn.end();
1723 int i = 0;
1724 for ( ; mod_it != mod_it_end; ++mod_it, ++i)
1725 CV_Assert(modalities[i]->name() == (String)(*mod_it));
1726 CV_Assert((int)fn["pyramid_levels"] == pyramid_levels);
1727
1728 // Detector should not already have this class
1729 String class_id;
1730 if (class_id_override.empty())
1731 {
1732 String class_id_tmp = fn["class_id"];
1733 CV_Assert(class_templates.find(class_id_tmp) == class_templates.end());
1734 class_id = class_id_tmp;
1735 }
1736 else
1737 {
1738 class_id = class_id_override;
1739 }
1740
1741 TemplatesMap::value_type v(class_id, std::vector<TemplatePyramid>());
1742 std::vector<TemplatePyramid>& tps = v.second;
1743 int expected_id = 0;
1744
1745 FileNode tps_fn = fn["template_pyramids"];
1746 tps.resize(tps_fn.size());
1747 FileNodeIterator tps_it = tps_fn.begin(), tps_it_end = tps_fn.end();
1748 for ( ; tps_it != tps_it_end; ++tps_it, ++expected_id)
1749 {
1750 int template_id = (*tps_it)["template_id"];
1751 CV_Assert(template_id == expected_id);
1752 FileNode templates_fn = (*tps_it)["templates"];
1753 tps[template_id].resize(templates_fn.size());
1754
1755 FileNodeIterator templ_it = templates_fn.begin(), templ_it_end = templates_fn.end();
1756 int idx = 0;
1757 for ( ; templ_it != templ_it_end; ++templ_it)
1758 {
1759 tps[template_id][idx++].read(*templ_it);
1760 }
1761 }
1762
1763 class_templates.insert(v);
1764 return class_id;
1765 }
1766
writeClass(const String & class_id,FileStorage & fs) const1767 void Detector::writeClass(const String& class_id, FileStorage& fs) const
1768 {
1769 TemplatesMap::const_iterator it = class_templates.find(class_id);
1770 CV_Assert(it != class_templates.end());
1771 const std::vector<TemplatePyramid>& tps = it->second;
1772
1773 fs << "class_id" << it->first;
1774 fs << "modalities" << "[:";
1775 for (size_t i = 0; i < modalities.size(); ++i)
1776 fs << modalities[i]->name();
1777 fs << "]"; // modalities
1778 fs << "pyramid_levels" << pyramid_levels;
1779 fs << "template_pyramids" << "[";
1780 for (size_t i = 0; i < tps.size(); ++i)
1781 {
1782 const TemplatePyramid& tp = tps[i];
1783 fs << "{";
1784 fs << "template_id" << int(i); //TODO is this cast correct? won't be good if rolls over...
1785 fs << "templates" << "[";
1786 for (size_t j = 0; j < tp.size(); ++j)
1787 {
1788 fs << "{";
1789 tp[j].write(fs);
1790 fs << "}"; // current template
1791 }
1792 fs << "]"; // templates
1793 fs << "}"; // current pyramid
1794 }
1795 fs << "]"; // pyramids
1796 }
1797
readClasses(const std::vector<String> & class_ids,const String & format)1798 void Detector::readClasses(const std::vector<String>& class_ids,
1799 const String& format)
1800 {
1801 for (size_t i = 0; i < class_ids.size(); ++i)
1802 {
1803 const String& class_id = class_ids[i];
1804 String filename = cv::format(format.c_str(), class_id.c_str());
1805 FileStorage fs(filename, FileStorage::READ);
1806 readClass(fs.root());
1807 }
1808 }
1809
writeClasses(const String & format) const1810 void Detector::writeClasses(const String& format) const
1811 {
1812 TemplatesMap::const_iterator it = class_templates.begin(), it_end = class_templates.end();
1813 for ( ; it != it_end; ++it)
1814 {
1815 const String& class_id = it->first;
1816 String filename = cv::format(format.c_str(), class_id.c_str());
1817 FileStorage fs(filename, FileStorage::WRITE);
1818 writeClass(class_id, fs);
1819 }
1820 }
1821
1822 static const int T_DEFAULTS[] = {5, 8};
1823
getDefaultLINE()1824 Ptr<Detector> getDefaultLINE()
1825 {
1826 std::vector< Ptr<Modality> > modalities;
1827 modalities.push_back(makePtr<ColorGradient>());
1828 return makePtr<Detector>(modalities, std::vector<int>(T_DEFAULTS, T_DEFAULTS + 2));
1829 }
1830
getDefaultLINEMOD()1831 Ptr<Detector> getDefaultLINEMOD()
1832 {
1833 std::vector< Ptr<Modality> > modalities;
1834 modalities.push_back(makePtr<ColorGradient>());
1835 modalities.push_back(makePtr<DepthNormal>());
1836 return makePtr<Detector>(modalities, std::vector<int>(T_DEFAULTS, T_DEFAULTS + 2));
1837 }
1838
1839 } // namespace linemod
1840 } // namespace cv
1841