You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
643 lines
19 KiB
643 lines
19 KiB
/* |
|
* Software License Agreement (BSD License) |
|
* |
|
* Copyright (c) 2012, Willow Garage, Inc. |
|
* All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* |
|
* * Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* * Redistributions in binary form must reproduce the above |
|
* copyright notice, this list of conditions and the following |
|
* disclaimer in the documentation and/or other materials provided |
|
* with the distribution. |
|
* * Neither the name of Willow Garage, Inc. nor the names of its |
|
* contributors may be used to endorse or promote products derived |
|
* from this software without specific prior written permission. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
* |
|
*/ |
|
|
|
/** This is an implementation of a fast plane detection loosely inspired by |
|
* Fast Plane Detection and Polygonalization in noisy 3D Range Images |
|
* Jann Poppinga, Narunas Vaskevicius, Andreas Birk, and Kaustubh Pathak |
|
* and the follow-up |
|
* Fast Plane Detection for SLAM from Noisy Range Images in |
|
* Both Structured and Unstructured Environments |
|
* Junhao Xiao, Jianhua Zhang and Jianwei Zhang |
|
* Houxiang Zhang and Hans Petter Hildre |
|
*/ |
|
|
|
#include "precomp.hpp" |
|
|
|
namespace cv |
|
{ |
|
namespace rgbd |
|
{ |
|
/** Structure defining a plane. The notations are from the second paper */ |
|
class PlaneBase |
|
{ |
|
public: |
|
PlaneBase(const Vec3f & m, const Vec3f &n_in, int index) |
|
: |
|
index_(index), |
|
n_(n_in), |
|
m_sum_(Vec3f(0, 0, 0)), |
|
m_(m), |
|
Q_(Matx33f::zeros()), |
|
mse_(0), |
|
K_(0) |
|
{ |
|
UpdateD(); |
|
} |
|
|
|
virtual |
|
~PlaneBase() |
|
{ |
|
} |
|
|
|
/** Compute the distance to the plane. This will be implemented by the children to take into account different |
|
* sensor models |
|
* @param p_j |
|
* @return |
|
*/ |
|
virtual |
|
float |
|
distance(const Vec3f& p_j) const = 0; |
|
|
|
/** The d coefficient in the plane equation ax+by+cz+d = 0 |
|
* @return |
|
*/ |
|
inline float |
|
d() const |
|
{ |
|
return d_; |
|
} |
|
|
|
/** The normal to the plane |
|
* @return the normal to the plane |
|
*/ |
|
const Vec3f & |
|
n() const |
|
{ |
|
return n_; |
|
} |
|
|
|
/** Update the different coefficients of the plane, based on the new statistics |
|
*/ |
|
void |
|
UpdateParameters() |
|
{ |
|
if (empty()) |
|
return; |
|
m_ = m_sum_ / K_; |
|
// Compute C |
|
Matx33f C = Q_ - m_sum_ * m_.t(); |
|
|
|
// Compute n |
|
SVD svd(C); |
|
n_ = Vec3f(svd.vt.at<float>(2, 0), svd.vt.at<float>(2, 1), svd.vt.at<float>(2, 2)); |
|
mse_ = svd.w.at<float>(2) / K_; |
|
|
|
UpdateD(); |
|
} |
|
|
|
/** Update the different sum of point and sum of point*point.t() |
|
*/ |
|
void |
|
UpdateStatistics(const Vec3f & point, const Matx33f & Q_local) |
|
{ |
|
m_sum_ += point; |
|
Q_ += Q_local; |
|
++K_; |
|
} |
|
|
|
inline size_t |
|
empty() const |
|
{ |
|
return K_ == 0; |
|
} |
|
|
|
inline int |
|
K() const |
|
{ |
|
return K_; |
|
} |
|
/** The index of the plane */ |
|
int index_; |
|
protected: |
|
/** The 4th coefficient in the plane equation ax+by+cz+d = 0 */ |
|
float d_; |
|
/** Normal of the plane */ |
|
Vec3f n_; |
|
private: |
|
inline void |
|
UpdateD() |
|
{ |
|
d_ = -m_.dot(n_); |
|
} |
|
/** The sum of the points */ |
|
Vec3f m_sum_; |
|
/** The mean of the points */ |
|
Vec3f m_; |
|
/** The sum of pi * pi^\top */ |
|
Matx33f Q_; |
|
/** The different matrices we need to update */ |
|
Matx33f C_; |
|
float mse_; |
|
/** the number of points that form the plane */ |
|
int K_; |
|
}; |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
/** Basic planar child, with no sensor error model |
|
*/ |
|
class Plane: public PlaneBase |
|
{ |
|
public: |
|
Plane(const Vec3f & m, const Vec3f &n_in, int index) |
|
: |
|
PlaneBase(m, n_in, index) |
|
{ |
|
} |
|
|
|
/** The computed distance is perfect in that case |
|
* @param p_j the point to compute its distance to |
|
* @return |
|
*/ |
|
float |
|
distance(const Vec3f& p_j) const |
|
{ |
|
return std::abs(float(p_j.dot(n_) + d_)); |
|
} |
|
}; |
|
|
|
/** Planar child with a quadratic error model |
|
*/ |
|
class PlaneABC: public PlaneBase |
|
{ |
|
public: |
|
PlaneABC(const Vec3f & m, const Vec3f &n_in, int index, float sensor_error_a, float sensor_error_b, |
|
float sensor_error_c) |
|
: |
|
PlaneBase(m, n_in, index), |
|
sensor_error_a_(sensor_error_a), |
|
sensor_error_b_(sensor_error_b), |
|
sensor_error_c_(sensor_error_c) |
|
{ |
|
} |
|
|
|
/** The distance is now computed by taking the sensor error into account */ |
|
inline |
|
float |
|
distance(const Vec3f& p_j) const |
|
{ |
|
float cst = p_j.dot(n_) + d_; |
|
float err = sensor_error_a_ * p_j[2] * p_j[2] + sensor_error_b_ * p_j[2] + sensor_error_c_; |
|
if (((cst - n_[2] * err <= 0) && (cst + n_[2] * err >= 0)) || ((cst + n_[2] * err <= 0) && (cst - n_[2] * err >= 0))) |
|
return 0; |
|
return std::min(std::abs(cst - err), std::abs(cst + err)); |
|
} |
|
private: |
|
float sensor_error_a_; |
|
float sensor_error_b_; |
|
float sensor_error_c_; |
|
}; |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
/** The PlaneGrid contains statistic about the individual tiles |
|
*/ |
|
class PlaneGrid |
|
{ |
|
public: |
|
PlaneGrid(const Mat_<Vec3f> & points3d, int block_size) |
|
: |
|
block_size_(block_size) |
|
{ |
|
// Figure out some dimensions |
|
int mini_rows = points3d.rows / block_size; |
|
if (points3d.rows % block_size != 0) |
|
++mini_rows; |
|
|
|
int mini_cols = points3d.cols / block_size; |
|
if (points3d.cols % block_size != 0) |
|
++mini_cols; |
|
|
|
// Compute all the interesting quantities |
|
m_.create(mini_rows, mini_cols); |
|
n_.create(mini_rows, mini_cols); |
|
Q_.create(points3d.rows, points3d.cols); |
|
mse_.create(mini_rows, mini_cols); |
|
for (int y = 0; y < mini_rows; ++y) |
|
for (int x = 0; x < mini_cols; ++x) |
|
{ |
|
// Update the tiles |
|
Matx33f Q = Matx33f::zeros(); |
|
Vec3f m = Vec3f(0, 0, 0); |
|
int K = 0; |
|
for (int j = y * block_size; j < std::min((y + 1) * block_size, points3d.rows); ++j) |
|
{ |
|
const Vec3f * vec = points3d.ptr < Vec3f > (j, x * block_size), *vec_end; |
|
float * pointpointt = reinterpret_cast<float*>(Q_.ptr < Vec<float, 9> > (j, x * block_size)); |
|
if (x == mini_cols - 1) |
|
vec_end = points3d.ptr < Vec3f > (j, points3d.cols - 1) + 1; |
|
else |
|
vec_end = vec + block_size; |
|
for (; vec != vec_end; ++vec, pointpointt += 9) |
|
{ |
|
if (cvIsNaN(vec->val[0])) |
|
continue; |
|
// Fill point*point.t() |
|
*pointpointt = vec->val[0] * vec->val[0]; |
|
*(pointpointt + 1) = vec->val[0] * vec->val[1]; |
|
*(pointpointt + 2) = vec->val[0] * vec->val[2]; |
|
*(pointpointt + 3) = *(pointpointt + 1); |
|
*(pointpointt + 4) = vec->val[1] * vec->val[1]; |
|
*(pointpointt + 5) = vec->val[1] * vec->val[2]; |
|
*(pointpointt + 6) = *(pointpointt + 2); |
|
*(pointpointt + 7) = *(pointpointt + 5); |
|
*(pointpointt + 8) = vec->val[2] * vec->val[2]; |
|
|
|
Q += *reinterpret_cast<Matx33f*>(pointpointt); |
|
m += (*vec); |
|
++K; |
|
} |
|
} |
|
if (K == 0) |
|
{ |
|
mse_(y, x) = std::numeric_limits<float>::max(); |
|
continue; |
|
} |
|
|
|
m /= K; |
|
m_(y, x) = m; |
|
|
|
// Compute C |
|
Matx33f C = Q - K * m * m.t(); |
|
|
|
// Compute n |
|
SVD svd(C); |
|
n_(y, x) = Vec3f(svd.vt.at<float>(2, 0), svd.vt.at<float>(2, 1), svd.vt.at<float>(2, 2)); |
|
mse_(y, x) = svd.w.at<float>(2) / K; |
|
} |
|
} |
|
|
|
/** The size of the block */ |
|
int block_size_; |
|
Mat_<Vec3f> m_; |
|
Mat_<Vec3f> n_; |
|
Mat_<Vec<float, 9> > Q_; |
|
Mat_<float> mse_; |
|
}; |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
class TileQueue |
|
{ |
|
public: |
|
struct PlaneTile |
|
{ |
|
PlaneTile(int x, int y, float mse) |
|
: |
|
x_(x), |
|
y_(y), |
|
mse_(mse) |
|
{ |
|
} |
|
|
|
bool |
|
operator<(const PlaneTile &tile2) const |
|
{ |
|
return mse_ < tile2.mse_; |
|
} |
|
|
|
int x_; |
|
int y_; |
|
float mse_; |
|
}; |
|
|
|
TileQueue(const PlaneGrid &plane_grid) |
|
{ |
|
done_tiles_ = Mat_<unsigned char>::zeros(plane_grid.mse_.rows, plane_grid.mse_.cols); |
|
tiles_.clear(); |
|
for (int y = 0; y < plane_grid.mse_.rows; ++y) |
|
for (int x = 0; x < plane_grid.mse_.cols; ++x) |
|
if (plane_grid.mse_(y, x) != std::numeric_limits<float>::max()) |
|
// Update the tiles |
|
tiles_.push_back(PlaneTile(x, y, plane_grid.mse_(y, x))); |
|
// Sort tiles by MSE |
|
tiles_.sort(); |
|
} |
|
|
|
bool |
|
empty() |
|
{ |
|
while (!tiles_.empty()) |
|
{ |
|
const PlaneTile & tile = tiles_.front(); |
|
if (done_tiles_(tile.y_, tile.x_)) |
|
tiles_.pop_front(); |
|
else |
|
break; |
|
} |
|
return tiles_.empty(); |
|
} |
|
|
|
const PlaneTile & |
|
front() const |
|
{ |
|
return tiles_.front(); |
|
} |
|
|
|
void |
|
remove(int y, int x) |
|
{ |
|
done_tiles_(y, x) = 1; |
|
} |
|
private: |
|
/** The list of tiles ordered from most planar to least */ |
|
std::list<PlaneTile> tiles_; |
|
/** contains 1 when the tiles has been studied, 0 otherwise */ |
|
Mat_<unsigned char> done_tiles_; |
|
}; |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
class InlierFinder |
|
{ |
|
public: |
|
InlierFinder(float err, const Mat_<Vec3f> & points3d, const Mat_<Vec3f> & normals, |
|
unsigned char plane_index, int block_size) |
|
: |
|
err_(err), |
|
points3d_(points3d), |
|
normals_(normals), |
|
plane_index_(plane_index), |
|
block_size_(block_size) |
|
{ |
|
} |
|
|
|
void |
|
Find(const PlaneGrid &plane_grid, Ptr<PlaneBase> & plane, TileQueue & tile_queue, |
|
std::set<TileQueue::PlaneTile> & neighboring_tiles, Mat_<unsigned char> & overall_mask, |
|
Mat_<unsigned char> & plane_mask) |
|
{ |
|
// Do not use reference as we pop the from later on |
|
TileQueue::PlaneTile tile = *(neighboring_tiles.begin()); |
|
|
|
// Figure the part of the image to look at |
|
Range range_x, range_y; |
|
int x = tile.x_ * block_size_, y = tile.y_ * block_size_; |
|
|
|
if (tile.x_ == plane_mask.cols - 1) |
|
range_x = Range(x, overall_mask.cols); |
|
else |
|
range_x = Range(x, x + block_size_); |
|
|
|
if (tile.y_ == plane_mask.rows - 1) |
|
range_y = Range(y, overall_mask.rows); |
|
else |
|
range_y = Range(y, y + block_size_); |
|
|
|
int n_valid_points = 0; |
|
for (int yy = range_y.start; yy != range_y.end; ++yy) |
|
{ |
|
uchar* data = overall_mask.ptr(yy, range_x.start), *data_end = data + range_x.size(); |
|
const Vec3f* point = points3d_.ptr < Vec3f > (yy, range_x.start); |
|
const Matx33f* Q_local = reinterpret_cast<const Matx33f *>(plane_grid.Q_.ptr < Vec<float, 9> |
|
> (yy, range_x.start)); |
|
|
|
// Depending on whether you have a normal, check it |
|
if (!normals_.empty()) |
|
{ |
|
const Vec3f* normal = normals_.ptr < Vec3f > (yy, range_x.start); |
|
for (; data != data_end; ++data, ++point, ++normal, ++Q_local) |
|
{ |
|
// Don't do anything if the point already belongs to another plane |
|
if (cvIsNaN(point->val[0]) || ((*data) != 255)) |
|
continue; |
|
|
|
// If the point is close enough to the plane |
|
if (plane->distance(*point) < err_) |
|
{ |
|
// make sure the normals are similar to the plane |
|
if (std::abs(plane->n().dot(*normal)) > 0.3) |
|
{ |
|
// The point now belongs to the plane |
|
plane->UpdateStatistics(*point, *Q_local); |
|
*data = plane_index_; |
|
++n_valid_points; |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
for (; data != data_end; ++data, ++point, ++Q_local) |
|
{ |
|
// Don't do anything if the point already belongs to another plane |
|
if (cvIsNaN(point->val[0]) || ((*data) != 255)) |
|
continue; |
|
|
|
// If the point is close enough to the plane |
|
if (plane->distance(*point) < err_) |
|
{ |
|
// The point now belongs to the plane |
|
plane->UpdateStatistics(*point, *Q_local); |
|
*data = plane_index_; |
|
++n_valid_points; |
|
} |
|
} |
|
} |
|
} |
|
|
|
plane->UpdateParameters(); |
|
|
|
// Mark the front as being done and pop it |
|
if (n_valid_points > (range_x.size() * range_y.size()) / 2) |
|
tile_queue.remove(tile.y_, tile.x_); |
|
plane_mask(tile.y_, tile.x_) = 1; |
|
neighboring_tiles.erase(neighboring_tiles.begin()); |
|
|
|
// Add potential neighbors of the tile |
|
std::vector<std::pair<int, int> > pairs; |
|
if (tile.x_ > 0) |
|
for (unsigned char * val = overall_mask.ptr<unsigned char>(range_y.start, range_x.start), *val_end = val |
|
+ range_y.size() * overall_mask.step; val != val_end; val += overall_mask.step) |
|
if (*val == plane_index_) |
|
{ |
|
pairs.push_back(std::pair<int, int>(tile.x_ - 1, tile.y_)); |
|
break; |
|
} |
|
if (tile.x_ < plane_mask.cols - 1) |
|
for (unsigned char * val = overall_mask.ptr<unsigned char>(range_y.start, range_x.end - 1), *val_end = val |
|
+ range_y.size() * overall_mask.step; val != val_end; val += overall_mask.step) |
|
if (*val == plane_index_) |
|
{ |
|
pairs.push_back(std::pair<int, int>(tile.x_ + 1, tile.y_)); |
|
break; |
|
} |
|
if (tile.y_ > 0) |
|
for (unsigned char * val = overall_mask.ptr<unsigned char>(range_y.start, range_x.start), *val_end = val |
|
+ range_x.size(); val != val_end; ++val) |
|
if (*val == plane_index_) |
|
{ |
|
pairs.push_back(std::pair<int, int>(tile.x_, tile.y_ - 1)); |
|
break; |
|
} |
|
if (tile.y_ < plane_mask.rows - 1) |
|
for (unsigned char * val = overall_mask.ptr<unsigned char>(range_y.end - 1, range_x.start), *val_end = val |
|
+ range_x.size(); val != val_end; ++val) |
|
if (*val == plane_index_) |
|
{ |
|
pairs.push_back(std::pair<int, int>(tile.x_, tile.y_ + 1)); |
|
break; |
|
} |
|
|
|
for (unsigned char i = 0; i < pairs.size(); ++i) |
|
if (!plane_mask(pairs[i].second, pairs[i].first)) |
|
neighboring_tiles.insert( |
|
TileQueue::PlaneTile(pairs[i].first, pairs[i].second, plane_grid.mse_(pairs[i].second, pairs[i].first))); |
|
} |
|
|
|
private: |
|
float err_; |
|
const Mat_<Vec3f> & points3d_; |
|
const Mat_<Vec3f> & normals_; |
|
unsigned char plane_index_; |
|
/** THe block size as defined in the main algorithm */ |
|
int block_size_; |
|
|
|
const InlierFinder & operator = (const InlierFinder &); |
|
}; |
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
void |
|
RgbdPlane::operator()(InputArray points3d_in, OutputArray mask_out, OutputArray plane_coefficients) |
|
{ |
|
this->operator()(points3d_in, Mat(), mask_out, plane_coefficients); |
|
} |
|
|
|
void |
|
RgbdPlane::operator()(InputArray points3d_in, InputArray normals_in, OutputArray mask_out, |
|
OutputArray plane_coefficients_out) |
|
{ |
|
Mat_<Vec3f> points3d, normals; |
|
if (points3d_in.depth() == CV_32F) |
|
points3d = points3d_in.getMat(); |
|
else |
|
points3d_in.getMat().convertTo(points3d, CV_32F); |
|
if (!normals_in.empty()) |
|
{ |
|
if (normals_in.depth() == CV_32F) |
|
normals = normals_in.getMat(); |
|
else |
|
normals_in.getMat().convertTo(normals, CV_32F); |
|
} |
|
|
|
// Pre-computations |
|
mask_out.create(points3d.size(), CV_8U); |
|
Mat mask_out_mat = mask_out.getMat(); |
|
Mat_<unsigned char> mask_out_uc = (Mat_<unsigned char>&) mask_out_mat; |
|
mask_out_uc.setTo(255); |
|
PlaneGrid plane_grid(points3d, block_size_); |
|
TileQueue plane_queue(plane_grid); |
|
size_t index_plane = 0; |
|
|
|
std::vector<Vec4f> plane_coefficients; |
|
float mse_min = (float)(threshold_ * threshold_); |
|
|
|
while (!plane_queue.empty()) |
|
{ |
|
// Get the first tile if it's good enough |
|
const TileQueue::PlaneTile front_tile = plane_queue.front(); |
|
if (front_tile.mse_ > mse_min) |
|
break; |
|
|
|
InlierFinder inlier_finder((float)threshold_, points3d, normals, (unsigned char)index_plane, block_size_); |
|
|
|
// Construct the plane for the first tile |
|
int x = front_tile.x_, y = front_tile.y_; |
|
const Vec3f & n = plane_grid.n_(y, x); |
|
Ptr<PlaneBase> plane; |
|
if ((sensor_error_a_ == 0) && (sensor_error_b_ == 0) && (sensor_error_c_ == 0)) |
|
plane = Ptr<PlaneBase>(new Plane(plane_grid.m_(y, x), n, (int)index_plane)); |
|
else |
|
plane = Ptr<PlaneBase>(new PlaneABC(plane_grid.m_(y, x), n, (int)index_plane, |
|
(float)sensor_error_a_, (float)sensor_error_b_, (float)sensor_error_c_)); |
|
|
|
Mat_<unsigned char> plane_mask = Mat_<unsigned char>::zeros(points3d.rows / block_size_, |
|
points3d.cols / block_size_); |
|
std::set<TileQueue::PlaneTile> neighboring_tiles; |
|
neighboring_tiles.insert(front_tile); |
|
plane_queue.remove(front_tile.y_, front_tile.x_); |
|
|
|
// Process all the neighboring tiles |
|
while (!neighboring_tiles.empty()) |
|
inlier_finder.Find(plane_grid, plane, plane_queue, neighboring_tiles, mask_out_uc, plane_mask); |
|
|
|
// Don't record the plane if it's empty |
|
if (plane->empty()) |
|
continue; |
|
// Don't record the plane if it's smaller than asked |
|
if (plane->K() < min_size_) { |
|
// Reset the plane index in the mask |
|
for (y = 0; y < plane_mask.rows; ++y) |
|
for (x = 0; x < plane_mask.cols; ++x) { |
|
if (!plane_mask(y, x)) |
|
continue; |
|
// Go over the tile |
|
for (int yy = y * block_size_; |
|
yy < std::min((y + 1) * block_size_, mask_out_uc.rows); ++yy) { |
|
uchar* data = mask_out_uc.ptr(yy, x * block_size_); |
|
uchar* data_end = data |
|
+ std::min(block_size_, |
|
mask_out_uc.cols - x * block_size_); |
|
for (; data != data_end; ++data) { |
|
if (*data == index_plane) |
|
*data = 255; |
|
} |
|
} |
|
} |
|
continue; |
|
} |
|
|
|
++index_plane; |
|
if (index_plane >= 255) |
|
break; |
|
Vec4f coeffs(plane->n()[0], plane->n()[1], plane->n()[2], plane->d()); |
|
if (coeffs(2) > 0) |
|
coeffs = -coeffs; |
|
plane_coefficients.push_back(coeffs); |
|
}; |
|
|
|
// Fill the plane coefficients |
|
if (plane_coefficients.empty()) |
|
return; |
|
plane_coefficients_out.create((int)plane_coefficients.size(), 1, CV_32FC4); |
|
Mat plane_coefficients_mat = plane_coefficients_out.getMat(); |
|
float* data = plane_coefficients_mat.ptr<float>(0); |
|
for(size_t i=0; i<plane_coefficients.size(); ++i) |
|
for(uchar j=0; j<4; ++j, ++data) |
|
*data = plane_coefficients[i][j]; |
|
} |
|
} |
|
}
|
|
|