1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 07:43:03 +04:00

Merge pull request #29224 from asmorkalov:as/ptcloud2

Dedicated pointcloud module #29224

OpenCV contrib: https://github.com/opencv/opencv_contrib/pull/4134

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [ ] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Alexander Smorkalov
2026-06-04 12:19:02 +03:00
committed by GitHub
parent 6a1a2754c8
commit fc3803c67b
90 changed files with 710 additions and 918 deletions
+14
View File
@@ -0,0 +1,14 @@
set(the_description "High level point cloud and mesh operations")
set(debug_modules "")
if(DEBUG_opencv_ptcloud)
list(APPEND debug_modules opencv_highgui)
endif()
ocv_define_module(ptcloud opencv_geometry opencv_imgproc opencv_flann ${debug_modules}
WRAP java objc python js
)
ocv_target_link_libraries(${the_module} ${LAPACK_LIBRARIES})
if(NOT HAVE_EIGEN)
message(STATUS "Geometry: Eigen support is disabled. Eigen is Required for Posegraph optimization")
endif()
+415
View File
@@ -0,0 +1,415 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef POINT_CLOUD_MODULE_HPP
#define POINT_CLOUD_MODULE_HPP
#include "opencv2/core.hpp"
#include "opencv2/ptcloud/depth.hpp"
#include "opencv2/ptcloud/volume.hpp"
#include "opencv2/ptcloud/odometry.hpp"
#include "opencv2/ptcloud/odometry_frame.hpp"
#include "opencv2/ptcloud/odometry_settings.hpp"
/**
@defgroup ptcloud Point Clound Processing
*/
//! @addtogroup ptcloud
//! @{
namespace cv {
/** @brief Loads a point cloud from a file.
*
* The function loads point cloud from the specified file and returns it.
* If the cloud cannot be read, throws an error.
* Vertex coordinates, normals and colors are returned as they are saved in the file
* even if these arrays have different sizes and their elements do not correspond to each other
* (which is typical for OBJ files for example)
*
* Currently, the following file formats are supported:
* - [Wavefront obj file *.obj](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
* - [Polygon File Format *.ply](https://en.wikipedia.org/wiki/PLY_(file_format))
*
* @param filename Name of the file
* @param vertices vertex coordinates, each value contains 3 floats
* @param normals per-vertex normals, each value contains 3 floats
* @param rgb per-vertex colors, each value contains 3 floats
*/
CV_EXPORTS_W void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals = noArray(), OutputArray rgb = noArray());
/** @brief Saves a point cloud to a specified file.
*
* The function saves point cloud to the specified file.
* File format is chosen based on the filename extension.
*
* @param filename Name of the file
* @param vertices vertex coordinates, each value contains 3 floats
* @param normals per-vertex normals, each value contains 3 floats
* @param rgb per-vertex colors, each value contains 3 floats
*/
CV_EXPORTS_W void savePointCloud(const String &filename, InputArray vertices, InputArray normals = noArray(), InputArray rgb = noArray());
/** @brief Loads a mesh from a file.
*
* The function loads mesh from the specified file and returns it.
* If the mesh cannot be read, throws an error
* Vertex attributes (i.e. space and texture coodinates, normals and colors) are returned in same-sized
* arrays with corresponding elements having the same indices.
* This means that if a face uses a vertex with a normal or a texture coordinate with different indices
* (which is typical for OBJ files for example), this vertex will be duplicated for each face it uses.
*
* Currently, the following file formats are supported:
* - [Wavefront obj file *.obj](https://en.wikipedia.org/wiki/Wavefront_.obj_file) (ONLY TRIANGULATED FACES)
* - [Polygon File Format *.ply](https://en.wikipedia.org/wiki/PLY_(file_format))
* @param filename Name of the file
* @param vertices vertex coordinates, each value contains 3 floats
* @param indices per-face list of vertices, each value is a vector of ints
* @param normals per-vertex normals, each value contains 3 floats
* @param colors per-vertex colors, each value contains 3 floats
* @param texCoords per-vertex texture coordinates, each value contains 2 or 3 floats
*/
CV_EXPORTS_W void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays indices,
OutputArray normals = noArray(), OutputArray colors = noArray(),
OutputArray texCoords = noArray());
/** @brief Saves a mesh to a specified file.
*
* The function saves mesh to the specified file.
* File format is chosen based on the filename extension.
*
* @param filename Name of the file.
* @param vertices vertex coordinates, each value contains 3 floats
* @param indices per-face list of vertices, each value is a vector of ints
* @param normals per-vertex normals, each value contains 3 floats
* @param colors per-vertex colors, each value contains 3 floats
* @param texCoords per-vertex texture coordinates, each value contains 2 or 3 floats
*/
CV_EXPORTS_W void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays indices,
InputArray normals = noArray(), InputArray colors = noArray(), InputArray texCoords = noArray());
//! Triangle fill settings
enum TriangleShadingType
{
RASTERIZE_SHADING_WHITE = 0, //!< a white color is used for the whole triangle
RASTERIZE_SHADING_FLAT = 1, //!< a color of 1st vertex of each triangle is used
RASTERIZE_SHADING_SHADED = 2 //!< a color is interpolated between 3 vertices with perspective correction
};
//! Face culling settings: what faces are drawn after face culling
enum TriangleCullingMode
{
RASTERIZE_CULLING_NONE = 0, //!< all faces are drawn, no culling is actually performed
RASTERIZE_CULLING_CW = 1, //!< triangles which vertices are given in clockwork order are drawn
RASTERIZE_CULLING_CCW = 2 //!< triangles which vertices are given in counterclockwork order are drawn
};
//! GL compatibility settings
enum TriangleGlCompatibleMode
{
RASTERIZE_COMPAT_DISABLED = 0, //!< Color and depth have their natural values and converted to internal formats if needed
RASTERIZE_COMPAT_INVDEPTH = 1 //!< Color is natural, Depth is transformed from [-zNear; -zFar] to [0; 1]
//!< by the following formula: \f$ \frac{z_{far} \left(z + z_{near}\right)}{z \left(z_{far} - z_{near}\right)} \f$ \n
//!< In this mode the input/output depthBuf is considered to be in this format,
//!< therefore it's faster since there're no conversions performed
};
/**
* @brief Structure to keep settings for rasterization
*/
struct CV_EXPORTS_W_SIMPLE TriangleRasterizeSettings
{
TriangleRasterizeSettings();
CV_WRAP TriangleRasterizeSettings& setShadingType(TriangleShadingType st) { shadingType = st; return *this; }
CV_WRAP TriangleRasterizeSettings& setCullingMode(TriangleCullingMode cm) { cullingMode = cm; return *this; }
CV_WRAP TriangleRasterizeSettings& setGlCompatibleMode(TriangleGlCompatibleMode gm) { glCompatibleMode = gm; return *this; }
TriangleShadingType shadingType;
TriangleCullingMode cullingMode;
TriangleGlCompatibleMode glCompatibleMode;
};
/** @brief Renders a set of triangles on a depth and color image
*
* Triangles can be drawn white (1.0, 1.0, 1.0), flat-shaded or with a color interpolation between vertices.
* In flat-shaded mode the 1st vertex color of each triangle is used to fill the whole triangle.
*
* The world2cam is an inverted camera pose matrix in fact. It transforms vertices from world to
* camera coordinate system.
*
* The camera coordinate system emulates the OpenGL's coordinate system having coordinate origin in a screen center,
* X axis pointing right, Y axis pointing up and Z axis pointing towards the viewer
* except that image is vertically flipped after the render.
* This means that all visible objects are placed in z-negative area, or exactly in -zNear > z > -zFar since
* zNear and zFar are positive.
* For example, at fovY = PI/2 the point (0, 1, -1) will be projected to (width/2, 0) screen point,
* (1, 0, -1) to (width/2 + height/2, height/2). Increasing fovY makes projection smaller and vice versa.
*
* The function does not create or clear output images before the rendering. This means that it can be used
* for drawing over an existing image or for rendering a model into a 3D scene using pre-filled Z-buffer.
*
* Empty scene results in a depth buffer filled by the maximum value since every pixel is infinitely far from the camera.
* Therefore, before rendering anything from scratch the depthBuf should be filled by zFar values (or by ones in INVDEPTH mode).
*
* There are special versions of this function named triangleRasterizeDepth and triangleRasterizeColor
* for cases if a user needs a color image or a depth image alone; they may run slightly faster.
*
* @param vertices vertices coordinates array. Should contain values of CV_32FC3 type or a compatible one (e.g. cv::Vec3f, etc.)
* @param indices triangle vertices index array, 3 per triangle. Each index indicates a vertex in a vertices array.
* Should contain CV_32SC3 values or compatible
* @param colors per-vertex colors of CV_32FC3 type or compatible. Can be empty or the same size as vertices array.
* If the values are out of [0; 1] range, the result correctness is not guaranteed
* @param colorBuf an array representing the final rendered image. Should containt CV_32FC3 values and be the same size as depthBuf.
* Not cleared before rendering, i.e. the content is reused as there is some pre-rendered scene.
* @param depthBuf an array of floats containing resulting Z buffer. Should contain float values and be the same size as colorBuf.
* Not cleared before rendering, i.e. the content is reused as there is some pre-rendered scene.
* Empty scene corresponds to all values set to zFar (or to 1.0 in INVDEPTH mode)
* @param world2cam a 4x3 or 4x4 float or double matrix containing inverted (sic!) camera pose
* @param fovY field of view in vertical direction, given in radians
* @param zNear minimum Z value to render, everything closer is clipped
* @param zFar maximum Z value to render, everything farther is clipped
* @param settings see TriangleRasterizeSettings. By default the smooth shading is on,
* with CW culling and with disabled GL compatibility
*/
CV_EXPORTS_W void triangleRasterize(InputArray vertices, InputArray indices, InputArray colors,
InputOutputArray colorBuf, InputOutputArray depthBuf,
InputArray world2cam, double fovY, double zNear, double zFar,
const TriangleRasterizeSettings& settings = TriangleRasterizeSettings());
/** @brief Overloaded version of triangleRasterize() with depth-only rendering
*
* @param vertices vertices coordinates array. Should contain values of CV_32FC3 type or a compatible one (e.g. cv::Vec3f, etc.)
* @param indices triangle vertices index array, 3 per triangle. Each index indicates a vertex in a vertices array.
* Should contain CV_32SC3 values or compatible
* @param depthBuf an array of floats containing resulting Z buffer. Should contain float values and be the same size as colorBuf.
* Not cleared before rendering, i.e. the content is reused as there is some pre-rendered scene.
* Empty scene corresponds to all values set to zFar (or to 1.0 in INVDEPTH mode)
* @param world2cam a 4x3 or 4x4 float or double matrix containing inverted (sic!) camera pose
* @param fovY field of view in vertical direction, given in radians
* @param zNear minimum Z value to render, everything closer is clipped
* @param zFar maximum Z value to render, everything farther is clipped
* @param settings see TriangleRasterizeSettings. By default the smooth shading is on,
* with CW culling and with disabled GL compatibility
*/
CV_EXPORTS_W void triangleRasterizeDepth(InputArray vertices, InputArray indices, InputOutputArray depthBuf,
InputArray world2cam, double fovY, double zNear, double zFar,
const TriangleRasterizeSettings& settings = TriangleRasterizeSettings());
/** @brief Overloaded version of triangleRasterize() with color-only rendering
*
* @param vertices vertices coordinates array. Should contain values of CV_32FC3 type or a compatible one (e.g. cv::Vec3f, etc.)
* @param indices triangle vertices index array, 3 per triangle. Each index indicates a vertex in a vertices array.
* Should contain CV_32SC3 values or compatible
* @param colors per-vertex colors of CV_32FC3 type or compatible. Can be empty or the same size as vertices array.
* If the values are out of [0; 1] range, the result correctness is not guaranteed
* @param colorBuf an array representing the final rendered image. Should containt CV_32FC3 values and be the same size as depthBuf.
* Not cleared before rendering, i.e. the content is reused as there is some pre-rendered scene.
* @param world2cam a 4x3 or 4x4 float or double matrix containing inverted (sic!) camera pose
* @param fovY field of view in vertical direction, given in radians
* @param zNear minimum Z value to render, everything closer is clipped
* @param zFar maximum Z value to render, everything farther is clipped
* @param settings see TriangleRasterizeSettings. By default the smooth shading is on,
* with CW culling and with disabled GL compatibility
*/
CV_EXPORTS_W void triangleRasterizeColor(InputArray vertices, InputArray indices, InputArray colors, InputOutputArray colorBuf,
InputArray world2cam, double fovY, double zNear, double zFar,
const TriangleRasterizeSettings& settings = TriangleRasterizeSettings());
/** @brief Octree for 3D vision.
*
* In 3D vision filed, the Octree is used to process and accelerate the pointcloud data. The class Octree represents
* the Octree data structure. Each Octree will have a fixed depth. The depth of Octree refers to the distance from
* the root node to the leaf node.All OctreeNodes will not exceed this depth.Increasing the depth will increase
* the amount of calculation exponentially. And the small number of depth refers low resolution of Octree.
* Each node contains 8 children, which are used to divide the space cube into eight parts. Each octree node represents
* a cube. And these eight children will have a fixed order, the order is described as follows:
*
* For illustration, assume,
*
* rootNode: origin == (0, 0, 0), size == 2
*
* Then,
*
* children[0]: origin == (0, 0, 0), size == 1
*
* children[1]: origin == (1, 0, 0), size == 1, along X-axis next to child 0
*
* children[2]: origin == (0, 1, 0), size == 1, along Y-axis next to child 0
*
* children[3]: origin == (1, 1, 0), size == 1, in X-Y plane
*
* children[4]: origin == (0, 0, 1), size == 1, along Z-axis next to child 0
*
* children[5]: origin == (1, 0, 1), size == 1, in X-Z plane
*
* children[6]: origin == (0, 1, 1), size == 1, in Y-Z plane
*
* children[7]: origin == (1, 1, 1), size == 1, furthest from child 0
*/
class CV_EXPORTS_W Octree
{
public:
//! Default constructor.
Octree();
/** @overload
* @brief Creates an empty Octree with given maximum depth
*
* @param maxDepth The max depth of the Octree
* @param size bounding box size for the Octree
* @param origin Initial center coordinate
* @param withColors Whether to keep per-point colors or not
* @return resulting Octree
*/
CV_WRAP static Ptr<Octree> createWithDepth(int maxDepth, double size, const Point3f& origin = { }, bool withColors = false);
/** @overload
* @brief Create an Octree from the PointCloud data with the specific maxDepth
*
* @param maxDepth Max depth of the octree
* @param pointCloud point cloud data, should be 3-channel float array
* @param colors color attribute of point cloud in the same 3-channel float format
* @return resulting Octree
*/
CV_WRAP static Ptr<Octree> createWithDepth(int maxDepth, InputArray pointCloud, InputArray colors = noArray());
/** @overload
* @brief Creates an empty Octree with given resolution
*
* @param resolution The size of the octree leaf node
* @param size bounding box size for the Octree
* @param origin Initial center coordinate
* @param withColors Whether to keep per-point colors or not
* @return resulting Octree
*/
CV_WRAP static Ptr<Octree> createWithResolution(double resolution, double size, const Point3f& origin = { }, bool withColors = false);
/** @overload
* @brief Create an Octree from the PointCloud data with the specific resolution
*
* @param resolution The size of the octree leaf node
* @param pointCloud point cloud data, should be 3-channel float array
* @param colors color attribute of point cloud in the same 3-channel float format
* @return resulting octree
*/
CV_WRAP static Ptr<Octree> createWithResolution(double resolution, InputArray pointCloud, InputArray colors = noArray());
//! Default destructor
~Octree();
/** @overload
* @brief Insert a point data with color to a OctreeNode.
*
* @param point The point data in Point3f format.
* @param color The color attribute of point in Point3f format.
* @return Returns whether the insertion is successful.
*/
CV_WRAP bool insertPoint(const Point3f& point, const Point3f& color = { });
/** @brief Determine whether the point is within the space range of the specific cube.
*
* @param point The point coordinates.
* @return If point is in bound, return ture. Otherwise, false.
*/
CV_WRAP bool isPointInBound(const Point3f& point) const;
//! returns true if the rootnode is NULL.
CV_WRAP bool empty() const;
/** @brief Reset all octree parameter.
*
* Clear all the nodes of the octree and initialize the parameters.
*/
CV_WRAP void clear();
/** @brief Delete a given point from the Octree.
*
* Delete the corresponding element from the pointList in the corresponding leaf node. If the leaf node
* does not contain other points after deletion, this node will be deleted. In the same way,
* its parent node may also be deleted if its last child is deleted.
* @param point The point coordinates, comparison is epsilon-based
* @return return ture if the point is deleted successfully.
*/
CV_WRAP bool deletePoint(const Point3f& point);
/** @brief restore point cloud data from Octree.
*
* Restore the point cloud data from existing octree. The points in same leaf node will be seen as the same point.
* This point is the center of the leaf node. If the resolution is small, it will work as a downSampling function.
* @param restoredPointCloud The output point cloud data, can be replaced by noArray() if not needed
* @param restoredColor The color attribute of point cloud data, can be omitted if not needed
*/
CV_WRAP void getPointCloudByOctree(OutputArray restoredPointCloud, OutputArray restoredColor = noArray());
/** @brief Radius Nearest Neighbor Search in Octree.
*
* Search all points that are less than or equal to radius.
* And return the number of searched points.
* @param query Query point.
* @param radius Retrieved radius value.
* @param points Point output. Contains searched points in 3-float format, and output vector is not in order,
* can be replaced by noArray() if not needed
* @param squareDists Dist output. Contains searched squared distance in floats, and output vector is not in order,
* can be omitted if not needed
* @return the number of searched points.
*/
CV_WRAP int radiusNNSearch(const Point3f& query, float radius, OutputArray points, OutputArray squareDists = noArray()) const;
/** @overload
* @brief Radius Nearest Neighbor Search in Octree.
*
* Search all points that are less than or equal to radius.
* And return the number of searched points.
* @param query Query point.
* @param radius Retrieved radius value.
* @param points Point output. Contains searched points in 3-float format, and output vector is not in order,
* can be replaced by noArray() if not needed
* @param colors Color output. Contains colors corresponding to points in pointSet, can be replaced by noArray() if not needed
* @param squareDists Dist output. Contains searched squared distance in floats, and output vector is not in order,
* can be replaced by noArray() if not needed
* @return the number of searched points.
*/
CV_WRAP int radiusNNSearch(const Point3f& query, float radius, OutputArray points, OutputArray colors, OutputArray squareDists) const;
/** @brief K Nearest Neighbor Search in Octree.
*
* Find the K nearest neighbors to the query point.
* @param query Query point.
* @param K amount of nearest neighbors to find
* @param points Point output. Contains K points in 3-float format, arranged in order of distance from near to far,
* can be replaced by noArray() if not needed
* @param squareDists Dist output. Contains K squared distance in floats, arranged in order of distance from near to far,
* can be omitted if not needed
*/
CV_WRAP void KNNSearch(const Point3f& query, const int K, OutputArray points, OutputArray squareDists = noArray()) const;
/** @overload
* @brief K Nearest Neighbor Search in Octree.
*
* Find the K nearest neighbors to the query point.
* @param query Query point.
* @param K amount of nearest neighbors to find
* @param points Point output. Contains K points in 3-float format, arranged in order of distance from near to far,
* can be replaced by noArray() if not needed
* @param colors Color output. Contains colors corresponding to points in pointSet, can be replaced by noArray() if not needed
* @param squareDists Dist output. Contains K squared distance in floats, arranged in order of distance from near to far,
* can be replaced by noArray() if not needed
*/
CV_WRAP void KNNSearch(const Point3f& query, const int K, OutputArray points, OutputArray colors, OutputArray squareDists) const;
protected:
struct Impl;
Ptr<Impl> p;
};
} // namespace cv
//! @} ptcloud
#endif
@@ -0,0 +1,181 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_DEPTH_HPP
#define OPENCV_3D_DEPTH_HPP
#include <opencv2/core.hpp>
#include <limits>
namespace cv
{
//! @addtogroup rgbd
//! @{
/** Object that can compute the normals in an image.
* It is an object as it can cache data for speed efficiency
* The implemented methods are either:
* - FALS (the fastest) and SRI from
* ``Fast and Accurate Computation of Surface Normals from Range Images``
* by H. Badino, D. Huber, Y. Park and T. Kanade
* - the normals with bilateral filtering on a depth image from
* ``Gradient Response Maps for Real-Time Detection of Texture-Less Objects``
* by S. Hinterstoisser, C. Cagniart, S. Ilic, P. Sturm, N. Navab, P. Fua, and V. Lepetit
*/
class CV_EXPORTS_W RgbdNormals
{
public:
enum RgbdNormalsMethod
{
RGBD_NORMALS_METHOD_FALS = 0,
RGBD_NORMALS_METHOD_LINEMOD = 1,
RGBD_NORMALS_METHOD_SRI = 2,
RGBD_NORMALS_METHOD_CROSS_PRODUCT = 3
};
RgbdNormals() { }
virtual ~RgbdNormals() { }
/** Creates new RgbdNormals object
* @param rows the number of rows of the depth image normals will be computed on
* @param cols the number of cols of the depth image normals will be computed on
* @param depth the depth of the normals (only CV_32F or CV_64F)
* @param K the calibration matrix to use
* @param window_size the window size to compute the normals: can only be 1,3,5 or 7
* @param diff_threshold threshold in depth difference, used in LINEMOD algirithm
* @param method one of the methods to use: RGBD_NORMALS_METHOD_SRI, RGBD_NORMALS_METHOD_FALS
*/
CV_WRAP static Ptr<RgbdNormals> create(int rows = 0, int cols = 0, int depth = 0, InputArray K = noArray(), int window_size = 5,
float diff_threshold = 50.f,
RgbdNormals::RgbdNormalsMethod method = RgbdNormals::RgbdNormalsMethod::RGBD_NORMALS_METHOD_FALS);
/** Given a set of 3d points in a depth image, compute the normals at each point.
* @param points a rows x cols x 3 matrix of CV_32F/CV64F or a rows x cols x 1 CV_U16S
* @param normals a rows x cols x 3 matrix
*/
CV_WRAP virtual void apply(InputArray points, OutputArray normals) const = 0;
/** Prepares cached data required for calculation
* If not called by user, called automatically at first calculation
*/
CV_WRAP virtual void cache() const = 0;
CV_WRAP virtual int getRows() const = 0;
CV_WRAP virtual void setRows(int val) = 0;
CV_WRAP virtual int getCols() const = 0;
CV_WRAP virtual void setCols(int val) = 0;
CV_WRAP virtual int getWindowSize() const = 0;
CV_WRAP virtual void setWindowSize(int val) = 0;
CV_WRAP virtual int getDepth() const = 0;
CV_WRAP virtual void getK(OutputArray val) const = 0;
CV_WRAP virtual void setK(InputArray val) = 0;
CV_WRAP virtual RgbdNormals::RgbdNormalsMethod getMethod() const = 0;
};
/** Registers depth data to an external camera
* Registration is performed by creating a depth cloud, transforming the cloud by
* the rigid body transformation between the cameras, and then projecting the
* transformed points into the RGB camera.
*
* uv_rgb = K_rgb * [R | t] * z * inv(K_ir) * uv_ir
*
* Currently does not check for negative depth values.
*
* @param unregisteredCameraMatrix the camera matrix of the depth camera
* @param registeredCameraMatrix the camera matrix of the external camera
* @param registeredDistCoeffs the distortion coefficients of the external camera
* @param Rt the rigid body transform between the cameras. Transforms points from depth camera frame to external camera frame.
* @param unregisteredDepth the input depth data
* @param outputImagePlaneSize the image plane dimensions of the external camera (width, height)
* @param registeredDepth the result of transforming the depth into the external camera
* @param depthDilation whether or not the depth is dilated to avoid holes and occlusion errors (optional)
*/
CV_EXPORTS_W void registerDepth(InputArray unregisteredCameraMatrix, InputArray registeredCameraMatrix, InputArray registeredDistCoeffs,
InputArray Rt, InputArray unregisteredDepth, const Size& outputImagePlaneSize,
OutputArray registeredDepth, bool depthDilation=false);
/**
* @param depth the depth image
* @param in_K
* @param in_points the list of xy coordinates
* @param points3d the resulting 3d points (point is represented by 4 chanels value [x, y, z, 0])
*/
CV_EXPORTS_W void depthTo3dSparse(InputArray depth, InputArray in_K, InputArray in_points, OutputArray points3d);
/** Converts a depth image to 3d points. If the mask is empty then the resulting array has the same dimensions as `depth`,
* otherwise it is 1d vector containing mask-enabled values only.
* The coordinate system is x pointing left, y down and z away from the camera
* @param depth the depth image (if given as short int CV_U, it is assumed to be the depth in millimeters
* (as done with the Microsoft Kinect), otherwise, if given as CV_32F or CV_64F, it is assumed in meters)
* @param K The calibration matrix
* @param points3d the resulting 3d points (point is represented by 4 channels value [x, y, z, 0]). They are of the same depth as `depth` if it is CV_32F or CV_64F, and the
* depth of `K` if `depth` is of depth CV_16U or CV_16S
* @param mask the mask of the points to consider (can be empty)
*/
CV_EXPORTS_W void depthTo3d(InputArray depth, InputArray K, OutputArray points3d, InputArray mask = noArray());
/** If the input image is of type CV_16UC1 (like the Kinect one), the image is converted to floats, divided
* by depth_factor to get a depth in meters, and the values 0 are converted to std::numeric_limits<float>::quiet_NaN()
* Otherwise, the image is simply converted to floats
* @param in the depth image (if given as short int CV_U, it is assumed to be the depth in millimeters
* (as done with the Microsoft Kinect), it is assumed in meters)
* @param type the desired output depth (CV_32F or CV_64F)
* @param out The rescaled float depth image
* @param depth_factor (optional) factor by which depth is converted to distance (by default = 1000.0 for Kinect sensor)
*/
CV_EXPORTS_W void rescaleDepth(InputArray in, int type, OutputArray out, double depth_factor = 1000.0);
/** Warps depth or RGB-D image by reprojecting it in 3d, applying Rt transformation
* and then projecting it back onto the image plane.
* This function can be used to visualize the results of the Odometry algorithm.
* @param depth Depth data, should be 1-channel CV_16U, CV_16S, CV_32F or CV_64F
* @param image RGB image (optional), should be 1-, 3- or 4-channel CV_8U
* @param mask Mask of used pixels (optional), should be CV_8UC1, CV_8SC1 or CV_BoolC1
* @param Rt Rotation+translation matrix (3x4 or 4x4) to be applied to depth points
* @param cameraMatrix Camera intrinsics matrix (3x3)
* @param warpedDepth The warped depth data (optional)
* @param warpedImage The warped RGB image (optional)
* @param warpedMask The mask of valid pixels in warped image (optional)
*/
CV_EXPORTS_W void warpFrame(InputArray depth, InputArray image, InputArray mask, InputArray Rt, InputArray cameraMatrix,
OutputArray warpedDepth = noArray(), OutputArray warpedImage = noArray(), OutputArray warpedMask = noArray());
enum RgbdPlaneMethod
{
RGBD_PLANE_METHOD_DEFAULT
};
/** Find the planes in a depth image
* @param points3d the 3d points organized like the depth image: rows x cols with 3 channels
* @param normals the normals for every point in the depth image; optional, can be empty
* @param mask An image where each pixel is labeled with the plane it belongs to
* and 255 if it does not belong to any plane
* @param plane_coefficients the coefficients of the corresponding planes (a,b,c,d) such that ax+by+cz+d=0, norm(a,b,c)=1
* and c < 0 (so that the normal points towards the camera)
* @param block_size The size of the blocks to look at for a stable MSE
* @param min_size The minimum size of a cluster to be considered a plane
* @param threshold The maximum distance of a point from a plane to belong to it (in meters)
* @param sensor_error_a coefficient of the sensor error. 0 by default, use 0.0075 for a Kinect
* @param sensor_error_b coefficient of the sensor error. 0 by default
* @param sensor_error_c coefficient of the sensor error. 0 by default
* @param method The method to use to compute the planes.
*/
CV_EXPORTS_W void findPlanes(InputArray points3d, InputArray normals, OutputArray mask, OutputArray plane_coefficients,
int block_size = 40, int min_size = 40*40, double threshold = 0.01,
double sensor_error_a = 0, double sensor_error_b = 0,
double sensor_error_c = 0,
RgbdPlaneMethod method = RGBD_PLANE_METHOD_DEFAULT);
// TODO Depth interpolation
// Curvature
// Get rescaleDepth return dubles if asked for
//! @}
} /* namespace cv */
#endif // include guard
@@ -0,0 +1,19 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_DETAIL_KINFU_FRAME_HPP
#define OPENCV_3D_DETAIL_KINFU_FRAME_HPP
#include <opencv2/core/affine.hpp>
namespace cv {
namespace detail {
CV_EXPORTS void renderPointsNormals(InputArray _points, InputArray _normals, OutputArray image, cv::Vec3f lightLoc);
CV_EXPORTS void renderPointsNormalsColors(InputArray _points, InputArray _normals, InputArray _colors, OutputArray image);
} // namespace detail
} // namespace cv
#endif // include guard
@@ -0,0 +1,77 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef POINTCLOUD_POSE_GRAPTH_HPP
#define POINTCLOUD_POSE_GRAPTH_HPP
#include "opencv2/core/affine.hpp"
#include "opencv2/core/quaternion.hpp"
#include "opencv2/geometry/3d.hpp"
#include "opencv2/geometry/detail/optimizer.hpp"
namespace cv
{
namespace detail
{
// ATTENTION! This class is used internally in Large KinFu.
// It has been pushed to publicly available headers for tests only.
// Source compatibility of this API is not guaranteed in the future.
// This class provides tools to solve so-called pose graph problem often arisen in SLAM problems
// The pose graph format, cost function and optimization techniques
// repeat the ones used in Ceres 3D Pose Graph Optimization:
// http://ceres-solver.org/nnls_tutorial.html#other-examples, pose_graph_3d.cc bullet
class CV_EXPORTS_W PoseGraph
{
public:
static Ptr<PoseGraph> create();
virtual ~PoseGraph();
// Node may have any id >= 0
virtual void addNode(size_t _nodeId, const Affine3d& _pose, bool fixed) = 0;
virtual bool isNodeExist(size_t nodeId) const = 0;
virtual bool setNodeFixed(size_t nodeId, bool fixed) = 0;
virtual bool isNodeFixed(size_t nodeId) const = 0;
virtual Affine3d getNodePose(size_t nodeId) const = 0;
virtual std::vector<size_t> getNodesIds() const = 0;
virtual size_t getNumNodes() const = 0;
// Edges have consequent indices starting from 0
virtual void addEdge(size_t _sourceNodeId, size_t _targetNodeId, const Affine3f& _transformation,
const Matx66f& _information = Matx66f::eye()) = 0;
virtual size_t getEdgeStart(size_t i) const = 0;
virtual size_t getEdgeEnd(size_t i) const = 0;
virtual Affine3d getEdgePose(size_t i) const = 0;
virtual Matx66f getEdgeInfo(size_t i) const = 0;
virtual size_t getNumEdges() const = 0;
// checks if graph is connected and each edge connects exactly 2 nodes
virtual bool isValid() const = 0;
// Calculates an initial pose estimate using the Minimum Spanning Tree (Prim's MST) algorithm.
// The result serves as a starting point for further optimization.
// Edge weights are calculated as:
// weight = translationNorm + lambda * rotationAngle
// The default lambda value (0.485) was empirically chosen based on its impact on optimizer performance,
// but can/should be tuned for different datasets.
virtual void initializePosesWithMST(double lambda = 0.485) = 0;
// creates an optimizer with user-defined settings and returns a pointer on it
virtual Ptr<cv::detail::LevMarqBase> createOptimizer(const LevMarq::Settings& settings) = 0;
// creates an optimizer with default settings and returns a pointer on it
virtual Ptr<cv::detail::LevMarqBase> createOptimizer() = 0;
// Creates an optimizer (with default settings) if it wasn't created before and runs it
// Returns number of iterations elapsed or -1 if failed to optimize
virtual LevMarq::Report optimize() = 0;
// calculate cost function based on current nodes parameters
virtual double calcEnergy() const = 0;
};
} // namespace detail
} // namespace cv
#endif // include guard
@@ -0,0 +1,558 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_DETAIL_SUBMAP_HPP
#define OPENCV_3D_DETAIL_SUBMAP_HPP
#include <opencv2/core.hpp>
#include <opencv2/core/affine.hpp>
#include "opencv2/geometry/detail/optimizer.hpp"
//TODO: remove it when it is rewritten to robust pose graph
#include "opencv2/core/dualquaternion.hpp"
#include <type_traits>
#include <vector>
#include <map>
#include <unordered_map>
namespace cv
{
namespace detail
{
template<typename MatType>
class Submap
{
public:
struct PoseConstraint
{
Affine3f estimatedPose;
int weight;
PoseConstraint() : weight(0){};
void accumulatePose(const Affine3f& _pose, int _weight = 1)
{
DualQuatf accPose = DualQuatf::createFromAffine3(estimatedPose) * float(weight) + DualQuatf::createFromAffine3(_pose) * float(_weight);
weight += _weight;
accPose = accPose / float(weight);
estimatedPose = accPose.toAffine3();
}
};
typedef std::map<int, PoseConstraint> Constraints;
Submap(int _id, const VolumeSettings& settings, const cv::Affine3f& _pose = cv::Affine3f::Identity(),
int _startFrameId = 0)
: id(_id), pose(_pose), cameraPose(Affine3f::Identity()), startFrameId(_startFrameId),
volume(VolumeType::HashTSDF, settings)
{ }
virtual ~Submap() = default;
virtual void integrate(InputArray _depth, const int currframeId);
virtual void raycast(const cv::Affine3f& cameraPose, cv::Size frameSize, cv::Matx33f K,
OutputArray points = noArray(), OutputArray normals = noArray());
virtual int getTotalAllocatedBlocks() const { return int(volume.getTotalVolumeUnits()); };
virtual int getVisibleBlocks(int currFrameId) const
{
CV_Assert(currFrameId >= startFrameId);
//return volume.getVisibleBlocks(currFrameId, FRAME_VISIBILITY_THRESHOLD);
return volume.getVisibleBlocks();
}
float calcVisibilityRatio(int currFrameId) const
{
int allocate_blocks = getTotalAllocatedBlocks();
int visible_blocks = getVisibleBlocks(currFrameId);
return float(visible_blocks) / float(allocate_blocks);
}
// Adding new Edge for Loop Closure Detection. Return true or false to indicate whether adding success.
bool addEdgeToSubmap(const int tarSubmapID, const Affine3f& tarPose);
//! TODO: Possibly useless
virtual void setStartFrameId(int _startFrameId) { startFrameId = _startFrameId; };
virtual void setStopFrameId(int _stopFrameId) { stopFrameId = _stopFrameId; };
void composeCameraPose(const cv::Affine3f& _relativePose) { cameraPose = cameraPose * _relativePose; }
PoseConstraint& getConstraint(const int _id)
{
//! Creates constraints if doesn't exist yet
return constraints[_id];
}
public:
const int id;
cv::Affine3f pose;
cv::Affine3f cameraPose;
Constraints constraints;
int startFrameId;
int stopFrameId;
//! TODO: Should we support submaps for regular volumes?
static constexpr int FRAME_VISIBILITY_THRESHOLD = 5;
//! TODO: Add support for GPU arrays (UMat)
OdometryFrame frame;
OdometryFrame renderFrame;
Volume volume;
};
template<typename MatType>
void Submap<MatType>::integrate(InputArray _depth, const int currFrameId)
{
CV_Assert(currFrameId >= startFrameId);
volume.integrate(_depth, cameraPose.matrix);
}
template<typename MatType>
void Submap<MatType>::raycast(const cv::Affine3f& _cameraPose, cv::Size frameSize, cv::Matx33f K,
OutputArray points, OutputArray normals)
{
if (!points.needed() && !normals.needed())
{
MatType pts, nrm;
//TODO: get depth instead of pts from raycast
volume.raycast(_cameraPose.matrix, frameSize.height, frameSize.width, K, pts, nrm);
std::vector<MatType> pch(3);
split(pts, pch);
renderFrame = frame;
frame = OdometryFrame(pch[2]);
}
else
{
volume.raycast(_cameraPose.matrix, frameSize.height, frameSize.width, K, points, normals);
}
}
template<typename MatType>
bool Submap<MatType>::addEdgeToSubmap(const int tarSubmapID, const Affine3f& tarPose)
{
auto iter = constraints.find(tarSubmapID);
// if there is NO edge of currSubmap to tarSubmap.
if(iter == constraints.end())
{
// Frome pose to tarPose transformation
Affine3f estimatePose = tarPose * pose.inv();
// Create new Edge.
PoseConstraint& preConstrain = getConstraint(tarSubmapID);
preConstrain.accumulatePose(estimatePose, 1);
return true;
} else
{
return false;
}
}
/**
* @brief: Manages all the created submaps for a particular scene
*/
template<typename MatType>
class SubmapManager
{
public:
enum class Type
{
NEW = 0,
CURRENT = 1,
RELOCALISATION = 2,
LOOP_CLOSURE = 3,
LOST = 4
};
struct ActiveSubmapData
{
Type type;
std::vector<Affine3f> constraints;
int trackingAttempts;
};
typedef Submap<MatType> SubmapT;
typedef std::map<int, Ptr<SubmapT>> IdToSubmapPtr;
typedef std::unordered_map<int, ActiveSubmapData> IdToActiveSubmaps;
explicit SubmapManager(const VolumeSettings& _volumeSettings) : volumeSettings(_volumeSettings) {}
virtual ~SubmapManager() = default;
void reset() { submapList.clear(); };
bool shouldCreateSubmap(int frameId);
bool shouldChangeCurrSubmap(int _frameId, int toSubmapId);
//! Adds a new submap/volume into the current list of managed/Active submaps
int createNewSubmap(bool isCurrentActiveMap, const int currFrameId = 0, const Affine3f& pose = cv::Affine3f::Identity());
void removeSubmap(int _id);
size_t numOfSubmaps(void) const { return submapList.size(); };
size_t numOfActiveSubmaps(void) const { return activeSubmaps.size(); };
Ptr<SubmapT> getSubmap(int _id) const;
Ptr<SubmapT> getCurrentSubmap(void) const;
int estimateConstraint(int fromSubmapId, int toSubmapId, int& inliers, Affine3f& inlierPose);
bool updateMap(int frameId, const OdometryFrame& frame);
bool addEdgeToCurrentSubmap(const int currentSubmapID, const int tarSubmapID);
Ptr<detail::PoseGraph> MapToPoseGraph();
void PoseGraphToMap(const Ptr<detail::PoseGraph>& updatedPoseGraph);
VolumeSettings volumeSettings;
std::vector<Ptr<SubmapT>> submapList;
IdToActiveSubmaps activeSubmaps;
Ptr<detail::PoseGraph> poseGraph;
};
template<typename MatType>
int SubmapManager<MatType>::createNewSubmap(bool isCurrentMap, int currFrameId, const Affine3f& pose)
{
int newId = int(submapList.size());
Ptr<SubmapT> newSubmap = cv::makePtr<SubmapT>(newId, volumeSettings, pose, currFrameId);
submapList.push_back(newSubmap);
ActiveSubmapData newSubmapData;
newSubmapData.trackingAttempts = 0;
newSubmapData.type = isCurrentMap ? Type::CURRENT : Type::NEW;
activeSubmaps[newId] = newSubmapData;
return newId;
}
template<typename MatType>
Ptr<Submap<MatType>> SubmapManager<MatType>::getSubmap(int _id) const
{
CV_Assert(submapList.size() > 0);
CV_Assert(_id >= 0 && _id < int(submapList.size()));
return submapList.at(_id);
}
template<typename MatType>
Ptr<Submap<MatType>> SubmapManager<MatType>::getCurrentSubmap(void) const
{
for (const auto& it : activeSubmaps)
{
if (it.second.type == Type::CURRENT)
return getSubmap(it.first);
}
return nullptr;
}
template<typename MatType>
bool SubmapManager<MatType>::shouldCreateSubmap(int currFrameId)
{
int currSubmapId = -1;
for (const auto& it : activeSubmaps)
{
auto submapData = it.second;
// No more than 1 new submap at a time!
if (submapData.type == Type::NEW)
{
return false;
}
if (submapData.type == Type::CURRENT)
{
currSubmapId = it.first;
}
}
//! TODO: This shouldn't be happening? since there should always be one active current submap
if (currSubmapId < 0)
{
return false;
}
Ptr<SubmapT> currSubmap = getSubmap(currSubmapId);
float ratio = currSubmap->calcVisibilityRatio(currFrameId);
//TODO: fix this when a new pose graph is ready
// if (ratio < 0.2f)
if (ratio < 0.5f)
return true;
return false;
}
template<typename MatType>
int SubmapManager<MatType>::estimateConstraint(int fromSubmapId, int toSubmapId, int& inliers, Affine3f& inlierPose)
{
static constexpr int MAX_ITER = 10;
static constexpr float CONVERGE_WEIGHT_THRESHOLD = 0.01f;
static constexpr float INLIER_WEIGHT_THRESH = 0.8f;
static constexpr int MIN_INLIERS = 10;
static constexpr int MAX_TRACKING_ATTEMPTS = 25;
//! thresh = HUBER_THRESH
auto huberWeight = [](float residual, float thresh = 0.1f) -> float {
float rAbs = abs(residual);
if (rAbs < thresh)
return 1.0;
float numerator = sqrt(2 * thresh * rAbs - thresh * thresh);
return numerator / rAbs;
};
Ptr<SubmapT> fromSubmap = getSubmap(fromSubmapId);
Ptr<SubmapT> toSubmap = getSubmap(toSubmapId);
ActiveSubmapData& fromSubmapData = activeSubmaps.at(fromSubmapId);
Affine3f TcameraToFromSubmap = fromSubmap->cameraPose;
Affine3f TcameraToToSubmap = toSubmap->cameraPose;
// FromSubmap -> ToSubmap transform
Affine3f candidateConstraint = TcameraToToSubmap * TcameraToFromSubmap.inv();
fromSubmapData.trackingAttempts++;
fromSubmapData.constraints.push_back(candidateConstraint);
std::vector<float> weights(fromSubmapData.constraints.size() + 1, 1.0f);
Affine3f prevConstraint = fromSubmap->getConstraint(toSubmap->id).estimatedPose;
int prevWeight = fromSubmap->getConstraint(toSubmap->id).weight;
// Iterative reweighted least squares with huber threshold to find the inliers in the past observations
Vec6f meanConstraint;
float sumWeight = 0.0f;
for (int i = 0; i < MAX_ITER; i++)
{
Vec6f constraintVec;
for (int j = 0; j < int(weights.size() - 1); j++)
{
Affine3f currObservation = fromSubmapData.constraints[j];
cv::vconcat(currObservation.rvec(), currObservation.translation(), constraintVec);
meanConstraint += weights[j] * constraintVec;
sumWeight += weights[j];
}
// Heavier weight given to the estimatedPose
cv::vconcat(prevConstraint.rvec(), prevConstraint.translation(), constraintVec);
meanConstraint += weights.back() * prevWeight * constraintVec;
sumWeight += prevWeight;
meanConstraint /= float(sumWeight);
float residual = 0.0f;
float diff = 0.0f;
for (int j = 0; j < int(weights.size()); j++)
{
int w;
if (j == int(weights.size() - 1))
{
cv::vconcat(prevConstraint.rvec(), prevConstraint.translation(), constraintVec);
w = prevWeight;
}
else
{
Affine3f currObservation = fromSubmapData.constraints[j];
cv::vconcat(currObservation.rvec(), currObservation.translation(), constraintVec);
w = 1;
}
cv::Vec6f residualVec = (constraintVec - meanConstraint);
residual = float(norm(residualVec));
float newWeight = huberWeight(residual);
diff += w * abs(newWeight - weights[j]);
weights[j] = newWeight;
}
if (diff / (prevWeight + weights.size() - 1) < CONVERGE_WEIGHT_THRESHOLD)
break;
}
int localInliers = 0;
DualQuatf inlierConstraint;
for (int i = 0; i < int(weights.size()); i++)
{
if (weights[i] > INLIER_WEIGHT_THRESH)
{
localInliers++;
if (i == int(weights.size() - 1))
inlierConstraint += DualQuatf::createFromMat(prevConstraint.matrix);
else
inlierConstraint += DualQuatf::createFromMat(fromSubmapData.constraints[i].matrix);
}
}
inlierConstraint = inlierConstraint * 1.0f/float(max(localInliers, 1));
inlierPose = inlierConstraint.toAffine3();
inliers = localInliers;
if (inliers >= MIN_INLIERS)
{
return 1;
}
if(fromSubmapData.trackingAttempts - inliers > (MAX_TRACKING_ATTEMPTS - MIN_INLIERS))
{
return -1;
}
return 0;
}
template<typename MatType>
bool SubmapManager<MatType>::shouldChangeCurrSubmap(int _frameId, int toSubmapId)
{
auto toSubmap = getSubmap(toSubmapId);
auto toSubmapData = activeSubmaps.at(toSubmapId);
auto currActiveSubmap = getCurrentSubmap();
int blocksInNewMap = toSubmap->getTotalAllocatedBlocks();
float newRatio = toSubmap->calcVisibilityRatio(_frameId);
float currRatio = currActiveSubmap->calcVisibilityRatio(_frameId);
//! TODO: Check for a specific threshold?
if (blocksInNewMap <= 0)
return false;
if ((newRatio > currRatio) && (toSubmapData.type == Type::NEW))
return true;
return false;
}
template<typename MatType>
bool SubmapManager<MatType>::addEdgeToCurrentSubmap(const int currentSubmapID, const int tarSubmapID)
{
Ptr<SubmapT> currentSubmap = getSubmap(currentSubmapID);
Ptr<SubmapT> tarSubmap = getSubmap(tarSubmapID);
return currentSubmap->addEdgeToSubmap(tarSubmapID, tarSubmap->pose);
}
template<typename MatType>
bool SubmapManager<MatType>::updateMap(int _frameId, const OdometryFrame& _frame)
{
bool mapUpdated = false;
int changedCurrentMapId = -1;
const int currSubmapId = getCurrentSubmap()->id;
for (auto& it : activeSubmaps)
{
int submapId = it.first;
auto& submapData = it.second;
if (submapData.type == Type::NEW || submapData.type == Type::LOOP_CLOSURE)
{
// Check with previous estimate
int inliers;
Affine3f inlierPose;
int constraintUpdate = estimateConstraint(submapId, currSubmapId, inliers, inlierPose);
if (constraintUpdate == 1)
{
typename SubmapT::PoseConstraint& submapConstraint = getSubmap(submapId)->getConstraint(currSubmapId);
submapConstraint.accumulatePose(inlierPose, inliers);
submapData.constraints.clear();
submapData.trackingAttempts = 0;
if (shouldChangeCurrSubmap(_frameId, submapId))
{
changedCurrentMapId = submapId;
}
mapUpdated = true;
}
else if(constraintUpdate == -1)
{
submapData.type = Type::LOST;
}
}
}
std::vector<int> createNewConstraintsList;
for (auto& it : activeSubmaps)
{
int submapId = it.first;
auto& submapData = it.second;
if (submapId == changedCurrentMapId)
{
submapData.type = Type::CURRENT;
}
if ((submapData.type == Type::CURRENT) && (changedCurrentMapId >= 0) && (submapId != changedCurrentMapId))
{
submapData.type = Type::LOST;
createNewConstraintsList.push_back(submapId);
}
if ((submapData.type == Type::NEW || submapData.type == Type::LOOP_CLOSURE) && (changedCurrentMapId >= 0))
{
//! TODO: Add a new type called NEW_LOST?
submapData.type = Type::LOST;
createNewConstraintsList.push_back(submapId);
}
}
for (typename IdToActiveSubmaps::iterator it = activeSubmaps.begin(); it != activeSubmaps.end();)
{
auto& submapData = it->second;
if (submapData.type == Type::LOST)
it = activeSubmaps.erase(it);
else
it++;
}
for (std::vector<int>::const_iterator it = createNewConstraintsList.begin(); it != createNewConstraintsList.end(); ++it)
{
int dataId = *it;
ActiveSubmapData newSubmapData;
newSubmapData.trackingAttempts = 0;
newSubmapData.type = Type::LOOP_CLOSURE;
activeSubmaps[dataId] = newSubmapData;
}
if (shouldCreateSubmap(_frameId))
{
Ptr<SubmapT> currActiveSubmap = getCurrentSubmap();
Affine3f newSubmapPose = currActiveSubmap->pose * currActiveSubmap->cameraPose;
int submapId = createNewSubmap(false, _frameId, newSubmapPose);
auto newSubmap = getSubmap(submapId);
newSubmap->frame = _frame;
}
return mapUpdated;
}
template<typename MatType>
Ptr<detail::PoseGraph> SubmapManager<MatType>::MapToPoseGraph()
{
Ptr<detail::PoseGraph> localPoseGraph = detail::PoseGraph::create();
for(const auto& currSubmap : submapList)
{
const typename SubmapT::Constraints& constraintList = currSubmap->constraints;
for(const auto& currConstraintPair : constraintList)
{
// TODO: Handle case with duplicate constraints A -> B and B -> A
/* Matx66f informationMatrix = Matx66f::eye() * (currConstraintPair.second.weight/10); */
Matx66f informationMatrix = Matx66f::eye();
localPoseGraph->addEdge(currSubmap->id, currConstraintPair.first, currConstraintPair.second.estimatedPose, informationMatrix);
}
}
for(const auto& currSubmap : submapList)
{
localPoseGraph->addNode(currSubmap->id, currSubmap->pose, (currSubmap->id == 0));
}
return localPoseGraph;
}
template <typename MatType>
void SubmapManager<MatType>::PoseGraphToMap(const Ptr<detail::PoseGraph>& updatedPoseGraph)
{
for(const auto& currSubmap : submapList)
{
Affine3d pose = updatedPoseGraph->getNodePose(currSubmap->id);
if(!updatedPoseGraph->isNodeFixed(currSubmap->id))
currSubmap->pose = pose;
}
}
} // namespace detail
} // namespace cv
#endif // include guard
@@ -0,0 +1,112 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_ODOMETRY_HPP
#define OPENCV_3D_ODOMETRY_HPP
#include <opencv2/core.hpp>
#include "odometry_frame.hpp"
#include "odometry_settings.hpp"
namespace cv
{
/** These constants are used to set a type of data which odometry will use
* @param DEPTH only depth data
* @param RGB only rgb image
* @param RGB_DEPTH depth and rgb data simultaneously
*/
enum class OdometryType
{
DEPTH = 0,
RGB = 1,
RGB_DEPTH = 2
};
/** These constants are used to set the speed and accuracy of odometry
* @param COMMON accurate but not so fast
* @param FAST less accurate but faster
*/
enum class OdometryAlgoType
{
COMMON = 0,
FAST = 1
};
class CV_EXPORTS_W Odometry
{
public:
CV_WRAP Odometry();
CV_WRAP explicit Odometry(OdometryType otype);
CV_WRAP Odometry(OdometryType otype, const OdometrySettings& settings, OdometryAlgoType algtype);
~Odometry();
/** Prepare frame for odometry calculation
* @param frame odometry prepare this frame as src frame and dst frame simultaneously
*/
CV_WRAP void prepareFrame(OdometryFrame& frame) const;
/** Prepare frame for odometry calculation
* @param srcFrame frame will be prepared as src frame ("original" image)
* @param dstFrame frame will be prepared as dsr frame ("rotated" image)
*/
CV_WRAP void prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const;
/** Compute Rigid Transformation between two frames so that Rt * src = dst
* Both frames, source and destination, should have been prepared by calling prepareFrame() first
*
* @param srcFrame src frame ("original" image)
* @param dstFrame dst frame ("rotated" image)
* @param Rt Rigid transformation, which will be calculated, in form:
* { R_11 R_12 R_13 t_1
* R_21 R_22 R_23 t_2
* R_31 R_32 R_33 t_3
* 0 0 0 1 }
* @return true on success, false if failed to find the transformation
*/
CV_WRAP bool compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const;
/**
* @brief Compute Rigid Transformation between two frames so that Rt * src = dst
*
* @param srcDepth source depth ("original" image)
* @param dstDepth destination depth ("rotated" image)
* @param Rt Rigid transformation, which will be calculated, in form:
* { R_11 R_12 R_13 t_1
* R_21 R_22 R_23 t_2
* R_31 R_32 R_33 t_3
* 0 0 0 1 }
* @return true on success, false if failed to find the transformation
*/
CV_WRAP bool compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const;
/**
* @brief Compute Rigid Transformation between two frames so that Rt * src = dst
*
* @param srcDepth source depth ("original" image)
* @param srcRGB source RGB
* @param dstDepth destination depth ("rotated" image)
* @param dstRGB destination RGB
* @param Rt Rigid transformation, which will be calculated, in form:
* { R_11 R_12 R_13 t_1
* R_21 R_22 R_23 t_2
* R_31 R_32 R_33 t_3
* 0 0 0 1 }
* @return true on success, false if failed to find the transformation
*/
CV_WRAP bool compute(InputArray srcDepth, InputArray srcRGB, InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const;
/**
* @brief Get the normals computer object used for normals calculation (if presented).
* The normals computer is generated at first need during prepareFrame when normals are required for the ICP algorithm
* but not presented by a user. Re-generated each time the related settings change or a new frame arrives with the different size.
*/
CV_WRAP Ptr<RgbdNormals> getNormalsComputer() const;
class Impl;
private:
Ptr<Impl> impl;
};
}
#endif //OPENCV_3D_ODOMETRY_HPP
@@ -0,0 +1,108 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_ODOMETRY_FRAME_HPP
#define OPENCV_3D_ODOMETRY_FRAME_HPP
#include <opencv2/core.hpp>
namespace cv
{
/** Indicates what pyramid is to access using getPyramidAt() method:
*/
enum OdometryFramePyramidType
{
PYR_IMAGE = 0, //!< The pyramid of grayscale images
PYR_DEPTH = 1, //!< The pyramid of depth images
PYR_MASK = 2, //!< The pyramid of masks
PYR_CLOUD = 3, //!< The pyramid of point clouds, produced from the pyramid of depths
PYR_DIX = 4, //!< The pyramid of dI/dx derivative images
PYR_DIY = 5, //!< The pyramid of dI/dy derivative images
PYR_TEXMASK = 6, //!< The pyramid of "textured" masks (i.e. additional masks for normals or grayscale images)
PYR_NORM = 7, //!< The pyramid of normals
PYR_NORMMASK = 8, //!< The pyramid of normals masks
N_PYRAMIDS
};
/**
* @brief An object that keeps per-frame data for Odometry algorithms from user-provided images to algorithm-specific precalculated data.
* When not empty, it contains a depth image, a mask of valid pixels and a set of pyramids generated from that data.
* A BGR/Gray image and normals are optional.
* OdometryFrame is made to be used together with Odometry class to reuse precalculated data between Rt data calculations.
* A correct way to do that is to call Odometry::prepareFrames() on prev and next frames and then pass them to Odometry::compute() method.
*/
class CV_EXPORTS_W_SIMPLE OdometryFrame
{
public:
/**
* @brief Construct a new OdometryFrame object. All non-empty images should have the same size.
*
* @param depth A depth image, should be CV_8UC1
* @param image An BGR or grayscale image (or noArray() if it's not required for used ICP algorithm).
* Should be CV_8UC3 or CV_8C4 if it's BGR image or CV_8UC1 if it's grayscale. If it's BGR then it's converted to grayscale
* image automatically.
* @param mask A user-provided mask of valid pixels, should be CV_8UC1
* @param normals A user-provided normals to the depth surface, should be CV_32FC4
*/
CV_WRAP explicit OdometryFrame(InputArray depth = noArray(), InputArray image = noArray(), InputArray mask = noArray(), InputArray normals = noArray());
~OdometryFrame() {};
/**
* @brief Get the original user-provided BGR/Gray image
*
* @param image Output image
*/
CV_WRAP void getImage(OutputArray image) const;
/**
* @brief Get the gray image generated from the user-provided BGR/Gray image
*
* @param image Output image
*/
CV_WRAP void getGrayImage(OutputArray image) const;
/**
* @brief Get the original user-provided depth image
*
* @param depth Output image
*/
CV_WRAP void getDepth(OutputArray depth) const;
/**
* @brief Get the depth image generated from the user-provided one after conversion, rescale or filtering for ICP algorithm needs
*
* @param depth Output image
*/
CV_WRAP void getProcessedDepth(OutputArray depth) const;
/**
* @brief Get the valid pixels mask generated for the ICP calculations intersected with the user-provided mask
*
* @param mask Output image
*/
CV_WRAP void getMask(OutputArray mask) const;
/**
* @brief Get the normals image either generated for the ICP calculations or user-provided
*
* @param normals Output image
*/
CV_WRAP void getNormals(OutputArray normals) const;
/**
* @brief Get the amount of levels in pyramids (all of them if not empty should have the same number of levels)
* or 0 if no pyramids were prepared yet
*/
CV_WRAP int getPyramidLevels() const;
/**
* @brief Get the image generated for the ICP calculations from one of the pyramids specified by pyrType. Returns empty image if
* the pyramid is empty or there's no such pyramid level
*
* @param img Output image
* @param pyrType Type of pyramid
* @param level Level in the pyramid
*/
CV_WRAP void getPyramidAt(OutputArray img, OdometryFramePyramidType pyrType, size_t level) const;
class Impl;
Ptr<Impl> impl;
};
}
#endif // !OPENCV_3D_ODOMETRY_FRAME_HPP
@@ -0,0 +1,65 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_ODOMETRY_SETTINGS_HPP
#define OPENCV_3D_ODOMETRY_SETTINGS_HPP
#include <opencv2/core.hpp>
namespace cv
{
class CV_EXPORTS_W_SIMPLE OdometrySettings
{
public:
CV_WRAP OdometrySettings();
OdometrySettings(const OdometrySettings&);
OdometrySettings& operator=(const OdometrySettings&);
~OdometrySettings() {};
CV_WRAP void setCameraMatrix(InputArray val);
CV_WRAP void getCameraMatrix(OutputArray val) const;
CV_WRAP void setIterCounts(InputArray val);
CV_WRAP void getIterCounts(OutputArray val) const;
CV_WRAP void setMinDepth(float val);
CV_WRAP float getMinDepth() const;
CV_WRAP void setMaxDepth(float val);
CV_WRAP float getMaxDepth() const;
CV_WRAP void setMaxDepthDiff(float val);
CV_WRAP float getMaxDepthDiff() const;
CV_WRAP void setMaxPointsPart(float val);
CV_WRAP float getMaxPointsPart() const;
CV_WRAP void setSobelSize(int val);
CV_WRAP int getSobelSize() const;
CV_WRAP void setSobelScale(double val);
CV_WRAP double getSobelScale() const;
CV_WRAP void setNormalWinSize(int val);
CV_WRAP int getNormalWinSize() const;
CV_WRAP void setNormalDiffThreshold(float val);
CV_WRAP float getNormalDiffThreshold() const;
CV_WRAP void setNormalMethod(RgbdNormals::RgbdNormalsMethod nm);
CV_WRAP RgbdNormals::RgbdNormalsMethod getNormalMethod() const;
CV_WRAP void setAngleThreshold(float val);
CV_WRAP float getAngleThreshold() const;
CV_WRAP void setMaxTranslation(float val);
CV_WRAP float getMaxTranslation() const;
CV_WRAP void setMaxRotation(float val);
CV_WRAP float getMaxRotation() const;
CV_WRAP void setMinGradientMagnitude(float val);
CV_WRAP float getMinGradientMagnitude() const;
CV_WRAP void setMinGradientMagnitudes(InputArray val);
CV_WRAP void getMinGradientMagnitudes(OutputArray val) const;
class Impl;
private:
Ptr<Impl> impl;
};
}
#endif //OPENCV_3D_ODOMETRY_SETTINGS_HPP
@@ -0,0 +1,172 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_VOLUME_HPP
#define OPENCV_3D_VOLUME_HPP
#include "volume_settings.hpp"
#include "opencv2/ptcloud/odometry_frame.hpp"
#include "opencv2/core/affine.hpp"
namespace cv
{
class CV_EXPORTS_W Volume
{
public:
/** @brief Constructor of custom volume.
* @param vtype the volume type [TSDF, HashTSDF, ColorTSDF].
* @param settings the custom settings for volume.
*/
CV_WRAP explicit Volume(VolumeType vtype = VolumeType::TSDF,
const VolumeSettings& settings = VolumeSettings(VolumeType::TSDF));
~Volume();
/** @brief Integrates the input data to the volume.
Camera intrinsics are taken from volume settings structure.
* @param frame the object from which to take depth and image data.
For color TSDF a depth data should be registered with color data, i.e. have the same intrinsics & camera pose.
This can be done using function registerDepth() from 3d module.
* @param pose the pose of camera in global coordinates.
*/
CV_WRAP_AS(integrateFrame) void integrate(const OdometryFrame& frame, InputArray pose);
/** @brief Integrates the input data to the volume.
Camera intrinsics are taken from volume settings structure.
* @param depth the depth image.
* @param pose the pose of camera in global coordinates.
*/
CV_WRAP_AS(integrate) void integrate(InputArray depth, InputArray pose);
/** @brief Integrates the input data to the volume.
Camera intrinsics are taken from volume settings structure.
* @param depth the depth image.
* @param image the color image (only for ColorTSDF).
For color TSDF a depth data should be registered with color data, i.e. have the same intrinsics & camera pose.
This can be done using function registerDepth() from 3d module.
* @param pose the pose of camera in global coordinates.
*/
CV_WRAP_AS(integrateColor) void integrate(InputArray depth, InputArray image, InputArray pose);
// Python bindings do not process noArray() default argument correctly, so the raycast() method is repeated several times
/** @brief Renders the volume contents into an image. The resulting points and normals are in camera's coordinate system.
Rendered image size and camera intrinsics are taken from volume settings structure.
* @param cameraPose the pose of camera in global coordinates.
* @param points image to store rendered points.
* @param normals image to store rendered normals corresponding to points.
*/
CV_WRAP void raycast(InputArray cameraPose, OutputArray points, OutputArray normals) const;
/** @brief Renders the volume contents into an image. The resulting points and normals are in camera's coordinate system.
Rendered image size and camera intrinsics are taken from volume settings structure.
* @param cameraPose the pose of camera in global coordinates.
* @param points image to store rendered points.
* @param normals image to store rendered normals corresponding to points.
* @param colors image to store rendered colors corresponding to points (only for ColorTSDF).
*/
CV_WRAP_AS(raycastColor) void raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const;
/** @brief Renders the volume contents into an image. The resulting points and normals are in camera's coordinate system.
Rendered image size and camera intrinsics are taken from volume settings structure.
* @param cameraPose the pose of camera in global coordinates.
* @param height the height of result image
* @param width the width of result image
* @param K camera raycast intrinsics
* @param points image to store rendered points.
* @param normals image to store rendered normals corresponding to points.
*/
CV_WRAP_AS(raycastEx) void raycast(InputArray cameraPose, int height, int width, InputArray K, OutputArray points, OutputArray normals) const;
/** @brief Renders the volume contents into an image. The resulting points and normals are in camera's coordinate system.
Rendered image size and camera intrinsics are taken from volume settings structure.
* @param cameraPose the pose of camera in global coordinates.
* @param height the height of result image
* @param width the width of result image
* @param K camera raycast intrinsics
* @param points image to store rendered points.
* @param normals image to store rendered normals corresponding to points.
* @param colors image to store rendered colors corresponding to points (only for ColorTSDF).
*/
CV_WRAP_AS(raycastExColor) void raycast(InputArray cameraPose, int height, int width, InputArray K, OutputArray points, OutputArray normals, OutputArray colors) const;
/** @brief Extract the all data from volume.
* @param points the input exist point.
* @param normals the storage of normals (corresponding to input points) in the image.
*/
CV_WRAP void fetchNormals(InputArray points, OutputArray normals) const;
/** @brief Extract the all data from volume.
* @param points the storage of all points.
* @param normals the storage of all normals, corresponding to points.
*/
CV_WRAP void fetchPointsNormals(OutputArray points, OutputArray normals) const;
/** @brief Extract the all data from volume.
* @param points the storage of all points.
* @param normals the storage of all normals, corresponding to points.
* @param colors the storage of all colors, corresponding to points (only for ColorTSDF).
*/
CV_WRAP void fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const;
/** @brief clear all data in volume.
*/
CV_WRAP void reset();
//TODO: remove this
/** @brief return visible blocks in volume.
*/
CV_WRAP int getVisibleBlocks() const;
/** @brief return number of volume units in volume.
*/
CV_WRAP size_t getTotalVolumeUnits() const;
enum BoundingBoxPrecision
{
VOLUME_UNIT = 0,
VOXEL = 1
};
/**
* @brief Gets bounding box in volume coordinates with given precision:
* VOLUME_UNIT - up to volume unit
* VOXEL - up to voxel (currently not supported)
* @param bb 6-float 1d array containing (min_x, min_y, min_z, max_x, max_y, max_z) in volume coordinates
* @param precision bounding box calculation precision
*/
CV_WRAP void getBoundingBox(OutputArray bb, int precision) const;
/**
* @brief Enables or disables new volume unit allocation during integration.
* Makes sense for HashTSDF only.
*/
CV_WRAP void setEnableGrowth(bool v);
/**
* @brief Returns if new volume units are allocated during integration or not.
* Makes sense for HashTSDF only.
*/
CV_WRAP bool getEnableGrowth() const;
class Impl;
private:
Ptr<Impl> impl;
};
} // namespace cv
#endif // include guard
@@ -0,0 +1,208 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_VOLUME_SETTINGS_HPP
#define OPENCV_3D_VOLUME_SETTINGS_HPP
#include <opencv2/core.hpp>
#include <opencv2/ptcloud/volume.hpp>
namespace cv
{
enum class VolumeType
{
TSDF = 0,
HashTSDF = 1,
ColorTSDF = 2
};
class CV_EXPORTS_W_SIMPLE VolumeSettings
{
public:
/** @brief Constructor of settings for custom Volume type.
* @param volumeType volume type.
*/
CV_WRAP explicit VolumeSettings(VolumeType volumeType = VolumeType::TSDF);
VolumeSettings(const VolumeSettings& vs);
VolumeSettings& operator=(const VolumeSettings&);
~VolumeSettings();
/** @brief Sets the width of the image for integration.
* @param val input value.
*/
CV_WRAP void setIntegrateWidth(int val);
/** @brief Returns the width of the image for integration.
*/
CV_WRAP int getIntegrateWidth() const;
/** @brief Sets the height of the image for integration.
* @param val input value.
*/
CV_WRAP void setIntegrateHeight(int val);
/** @brief Returns the height of the image for integration.
*/
CV_WRAP int getIntegrateHeight() const;
/** @brief Sets the width of the raycasted image, used when user does not provide it at raycast() call.
* @param val input value.
*/
CV_WRAP void setRaycastWidth(int val);
/** @brief Returns the width of the raycasted image, used when user does not provide it at raycast() call.
*/
CV_WRAP int getRaycastWidth() const;
/** @brief Sets the height of the raycasted image, used when user does not provide it at raycast() call.
* @param val input value.
*/
CV_WRAP void setRaycastHeight(int val);
/** @brief Returns the height of the raycasted image, used when user does not provide it at raycast() call.
*/
CV_WRAP int getRaycastHeight() const;
/** @brief Sets depth factor, witch is the number for depth scaling.
* @param val input value.
*/
CV_WRAP void setDepthFactor(float val);
/** @brief Returns depth factor, witch is the number for depth scaling.
*/
CV_WRAP float getDepthFactor() const;
/** @brief Sets the size of voxel.
* @param val input value.
*/
CV_WRAP void setVoxelSize(float val);
/** @brief Returns the size of voxel.
*/
CV_WRAP float getVoxelSize() const;
/** @brief Sets TSDF truncation distance. Distances greater than value from surface will be truncated to 1.0.
* @param val input value.
*/
CV_WRAP void setTsdfTruncateDistance(float val);
/** @brief Returns TSDF truncation distance. Distances greater than value from surface will be truncated to 1.0.
*/
CV_WRAP float getTsdfTruncateDistance() const;
/** @brief Sets threshold for depth truncation in meters. Truncates the depth greater than threshold to 0.
* @param val input value.
*/
CV_WRAP void setMaxDepth(float val);
/** @brief Returns threshold for depth truncation in meters. Truncates the depth greater than threshold to 0.
*/
CV_WRAP float getMaxDepth() const;
/** @brief Sets max number of frames to integrate per voxel.
Represents the max number of frames over which a running average of the TSDF is calculated for a voxel.
* @param val input value.
*/
CV_WRAP void setMaxWeight(int val);
/** @brief Returns max number of frames to integrate per voxel.
Represents the max number of frames over which a running average of the TSDF is calculated for a voxel.
*/
CV_WRAP int getMaxWeight() const;
/** @brief Sets length of single raycast step.
Describes the percentage of voxel length that is skipped per march.
* @param val input value.
*/
CV_WRAP void setRaycastStepFactor(float val);
/** @brief Returns length of single raycast step.
Describes the percentage of voxel length that is skipped per march.
*/
CV_WRAP float getRaycastStepFactor() const;
/** @brief Sets volume pose.
* @param val input value.
*/
CV_WRAP void setVolumePose(InputArray val);
/** @brief Sets volume pose.
* @param val output value.
*/
CV_WRAP void getVolumePose(OutputArray val) const;
/** @brief Resolution of voxel space.
Number of voxels in each dimension.
Applicable only for TSDF Volume.
HashTSDF volume only supports equal resolution in all three dimensions.
* @param val input value.
*/
CV_WRAP void setVolumeResolution(InputArray val);
/** @brief Resolution of voxel space.
Number of voxels in each dimension.
Applicable only for TSDF Volume.
HashTSDF volume only supports equal resolution in all three dimensions.
* @param val output value.
*/
CV_WRAP void getVolumeResolution(OutputArray val) const;
/** @brief Returns 3 integers representing strides by x, y and z dimension.
Can be used to iterate over raw volume unit data.
* @param val output value.
*/
CV_WRAP void getVolumeStrides(OutputArray val) const;
/** @brief Sets intrinsics of camera for integrations.
* Format of input:
* [ fx 0 cx ]
* [ 0 fy cy ]
* [ 0 0 1 ]
* where fx and fy are focus points of Ox and Oy axises, and cx and cy are central points of Ox and Oy axises.
* @param val input value.
*/
CV_WRAP void setCameraIntegrateIntrinsics(InputArray val);
/** @brief Returns intrinsics of camera for integrations.
* Format of output:
* [ fx 0 cx ]
* [ 0 fy cy ]
* [ 0 0 1 ]
* where fx and fy are focus points of Ox and Oy axises, and cx and cy are central points of Ox and Oy axises.
* @param val output value.
*/
CV_WRAP void getCameraIntegrateIntrinsics(OutputArray val) const;
/** @brief Sets camera intrinsics for raycast image which, used when user does not provide them at raycast() call.
* Format of input:
* [ fx 0 cx ]
* [ 0 fy cy ]
* [ 0 0 1 ]
* where fx and fy are focus points of Ox and Oy axises, and cx and cy are central points of Ox and Oy axises.
* @param val input value.
*/
CV_WRAP void setCameraRaycastIntrinsics(InputArray val);
/** @brief Returns camera intrinsics for raycast image, used when user does not provide them at raycast() call.
* Format of output:
* [ fx 0 cx ]
* [ 0 fy cy ]
* [ 0 0 1 ]
* where fx and fy are focus points of Ox and Oy axises, and cx and cy are central points of Ox and Oy axises.
* @param val output value.
*/
CV_WRAP void getCameraRaycastIntrinsics(OutputArray val) const;
class Impl;
private:
Ptr<Impl> impl;
};
}
#endif // !OPENCV_3D_VOLUME_SETTINGS_HPP
@@ -0,0 +1,46 @@
#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import os, numpy
import math
import unittest
import cv2 as cv
from tests_common import NewOpenCVTests
class raster_test(NewOpenCVTests):
def test_loadCloud(self):
fin = self.find_file("pointcloudio/orig.obj")
points, normals, colors = cv.loadPointCloud(fin)
if points.shape != (8, 1, 3):
self.fail('point array should be 8x1x3')
if normals.shape != (6, 1, 3):
self.fail('normals array should be 6x1x3')
if colors.shape != (8, 1, 3):
self.fail('colors array should be 8x1x3')
def test_loadMesh(self):
fin = self.find_file("pointcloudio/orig.obj")
points, indices, normals, colors, texCoords = cv.loadMesh(fin)
goodShapes = [(1, 18, 3), (18, 1, 3)]
errorMsg = "%s array should be 18x1x3 or 1x18x3"
for a, s in [(points, 'points'), (normals, 'normals'), (colors, 'colors')]:
if a.shape not in goodShapes:
self.fail(errorMsg % s)
if texCoords.shape not in [(1, 18, 2), (18, 1, 2), (18, 2)]:
self.fail('texture coordinates array should be 1x18x2 or 18x1x2')
if isinstance(indices, numpy.ndarray):
if indices.shape not in [(1, 6, 3), (6, 1, 3)]:
self.fail('indices array should be 1x6x3 or 6x1x3')
elif isinstance(indices, list) or isinstance(indices, tuple):
for i in indices:
if len(indices) != 6 or i.shape != (1, 3):
self.fail('indices array should be 1x6x3 or 6x1x3')
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
@@ -0,0 +1,31 @@
#!/usr/bin/env python
import os
import numpy as np
import math
import unittest
import cv2 as cv
from tests_common import NewOpenCVTests
class octree_test(NewOpenCVTests):
def test_octree_basic_test(self):
pointCloudSize = 1000
resolution = 0.0001
scale = 1 << 20
pointCloud = np.random.randint(-scale, scale, size=(pointCloudSize, 3)) * (10.0 / scale)
pointCloud = pointCloud.astype(np.float32)
octree = cv.Octree_createWithResolution(resolution, pointCloud)
restPoint = np.random.randint(-scale, scale, size=(1, 3)) * (10.0 / scale)
restPoint = [restPoint[0, 0], restPoint[0, 1], restPoint[0, 2]]
self.assertTrue(octree.isPointInBound(restPoint))
self.assertFalse(octree.deletePoint(restPoint))
self.assertTrue(octree.insertPoint(restPoint))
self.assertTrue(octree.deletePoint(restPoint))
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
@@ -0,0 +1,88 @@
#!/usr/bin/env python
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests
class odometry_test(NewOpenCVTests):
def commonOdometryTest(self, needRgb, otype, algoType, useFrame):
depth = self.get_sample('cv/rgbd/depth.png', cv.IMREAD_ANYDEPTH).astype(np.float32)
if needRgb:
rgb = self.get_sample('cv/rgbd/rgb.png', cv.IMREAD_ANYCOLOR)
radian = np.radians(1)
Rt_warp = np.array(
[[np.cos(radian), -np.sin(radian), 0],
[np.sin(radian), np.cos(radian), 0],
[0, 0, 1]], dtype=np.float32
)
Rt_curr = np.array(
[[np.cos(radian), -np.sin(radian), 0, 0],
[np.sin(radian), np.cos(radian), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]], dtype=np.float32
)
Rt_res = np.zeros((4, 4))
if otype is not None:
settings = cv.OdometrySettings()
odometry = cv.Odometry(otype, settings, algoType)
else:
odometry = cv.Odometry()
warped_depth = cv.warpPerspective(depth, Rt_warp, (640, 480))
if needRgb:
warped_rgb = cv.warpPerspective(rgb, Rt_warp, (640, 480))
if useFrame:
if needRgb:
srcFrame = cv.OdometryFrame(depth, rgb)
dstFrame = cv.OdometryFrame(warped_depth, warped_rgb)
else:
srcFrame = cv.OdometryFrame(depth)
dstFrame = cv.OdometryFrame(warped_depth)
odometry.prepareFrames(srcFrame, dstFrame)
isCorrect = odometry.compute(srcFrame, dstFrame, Rt_res)
else:
if needRgb:
isCorrect = odometry.compute(depth, rgb, warped_depth, warped_rgb, Rt_res)
else:
isCorrect = odometry.compute(depth, warped_depth, Rt_res)
res = np.absolute(Rt_curr - Rt_res).sum()
eps = 0.15
self.assertLessEqual(res, eps)
self.assertTrue(isCorrect)
def test_OdometryDefault(self):
self.commonOdometryTest(False, None, None, False)
def test_OdometryDefaultFrame(self):
self.commonOdometryTest(False, None, None, True)
def test_OdometryDepth(self):
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_COMMON, False)
def test_OdometryDepthFast(self):
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_FAST, False)
def test_OdometryDepthFrame(self):
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_COMMON, True)
def test_OdometryDepthFastFrame(self):
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_FAST, True)
def test_OdometryRGB(self):
self.commonOdometryTest(True, cv.OdometryType_RGB, cv.OdometryAlgoType_COMMON, False)
def test_OdometryRGBFrame(self):
self.commonOdometryTest(True, cv.OdometryType_RGB, cv.OdometryAlgoType_COMMON, True)
def test_OdometryRGB_Depth(self):
self.commonOdometryTest(True, cv.OdometryType_RGB_DEPTH, cv.OdometryAlgoType_COMMON, False)
def test_OdometryRGB_DepthFrame(self):
self.commonOdometryTest(True, cv.OdometryType_RGB_DEPTH, cv.OdometryAlgoType_COMMON, True)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import os, numpy
import math
import unittest
import cv2 as cv
from tests_common import NewOpenCVTests
def lookAtMatrixCal(position, lookat, upVector):
tmp = position - lookat
norm = numpy.linalg.norm(tmp)
w = tmp / norm
tmp = numpy.cross(upVector, w)
norm = numpy.linalg.norm(tmp)
u = tmp / norm
v = numpy.cross(w, u)
res = numpy.array([
[u[0], u[1], u[2], 0],
[v[0], v[1], v[2], 0],
[w[0], w[1], w[2], 0],
[0, 0, 0, 1.0]
], dtype=numpy.float32)
translate = numpy.array([
[1.0, 0, 0, -position[0]],
[0, 1.0, 0, -position[1]],
[0, 0, 1.0, -position[2]],
[0, 0, 0, 1.0]
], dtype=numpy.float32)
res = numpy.matmul(res, translate)
return res
class raster_test(NewOpenCVTests):
def prepareData(self):
self.vertices = numpy.array([
[ 2.0, 0.0, -2.0],
[ 0.0, -6.0, -2.0],
[-2.0, 0.0, -2.0],
[ 3.5, -1.0, -5.0],
[ 2.5, -2.5, -5.0],
[-1.0, 1.0, -5.0],
[-6.5, -1.0, -3.0],
[-2.5, -2.0, -3.0],
[ 1.0, 1.0, -5.0],
], dtype=numpy.float32)
self.indices = numpy.array([ [0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=int)
col1 = [ 185.0, 238.0, 217.0 ]
col2 = [ 238.0, 217.0, 185.0 ]
col3 = [ 238.0, 10.0, 150.0 ]
self.colors = numpy.array([
col1, col2, col3,
col2, col3, col1,
col3, col1, col2,
], dtype=numpy.float32)
self.colors = self.colors / 255.0
self.zNear = 0.1
self.zFar = 50.0
self.fovy = 45.0 * math.pi / 180.0
position = numpy.array([0.0, 0.0, 5.0], dtype=numpy.float32)
lookat = numpy.array([0.0, 0.0, 0.0], dtype=numpy.float32)
upVector = numpy.array([0.0, 1.0, 0.0], dtype=numpy.float32)
self.cameraPose = lookAtMatrixCal(position, lookat, upVector)
self.depth_buf = numpy.ones((240, 320), dtype=numpy.float32) * self.zFar
self.color_buf = numpy.zeros((240, 320, 3), dtype=numpy.float32)
self.settings = cv.TriangleRasterizeSettings().setShadingType(cv.RASTERIZE_SHADING_SHADED)
self.settings = self.settings.setCullingMode(cv.RASTERIZE_CULLING_NONE)
def compareResults(self, needRgb, needDepth):
if needDepth:
depth = self.get_sample('rendering/depth_image_Clipping_320x240_CullNone.png', cv.IMREAD_ANYDEPTH).astype(numpy.float32)
depthFactor = 1000.0
diff = depth/depthFactor - self.depth_buf
norm = numpy.linalg.norm(diff)
self.assertLessEqual(norm, 356.0)
if needRgb:
rgb = self.get_sample('rendering/example_image_Clipping_320x240_CullNone_Shaded.png', cv.IMREAD_ANYCOLOR)
diff = rgb/255.0 - self.color_buf
norm = numpy.linalg.norm(diff)
self.assertLessEqual(norm, 11.62)
def test_rasterizeBoth(self):
self.prepareData()
self.color_buf, self.depth_buf = cv.triangleRasterize(self.vertices, self.indices, self.colors, self.color_buf, self.depth_buf,
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings)
self.compareResults(needRgb=True, needDepth=True)
def test_rasterizeDepth(self):
self.prepareData()
self.depth_buf = cv.triangleRasterizeDepth(self.vertices, self.indices, self.depth_buf,
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings)
self.compareResults(needRgb=False, needDepth=True)
def test_rasterizeColor(self):
self.prepareData()
self.color_buf = cv.triangleRasterizeColor(self.vertices, self.indices, self.colors, self.color_buf,
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings)
self.compareResults(needRgb=True, needDepth=False)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python
# Python 2/3 compatibility
from __future__ import print_function
import os, numpy
import unittest
import cv2 as cv
from tests_common import NewOpenCVTests
class rgbd_test(NewOpenCVTests):
def test_computeRgbdPlane(self):
depth_image = self.get_sample('cv/rgbd/depth.png', cv.IMREAD_ANYDEPTH)
if depth_image is None:
raise unittest.SkipTest("Missing files with test data")
K = numpy.array([[525, 0, 320.5], [0, 525, 240.5], [0, 0, 1]])
points3d = cv.depthTo3d(depth_image, K)
normals = cv.RgbdNormals_create(480, 640, cv.CV_32F, K).apply(points3d)
_, planes_coeff = cv.findPlanes(points3d, normals, numpy.array([]), numpy.array([]), 40, 1600, 0.01, 0, 0, 0, cv.RGBD_PLANE_METHOD_DEFAULT)
planes_coeff_expected = \
numpy.asarray([[[-0.02447728, -0.8678335 , -0.49625182, 4.02800846]],
[[-0.05055107, -0.86144137, -0.50533485, 3.95456314]],
[[-0.03294908, -0.86964548, -0.49257591, 3.97052431]],
[[-0.02886586, -0.87153459, -0.48948362, 7.77550507]],
[[-0.04455929, -0.87659335, -0.47916424, 3.93200684]],
[[-0.21514639, 0.18835169, -0.95824611, 7.59479475]],
[[-0.01006953, -0.86679155, -0.49856904, 4.01355648]],
[[-0.00876531, -0.87571168, -0.48275498, 3.96768975]],
[[-0.06395926, -0.86951321, -0.48975089, 4.08618736]],
[[-0.01403128, -0.87593341, -0.48222789, 7.74559402]],
[[-0.01143177, -0.87495202, -0.4840748 , 7.75355816]]],
dtype=numpy.float32)
eps = 0.05
self.assertLessEqual(cv.norm(planes_coeff, planes_coeff_expected, cv.NORM_L2), eps)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
+123
View File
@@ -0,0 +1,123 @@
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests
class volume_test(NewOpenCVTests):
def test_VolumeDefault(self):
depthPath = 'cv/rgbd/depth.png'
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
if depth.size <= 0:
raise Exception('Failed to load depth file: %s' % depthPath)
Rt = np.eye(4)
volume = cv.Volume()
volume.integrate(depth, Rt)
size = (480, 640, 4)
points = np.zeros(size, np.float32)
normals = np.zeros(size, np.float32)
Kraycast = np.array([[525. , 0. , 319.5],
[ 0. , 525. , 239.5],
[ 0. , 0. , 1. ]])
volume.raycastEx(Rt, size[0], size[1], Kraycast, points, normals)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
volume.raycast(Rt, points, normals)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
def test_VolumeTSDF(self):
depthPath = 'cv/rgbd/depth.png'
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
if depth.size <= 0:
raise Exception('Failed to load depth file: %s' % depthPath)
Rt = np.eye(4)
settings = cv.VolumeSettings(cv.VolumeType_TSDF)
volume = cv.Volume(cv.VolumeType_TSDF, settings)
volume.integrate(depth, Rt)
size = (480, 640, 4)
points = np.zeros(size, np.float32)
normals = np.zeros(size, np.float32)
Kraycast = settings.getCameraRaycastIntrinsics()
volume.raycastEx(Rt, size[0], size[1], Kraycast, points, normals)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
volume.raycast(Rt, points, normals)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
def test_VolumeHashTSDF(self):
depthPath = 'cv/rgbd/depth.png'
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
if depth.size <= 0:
raise Exception('Failed to load depth file: %s' % depthPath)
Rt = np.eye(4)
settings = cv.VolumeSettings(cv.VolumeType_HashTSDF)
volume = cv.Volume(cv.VolumeType_HashTSDF, settings)
volume.integrate(depth, Rt)
size = (480, 640, 4)
points = np.zeros(size, np.float32)
normals = np.zeros(size, np.float32)
Kraycast = settings.getCameraRaycastIntrinsics()
volume.raycastEx(Rt, size[0], size[1], Kraycast, points, normals)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
volume.raycast(Rt, points, normals)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
def test_VolumeColorTSDF(self):
depthPath = 'cv/rgbd/depth.png'
rgbPath = 'cv/rgbd/rgb.png'
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
rgb = self.get_sample(rgbPath, cv.IMREAD_ANYCOLOR).astype(np.float32)
if depth.size <= 0:
raise Exception('Failed to load depth file: %s' % depthPath)
if rgb.size <= 0:
raise Exception('Failed to load RGB file: %s' % rgbPath)
Rt = np.eye(4)
settings = cv.VolumeSettings(cv.VolumeType_ColorTSDF)
volume = cv.Volume(cv.VolumeType_ColorTSDF, settings)
volume.integrateColor(depth, rgb, Rt)
size = (480, 640, 4)
points = np.zeros(size, np.float32)
normals = np.zeros(size, np.float32)
colors = np.zeros(size, np.float32)
Kraycast = settings.getCameraRaycastIntrinsics()
volume.raycastExColor(Rt, size[0], size[1], Kraycast, points, normals, colors)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
volume.raycastColor(Rt, points, normals, colors)
self.assertEqual(points.shape, size)
self.assertEqual(points.shape, normals.shape)
self.assertEqual(points.shape, colors.shape)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
+7
View File
@@ -0,0 +1,7 @@
#include "perf_precomp.hpp"
#if defined(HAVE_HPX)
#include <hpx/hpx_main.hpp>
#endif
CV_PERF_TEST_MAIN(calib3d)
+14
View File
@@ -0,0 +1,14 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef __OPENCV_PERF_PRECOMP_HPP__
#define __OPENCV_PERF_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/ptcloud.hpp"
#ifdef HAVE_OPENCL
#include <opencv2/core/ocl.hpp>
#endif
#endif
+342
View File
@@ -0,0 +1,342 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "perf_precomp.hpp"
namespace opencv_test
{
// that was easier than using CV_ENUM() macro
namespace
{
using namespace cv;
struct ShadingTypeEnum
{
static const std::array<TriangleShadingType, 3> vals;
static const std::array<std::string, 3> svals;
ShadingTypeEnum(TriangleShadingType v = RASTERIZE_SHADING_WHITE) : val(v) {}
operator TriangleShadingType() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < (int)vals.size())
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<ShadingTypeEnum> all()
{
return ::testing::Values(ShadingTypeEnum(vals[0]),
ShadingTypeEnum(vals[1]),
ShadingTypeEnum(vals[2]));
}
private:
TriangleShadingType val;
};
const std::array<TriangleShadingType, 3> ShadingTypeEnum::vals
{
RASTERIZE_SHADING_WHITE,
RASTERIZE_SHADING_FLAT,
RASTERIZE_SHADING_SHADED
};
const std::array<std::string, 3> ShadingTypeEnum::svals
{
std::string("White"),
std::string("Flat"),
std::string("Shaded")
};
static inline void PrintTo(const ShadingTypeEnum &t, std::ostream *os) { t.PrintTo(os); }
using namespace cv;
struct GlCompatibleModeEnum
{
static const std::array<TriangleGlCompatibleMode, 2> vals;
static const std::array<std::string, 2> svals;
GlCompatibleModeEnum(TriangleGlCompatibleMode v = RASTERIZE_COMPAT_DISABLED) : val(v) {}
operator TriangleGlCompatibleMode() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < (int)vals.size())
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<GlCompatibleModeEnum> all()
{
return ::testing::Values(GlCompatibleModeEnum(vals[0]),
GlCompatibleModeEnum(vals[1]));
}
private:
TriangleGlCompatibleMode val;
};
const std::array<TriangleGlCompatibleMode, 2> GlCompatibleModeEnum::vals
{
RASTERIZE_COMPAT_DISABLED,
RASTERIZE_COMPAT_INVDEPTH,
};
const std::array<std::string, 2> GlCompatibleModeEnum::svals
{
std::string("Disabled"),
std::string("InvertedDepth"),
};
static inline void PrintTo(const GlCompatibleModeEnum &t, std::ostream *os) { t.PrintTo(os); }
}
enum class Outputs
{
DepthOnly = 0,
ColorOnly = 1,
DepthColor = 2,
};
// that was easier than using CV_ENUM() macro
namespace
{
using namespace cv;
struct OutputsEnum
{
static const std::array<Outputs, 3> vals;
static const std::array<std::string, 3> svals;
OutputsEnum(Outputs v = Outputs::DepthColor) : val(v) {}
operator Outputs() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < (int)vals.size())
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<OutputsEnum> all()
{
return ::testing::Values(OutputsEnum(vals[0]),
OutputsEnum(vals[1]),
OutputsEnum(vals[2]));
}
private:
Outputs val;
};
const std::array<Outputs, 3> OutputsEnum::vals
{
Outputs::DepthOnly,
Outputs::ColorOnly,
Outputs::DepthColor
};
const std::array<std::string, 3> OutputsEnum::svals
{
std::string("DepthOnly"),
std::string("ColorOnly"),
std::string("DepthColor")
};
static inline void PrintTo(const OutputsEnum &t, std::ostream *os) { t.PrintTo(os); }
}
static Matx44d lookAtMatrixCal(const Vec3d& position, const Vec3d& lookat, const Vec3d& upVector)
{
Vec3d w = cv::normalize(position - lookat);
Vec3d u = cv::normalize(upVector.cross(w));
Vec3d v = w.cross(u);
Matx44d res(u[0], u[1], u[2], 0,
v[0], v[1], v[2], 0,
w[0], w[1], w[2], 0,
0, 0, 0, 1.0);
Matx44d translate(1.0, 0, 0, -position[0],
0, 1.0, 0, -position[1],
0, 0, 1.0, -position[2],
0, 0, 0, 1.0);
res = res * translate;
return res;
}
static void generateNormals(const std::vector<Vec3f>& points, const std::vector<std::vector<int>>& indices,
std::vector<Vec3f>& normals)
{
std::vector<std::vector<Vec3f>> preNormals(points.size(), std::vector<Vec3f>());
for (const auto& tri : indices)
{
Vec3f p0 = points[tri[0]];
Vec3f p1 = points[tri[1]];
Vec3f p2 = points[tri[2]];
Vec3f cross = cv::normalize((p1 - p0).cross(p2 - p0));
for (int i = 0; i < 3; i++)
{
preNormals[tri[i]].push_back(cross);
}
}
normals.reserve(points.size());
for (const auto& pn : preNormals)
{
Vec3f sum { };
for (const auto& n : pn)
{
sum += n;
}
normals.push_back(cv::normalize(sum));
}
}
// load model once and keep it in static memory
static void getModelOnce(const std::string& objectPath, std::vector<Vec3f>& vertices,
std::vector<Vec3i>& indices, std::vector<Vec3f>& colors)
{
static bool load = false;
static std::vector<Vec3f> vert, col;
static std::vector<Vec3i> ind;
if (!load)
{
std::vector<vector<int>> indvec;
// using per-vertex normals as colors
loadMesh(objectPath, vert, indvec);
generateNormals(vert, indvec, col);
for (const auto &vec : indvec)
{
ind.push_back({vec[0], vec[1], vec[2]});
}
for (auto &color : col)
{
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2]));
}
load = true;
}
vertices = vert;
colors = col;
indices = ind;
}
template<typename T>
std::string printEnum(T v)
{
std::ostringstream ss;
v.PrintTo(&ss);
return ss.str();
}
// resolution, shading type, outputs needed
typedef perf::TestBaseWithParam<std::tuple<std::tuple<int, int>, ShadingTypeEnum, OutputsEnum, GlCompatibleModeEnum>> RenderingTest;
PERF_TEST_P(RenderingTest, rasterizeTriangles, ::testing::Combine(
::testing::Values(std::make_tuple(1920, 1080), std::make_tuple(1024, 768), std::make_tuple(640, 480)),
ShadingTypeEnum::all(),
OutputsEnum::all(),
GlCompatibleModeEnum::all()
))
{
auto t = GetParam();
auto wh = std::get<0>(t);
int width = std::get<0>(wh);
int height = std::get<1>(wh);
auto shadingType = std::get<1>(t);
auto outputs = std::get<2>(t);
auto glCompatibleMode = std::get<3>(t);
string objectPath = findDataFile("viz/dragon.ply");
Vec3f position = Vec3d( 1.9, 0.4, 1.3);
Vec3f lookat = Vec3d( 0.0, 0.0, 0.0);
Vec3f upVector = Vec3d( 0.0, 1.0, 0.0);
double fovy = 45.0;
std::vector<Vec3f> vertices;
std::vector<Vec3i> indices;
std::vector<Vec3f> colors;
getModelOnce(objectPath, vertices, indices, colors);
if (shadingType != RASTERIZE_SHADING_WHITE)
{
// let vertices be in BGR format to avoid later color conversions
// mixChannels does not support in-place operation
cv::mixChannels(Mat(colors).clone(), colors, {0, 2, 1, 1, 2, 0});
}
double zNear = 0.1, zFar = 50.0;
Matx44d cameraPose = lookAtMatrixCal(position, lookat, upVector);
double fovYradians = fovy * (CV_PI / 180.0);
TriangleRasterizeSettings settings;
settings.setCullingMode(RASTERIZE_CULLING_CW)
.setShadingType(shadingType)
.setGlCompatibleMode(glCompatibleMode);
Mat depth_buf, color_buf;
while (next())
{
// Prefilled to measure pure rendering time w/o allocation and clear
float zMax = (glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) ? 1.f : (float)zFar;
depth_buf = Mat(height, width, CV_32F, zMax);
color_buf = Mat(height, width, CV_32FC3, Scalar::all(0));
startTimer();
if (outputs == Outputs::ColorOnly)
{
cv::triangleRasterizeColor(vertices, indices, colors, color_buf, cameraPose,
fovYradians, zNear, zFar, settings);
}
else if (outputs == Outputs::DepthOnly)
{
cv::triangleRasterizeDepth(vertices, indices, depth_buf, cameraPose,
fovYradians, zNear, zFar, settings);
}
else // Outputs::DepthColor
{
cv::triangleRasterize(vertices, indices, colors, color_buf, depth_buf,
cameraPose, fovYradians, zNear, zFar, settings);
}
stopTimer();
}
if (debugLevel > 0)
{
depth_buf.convertTo(depth_buf, CV_16U, 1000.0);
std::string shadingName = printEnum(shadingType);
std::string suffix = cv::format("%dx%d_%s", width, height, shadingName.c_str());
imwrite("perf_color_image_" + suffix + ".png", color_buf * 255.f);
imwrite("perf_depth_image_" + suffix + ".png", depth_buf);
}
SANITY_CHECK_NOTHING();
}
} // namespace
+683
View File
@@ -0,0 +1,683 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "perf_precomp.hpp"
namespace opencv_test { namespace {
using namespace cv;
/** Reprojects screen point to camera space given z coord. */
struct Reprojector
{
Reprojector() {}
inline Reprojector(Matx33f intr)
{
fxinv = 1.f / intr(0, 0), fyinv = 1.f / intr(1, 1);
cx = intr(0, 2), cy = intr(1, 2);
}
template<typename T>
inline cv::Point3_<T> operator()(cv::Point3_<T> p) const
{
T x = p.z * (p.x - cx) * fxinv;
T y = p.z * (p.y - cy) * fyinv;
return cv::Point3_<T>(x, y, p.z);
}
float fxinv, fyinv, cx, cy;
};
template<class Scene>
struct RenderInvoker : ParallelLoopBody
{
RenderInvoker(Mat_<float>& _frame, Affine3f _pose,
Reprojector _reproj, float _depthFactor, bool _onlySemisphere)
: ParallelLoopBody(),
frame(_frame),
pose(_pose),
reproj(_reproj),
depthFactor(_depthFactor),
onlySemisphere(_onlySemisphere)
{ }
virtual void operator ()(const cv::Range& r) const
{
for (int y = r.start; y < r.end; y++)
{
float* frameRow = frame[y];
for (int x = 0; x < frame.cols; x++)
{
float pix = 0;
Point3f orig = pose.translation();
// direction through pixel
Point3f screenVec = reproj(Point3f((float)x, (float)y, 1.f));
float xyt = 1.f / (screenVec.x * screenVec.x +
screenVec.y * screenVec.y + 1.f);
Point3f dir = normalize(Vec3f(pose.rotation() * screenVec));
// screen space axis
dir.y = -dir.y;
const float maxDepth = 20.f;
const float maxSteps = 256;
float t = 0.f;
for (int step = 0; step < maxSteps && t < maxDepth; step++)
{
Point3f p = orig + dir * t;
float d = Scene::map(p, onlySemisphere);
if (d < 0.000001f)
{
float depth = std::sqrt(t * t * xyt);
pix = depth * depthFactor;
break;
}
t += d;
}
frameRow[x] = pix;
}
}
}
Mat_<float>& frame;
Affine3f pose;
Reprojector reproj;
float depthFactor;
bool onlySemisphere;
};
template<class Scene>
struct RenderColorInvoker : ParallelLoopBody
{
RenderColorInvoker(Mat_<Vec3f>& _frame, Affine3f _pose,
Reprojector _reproj,
float _depthFactor, bool _onlySemisphere) : ParallelLoopBody(),
frame(_frame),
pose(_pose),
reproj(_reproj),
depthFactor(_depthFactor),
onlySemisphere(_onlySemisphere)
{ }
virtual void operator ()(const cv::Range& r) const
{
for (int y = r.start; y < r.end; y++)
{
Vec3f* frameRow = frame[y];
for (int x = 0; x < frame.cols; x++)
{
Vec3f pix = 0;
Point3f orig = pose.translation();
// direction through pixel
Point3f screenVec = reproj(Point3f((float)x, (float)y, 1.f));
Point3f dir = normalize(Vec3f(pose.rotation() * screenVec));
// screen space axis
dir.y = -dir.y;
const float maxDepth = 20.f;
const float maxSteps = 256;
float t = 0.f;
for (int step = 0; step < maxSteps && t < maxDepth; step++)
{
Point3f p = orig + dir * t;
float d = Scene::map(p, onlySemisphere);
if (d < 0.000001f)
{
float m = 0.25f;
float p0 = float(abs(fmod(p.x, m)) > m / 2.f);
float p1 = float(abs(fmod(p.y, m)) > m / 2.f);
float p2 = float(abs(fmod(p.z, m)) > m / 2.f);
pix[0] = p0 + p1;
pix[1] = p1 + p2;
pix[2] = p0 + p2;
pix *= 128.f;
break;
}
t += d;
}
frameRow[x] = pix;
}
}
}
Mat_<Vec3f>& frame;
Affine3f pose;
Reprojector reproj;
float depthFactor;
bool onlySemisphere;
};
struct Scene
{
virtual ~Scene() {}
static Ptr<Scene> create(Size sz, Matx33f _intr, float _depthFactor, bool onlySemisphere);
virtual Mat_<float> depth(Affine3f pose) = 0;
virtual Mat_<Vec3f> rgb(Affine3f pose) = 0;
virtual std::vector<Affine3f> getPoses() = 0;
};
struct SemisphereScene : Scene
{
const int framesPerCycle = 72;
const float nCycles = 0.25f;
const Affine3f startPose = Affine3f(Vec3f(0.f, 0.f, 0.f), Vec3f(1.5f, 0.3f, -2.1f));
Size frameSize;
Matx33f intr;
float depthFactor;
bool onlySemisphere;
SemisphereScene(Size sz, Matx33f _intr, float _depthFactor, bool _onlySemisphere) :
frameSize(sz), intr(_intr), depthFactor(_depthFactor), onlySemisphere(_onlySemisphere)
{ }
static float map(Point3f p, bool onlySemisphere)
{
float plane = p.y + 0.5f;
Point3f spherePose = p - Point3f(-0.0f, 0.3f, 1.1f);
float sphereRadius = 0.5f;
float sphere = (float)cv::norm(spherePose) - sphereRadius;
float sphereMinusBox = sphere;
float subSphereRadius = 0.05f;
Point3f subSpherePose = p - Point3f(0.3f, -0.1f, -0.3f);
float subSphere = (float)cv::norm(subSpherePose) - subSphereRadius;
float res;
if (!onlySemisphere)
res = min({ sphereMinusBox, subSphere, plane });
else
res = sphereMinusBox;
return res;
}
Mat_<float> depth(Affine3f pose) override
{
Mat_<float> frame(frameSize);
Reprojector reproj(intr);
Range range(0, frame.rows);
parallel_for_(range, RenderInvoker<SemisphereScene>(frame, pose, reproj, depthFactor, onlySemisphere));
return frame;
}
Mat_<Vec3f> rgb(Affine3f pose) override
{
Mat_<Vec3f> frame(frameSize);
Reprojector reproj(intr);
Range range(0, frame.rows);
parallel_for_(range, RenderColorInvoker<SemisphereScene>(frame, pose, reproj, depthFactor, onlySemisphere));
return frame;
}
std::vector<Affine3f> getPoses() override
{
std::vector<Affine3f> poses;
for (int i = 0; i < framesPerCycle * nCycles; i++)
{
float angle = (float)(CV_2PI * i / framesPerCycle);
Affine3f pose;
pose = pose.rotate(startPose.rotation());
pose = pose.rotate(Vec3f(0.f, -0.5f, 0.f) * angle);
pose = pose.translate(Vec3f(startPose.translation()[0] * sin(angle),
startPose.translation()[1],
startPose.translation()[2] * cos(angle)));
poses.push_back(pose);
}
return poses;
}
};
Ptr<Scene> Scene::create(Size sz, Matx33f _intr, float _depthFactor, bool _onlySemisphere)
{
return makePtr<SemisphereScene>(sz, _intr, _depthFactor, _onlySemisphere);
}
// this is a temporary solution
// ----------------------------
typedef cv::Vec4f ptype;
typedef cv::Mat_< ptype > Points;
typedef cv::Mat_< ptype > Colors;
typedef Points Normals;
typedef Size2i Size;
template<int p>
inline float specPow(float x)
{
if (p % 2 == 0)
{
float v = specPow<p / 2>(x);
return v * v;
}
else
{
float v = specPow<(p - 1) / 2>(x);
return v * v * x;
}
}
template<>
inline float specPow<0>(float /*x*/)
{
return 1.f;
}
template<>
inline float specPow<1>(float x)
{
return x;
}
inline cv::Vec3f fromPtype(const ptype& x)
{
return cv::Vec3f(x[0], x[1], x[2]);
}
inline Point3f normalize(const Vec3f& v)
{
double nv = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
return v * (nv ? 1. / nv : 0.);
}
void renderPointsNormals(InputArray _points, InputArray _normals, OutputArray image, Affine3f lightPose)
{
Size sz = _points.size();
image.create(sz, CV_8UC4);
Points points = _points.getMat();
Normals normals = _normals.getMat();
Mat_<Vec4b> img = image.getMat();
Mat goods;
finiteMask(points, goods);
Range range(0, sz.height);
const int nstripes = -1;
parallel_for_(range, [&](const Range&)
{
for (int y = range.start; y < range.end; y++)
{
Vec4b* imgRow = img[y];
const ptype* ptsRow = points[y];
const ptype* nrmRow = normals[y];
const uchar* goodRow = goods.ptr<uchar>(y);
for (int x = 0; x < sz.width; x++)
{
Point3f p = fromPtype(ptsRow[x]);
Point3f n = fromPtype(nrmRow[x]);
Vec4b color;
if ( !goodRow[x] )
{
color = Vec4b(0, 32, 0, 0);
}
else
{
const float Ka = 0.3f; //ambient coeff
const float Kd = 0.5f; //diffuse coeff
const float Ks = 0.2f; //specular coeff
const int sp = 20; //specular power
const float Ax = 1.f; //ambient color, can be RGB
const float Dx = 1.f; //diffuse color, can be RGB
const float Sx = 1.f; //specular color, can be RGB
const float Lx = 1.f; //light color
Point3f l = normalize(lightPose.translation() - Vec3f(p));
Point3f v = normalize(-Vec3f(p));
Point3f r = normalize(Vec3f(2.f * n * n.dot(l) - l));
uchar ix = (uchar)((Ax * Ka * Dx + Lx * Kd * Dx * max(0.f, n.dot(l)) +
Lx * Ks * Sx * specPow<sp>(max(0.f, r.dot(v)))) * 255.f);
color = Vec4b(ix, ix, ix, 0);
}
imgRow[x] = color;
}
}
}, nstripes);
}
void renderPointsNormalsColors(InputArray _points, InputArray, InputArray _colors, OutputArray image, Affine3f)
{
Size sz = _points.size();
image.create(sz, CV_8UC4);
Points points = _points.getMat();
Colors colors = _colors.getMat();
Mat goods, goodp, goodc;
finiteMask(points, goodp);
finiteMask(colors, goodc);
goods = goodp & goodc;
Mat_<Vec4b> img = image.getMat();
Range range(0, sz.height);
const int nstripes = -1;
parallel_for_(range, [&](const Range&)
{
for (int y = range.start; y < range.end; y++)
{
Vec4b* imgRow = img[y];
const ptype* clrRow = colors[y];
const uchar* goodRow = goods.ptr<uchar>(y);
for (int x = 0; x < sz.width; x++)
{
Point3f c = fromPtype(clrRow[x]);
Vec4b color;
if ( !goodRow[x] )
{
color = Vec4b(0, 32, 0, 0);
}
else
{
color = Vec4b((uchar)c.x, (uchar)c.y, (uchar)c.z, (uchar)0);
}
imgRow[x] = color;
}
}
}, nstripes);
}
// ----------------------------
void displayImage(Mat depth, Mat points, Mat normals, float depthFactor, Vec3f lightPose)
{
Mat image;
patchNaNs(points);
imshow("depth", depth * (1.f / depthFactor / 4.f));
renderPointsNormals(points, normals, image, lightPose);
imshow("render", image);
waitKey(100);
}
void displayColorImage(Mat depth, Mat rgb, Mat points, Mat normals, Mat colors, float depthFactor, Vec3f lightPose)
{
Mat image;
patchNaNs(points);
imshow("depth", depth * (1.f / depthFactor / 4.f));
imshow("rgb", rgb * (1.f / 255.f));
renderPointsNormalsColors(points, normals, colors, image, lightPose);
imshow("render", image);
waitKey(100);
}
static const bool display = false;
enum PlatformType
{
CPU = 0, GPU = 1
};
CV_ENUM(PlatformTypeEnum, PlatformType::CPU, PlatformType::GPU);
enum Sequence
{
ALL = 0, FIRST = 1
};
CV_ENUM(SequenceEnum, Sequence::ALL, Sequence::FIRST);
enum class VolumeTestSrcType
{
MAT = 0,
ODOMETRY_FRAME = 1
};
// used to store current OpenCL status (on/off) and revert it after test is done
// works even after exceptions thrown in test body
struct OpenCLStatusRevert
{
#ifdef HAVE_OPENCL
OpenCLStatusRevert()
{
originalOpenCLStatus = cv::ocl::useOpenCL();
}
~OpenCLStatusRevert()
{
cv::ocl::setUseOpenCL(originalOpenCLStatus);
}
void off()
{
cv::ocl::setUseOpenCL(false);
}
bool originalOpenCLStatus;
#else
void off() { }
#endif
};
// CV_ENUM does not support enum class types, so let's implement the class explicitly
namespace
{
struct VolumeTypeEnum
{
static const std::array<VolumeType, 3> vals;
static const std::array<std::string, 3> svals;
VolumeTypeEnum(VolumeType v = VolumeType::TSDF) : val(v) {}
operator VolumeType() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < 3)
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<VolumeTypeEnum> all()
{
return ::testing::Values(VolumeTypeEnum(vals[0]), VolumeTypeEnum(vals[1]), VolumeTypeEnum(vals[2]));
}
private:
VolumeType val;
};
const std::array<VolumeType, 3> VolumeTypeEnum::vals{VolumeType::TSDF, VolumeType::HashTSDF, VolumeType::ColorTSDF};
const std::array<std::string, 3> VolumeTypeEnum::svals{std::string("TSDF"), std::string("HashTSDF"), std::string("ColorTSDF")};
static inline void PrintTo(const VolumeTypeEnum &t, std::ostream *os) { t.PrintTo(os); }
struct VolumeTestSrcTypeEnum
{
static const std::array<VolumeTestSrcType, 2> vals;
static const std::array<std::string, 2> svals;
VolumeTestSrcTypeEnum(VolumeTestSrcType v = VolumeTestSrcType::MAT) : val(v) {}
operator VolumeTestSrcType() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < 3)
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<VolumeTestSrcTypeEnum> all()
{
return ::testing::Values(VolumeTestSrcTypeEnum(vals[0]), VolumeTestSrcTypeEnum(vals[1]));
}
private:
VolumeTestSrcType val;
};
const std::array<VolumeTestSrcType, 2> VolumeTestSrcTypeEnum::vals{VolumeTestSrcType::MAT, VolumeTestSrcType::ODOMETRY_FRAME};
const std::array<std::string, 2> VolumeTestSrcTypeEnum::svals{std::string("UMat"), std::string("OdometryFrame")};
static inline void PrintTo(const VolumeTestSrcTypeEnum &t, std::ostream *os) { t.PrintTo(os); }
}
typedef std::tuple<PlatformTypeEnum, VolumeTypeEnum> PlatformVolumeType;
class VolumePerfFixture : public perf::TestBaseWithParam<std::tuple<PlatformVolumeType, VolumeTestSrcTypeEnum, SequenceEnum>>
{
protected:
void SetUp() override
{
TestBase::SetUp();
auto p = GetParam();
gpu = (std::get<0>(std::get<0>(p)) == PlatformType::GPU);
volumeType = std::get<1>(std::get<0>(p));
testSrcType = std::get<1>(p);
repeat1st = (std::get<2>(p) == Sequence::FIRST);
if (!gpu)
oclStatus.off();
VolumeSettings vs(volumeType);
volume = makePtr<Volume>(volumeType, vs);
frameSize = Size(vs.getRaycastWidth(), vs.getRaycastHeight());
Matx33f intrIntegrate;
vs.getCameraIntegrateIntrinsics(intrIntegrate);
vs.getCameraRaycastIntrinsics(intrRaycast);
bool onlySemisphere = false;
depthFactor = vs.getDepthFactor();
scene = Scene::create(frameSize, intrIntegrate, depthFactor, onlySemisphere);
poses = scene->getPoses();
}
bool gpu;
VolumeType volumeType;
VolumeTestSrcType testSrcType;
bool repeat1st;
OpenCLStatusRevert oclStatus;
Ptr<Volume> volume;
Size frameSize;
Matx33f intrRaycast;
Ptr<Scene> scene;
std::vector<Affine3f> poses;
float depthFactor;
};
PERF_TEST_P_(VolumePerfFixture, integrate)
{
for (size_t i = 0; i < (repeat1st ? 1 : poses.size()); i++)
{
Matx44f pose = poses[i].matrix;
Mat depth = scene->depth(pose);
Mat rgb = scene->rgb(pose);
UMat urgb, udepth;
depth.copyTo(udepth);
rgb.copyTo(urgb);
OdometryFrame odf(udepth, urgb);
bool done = false;
while (repeat1st ? next() : !done)
{
startTimer();
if (testSrcType == VolumeTestSrcType::MAT)
if (volumeType == VolumeType::ColorTSDF)
volume->integrate(udepth, urgb, pose);
else
volume->integrate(udepth, pose);
else if (testSrcType == VolumeTestSrcType::ODOMETRY_FRAME)
volume->integrate(odf, pose);
stopTimer();
// perf check makes sense only for identical states
if (repeat1st)
volume->reset();
done = true;
}
}
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(VolumePerfFixture, raycast)
{
for (size_t i = 0; i < (repeat1st ? 1 : poses.size()); i++)
{
Matx44f pose = poses[i].matrix;
Mat depth = scene->depth(pose);
Mat rgb = scene->rgb(pose);
UMat urgb, udepth;
depth.copyTo(udepth);
rgb.copyTo(urgb);
OdometryFrame odf(udepth, urgb);
if (testSrcType == VolumeTestSrcType::MAT)
if (volumeType == VolumeType::ColorTSDF)
volume->integrate(udepth, urgb, pose);
else
volume->integrate(udepth, pose);
else if (testSrcType == VolumeTestSrcType::ODOMETRY_FRAME)
volume->integrate(odf, pose);
UMat upoints, unormals, ucolors;
bool done = false;
while (repeat1st ? next() : !done)
{
startTimer();
if (volumeType == VolumeType::ColorTSDF)
volume->raycast(pose, frameSize.height, frameSize.width, intrRaycast, upoints, unormals, ucolors);
else
volume->raycast(pose, frameSize.height, frameSize.width, intrRaycast, upoints, unormals);
stopTimer();
done = true;
}
if (display)
{
Mat points, normals, colors;
points = upoints.getMat(ACCESS_READ);
normals = unormals.getMat(ACCESS_READ);
colors = ucolors.getMat(ACCESS_READ);
Vec3f lightPose = Vec3f::all(0.f);
if (volumeType == VolumeType::ColorTSDF)
displayColorImage(depth, rgb, points, normals, colors, depthFactor, lightPose);
else
displayImage(depth, points, normals, depthFactor, lightPose);
}
}
SANITY_CHECK_NOTHING();
}
//TODO: fix it when ColorTSDF gets GPU version
INSTANTIATE_TEST_CASE_P(Volume, VolumePerfFixture, /*::testing::Combine(PlatformTypeEnum::all(), VolumeTypeEnum::all())*/
::testing::Combine(
::testing::Values(PlatformVolumeType {PlatformType::CPU, VolumeType::TSDF},
PlatformVolumeType {PlatformType::CPU, VolumeType::HashTSDF},
PlatformVolumeType {PlatformType::CPU, VolumeType::ColorTSDF},
PlatformVolumeType {PlatformType::GPU, VolumeType::TSDF},
PlatformVolumeType {PlatformType::GPU, VolumeType::HashTSDF}),
VolumeTestSrcTypeEnum::all(), SequenceEnum::all()));
}} // namespace
@@ -0,0 +1,225 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include <opencv2/ptcloud.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/core/quaternion.hpp>
#include <iostream>
#include <fstream>
using namespace std;
using namespace cv;
#define BILATERAL_FILTER 0// if 1 then bilateral filter will be used for the depth
static
void writeResults( const string& filename, const vector<string>& timestamps, const vector<Mat>& Rt )
{
CV_Assert( timestamps.size() == Rt.size() );
ofstream file( filename.c_str() );
if( !file.is_open() )
return;
cout.precision(4);
for( size_t i = 0; i < Rt.size(); i++ )
{
const Mat& Rt_curr = Rt[i];
if( Rt_curr.empty() )
continue;
CV_Assert( Rt_curr.type() == CV_64FC1 );
Quatd rot = Quatd::createFromRotMat(Rt_curr(Rect(0, 0, 3, 3)));
// timestamp tx ty tz qx qy qz qw
file << timestamps[i] << " " << fixed
<< Rt_curr.at<double>(0,3) << " " << Rt_curr.at<double>(1,3) << " " << Rt_curr.at<double>(2,3) << " "
<< rot.x << " " << rot.y << " " << rot.z << " " << rot.w << endl;
}
file.close();
}
static
void setCameraMatrixFreiburg1(float& fx, float& fy, float& cx, float& cy)
{
fx = 517.3f; fy = 516.5f; cx = 318.6f; cy = 255.3f;
}
static
void setCameraMatrixFreiburg2(float& fx, float& fy, float& cx, float& cy)
{
fx = 520.9f; fy = 521.0f; cx = 325.1f; cy = 249.7f;
}
/*
* This sample helps to evaluate odometry on TUM datasets and benchmark http://vision.in.tum.de/data/datasets/rgbd-dataset.
* At this link you can find instructions for evaluation. The sample runs some opencv odometry and saves a camera trajectory
* to file of format that the benchmark requires. Saved file can be used for online evaluation.
*/
int main(int argc, char** argv)
{
if(argc != 4)
{
cout << "Format: file_with_rgb_depth_pairs trajectory_file odometry_name [Rgbd or ICP or RgbdICP or FastICP]" << endl;
return -1;
}
vector<string> timestamps;
vector<Mat> Rts;
const string filename = argv[1];
ifstream file( filename.c_str() );
if( !file.is_open() )
return -1;
char dlmrt1 = '/';
char dlmrt2 = '\\';
size_t pos1 = filename.rfind(dlmrt1);
size_t pos2 = filename.rfind(dlmrt2);
size_t pos = pos1 < pos2 ? pos1 : pos2;
char dlmrt = pos1 < pos2 ? dlmrt1 : dlmrt2;
string dirname = pos == string::npos ? "" : filename.substr(0, pos) + dlmrt;
const int timestampLength = 17;
const int rgbPathLehgth = 17+8;
const int depthPathLehgth = 17+10;
float fx = 525.0f, // default
fy = 525.0f,
cx = 319.5f,
cy = 239.5f;
if(filename.find("freiburg1") != string::npos)
setCameraMatrixFreiburg1(fx, fy, cx, cy);
if(filename.find("freiburg2") != string::npos)
setCameraMatrixFreiburg2(fx, fy, cx, cy);
Mat cameraMatrix = Mat::eye(3,3,CV_32FC1);
{
cameraMatrix.at<float>(0,0) = fx;
cameraMatrix.at<float>(1,1) = fy;
cameraMatrix.at<float>(0,2) = cx;
cameraMatrix.at<float>(1,2) = cy;
}
OdometrySettings ods;
ods.setCameraMatrix(cameraMatrix);
Odometry odometry;
String odname = string(argv[3]);
if (odname == "Rgbd")
odometry = Odometry(OdometryType::RGB, ods, OdometryAlgoType::COMMON);
else if (odname == "ICP")
odometry = Odometry(OdometryType::DEPTH, ods, OdometryAlgoType::COMMON);
else if (odname == "RgbdICP")
odometry = Odometry(OdometryType::RGB_DEPTH, ods, OdometryAlgoType::COMMON);
else if (odname == "FastICP")
odometry = Odometry(OdometryType::DEPTH, ods, OdometryAlgoType::FAST);
else
{
std::cout << "Can not create Odometry algorithm. Check the passed odometry name." << std::endl;
return -1;
}
OdometryFrame frame_prev, frame_curr;
TickMeter gtm;
int count = 0;
for(int i = 0; !file.eof(); i++)
{
string str;
std::getline(file, str);
if(str.empty()) break;
if(str.at(0) == '#') continue; /* comment */
Mat image, depth;
// Read one pair (rgb and depth)
// example: 1305031453.359684 rgb/1305031453.359684.png 1305031453.374112 depth/1305031453.374112.png
#if BILATERAL_FILTER
TickMeter tm_bilateral_filter;
#endif
{
string rgbFilename = str.substr(timestampLength + 1, rgbPathLehgth );
string timestap = str.substr(0, timestampLength);
string depthFilename = str.substr(2*timestampLength + rgbPathLehgth + 3, depthPathLehgth );
image = imread(dirname + rgbFilename);
depth = imread(dirname + depthFilename, -1);
CV_Assert(!image.empty());
CV_Assert(!depth.empty());
CV_Assert(depth.type() == CV_16UC1);
// scale depth
Mat depth_flt;
depth.convertTo(depth_flt, CV_32FC1, 1.f/5000.f);
#if !BILATERAL_FILTER
depth_flt.setTo(std::numeric_limits<float>::quiet_NaN(), depth == 0);
depth = depth_flt;
#else
tm_bilateral_filter.start();
depth = Mat(depth_flt.size(), CV_32FC1, Scalar(0));
const double depth_sigma = 0.03;
const double space_sigma = 4.5; // in pixels
Mat invalidDepthMask = depth_flt == 0.f;
depth_flt.setTo(-5*depth_sigma, invalidDepthMask);
bilateralFilter(depth_flt, depth, -1, depth_sigma, space_sigma);
depth.setTo(std::numeric_limits<float>::quiet_NaN(), invalidDepthMask);
tm_bilateral_filter.stop();
cout << "Time filter " << tm_bilateral_filter.getTimeSec() << endl;
#endif
timestamps.push_back( timestap );
}
{
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
frame_curr = OdometryFrame(depth, gray);
Mat Rt;
if(!Rts.empty())
{
TickMeter tm;
tm.start();
gtm.start();
odometry.prepareFrames(frame_curr, frame_prev);
bool res = odometry.compute(frame_curr, frame_prev, Rt);
gtm.stop();
tm.stop();
count++;
cout << "Time " << tm.getTimeSec() << endl;
#if BILATERAL_FILTER
cout << "Time ratio " << tm_bilateral_filter.getTimeSec() / tm.getTimeSec() << endl;
#endif
if(!res)
Rt = Mat::eye(4,4,CV_64FC1);
}
if( Rts.empty() )
Rts.push_back(Mat::eye(4,4,CV_64FC1));
else
{
Mat& prevRt = *Rts.rbegin();
cout << "Rt " << Rt << endl;
Rts.push_back( prevRt * Rt );
}
//if (!frame_prev.empty())
// frame_prev.release();
frame_prev = frame_curr;
frame_curr = OdometryFrame();
//std::swap(frame_prev, frame_curr);
}
}
std::cout << "Average time " << gtm.getAvgTimeSec() << std::endl;
writeResults(argv[2], timestamps, Rts);
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
#ifndef OPENCV_3D_COLORED_TSDF_FUNCTIONS_HPP
#define OPENCV_3D_COLORED_TSDF_FUNCTIONS_HPP
#include <unordered_set>
#include "utils.hpp"
#include "tsdf_functions.hpp"
#define USE_INTERPOLATION_IN_GETNORMAL 1
namespace cv
{
void integrateColorTsdfVolumeUnit(const VolumeSettings &settings, const Matx44f &cameraPose,
InputArray _depth, InputArray _rgb, InputArray _pixNorms, InputArray _volume);
void integrateColorTsdfVolumeUnit(const VolumeSettings &settings, const Matx44f &volumePose, const Matx44f &cameraPose,
InputArray _depth, InputArray _rgb, InputArray _pixNorms, InputArray _volume);
void raycastColorTsdfVolumeUnit(const VolumeSettings &settings, const Matx44f &cameraPose,
int height, int width, InputArray intr,
InputArray _volume, OutputArray _points, OutputArray _normals, OutputArray _colors);
void fetchNormalsFromColorTsdfVolumeUnit(const VolumeSettings &settings, InputArray _volume,
InputArray _points, OutputArray _normals);
void fetchPointsNormalsFromColorTsdfVolumeUnit(const VolumeSettings &settings, InputArray _volume,
OutputArray _points, OutputArray _normals);
void fetchPointsNormalsColorsFromColorTsdfVolumeUnit(const VolumeSettings &settings, InputArray _volume,
OutputArray _points, OutputArray _normals, OutputArray _colors);
} // namespace cv
#endif
+315
View File
@@ -0,0 +1,315 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
namespace cv
{
///////////////////////////////////////////////////////////////////////////////////
// Our three input types have a different value for a depth pixel with no depth
template<typename DepthDepth>
inline DepthDepth noDepthSentinelValue()
{
return 0;
}
template<>
inline float
noDepthSentinelValue<float>()
{
return std::numeric_limits<float>::quiet_NaN();
}
template<>
inline double
noDepthSentinelValue<double>()
{
return std::numeric_limits<double>::quiet_NaN();
}
///////////////////////////////////////////////////////////////////////////////////
// Testing for depth pixels with no depth isn't straightforward for NaN values. We
// need to specialize the equality check for floats and doubles.
template<typename DepthDepth>
inline bool
isEqualToNoDepthSentinelValue(const DepthDepth& value)
{
return value == noDepthSentinelValue<DepthDepth>();
}
template<>
inline bool
isEqualToNoDepthSentinelValue<float>(const float& value)
{
return cvIsNaN(value) != 0;
}
template<>
inline bool
isEqualToNoDepthSentinelValue<double>(const double& value)
{
return cvIsNaN(value) != 0;
}
///////////////////////////////////////////////////////////////////////////////////
// When using the unsigned short representation, we'd like to round the values to the nearest
// integer value. The float/double representations don't need to be rounded
template<typename DepthDepth>
inline DepthDepth
floatToInputDepth(const float& value)
{
return (DepthDepth)value;
}
template<>
inline unsigned short
floatToInputDepth<unsigned short>(const float& value)
{
return (unsigned short)(value + 0.5);
}
///////////////////////////////////////////////////////////////////////////////////
/** Computes a registered depth image from an unregistered image.
*
* @param unregisteredDepth the input depth data
* @param unregisteredCameraMatrix the camera matrix of the depth camera
* @param registeredCameraMatrix the camera matrix of the external camera
* @param registeredDistCoeffs the distortion coefficients of the external camera
* @param rbtRgb2Depth the rigid body transform between the cameras.
* @param outputImagePlaneSize the image plane dimensions of the external camera (width, height)
* @param depthDilation whether or not the depth is dilated to avoid holes and occlusion errors
* @param inputDepthToMetersScale the scale needed to transform the input depth units to meters
* @param registeredDepth the result of transforming the depth into the external camera
*/
template<typename DepthDepth>
void performRegistration(const Mat_<DepthDepth>& unregisteredDepth,
const Matx33f& unregisteredCameraMatrix,
const Matx33f& registeredCameraMatrix,
const Mat_<float>& registeredDistCoeffs,
const Matx44f& rbtRgb2Depth,
const Size outputImagePlaneSize,
const bool depthDilation,
const float inputDepthToMetersScale,
Mat& registeredDepth)
{
// Create output Mat of the correct type, filled with an initial value indicating no depth
registeredDepth = Mat_<DepthDepth>(outputImagePlaneSize, noDepthSentinelValue<DepthDepth>());
// Figure out whether we'll have to apply a distortion
bool hasDistortion = (countNonZero(registeredDistCoeffs) > 0);
// A point (i,j,1) will have to be converted to 3d first, by multiplying it by K.inv()
// It will then be transformed by rbtRgb2Depth.
// Finally, it will be projected into the external camera via registeredCameraMatrix and
// its distortion coefficients. If there is no distortion in the external camera, we
// can linearly chain all three operations together.
Matx44f K = Matx44f::zeros();
for (unsigned char j = 0; j < 3; ++j)
for (unsigned char i = 0; i < 3; ++i)
{
K(j, i) = unregisteredCameraMatrix(j, i);
}
K(3, 3) = 1;
Matx44f initialProjection;
if (hasDistortion)
{
// The projection into the external camera will be done separately with distortion
initialProjection = rbtRgb2Depth * K.inv();
}
else
{
// No distortion, so all operations can be chained
initialProjection = Matx44f::zeros();
for (unsigned char j = 0; j < 3; ++j)
for (unsigned char i = 0; i < 3; ++i)
initialProjection(j, i) = registeredCameraMatrix(j, i);
initialProjection(3, 3) = 1;
initialProjection = initialProjection * rbtRgb2Depth * K.inv();
}
// Apply the initial projection to the input depth
Mat_<Point3f> transformedCloud;
{
Mat_<Point3f> point_tmp(outputImagePlaneSize, Point3f(0., 0., 0.));
for (int j = 0; j < unregisteredDepth.rows; ++j)
{
const DepthDepth* unregisteredDepthPtr = unregisteredDepth[j];
Point3f* point = point_tmp[j];
for (int i = 0; i < unregisteredDepth.cols; ++i, ++unregisteredDepthPtr, ++point)
{
float rescaled_depth = float(*unregisteredDepthPtr) * inputDepthToMetersScale;
// If the DepthDepth is of type unsigned short, zero is a sentinel value to indicate
// no depth. CV_32F and CV_64F should already have NaN for no depth values.
if (rescaled_depth == 0)
{
rescaled_depth = std::numeric_limits<float>::quiet_NaN();
}
point->x = i * rescaled_depth;
point->y = j * rescaled_depth;
point->z = rescaled_depth;
}
}
perspectiveTransform(point_tmp, transformedCloud, initialProjection);
}
std::vector<Point2f> transformedAndProjectedPoints(transformedCloud.cols);
const float metersToInputUnitsScale = 1 / inputDepthToMetersScale;
const Rect registeredDepthBounds(Point(), outputImagePlaneSize);
for (int y = 0; y < transformedCloud.rows; y++)
{
if (hasDistortion)
{
// Project an entire row of points with distortion.
// Doing this for the entire image at once would require more memory.
projectPoints(transformedCloud.row(y),
Vec3f(0, 0, 0),
Vec3f(0, 0, 0),
registeredCameraMatrix,
registeredDistCoeffs,
transformedAndProjectedPoints);
}
else
{
// With no distortion, we just have to dehomogenize the point since all major transforms
// already happened with initialProjection.
Point2f* point2d = &transformedAndProjectedPoints[0];
const Point2f* point2d_end = point2d + transformedAndProjectedPoints.size();
const Point3f* point3d = transformedCloud[y];
for (; point2d < point2d_end; ++point2d, ++point3d)
{
point2d->x = point3d->x / point3d->z;
point2d->y = point3d->y / point3d->z;
}
}
const Point2f* outputProjectedPoint = &transformedAndProjectedPoints[0];
const Point3f* p = transformedCloud[y], * p_end = p + transformedCloud.cols;
for (; p < p_end; ++outputProjectedPoint, ++p)
{
// Skip this one if there isn't a valid depth
const Point2f projectedPixelFloatLocation = *outputProjectedPoint;
if (cvIsNaN(projectedPixelFloatLocation.x))
continue;
//Get integer pixel location
const Point2i projectedPixelLocation = projectedPixelFloatLocation;
// Ensure that the projected point is actually contained in our output image
if (!registeredDepthBounds.contains(projectedPixelLocation))
continue;
// Go back to our original scale, since that's what our output will be
// The templated function is to ensure that integer values are rounded to the nearest integer
const DepthDepth cloudDepth = floatToInputDepth<DepthDepth>(p->z * metersToInputUnitsScale);
DepthDepth& outputDepth = registeredDepth.at<DepthDepth>(projectedPixelLocation.y, projectedPixelLocation.x);
// Occlusion check
if (isEqualToNoDepthSentinelValue<DepthDepth>(outputDepth) || (outputDepth > cloudDepth))
outputDepth = cloudDepth;
// If desired, dilate this point to avoid holes in the final image
if (depthDilation)
{
// Choosing to dilate in a 2x2 region, where the original projected location is in the bottom right of this
// region. This is what's done on PrimeSense devices, but a more accurate scheme could be used.
const Point2i dilatedProjectedLocations[3] = { Point2i(projectedPixelLocation.x - 1, projectedPixelLocation.y),
Point2i(projectedPixelLocation.x , projectedPixelLocation.y - 1),
Point2i(projectedPixelLocation.x - 1, projectedPixelLocation.y - 1) };
for (int i = 0; i < 3; i++)
{
const Point2i& dilatedCoordinates = dilatedProjectedLocations[i];
if (!registeredDepthBounds.contains(dilatedCoordinates))
continue;
DepthDepth& outputDilatedDepth = registeredDepth.at<DepthDepth>(dilatedCoordinates.y, dilatedCoordinates.x);
// Occlusion check
if (isEqualToNoDepthSentinelValue(outputDilatedDepth) || (outputDilatedDepth > cloudDepth))
outputDilatedDepth = cloudDepth;
}
} // depthDilation
} // iterate cols
} // iterate rows
}
void
registerDepth(InputArray unregisteredCameraMatrix, InputArray registeredCameraMatrix, InputArray registeredDistCoeffs,
InputArray Rt, InputArray unregisteredDepth, const Size& outputImagePlaneSize,
OutputArray registeredDepth, bool depthDilation)
{
CV_Assert(unregisteredCameraMatrix.depth() == CV_64F || unregisteredCameraMatrix.depth() == CV_32F);
CV_Assert(registeredCameraMatrix.depth() == CV_64F || registeredCameraMatrix.depth() == CV_32F);
CV_Assert(registeredDistCoeffs.empty() || registeredDistCoeffs.depth() == CV_64F || registeredDistCoeffs.depth() == CV_32F);
CV_Assert(Rt.depth() == CV_64F || Rt.depth() == CV_32F);
CV_Assert(unregisteredDepth.cols() > 0 && unregisteredDepth.rows() > 0 &&
(unregisteredDepth.depth() == CV_32F || unregisteredDepth.depth() == CV_64F || unregisteredDepth.depth() == CV_16U));
CV_Assert(outputImagePlaneSize.height > 0 && outputImagePlaneSize.width > 0);
// Implicitly checking dimensions of the InputArrays
Matx33f _unregisteredCameraMatrix = unregisteredCameraMatrix.getMat();
Matx33f _registeredCameraMatrix = registeredCameraMatrix.getMat();
Mat_<float> _registeredDistCoeffs = registeredDistCoeffs.getMat();
Matx44f _rbtRgb2Depth = Rt.getMat();
Mat& registeredDepthMat = registeredDepth.getMatRef();
switch (unregisteredDepth.depth())
{
case CV_16U:
{
performRegistration<unsigned short>(unregisteredDepth.getMat(), _unregisteredCameraMatrix,
_registeredCameraMatrix, _registeredDistCoeffs,
_rbtRgb2Depth, outputImagePlaneSize, depthDilation,
.001f, registeredDepthMat);
break;
}
case CV_32F:
{
performRegistration<float>(unregisteredDepth.getMat(), _unregisteredCameraMatrix,
_registeredCameraMatrix, _registeredDistCoeffs,
_rbtRgb2Depth, outputImagePlaneSize, depthDilation,
1.0f, registeredDepthMat);
break;
}
case CV_64F:
{
performRegistration<double>(unregisteredDepth.getMat(), _unregisteredCameraMatrix,
_registeredCameraMatrix, _registeredDistCoeffs,
_rbtRgb2Depth, outputImagePlaneSize, depthDilation,
1.0f, registeredDepthMat);
break;
}
default:
{
CV_Error(Error::StsUnsupportedFormat, "Input depth must be unsigned short, float, or double.");
}
}
}
} /* namespace cv */
+254
View File
@@ -0,0 +1,254 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "depth_to_3d.hpp"
namespace cv
{
/** If the input image is of type CV_16UC1 (like the Kinect one), the image is converted to floats, divided
* by 1000 to get a depth in meters, and the values 0 are converted to std::numeric_limits<float>::quiet_NaN()
* Otherwise, the image is simply converted to floats
* @param in_in the depth image (if given as short int CV_U, it is assumed to be the depth in millimeters
* (as done with the Microsoft Kinect), it is assumed in meters)
* @param depth the desired output depth (floats or double)
* @param out_out The rescaled float depth image
*/
void rescaleDepth(InputArray in_in, int type, OutputArray out_out, double depth_factor)
{
cv::Mat in = in_in.getMat();
CV_Assert(in.type() == CV_64FC1 || in.type() == CV_32FC1 || in.type() == CV_16UC1 || in.type() == CV_16SC1);
CV_Assert(type == CV_64FC1 || type == CV_32FC1);
int in_depth = in.depth();
out_out.create(in.size(), type);
cv::Mat out = out_out.getMat();
if (in_depth == CV_16U)
{
in.convertTo(out, type, 1 / depth_factor); //convert to float so that it is in meters
cv::Mat valid_mask = in == std::numeric_limits<ushort>::min(); // Should we do std::numeric_limits<ushort>::max() too ?
out.setTo(std::numeric_limits<float>::quiet_NaN(), valid_mask); //set a$
}
if (in_depth == CV_16S)
{
in.convertTo(out, type, 1 / depth_factor); //convert to float so tha$
cv::Mat valid_mask = (in == std::numeric_limits<short>::min()) | (in == std::numeric_limits<short>::max()); // Should we do std::numeric_limits<ushort>::max() too ?
out.setTo(std::numeric_limits<float>::quiet_NaN(), valid_mask); //set a$
}
if ((in_depth == CV_32F) || (in_depth == CV_64F))
in.convertTo(out, type);
}
/**
* @param K
* @param depth the depth image
* @param mask the mask of the points to consider (can be empty)
* @param points3d the resulting 3d points, a 3-channel matrix
*/
static void depthTo3d_from_uvz(const cv::Mat& in_K, const cv::Mat& u_mat, const cv::Mat& v_mat, const cv::Mat& z_mat,
cv::Mat& points3d)
{
CV_Assert((u_mat.size() == z_mat.size()) && (v_mat.size() == z_mat.size()));
if (u_mat.empty())
return;
CV_Assert((u_mat.type() == z_mat.type()) && (v_mat.type() == z_mat.type()));
//grab camera params
cv::Mat_<float> K;
if (in_K.depth() == CV_32F)
K = in_K;
else
in_K.convertTo(K, CV_32F);
float fx = K(0, 0);
float fy = K(1, 1);
float s = K(0, 1);
float cx = K(0, 2);
float cy = K(1, 2);
std::vector<cv::Mat> coordinates(4);
coordinates[0] = (u_mat - cx) / fx;
if (s != 0)
coordinates[0] = coordinates[0] + (-(s / fy) * v_mat + cy * s / fy) / fx;
coordinates[0] = coordinates[0].mul(z_mat);
coordinates[1] = (v_mat - cy).mul(z_mat) * (1. / fy);
coordinates[2] = z_mat;
coordinates[3] = Mat(u_mat.size(), CV_32F, Scalar(0));
cv::merge(coordinates, points3d);
}
/**
* @param K
* @param depth the depth image
* @param mask the mask of the points to consider (can be empty)
* @param points3d the resulting 3d points
*/
static void depthTo3dMask(const cv::Mat& depth, const cv::Mat& K, const cv::Mat& mask, cv::Mat& points3d)
{
// Create 3D points in one go.
cv::Mat_<float> u_mat, v_mat, z_mat;
cv::Mat_<uchar> uchar_mask = mask;
if (mask.depth() != (CV_8U))
mask.convertTo(uchar_mask, CV_8U);
// Figure out the interesting indices
size_t n_points;
if (depth.depth() == CV_16U)
n_points = convertDepthToFloat<ushort>(depth, mask, 1.0f / 1000.0f, u_mat, v_mat, z_mat);
else if (depth.depth() == CV_16S)
n_points = convertDepthToFloat<short>(depth, mask, 1.0f / 1000.0f, u_mat, v_mat, z_mat);
else
{
CV_Assert(depth.type() == CV_32F);
n_points = convertDepthToFloat<float>(depth, mask, 1.0f, u_mat, v_mat, z_mat);
}
if (n_points == 0)
return;
u_mat.resize(n_points);
v_mat.resize(n_points);
z_mat.resize(n_points);
depthTo3d_from_uvz(K, u_mat, v_mat, z_mat, points3d);
points3d = points3d.reshape(4, 1);
}
/**
* @param K
* @param depth the depth image
* @param points3d the resulting 3d points
*/
template<typename T>
void depthTo3dNoMask(const cv::Mat& in_depth, const cv::Mat_<T>& K, cv::Mat& points3d)
{
const T inv_fx = T(1) / K(0, 0);
const T inv_fy = T(1) / K(1, 1);
const T ox = K(0, 2);
const T oy = K(1, 2);
// Build z
cv::Mat_<T> z_mat;
if (z_mat.depth() == in_depth.depth())
z_mat = in_depth;
else
rescaleDepthTemplated<T>(in_depth, z_mat);
// Pre-compute some constants
cv::Mat_<T> x_cache(1, in_depth.cols), y_cache(in_depth.rows, 1);
T* x_cache_ptr = x_cache[0], * y_cache_ptr = y_cache[0];
for (int x = 0; x < in_depth.cols; ++x, ++x_cache_ptr)
*x_cache_ptr = (x - ox) * inv_fx;
for (int y = 0; y < in_depth.rows; ++y, ++y_cache_ptr)
*y_cache_ptr = (y - oy) * inv_fy;
y_cache_ptr = y_cache[0];
for (int y = 0; y < in_depth.rows; ++y, ++y_cache_ptr)
{
cv::Vec<T, 4>* point = points3d.ptr<cv::Vec<T, 4> >(y);
const T* x_cache_ptr_end = x_cache[0] + in_depth.cols;
const T* depth = z_mat[y];
for (x_cache_ptr = x_cache[0]; x_cache_ptr != x_cache_ptr_end; ++x_cache_ptr, ++point, ++depth)
{
T z = *depth;
(*point)[0] = (*x_cache_ptr) * z;
(*point)[1] = (*y_cache_ptr) * z;
(*point)[2] = z;
(*point)[3] = 0;
}
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* @param K
* @param depth the depth image
* @param u_mat the list of x coordinates
* @param v_mat the list of matching y coordinates
* @param points3d the resulting 3d points
*/
void depthTo3dSparse(InputArray depth_in, InputArray K_in, InputArray points_in, OutputArray points3d_out)
{
// Make sure we use foat types
cv::Mat points = points_in.getMat();
cv::Mat depth = depth_in.getMat();
cv::Mat points_float;
if (points.depth() != CV_32F)
points.convertTo(points_float, CV_32FC2);
else
points_float = points;
// Fill the depth matrix
cv::Mat_<float> z_mat;
if (depth.depth() == CV_16U)
convertDepthToFloat<ushort>(depth, 1.0f / 1000.0f, points_float, z_mat);
else if (depth.depth() == CV_16U)
convertDepthToFloat<short>(depth, 1.0f / 1000.0f, points_float, z_mat);
else
{
CV_Assert(depth.type() == CV_32F);
convertDepthToFloat<float>(depth, 1.0f, points_float, z_mat);
}
std::vector<cv::Mat> channels(2);
cv::split(points_float, channels);
points3d_out.create(channels[0].rows, channels[0].cols, CV_32FC4);
cv::Mat points3d = points3d_out.getMat();
depthTo3d_from_uvz(K_in.getMat(), channels[0], channels[1], z_mat, points3d);
}
/**
* @param depth the depth image (if given as short int CV_U, it is assumed to be the depth in millimeters
* (as done with the Microsoft Kinect), otherwise, if given as CV_32F, it is assumed in meters)
* @param K The calibration matrix
* @param points3d the resulting 3d points. They are of depth the same as `depth` if it is CV_32F or CV_64F, and the
* depth of `K` if `depth` is of depth CV_U
* @param mask the mask of the points to consider (can be empty)
*/
void depthTo3d(InputArray depth_in, InputArray K_in, OutputArray points3d_out, InputArray mask_in)
{
cv::Mat depth = depth_in.getMat();
cv::Mat K = K_in.getMat();
cv::Mat mask = mask_in.getMat();
CV_Assert(K.cols == 3 && K.rows == 3 && (K.depth() == CV_64F || K.depth() == CV_32F));
CV_Assert(depth.type() == CV_64FC1 || depth.type() == CV_32FC1 || depth.type() == CV_16UC1 || depth.type() == CV_16SC1);
CV_Assert(mask.empty() || mask.channels() == 1);
cv::Mat K_new;
K.convertTo(K_new, depth.depth() == CV_64F ? CV_64F : CV_32F); // issue #1021
// Create 3D points in one go.
if (!mask.empty())
{
cv::Mat points3d;
depthTo3dMask(depth, K_new, mask, points3d);
points3d_out.create(points3d.size(), CV_MAKETYPE(K_new.depth(), 4));
points3d.copyTo(points3d_out.getMat());
}
else
{
points3d_out.create(depth.size(), CV_MAKETYPE(K_new.depth(), 4));
cv::Mat points3d = points3d_out.getMat();
if (K_new.depth() == CV_64F)
depthTo3dNoMask<double>(depth, K_new, points3d);
else
depthTo3dNoMask<float>(depth, K_new, points3d);
}
}
}
+115
View File
@@ -0,0 +1,115 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_DEPTH_TO_3D_HPP
#define OPENCV_3D_DEPTH_TO_3D_HPP
#include "precomp.hpp"
#include "utils.hpp"
namespace cv
{
/** If the input image is of type CV_16UC1 (like the Kinect one), the image is converted to floats, divided
* by 1000 to get a depth in meters, and the values 0 are converted to std::numeric_limits<float>::quiet_NaN()
* Otherwise, the image is simply converted to floats
* @param in the depth image (if given as short int CV_U, it is assumed to be the depth in millimeters
* (as done with the Microsoft Kinect), it is assumed in meters)
* @param the desired output depth (floats or double)
* @param out The rescaled float depth image
*/
/* void rescaleDepth(InputArray in_in, int depth, OutputArray out_out); */
template<typename T>
void
rescaleDepthTemplated(const Mat& in, Mat& out);
template<>
inline void
rescaleDepthTemplated<float>(const Mat& in, Mat& out)
{
rescaleDepth(in, CV_32F, out);
}
template<>
inline void
rescaleDepthTemplated<double>(const Mat& in, Mat& out)
{
rescaleDepth(in, CV_64F, out);
}
/**
* @param depth the depth image, containing depth with the value T
* @param the mask, containing CV_8UC1
*/
template <typename T>
size_t convertDepthToFloat(const cv::Mat& depth, const cv::Mat& mask, float scale, cv::Mat_<float> &u_mat, cv::Mat_<float> &v_mat, cv::Mat_<float> &z_mat)
{
CV_Assert(depth.size == mask.size);
cv::Size depth_size = depth.size();
cv::Mat_<uchar> uchar_mask = mask;
if ((mask.depth() != CV_8S) && (mask.depth() != CV_8U) && (mask.depth() != CV_Bool))
mask.convertTo(uchar_mask, CV_8U);
u_mat = cv::Mat_<float>(depth_size.area(), 1);
v_mat = cv::Mat_<float>(depth_size.area(), 1);
z_mat = cv::Mat_<float>(depth_size.area(), 1);
// Raw data from the Kinect has int
size_t n_points = 0;
for (int v = 0; v < depth_size.height; v++)
{
uchar* r = uchar_mask.ptr<uchar>(v, 0);
for (int u = 0; u < depth_size.width; u++, ++r)
if (*r)
{
u_mat((int)n_points, 0) = (float)u;
v_mat((int)n_points, 0) = (float)v;
T depth_i = depth.at<T>(v, u);
if (cvIsNaN((float)depth_i) || (depth_i == std::numeric_limits<T>::min()) || (depth_i == std::numeric_limits<T>::max()))
z_mat((int)n_points, 0) = std::numeric_limits<float>::quiet_NaN();
else
z_mat((int)n_points, 0) = depth_i * scale;
++n_points;
}
}
return n_points;
}
/**
* @param depth the depth image, containing depth with the value T
* @param the mask, containing CV_8UC1
*/
template <typename T>
void convertDepthToFloat(const cv::Mat& depth, float scale, const cv::Mat &uv_mat, cv::Mat_<float> &z_mat)
{
z_mat = cv::Mat_<float>(uv_mat.size());
// Raw data from the Kinect has int
float* z_mat_iter = reinterpret_cast<float*>(z_mat.data);
for (cv::Mat_<cv::Vec2f>::const_iterator uv_iter = uv_mat.begin<cv::Vec2f>(), uv_end = uv_mat.end<cv::Vec2f>();
uv_iter != uv_end; ++uv_iter, ++z_mat_iter)
{
T depth_i = depth.at < T >((int)(*uv_iter)[1], (int)(*uv_iter)[0]);
if (cvIsNaN((float)depth_i) || (depth_i == std::numeric_limits < T > ::min())
|| (depth_i == std::numeric_limits < T > ::max()))
*z_mat_iter = std::numeric_limits<float>::quiet_NaN();
else
*z_mat_iter = depth_i * scale;
}
}
}
#endif // include guard
File diff suppressed because it is too large Load Diff
+324
View File
@@ -0,0 +1,324 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
#ifndef OPENCV_3D_HASH_TSDF_FUNCTIONS_HPP
#define OPENCV_3D_HASH_TSDF_FUNCTIONS_HPP
#include <unordered_set>
#include "utils.hpp"
#include "tsdf_functions.hpp"
#define USE_INTERPOLATION_IN_GETNORMAL 1
#define VOLUMES_SIZE 8192
namespace cv
{
//! Spatial hashing
struct tsdf_hash
{
size_t operator()(const Vec3i& x) const noexcept
{
size_t seed = 0;
constexpr uint32_t GOLDEN_RATIO = 0x9e3779b9;
for (uint16_t i = 0; i < 3; i++)
{
seed ^= std::hash<int>()(x[i]) + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
}
return seed;
}
};
struct VolumeUnit
{
cv::Vec3i coord;
int index;
cv::Matx44f pose;
int lastVisibleIndex = 0;
bool isActive;
};
class CustomHashSet
{
public:
static const int hashDivisor = 32768;
static const int startCapacity = 2048;
std::vector<int> hashes;
// 0-3 for key, 4th for internal use
// don't keep keep value
std::vector<Vec4i> data;
int capacity;
int last;
CustomHashSet()
{
hashes.resize(hashDivisor);
for (int i = 0; i < hashDivisor; i++)
hashes[i] = -1;
capacity = startCapacity;
data.resize(capacity);
for (int i = 0; i < capacity; i++)
data[i] = { 0, 0, 0, -1 };
last = 0;
}
~CustomHashSet() { }
inline size_t calc_hash(Vec3i x) const
{
uint32_t seed = 0;
constexpr uint32_t GOLDEN_RATIO = 0x9e3779b9;
for (int i = 0; i < 3; i++)
{
seed ^= x[i] + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
}
return seed;
}
// should work on existing elements too
// 0 - need resize
// 1 - idx is inserted
// 2 - idx already exists
int insert(Vec3i idx)
{
if (last < capacity)
{
int hash = int(calc_hash(idx) % hashDivisor);
int place = hashes[hash];
if (place >= 0)
{
int oldPlace = place;
while (place >= 0)
{
if (data[place][0] == idx[0] &&
data[place][1] == idx[1] &&
data[place][2] == idx[2])
return 2;
else
{
oldPlace = place;
place = data[place][3];
//std::cout << "place=" << place << std::endl;
}
}
// found, create here
data[oldPlace][3] = last;
}
else
{
// insert at last
hashes[hash] = last;
}
data[last][0] = idx[0];
data[last][1] = idx[1];
data[last][2] = idx[2];
data[last][3] = -1;
last++;
return 1;
}
else
return 0;
}
int find(Vec3i idx) const
{
int hash = int(calc_hash(idx) % hashDivisor);
int place = hashes[hash];
// search a place
while (place >= 0)
{
if (data[place][0] == idx[0] &&
data[place][1] == idx[1] &&
data[place][2] == idx[2])
break;
else
{
place = data[place][3];
}
}
return place;
}
};
// TODO: remove this structure as soon as HashTSDFGPU data is completely on GPU;
// until then CustomHashTable can be replaced by this one if needed
const int NAN_ELEMENT = -2147483647;
struct Volume_NODE
{
Vec4i idx = Vec4i(NAN_ELEMENT);
int32_t row = -1;
int32_t nextVolumeRow = -1;
int32_t dummy = 0;
int32_t dummy2 = 0;
};
const int _hash_divisor = 32768;
const int _list_size = 4;
class VolumesTable
{
public:
const int hash_divisor = _hash_divisor;
const int list_size = _list_size;
const int32_t free_row = -1;
const int32_t free_isActive = 0;
const cv::Vec4i nan4 = cv::Vec4i(NAN_ELEMENT);
int bufferNums;
cv::Mat volumes;
VolumesTable() : bufferNums(1)
{
this->volumes = cv::Mat(hash_divisor * list_size, 1, rawType<Volume_NODE>());
for (int i = 0; i < volumes.size().height; i++)
{
Volume_NODE* v = volumes.ptr<Volume_NODE>(i);
v->idx = nan4;
v->row = -1;
v->nextVolumeRow = -1;
}
}
const VolumesTable& operator=(const VolumesTable& vt)
{
this->volumes = vt.volumes;
this->bufferNums = vt.bufferNums;
return *this;
}
~VolumesTable() {};
bool insert(Vec3i idx, int row)
{
CV_Assert(row >= 0);
int bufferNum = 0;
int hash = int(calc_hash(idx) % hash_divisor);
int start = getPos(idx, bufferNum);
int i = start;
while (i >= 0)
{
Volume_NODE* v = volumes.ptr<Volume_NODE>(i);
if (v->idx[0] == NAN_ELEMENT)
{
Vec4i idx4(idx[0], idx[1], idx[2], 0);
bool extend = false;
if (i != start && i % list_size == 0)
{
if (bufferNum >= bufferNums - 1)
{
extend = true;
volumes.resize(hash_divisor * bufferNums);
bufferNums++;
}
bufferNum++;
v->nextVolumeRow = (bufferNum * hash_divisor + hash) * list_size;
}
else
{
v->nextVolumeRow = i + 1;
}
v->idx = idx4;
v->row = row;
return extend;
}
i = v->nextVolumeRow;
}
return false;
}
int findRow(Vec3i idx) const
{
int bufferNum = 0;
int i = getPos(idx, bufferNum);
while (i >= 0)
{
const Volume_NODE* v = volumes.ptr<Volume_NODE>(i);
if (v->idx == Vec4i(idx[0], idx[1], idx[2], 0))
return v->row;
else
i = v->nextVolumeRow;
}
return -1;
}
inline int getPos(Vec3i idx, int bufferNum) const
{
int hash = int(calc_hash(idx) % hash_divisor);
return (bufferNum * hash_divisor + hash) * list_size;
}
inline size_t calc_hash(Vec3i x) const
{
uint32_t seed = 0;
constexpr uint32_t GOLDEN_RATIO = 0x9e3779b9;
for (int i = 0; i < 3; i++)
{
seed ^= x[i] + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
}
return seed;
}
};
int calcVolumeUnitDegree(Point3i volumeResolution);
typedef std::unordered_map<cv::Vec3i, VolumeUnit, tsdf_hash> VolumeUnitIndexes;
void integrateHashTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose, int& lastVolIndex, const int frameId, const int volumeUnitDegree, bool enableGrowth,
InputArray _depth, InputArray _pixNorms, InputOutputArray _volUnitsData, VolumeUnitIndexes& volumeUnits);
void raycastHashTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose, int height, int width, InputArray intr, const int volumeUnitDegree,
InputArray _volUnitsData, const VolumeUnitIndexes& volumeUnits, OutputArray _points, OutputArray _normals);
void fetchNormalsFromHashTsdfVolumeUnit(
const VolumeSettings& settings, InputArray _volUnitsData, const VolumeUnitIndexes& volumeUnits,
const int volumeUnitDegree, InputArray _points, OutputArray _normals);
void fetchPointsNormalsFromHashTsdfVolumeUnit(
const VolumeSettings& settings, InputArray _volUnitsData, const VolumeUnitIndexes& volumeUnits,
const int volumeUnitDegree, OutputArray _points, OutputArray _normals);
#ifdef HAVE_OPENCL
void ocl_integrateHashTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose, int& lastVolIndex, const int frameId, int& bufferSizeDegree, const int volumeUnitDegree, bool enableGrowth,
InputArray _depth, InputArray _pixNorms, InputArray _lastVisibleIndices, InputOutputArray _volUnitsDataCopy, InputOutputArray _volUnitsData, CustomHashSet& hashTable, InputArray _isActiveFlags);
void ocl_raycastHashTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose, int height, int width, InputArray intr, const int volumeUnitDegree,
const CustomHashSet& hashTable, InputArray _volUnitsData, OutputArray _points, OutputArray _normals);
void ocl_fetchNormalsFromHashTsdfVolumeUnit(
const VolumeSettings& settings, const int volumeUnitDegree, InputArray _volUnitsData, InputArray _volUnitsDataCopy,
const CustomHashSet& hashTable, InputArray _points, OutputArray _normals);
void ocl_fetchPointsNormalsFromHashTsdfVolumeUnit(
const VolumeSettings& settings, const int volumeUnitDegree, InputArray _volUnitsData, InputArray _volUnitsDataCopy,
const CustomHashSet& hashTable, OutputArray _points, OutputArray _normals);
#endif
} // namespace cv
#endif
+35
View File
@@ -0,0 +1,35 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#include "io_base.hpp"
namespace cv {
void BasePointCloudDecoder::setSource(const std::string& filename) noexcept
{
m_filename = filename;
}
void BasePointCloudDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3f> &rgb)
{
std::vector<std::vector<int32_t>> indices;
std::vector<Point3f> texCoords;
int nTexCoords;
readData(points, normals, rgb, texCoords, nTexCoords, indices, READ_AS_IS_FLAG);
}
void BasePointCloudEncoder::setDestination(const std::string& filename) noexcept
{
m_filename = filename;
}
void BasePointCloudEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3f> &rgb)
{
std::vector<std::vector<int32_t>> indices;
std::vector<Point3f> texCoords;
writeData(points, normals, rgb, texCoords, 0, indices);
}
} /* namespace cv */
+59
View File
@@ -0,0 +1,59 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef _CODERS_BASE_H_
#define _CODERS_BASE_H_
#include <vector>
#include <memory>
#include <cstdint>
#include <opencv2/core.hpp>
namespace cv {
class BasePointCloudDecoder;
class BasePointCloudEncoder;
using PointCloudDecoder = std::unique_ptr<BasePointCloudDecoder>;
using PointCloudEncoder = std::unique_ptr<BasePointCloudEncoder>;
// for OBJ files: to read vertices, normals and texture coords as they are given in the file
// or duplicate them according to faces
const int READ_AS_IS_FLAG = 1;
///////////////////////////////// base class for decoders ////////////////////////
class BasePointCloudDecoder
{
public:
virtual ~BasePointCloudDecoder() = default;
virtual void setSource(const std::string& filename) noexcept;
virtual void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb);
virtual void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
std::vector<Point3f>& texCoords, int& nTexCoords,
std::vector<std::vector<int32_t>>& indices, int flags) = 0;
protected:
std::string m_filename;
};
///////////////////////////////// base class for encoders ////////////////////////
class BasePointCloudEncoder
{
public:
virtual ~BasePointCloudEncoder() = default;
virtual void setDestination(const std::string& filename) noexcept;
virtual void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb);
virtual void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
const std::vector<Point3f>& texCoords, int nTexCoords,
const std::vector<std::vector<int32_t>>& indices) = 0;
protected:
std::string m_filename;
};
} /* namespace cv */
#endif
+293
View File
@@ -0,0 +1,293 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#include "io_obj.hpp"
#include <fstream>
#include <opencv2/core/utils/logger.hpp>
#include "utils.hpp"
namespace cv {
std::unordered_set<std::string> ObjDecoder::m_unsupportedKeys;
void ObjDecoder::readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb)
{
std::vector<Point3f> texCoords;
int nTexCoords;
std::vector<std::vector<int32_t>> indices;
this->readData(points, normals, rgb, texCoords, nTexCoords, indices, READ_AS_IS_FLAG);
}
void ObjDecoder::readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
std::vector<Point3f>& texCoords, int& nTexCoords,
std::vector<std::vector<int32_t>>& indices, int flags)
{
std::vector<Point3f> ptsList, nrmList, texCoordList, rgbList;
std::vector<std::vector<int32_t>> idxList, texIdxList, normalIdxList;
nTexCoords = 0;
bool duplicateVertices = false;
std::ifstream file(m_filename, std::ios::binary);
if (!file)
{
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
return;
}
std::string s;
while (!file.eof())
{
std::getline(file, s);
// "\r" symbols are not trimmed by default
s = trimSpaces(s);
if (s.empty())
continue;
std::stringstream ss(s);
std::string key;
ss >> key;
if (key == "#")
continue;
else if (key == "v")
{
// (x, y, z, [w], [r, g, b])
auto splitArr = split(s, ' ');
if (splitArr.size() <= 3)
{
CV_LOG_ERROR(NULL, "Vertex should have at least 3 coordinate values.");
return;
}
Point3f vertex;
ss >> vertex.x >> vertex.y >> vertex.z;
ptsList.push_back(vertex);
if (splitArr.size() == 5 || splitArr.size() == 8)
{
float w;
ss >> w;
CV_UNUSED(w);
}
if (splitArr.size() >= 7)
{
Point3f color;
if (ss.rdbuf()->in_avail() != 0)
{
ss >> color.x >> color.y >> color.z;
rgbList.push_back(color);
}
}
}
else if (key == "vn")
{
Point3f normal;
ss >> normal.x >> normal.y >> normal.z;
nrmList.push_back(normal);
}
else if (key == "f")
{
// format: "f v0 / t0 / n0 v1 / t1 / n1 v2/t2/n2 ..."
std::vector<int> vertexInd, normInd, texInd;
vertexInd.reserve(3); normInd.reserve(3); texInd.reserve(3);
auto tokens = split(s, ' ');
for (size_t i = 1; i < tokens.size(); i++)
{
auto vertexinfo = split(tokens[i], '/');
std::array<int, 3> idx = { -1, -1, -1 };
for (int j = 0; j < (int)vertexinfo.size(); j++)
{
std::string sj = vertexinfo[j];
// trimming spaces; as a result s can become empty - this is not an error
auto si = std::find_if(sj.begin(), sj.end(), [](char c) { return (c >= '0' && c <= '9'); });
auto ei = std::find_if(sj.rbegin(), sj.rend(), [](char c) { return (c >= '0' && c <= '9'); });
if (si != sj.end() && ei != sj.rend())
{
auto first = std::distance(si, sj.begin());
auto last = std::distance(ei, sj.rend());
sj = sj.substr(first, last - first + 1);
try
{
idx[j] = std::stoi(sj);
}
// std::invalid_exception, std::out_of_range
catch(const std::exception&)
{
CV_LOG_ERROR(NULL, "Failed to parse face index: " + sj);
return;
}
}
}
int vertexIndex = idx[0];
int texCoordIndex = idx[1];
int normalIndex = idx[2];
if (vertexIndex <= 0)
{
CV_LOG_ERROR(NULL, "Vertex index is not present or incorrect");
return;
}
if ((vertexIndex != texCoordIndex && texCoordIndex >= 0) ||
(vertexIndex != normalIndex && normalIndex >= 0))
{
duplicateVertices = !(flags & READ_AS_IS_FLAG);
}
vertexInd.push_back(vertexIndex - 1);
normInd.push_back(normalIndex - 1);
texInd.push_back(texCoordIndex - 1);
}
idxList.push_back(vertexInd);
texIdxList.push_back(texInd);
normalIdxList.push_back(normInd);
}
else if (key == "vt")
{
// (u, [v, [w]])
auto splitArr = split(s, ' ');
int ncoords = (int)splitArr.size() - 1;
if (!nTexCoords)
{
nTexCoords = ncoords;
if (nTexCoords < 1 || nTexCoords > 3)
{
CV_LOG_ERROR(NULL, "The amount of texture coordinates should be between 1 and 3");
return;
}
}
if (ncoords != nTexCoords)
{
CV_LOG_ERROR(NULL, "All points should have the same number of texture coordinates");
return;
}
Vec3f tc;
for (int i = 0; i < nTexCoords; i++)
{
ss >> tc[i];
}
texCoordList.push_back(tc);
}
else
{
if (m_unsupportedKeys.find(key) == m_unsupportedKeys.end()) {
m_unsupportedKeys.insert(key);
CV_LOG_WARNING(NULL, "Key " << key << " not supported");
}
}
}
if (duplicateVertices)
{
points.clear();
normals.clear();
rgb.clear();
texCoords.clear();
indices.clear();
for (int tri = 0; tri < (int)idxList.size(); tri++)
{
auto vi = idxList[tri];
auto ti = texIdxList[tri];
auto ni = normalIdxList[tri];
std::vector<int32_t> newvi;
newvi.reserve(3);
for (int i = 0; i < (int)vi.size(); i++)
{
newvi.push_back((int)points.size());
points.push_back(ptsList.at(vi[i]));
if (!rgbList.empty())
{
rgb.push_back(rgbList.at(vi[i]));
}
texCoords.push_back(texCoordList.at(ti[i]));
normals.push_back(nrmList.at(ni[i]));
}
indices.push_back(newvi);
}
}
else
{
points = std::move(ptsList);
normals = std::move(nrmList);
rgb = std::move(rgbList);
texCoords = std::move(texCoordList);
indices = std::move(idxList);
}
file.close();
}
void ObjEncoder::writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
const std::vector<Point3f>& texCoords, int nTexCoords,
const std::vector<std::vector<int32_t>>& indices)
{
std::ofstream file(m_filename, std::ios::binary);
if (!file) {
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
return;
}
if (!rgb.empty() && rgb.size() != points.size()) {
CV_LOG_ERROR(NULL, "Vertices and Colors have different size.");
return;
}
if (texCoords.empty() && nTexCoords > 0)
{
CV_LOG_ERROR(NULL, "No texture coordinates provided while having nTexCoord > 0");
return;
}
file << "# OBJ file writer" << std::endl;
file << "o Point_Cloud" << std::endl;
for (size_t i = 0; i < points.size(); ++i)
{
file << "v " << points[i].x << " " << points[i].y << " " << points[i].z;
if (!rgb.empty())
{
file << " " << rgb[i].x << " " << rgb[i].y << " " << rgb[i].z;
}
file << std::endl;
}
for (const auto& normal : normals)
{
file << "vn " << normal.x << " " << normal.y << " " << normal.z << std::endl;
}
for (const auto& tc : texCoords)
{
file << "vt " << tc.x;
if (nTexCoords > 1)
{
file << " " << tc.y;
}
if (nTexCoords > 2)
{
file << " " << tc.z;
}
file << std::endl;
}
for (const auto& faceIndices : indices)
{
file << "f ";
for (const auto& index : faceIndices)
{
file << index + 1 << " ";
}
file << std::endl;
}
file.close();
}
} /* namespace cv */
+36
View File
@@ -0,0 +1,36 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef _CODERS_OBJ_H_
#define _CODERS_OBJ_H_
#include "io_base.hpp"
#include <unordered_set>
namespace cv {
class ObjDecoder CV_FINAL : public BasePointCloudDecoder
{
public:
void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb) CV_OVERRIDE;
void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
std::vector<Point3f>& texCoords, int& nTexCoords,
std::vector<std::vector<int32_t>>& indices, int flags) CV_OVERRIDE;
protected:
static std::unordered_set<std::string> m_unsupportedKeys;
};
class ObjEncoder CV_FINAL : public BasePointCloudEncoder
{
public:
void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
const std::vector<Point3f>& texCoords, int nTexCoords,
const std::vector<std::vector<int32_t>>& indices) CV_OVERRIDE;
};
} /* namespace cv */
#endif
+682
View File
@@ -0,0 +1,682 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#include "io_ply.hpp"
#include "utils.hpp"
#include <opencv2/core/utils/logger.hpp>
#include <fstream>
#include <string>
#include <iomanip>
#include <cstddef>
namespace cv {
static const std::set<std::string> colorKeys = { "red", "diffuse_red", "green", "diffuse_green", "blue", "diffuse_blue" };
void PlyDecoder::readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
std::vector<Point3f>& texCoords, int& nTexCoords,
std::vector<std::vector<int32_t>>& indices, int /*flags*/)
{
points.clear();
normals.clear();
rgb.clear();
texCoords.clear();
indices.clear();
nTexCoords = 0;
std::ifstream file(m_filename, std::ios::binary);
if (parseHeader(file, nTexCoords))
{
parseBody(file, points, normals, rgb, texCoords, indices);
}
}
bool PlyDecoder::parseHeader(std::ifstream &file, int& nTexCoords)
{
std::string s;
std::getline(file, s);
if (trimSpaces(s) != "ply")
{
CV_LOG_ERROR(NULL, "Provided file is not in PLY format");
return false;
}
std::getline(file, s);
auto splitArr = split(s, ' ');
// "\r" symbols are not trimmed by default
for (auto& e : splitArr)
{
e = trimSpaces(e);
}
if (splitArr[0] != "format")
{
CV_LOG_ERROR(NULL, "Provided file doesn't have format");
return false;
}
if (splitArr[1] == "ascii")
{
m_inputDataFormat = DataFormat::ASCII;
}
else if (splitArr[1] == "binary_little_endian")
{
m_inputDataFormat = DataFormat::BinaryLittleEndian;
}
else if (splitArr[1] == "binary_big_endian")
{
m_inputDataFormat = DataFormat::BinaryBigEndian;
}
else
{
CV_LOG_ERROR(NULL, "Provided PLY file format is not supported");
return false;
}
const std::map<std::string, int> dataTypes =
{
{ "char", CV_8S }, { "int8", CV_8S },
{ "uchar", CV_8U }, { "uint8", CV_8U },
{ "short", CV_16S }, { "int16", CV_16S },
{ "ushort", CV_16U }, { "uint16", CV_16U },
{ "int", CV_32S }, { "int32", CV_32S },
{ "uint", CV_32U }, { "uint32", CV_32U },
{ "float", CV_32F }, { "float32", CV_32F },
{ "double", CV_64F }, { "float64", CV_64F },
};
enum ReadElement
{
READ_OTHER = 0,
READ_VERTEX = 1,
READ_FACE = 2
};
ReadElement elemRead = READ_OTHER;
m_vertexDescription = ElementDescription();
m_faceDescription = ElementDescription();
while (std::getline(file, s))
{
if (startsWith(s, "element"))
{
std::vector<std::string> splitArrElem = split(s, ' ');
// "\r" symbols are not trimmed by default
for (auto& e : splitArrElem)
{
e = trimSpaces(e);
}
std::string elemName = splitArrElem.at(1);
if (elemName == "vertex")
{
elemRead = READ_VERTEX;
if(splitArrElem.size() != 3)
{
CV_LOG_ERROR(NULL, "Vertex element description has " << splitArrElem.size()
<< " words instead of 3");
return false;
}
std::istringstream iss(splitArrElem[2]);
iss >> m_vertexDescription.amount;
}
else if (elemName == "face")
{
elemRead = READ_FACE;
if(splitArrElem.size() != 3)
{
CV_LOG_ERROR(NULL, "Face element description has " << splitArrElem.size()
<< " words instead of 3");
return false;
}
std::istringstream iss(splitArrElem[2]);
iss >> m_faceDescription.amount;
}
else
{
elemRead = READ_OTHER;
}
continue;
}
if (startsWith(s, "property"))
{
Property property;
std::string elName = (elemRead == READ_VERTEX) ? "Vertex" : "Face";
std::vector<std::string> splitArrElem = split(s, ' ');
// "\r" symbols are not trimmed by default
for (auto& e : splitArrElem)
{
e = trimSpaces(e);
}
if (splitArrElem.size() < 3)
{
CV_LOG_ERROR(NULL, elName << " property has " << splitArrElem.size()
<< " words instead of at least 3");
return false;
}
std::string propType = splitArrElem[1];
if (propType == "list")
{
property.isList = true;
if (splitArrElem.size() < 5)
{
CV_LOG_ERROR(NULL, elName << " property has " << splitArrElem.size()
<< " words instead of at least 5");
return false;
}
std::string amtTypeString = splitArrElem[2];
if (dataTypes.count(amtTypeString) == 0)
{
CV_LOG_ERROR(NULL, "Property type " << amtTypeString
<< " is not supported");
return false;
}
else
{
property.counterType = dataTypes.at(amtTypeString);
}
std::string idxTypeString = splitArrElem[3];
if (dataTypes.count(idxTypeString) == 0)
{
CV_LOG_ERROR(NULL, "Property type " << idxTypeString
<< " is not supported");
return false;
}
else
{
property.valType = dataTypes.at(idxTypeString);
}
property.name = splitArrElem[4];
}
else
{
property.isList = false;
if (dataTypes.count(propType) == 0)
{
CV_LOG_ERROR(NULL, "Property type " << propType
<< " is not supported");
return false;
}
else
{
property.valType = dataTypes.at(propType);
}
property.name = splitArrElem[2];
}
if (elemRead == READ_VERTEX)
{
m_vertexDescription.properties.push_back(property);
}
else if (elemRead == READ_FACE)
{
m_faceDescription.properties.push_back(property);
}
continue;
}
if (startsWith(s, "end_header"))
break;
}
static const std::set<std::string> texCoordKeys = { "texture_u", "s", "texture_v", "t", "texture_w" };
bool good = true;
m_vertexCount = m_vertexDescription.amount;
std::map<std::string, int> amtProps;
for (const auto& p : m_vertexDescription.properties)
{
bool known = false;
if (p.name == "x" || p.name == "y" || p.name == "z")
{
known = true;
if (p.valType != CV_32F)
{
CV_LOG_ERROR(NULL, "Vertex property " << p.name
<< " should be float");
good = false;
}
}
if (p.name == "nx" || p.name == "ny" || p.name == "nz")
{
known = true;
if (p.valType != CV_32F)
{
CV_LOG_ERROR(NULL, "Vertex property " << p.name
<< " should be float");
good = false;
}
m_hasNormal = true;
}
if (colorKeys.count(p.name) > 0)
{
known = true;
if (p.valType != CV_8U)
{
CV_LOG_ERROR(NULL, "Vertex property " << p.name
<< " should be uchar");
good = false;
}
m_hasColour = true;
}
if (texCoordKeys.count(p.name) > 0)
{
known = true;
if (p.valType != CV_32F)
{
CV_LOG_ERROR(NULL, "Vertex property " << p.name
<< " should be float");
good = false;
}
m_hasTexCoord = true;
}
if (p.isList)
{
CV_LOG_ERROR(NULL, "List properties for vertices are not supported");
good = false;
}
if (known)
{
amtProps[p.name]++;
}
}
// check if we have no duplicates
for (const auto& a : amtProps)
{
if (a.second > 1)
{
CV_LOG_ERROR(NULL, "Vertex property " << a.first << " is duplicated");
good = false;
}
}
const std::array<std::string, 3> vertKeys = {"x", "y", "z"};
for (const std::string& c : vertKeys)
{
if (amtProps.count(c) == 0)
{
CV_LOG_ERROR(NULL, "Vertex property " << c << " is not presented in the file");
good = false;
}
}
// check for synonyms
std::vector<std::pair<size_t, size_t>> propCounts;
std::vector<std::pair<std::string, std::string>> synonyms = {
{"red", "diffuse_red"},
{"green", "diffuse_green"},
{"blue", "diffuse_blue"},
{"texture_u", "s"},
{"texture_v", "t"},
};
for (const auto& p : synonyms)
{
std::string a, b;
a = p.first; b = p.second;
size_t ca = amtProps.count(a), cb = amtProps.count(b);
propCounts.push_back({ca, cb});
if (ca + cb > 1)
{
CV_LOG_ERROR(NULL, "Vertex property " << a << " should not go with its synonym " << b);
good = false;
}
}
// check for color conventions
bool shortColorConv = propCounts[0].first || propCounts[1].first || propCounts[2].first;
bool diffuseColorConv = propCounts[0].second || propCounts[1].second || propCounts[2].second;
if (shortColorConv && diffuseColorConv)
{
CV_LOG_ERROR(NULL, "Vertex color properties should not be diffuse and not diffuse at the same time");
good = false;
}
// check for texture conventions
bool shortTexConv = propCounts[3].second || propCounts[4].second;
bool longTexConv = propCounts[3].first || propCounts[4].first;
if (shortTexConv && longTexConv)
{
CV_LOG_ERROR(NULL, "Vertex texture coordinates properties should not be in a short and in a long form at the same time");
good = false;
}
nTexCoords = 0;
for (const auto& k : texCoordKeys)
{
nTexCoords += (int)(amtProps.count(k));
}
m_faceCount = m_faceDescription.amount;
int amtLists = 0;
for (const auto& p : m_faceDescription.properties)
{
if (p.isList)
{
amtLists++;
if (!(p.counterType == CV_8U && (p.valType == CV_32S || p.valType == CV_32U)))
{
CV_LOG_ERROR(NULL, "List property " << p.name
<< " should have type uint8 for counter and uint32 for values");
good = false;
}
if (!(p.name == "vertex_index" || p.name == "vertex_indices"))
{
CV_LOG_ERROR(NULL, "List property should be vertex_index or vertex_indices, "
<< p.name << " is not supported");
good = false;
}
}
}
if (amtLists > 1)
{
CV_LOG_ERROR(NULL, "Only 1 list property is supported per face");
good = false;
}
return good;
}
template <typename T>
T readNext(std::ifstream &file, DataFormat format)
{
T val;
if (format == DataFormat::ASCII)
{
file >> val;
return val;
}
file.read((char *)&val, sizeof(T));
#ifdef WORDS_BIGENDIAN
if (!(format == DataFormat::BinaryBigEndian) )
{
swapEndian<T>(val);
}
#else
if (format == DataFormat::BinaryBigEndian)
{
swapEndian<T>(val);
}
#endif
return val;
}
template <>
uchar readNext<uchar>(std::ifstream &file, DataFormat format)
{
if (format == DataFormat::ASCII)
{
int val;
file >> val;
return (uchar)val;
}
uchar val;
file.read((char *)&val, sizeof(uchar));
// 1 byte does not have to be endian-swapped
return val;
}
void PlyDecoder::parseBody(std::ifstream &file,
std::vector<Point3f>& points, std::vector<Point3f>& normals,
std::vector<Point3f>& rgb, std::vector<Point3f>& texCoords,
std::vector<std::vector<int32_t>> &indices)
{
points.reserve(m_vertexCount);
if (m_hasColour)
{
rgb.reserve(m_vertexCount);
}
if (m_hasNormal)
{
normals.reserve(m_vertexCount);
}
struct VertexFields
{
float vx, vy, vz;
float nx, ny, nz;
float u, v, w;
float r, g, b;
};
union VertexData
{
std::array<uchar, sizeof(VertexFields)> bytes;
VertexFields vf;
};
// to avoid string matching at file loading
std::vector<size_t> vertexOffsets(m_vertexDescription.properties.size(), (size_t)(-1));
for (size_t j = 0; j < m_vertexDescription.properties.size(); j++)
{
const auto& p = m_vertexDescription.properties[j];
size_t offset = (size_t)(-1);
if (p.name == "x")
offset = offsetof(VertexFields, vx);
if (p.name == "y")
offset = offsetof(VertexFields, vy);
if (p.name == "z")
offset = offsetof(VertexFields, vz);
if (p.name == "nx")
offset = offsetof(VertexFields, nx);
if (p.name == "ny")
offset = offsetof(VertexFields, ny);
if (p.name == "nz")
offset = offsetof(VertexFields, nz);
if (p.name == "texture_u" || p.name == "s")
offset = offsetof(VertexFields, u);
if (p.name == "texture_v" || p.name == "t")
offset = offsetof(VertexFields, v);
if (p.name == "texture_w")
offset = offsetof(VertexFields, w);
if (p.name == "red" || p.name == "diffuse_red")
offset = offsetof(VertexFields, r);
if (p.name == "green" || p.name == "diffuse_green")
offset = offsetof(VertexFields, g);
if (p.name == "blue" || p.name == "diffuse_blue")
offset = offsetof(VertexFields, b);
vertexOffsets[j] = offset;
}
for (size_t i = 0; i < m_vertexCount; i++)
{
VertexData vertexData{ };
for (size_t j = 0; j < m_vertexDescription.properties.size(); j++)
{
const auto& p = m_vertexDescription.properties[j];
uint ival = 0; float fval = 0;
// here signedness is not important
switch (p.valType)
{
case CV_8U: case CV_8S:
ival = readNext<uchar>(file, m_inputDataFormat);
break;
case CV_16U: case CV_16S:
ival = readNext<ushort>(file, m_inputDataFormat);
break;
case CV_32S: case CV_32U:
ival = readNext<uint>(file, m_inputDataFormat);
break;
case CV_32F:
fval = readNext<float>(file, m_inputDataFormat);
break;
case CV_64F:
fval = (float)readNext<double>(file, m_inputDataFormat);
break;
default:
break;
}
size_t offset = vertexOffsets[j];
if (offset != (size_t)(-1))
{
if (colorKeys.count(p.name) > 0 && p.valType == CV_8U)
{
fval = ival / 255.f;
}
*(float*)(vertexData.bytes.data() + offset) = fval;
}
}
points.push_back({ vertexData.vf.vx, vertexData.vf.vy, vertexData.vf.vz });
if (m_hasColour)
{
rgb.push_back({ vertexData.vf.r, vertexData.vf.g, vertexData.vf.b });
}
if (m_hasNormal)
{
normals.push_back({ vertexData.vf.nx, vertexData.vf.ny, vertexData.vf.nz });
}
if (m_hasTexCoord)
{
texCoords.push_back({ vertexData.vf.u, vertexData.vf.v, vertexData.vf.w });
}
}
indices.reserve(m_faceCount);
for (size_t i = 0; i < m_faceCount; i++)
{
for (const auto& p : m_faceDescription.properties)
{
if (p.isList)
{
size_t nVerts = readNext<uchar>(file, m_inputDataFormat);
if (nVerts < 3)
{
CV_LOG_ERROR(NULL, "Face should have at least 3 vertices but has " << nVerts);
return;
}
// PLY can have faces with >3 vertices in TRIANGLE_FAN format
// in this case we load them as separate triangles
int vert1 = readNext<int>(file, m_inputDataFormat);
int vert2 = readNext<int>(file, m_inputDataFormat);
for (size_t j = 2; j < nVerts; j++)
{
int vert3 = readNext<int>(file, m_inputDataFormat);
indices.push_back({vert1, vert2, vert3});
vert2 = vert3;
}
}
else
{
// read and discard
switch (p.valType)
{
case CV_8U: case CV_8S:
readNext<uchar>(file, m_inputDataFormat);
break;
case CV_16U: case CV_16S:
readNext<ushort>(file, m_inputDataFormat);
break;
case CV_32S: case CV_32U:
readNext<uint>(file, m_inputDataFormat);
break;
case CV_32F:
readNext<float>(file, m_inputDataFormat);
break;
case CV_64F:
readNext<double>(file, m_inputDataFormat);
break;
default:
break;
}
}
}
}
}
void PlyEncoder::writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
const std::vector<Point3f>& texCoords, int nTexCoords,
const std::vector<std::vector<int32_t>>& indices)
{
std::ofstream file(m_filename, std::ios::binary);
if (!file)
{
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
return;
}
bool hasNormals = !normals.empty(), hasColor = !rgb.empty();
if (texCoords.empty() && nTexCoords > 0)
{
CV_LOG_ERROR(NULL, "No texture coordinates provided while having nTexCoord > 0");
return;
}
file << "ply" << std::endl;
file << "format ascii 1.0" << std::endl;
file << "comment created by OpenCV" << std::endl;
file << "element vertex " << points.size() << std::endl;
file << "property float x" << std::endl;
file << "property float y" << std::endl;
file << "property float z" << std::endl;
if(hasColor)
{
file << "property uchar red" << std::endl;
file << "property uchar green" << std::endl;
file << "property uchar blue" << std::endl;
}
if (hasNormals)
{
file << "property float nx" << std::endl;
file << "property float ny" << std::endl;
file << "property float nz" << std::endl;
}
if (nTexCoords > 0)
{
file << "property float texture_u" << std::endl;
}
if (nTexCoords > 1)
{
file << "property float texture_v" << std::endl;
}
if (nTexCoords > 2)
{
file << "property float texture_w" << std::endl;
}
if (!indices.empty())
{
file << "element face " << indices.size() << std::endl;
file << "property list uchar int vertex_indices" << std::endl;
}
file << "end_header" << std::endl;
for (size_t i = 0; i < points.size(); i++)
{
file << std::setprecision(9) << points[i].x << " " << points[i].y << " " << points[i].z;
if (hasColor)
{
file << " " << static_cast<int>(rgb[i].x * 255.f) << " " << static_cast<int>(rgb[i].y * 255.f) << " " << static_cast<int>(rgb[i].z * 255.f);
}
if (hasNormals)
{
file << " " << std::setprecision(9) << normals[i].x << " " << normals[i].y << " " << normals[i].z;
}
if (nTexCoords > 0)
{
file << " " << std::setprecision(9) << texCoords[i].x;
}
if (nTexCoords > 1)
{
file << " " << std::setprecision(9) << texCoords[i].y;
}
if (nTexCoords > 2)
{
file << " " << std::setprecision(9) << texCoords[i].z;
}
file << std::endl;
}
for (const auto& faceIndices : indices)
{
file << faceIndices.size();
for (const auto& index : faceIndices)
{
file << " " << index;
}
file << std::endl;
}
file.close();
}
} /* namespace cv */
+70
View File
@@ -0,0 +1,70 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef _CODERS_PLY_H_
#define _CODERS_PLY_H_
#include "io_base.hpp"
#include <istream>
#include <vector>
namespace cv {
enum class DataFormat
{
ASCII,
BinaryLittleEndian,
BinaryBigEndian
};
struct Property
{
bool isList;
int counterType;
int valType;
std::string name;
};
struct ElementDescription
{
size_t amount;
std::vector<Property> properties;
};
class PlyDecoder CV_FINAL : public BasePointCloudDecoder
{
public:
void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
std::vector<Point3f>& texCoords, int& nTexCoords,
std::vector<std::vector<int32_t>>& indices, int flags) CV_OVERRIDE;
protected:
bool parseHeader(std::ifstream &file, int& nTexCoords);
void parseBody(std::ifstream &file,
std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
std::vector<Point3f>& texCoords,
std::vector<std::vector<int32_t>>& indices);
DataFormat m_inputDataFormat;
size_t m_vertexCount{0};
size_t m_faceCount{0};
bool m_hasColour{false};
bool m_hasNormal{false};
bool m_hasTexCoord{false};
ElementDescription m_vertexDescription;
ElementDescription m_faceDescription;
};
class PlyEncoder CV_FINAL : public BasePointCloudEncoder
{
public:
void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
const std::vector<Point3f>& texCoords, int nTexCoords,
const std::vector<std::vector<int32_t>>& indices) CV_OVERRIDE;
};
} /* namespace cv */
#endif
+783
View File
@@ -0,0 +1,783 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
#include "precomp.hpp"
#include "opencv2/ptcloud/detail/kinfu_frame.hpp"
#include "utils.hpp"
#include "opencl_kernels_ptcloud.hpp"
namespace cv {
static void computePointsNormals(const cv::Intr, float depthFactor, const Depth, Points, Normals );
void computePointsNormalsColors(const Intr, const Intr, float, const Depth, const Colors, Points, Normals, Colors);
static Depth pyrDownBilateral(const Depth depth, float sigma);
static void pyrDownPointsNormals(const Points p, const Normals n, Points& pdown, Normals& ndown);
template<int p>
inline float specPow(float x)
{
if(p % 2 == 0)
{
float v = specPow<p/2>(x);
return v*v;
}
else
{
float v = specPow<(p-1)/2>(x);
return v*v*x;
}
}
template<>
inline float specPow<0>(float /*x*/)
{
return 1.f;
}
template<>
inline float specPow<1>(float x)
{
return x;
}
struct RenderInvoker : ParallelLoopBody
{
RenderInvoker(const Points& _points, const Normals& _normals, Mat_<Vec4b>& _img, Vec3f _lightPt, Size _sz) :
ParallelLoopBody(),
points(_points),
normals(_normals),
img(_img),
lightPt(_lightPt),
sz(_sz)
{ }
virtual void operator ()(const Range& range) const override
{
for(int y = range.start; y < range.end; y++)
{
Vec4b* imgRow = img[y];
const ptype* ptsRow = points[y];
const ptype* nrmRow = normals[y];
for(int x = 0; x < sz.width; x++)
{
Point3f p = fromPtype(ptsRow[x]);
Point3f n = fromPtype(nrmRow[x]);
Vec4b color;
if(isNaN(p))
{
color = Vec4b(0, 32, 0, 0);
}
else
{
const float Ka = 0.3f; //ambient coeff
const float Kd = 0.5f; //diffuse coeff
const float Ks = 0.2f; //specular coeff
const int sp = 20; //specular power
const float Ax = 1.f; //ambient color, can be RGB
const float Dx = 1.f; //diffuse color, can be RGB
const float Sx = 1.f; //specular color, can be RGB
const float Lx = 1.f; //light color
Point3f l = normalize(lightPt - Vec3f(p));
Point3f v = normalize(-Vec3f(p));
Point3f r = normalize(Vec3f(2.f*n*n.dot(l) - l));
uchar ix = (uchar)((Ax*Ka*Dx + Lx*Kd*Dx*max(0.f, n.dot(l)) +
Lx*Ks*Sx*specPow<sp>(max(0.f, r.dot(v))))*255.f);
color = Vec4b(ix, ix, ix, 0);
}
imgRow[x] = color;
}
}
}
const Points& points;
const Normals& normals;
Mat_<Vec4b>& img;
Vec3f lightPt;
Size sz;
};
struct RenderColorInvoker : ParallelLoopBody
{
RenderColorInvoker(const Points& _points, const Colors& _colors, Mat_<Vec4b>& _img, Size _sz) :
ParallelLoopBody(),
points(_points),
colors(_colors),
img(_img),
sz(_sz)
{ }
virtual void operator ()(const Range& range) const override
{
for(int y = range.start; y < range.end; y++)
{
Vec4b* imgRow = img[y];
const ptype* ptsRow = points[y];
const ptype* clrRow = colors[y];
for(int x = 0; x < sz.width; x++)
{
Point3f p = fromPtype(ptsRow[x]);
Point3f c = fromPtype(clrRow[x]);
Vec4b color;
if(isNaN(p) || isNaN(c))
{
color = Vec4b(0, 32, 0, 0);
}
else
{
color = Vec4b((uchar)c.x, (uchar)c.y, (uchar)c.z, (uchar)0);
}
imgRow[x] = color;
}
}
}
const Points& points;
const Colors& colors;
Mat_<Vec4b>& img;
Size sz;
};
void pyrDownPointsNormals(const Points p, const Normals n, Points &pdown, Normals &ndown)
{
CV_TRACE_FUNCTION();
for(int y = 0; y < pdown.rows; y++)
{
ptype* ptsRow = pdown[y];
ptype* nrmRow = ndown[y];
const ptype* pUpRow0 = p[2*y];
const ptype* pUpRow1 = p[2*y+1];
const ptype* nUpRow0 = n[2*y];
const ptype* nUpRow1 = n[2*y+1];
for(int x = 0; x < pdown.cols; x++)
{
Point3f point = nan3, normal = nan3;
Point3f d00 = fromPtype(pUpRow0[2*x]);
Point3f d01 = fromPtype(pUpRow0[2*x+1]);
Point3f d10 = fromPtype(pUpRow1[2*x]);
Point3f d11 = fromPtype(pUpRow1[2*x+1]);
if(!(isNaN(d00) || isNaN(d01) || isNaN(d10) || isNaN(d11)))
{
point = (d00 + d01 + d10 + d11)*0.25f;
Point3f n00 = fromPtype(nUpRow0[2*x]);
Point3f n01 = fromPtype(nUpRow0[2*x+1]);
Point3f n10 = fromPtype(nUpRow1[2*x]);
Point3f n11 = fromPtype(nUpRow1[2*x+1]);
normal = (n00 + n01 + n10 + n11)*0.25f;
}
ptsRow[x] = toPtype(point);
nrmRow[x] = toPtype(normal);
}
}
}
struct PyrDownBilateralInvoker : ParallelLoopBody
{
PyrDownBilateralInvoker(const Depth& _depth, Depth& _depthDown, float _sigma) :
ParallelLoopBody(),
depth(_depth),
depthDown(_depthDown),
sigma(_sigma)
{ }
virtual void operator ()(const Range& range) const override
{
float sigma3 = sigma*3;
const int D = 5;
for(int y = range.start; y < range.end; y++)
{
depthType* downRow = depthDown[y];
const depthType* srcCenterRow = depth[2*y];
for(int x = 0; x < depthDown.cols; x++)
{
depthType center = srcCenterRow[2*x];
int sx = max(0, 2*x - D/2), ex = min(2*x - D/2 + D, depth.cols-1);
int sy = max(0, 2*y - D/2), ey = min(2*y - D/2 + D, depth.rows-1);
depthType sum = 0;
int count = 0;
for(int iy = sy; iy < ey; iy++)
{
const depthType* srcRow = depth[iy];
for(int ix = sx; ix < ex; ix++)
{
depthType val = srcRow[ix];
if(abs(val - center) < sigma3)
{
sum += val; count ++;
}
}
}
downRow[x] = (count == 0) ? 0 : sum / count;
}
}
}
const Depth& depth;
Depth& depthDown;
float sigma;
};
Depth pyrDownBilateral(const Depth depth, float sigma)
{
CV_TRACE_FUNCTION();
Depth depthDown(depth.rows/2, depth.cols/2);
PyrDownBilateralInvoker pdi(depth, depthDown, sigma);
Range range(0, depthDown.rows);
const int nstripes = -1;
parallel_for_(range, pdi, nstripes);
return depthDown;
}
struct ComputePointsNormalsInvoker : ParallelLoopBody
{
ComputePointsNormalsInvoker(const Depth& _depth, Points& _points, Normals& _normals,
const Intr::Reprojector& _reproj, float _dfac) :
ParallelLoopBody(),
depth(_depth),
points(_points),
normals(_normals),
reproj(_reproj),
dfac(_dfac)
{ }
virtual void operator ()(const Range& range) const override
{
for(int y = range.start; y < range.end; y++)
{
const depthType* depthRow0 = depth[y];
const depthType* depthRow1 = (y < depth.rows - 1) ? depth[y + 1] : 0;
ptype *ptsRow = points[y];
ptype *normRow = normals[y];
for(int x = 0; x < depth.cols; x++)
{
depthType d00 = depthRow0[x];
depthType z00 = d00*dfac;
Point3f v00 = reproj(Point3f((float)x, (float)y, z00));
Point3f p = nan3, n = nan3;
if(x < depth.cols - 1 && y < depth.rows - 1)
{
depthType d01 = depthRow0[x+1];
depthType d10 = depthRow1[x];
depthType z01 = d01*dfac;
depthType z10 = d10*dfac;
// before it was
//if(z00*z01*z10 != 0)
if(z00 != 0 && z01 != 0 && z10 != 0)
{
Point3f v01 = reproj(Point3f((float)(x+1), (float)(y+0), z01));
Point3f v10 = reproj(Point3f((float)(x+0), (float)(y+1), z10));
cv::Vec3f vec = (v01-v00).cross(v10-v00);
n = -normalize(vec);
p = v00;
}
}
ptsRow[x] = toPtype(p);
normRow[x] = toPtype(n);
}
}
}
const Depth& depth;
Points& points;
Normals& normals;
const Intr::Reprojector& reproj;
float dfac;
};
void computePointsNormals(const Intr intr, float depthFactor, const Depth depth,
Points points, Normals normals)
{
CV_TRACE_FUNCTION();
CV_Assert(!points.empty() && !normals.empty());
CV_Assert(depth.size() == points.size());
CV_Assert(depth.size() == normals.size());
// conversion to meters
// before it was:
//float dfac = 0.001f/depthFactor;
float dfac = 1.f/depthFactor;
Intr::Reprojector reproj = intr.makeReprojector();
ComputePointsNormalsInvoker ci(depth, points, normals, reproj, dfac);
Range range(0, depth.rows);
const int nstripes = -1;
parallel_for_(range, ci, nstripes);
}
///////// GPU implementation /////////
#ifdef HAVE_OPENCL
static bool ocl_renderPointsNormals(const UMat points, const UMat normals, UMat image, Vec3f lightLoc);
static bool ocl_makeFrameFromDepth(const UMat depth, OutputArrayOfArrays points, OutputArrayOfArrays normals,
const Intr intr, int levels, float depthFactor,
float sigmaDepth, float sigmaSpatial, int kernelSize,
float truncateThreshold);
static bool ocl_buildPyramidPointsNormals(const UMat points, const UMat normals,
OutputArrayOfArrays pyrPoints, OutputArrayOfArrays pyrNormals,
int levels);
static bool computePointsNormalsGpu(const Intr intr, float depthFactor, const UMat& depth, UMat& points, UMat& normals);
static bool pyrDownBilateralGpu(const UMat& depth, UMat& depthDown, float sigma);
static bool customBilateralFilterGpu(const UMat src, UMat& dst, int kernelSize, float sigmaDepth, float sigmaSpatial);
static bool pyrDownPointsNormalsGpu(const UMat p, const UMat n, UMat &pdown, UMat &ndown);
bool computePointsNormalsGpu(const Intr intr, float depthFactor, const UMat& depth,
UMat& points, UMat& normals)
{
CV_TRACE_FUNCTION();
CV_Assert(!points.empty() && !normals.empty());
CV_Assert(depth.size() == points.size());
CV_Assert(depth.size() == normals.size());
CV_Assert(depth.type() == DEPTH_TYPE);
CV_Assert(points.type() == POINT_TYPE);
CV_Assert(normals.type() == POINT_TYPE);
// conversion to meters
float dfac = 1.f/depthFactor;
Intr::Reprojector reproj = intr.makeReprojector();
cv::String errorStr;
cv::String name = "computePointsNormals";
ocl::ProgramSource source = ocl::ptcloud::kinfu_frame_oclsrc;
cv::String options = "-cl-mad-enable";
ocl::Kernel k;
k.create(name.c_str(), source, options, &errorStr);
if(k.empty())
return false;
Vec2f fxyinv(reproj.fxinv, reproj.fyinv), cxy(reproj.cx, reproj.cy);
k.args(ocl::KernelArg::WriteOnlyNoSize(points),
ocl::KernelArg::WriteOnlyNoSize(normals),
ocl::KernelArg::ReadOnly(depth),
fxyinv.val,
cxy.val,
dfac);
size_t globalSize[2];
globalSize[0] = (size_t)depth.cols;
globalSize[1] = (size_t)depth.rows;
return k.run(2, globalSize, NULL, true);
}
bool pyrDownBilateralGpu(const UMat& depth, UMat& depthDown, float sigma)
{
CV_TRACE_FUNCTION();
depthDown.create(depth.rows/2, depth.cols/2, DEPTH_TYPE);
cv::String errorStr;
cv::String name = "pyrDownBilateral";
ocl::ProgramSource source = ocl::ptcloud::kinfu_frame_oclsrc;
cv::String options = "-cl-mad-enable";
ocl::Kernel k;
k.create(name.c_str(), source, options, &errorStr);
if(k.empty())
return false;
k.args(ocl::KernelArg::ReadOnly(depth),
ocl::KernelArg::WriteOnly(depthDown),
sigma);
size_t globalSize[2];
globalSize[0] = (size_t)depthDown.cols;
globalSize[1] = (size_t)depthDown.rows;
return k.run(2, globalSize, NULL, true);
}
//TODO: remove it when OpenCV's bilateral processes 32f on GPU
bool customBilateralFilterGpu(const UMat src /* udepth */, UMat& dst /* smooth */,
int kernelSize, float sigmaDepth, float sigmaSpatial)
{
CV_TRACE_FUNCTION();
Size frameSize = src.size();
CV_Assert(frameSize.area() > 0);
CV_Assert(src.type() == DEPTH_TYPE);
dst.create(frameSize, DEPTH_TYPE);
cv::String errorStr;
cv::String name = "customBilateral";
ocl::ProgramSource source = ocl::ptcloud::kinfu_frame_oclsrc;
cv::String options = "-cl-mad-enable";
ocl::Kernel k;
k.create(name.c_str(), source, options, &errorStr);
if(k.empty())
return false;
k.args(ocl::KernelArg::ReadOnlyNoSize(src),
ocl::KernelArg::WriteOnlyNoSize(dst),
frameSize,
kernelSize,
0.5f / (sigmaSpatial * sigmaSpatial),
0.5f / (sigmaDepth * sigmaDepth));
size_t globalSize[2];
globalSize[0] = (size_t)src.cols;
globalSize[1] = (size_t)src.rows;
return k.run(2, globalSize, NULL, true);
}
bool pyrDownPointsNormalsGpu(const UMat p, const UMat n, UMat &pdown, UMat &ndown)
{
CV_TRACE_FUNCTION();
cv::String errorStr;
cv::String name = "pyrDownPointsNormals";
ocl::ProgramSource source = ocl::ptcloud::kinfu_frame_oclsrc;
cv::String options = "-cl-mad-enable";
ocl::Kernel k;
k.create(name.c_str(), source, options, &errorStr);
if(k.empty())
return false;
Size downSize = pdown.size();
k.args(ocl::KernelArg::ReadOnlyNoSize(p),
ocl::KernelArg::ReadOnlyNoSize(n),
ocl::KernelArg::WriteOnlyNoSize(pdown),
ocl::KernelArg::WriteOnlyNoSize(ndown),
downSize);
size_t globalSize[2];
globalSize[0] = (size_t)pdown.cols;
globalSize[1] = (size_t)pdown.rows;
return k.run(2, globalSize, NULL, true);
}
static bool ocl_renderPointsNormals(const UMat points, const UMat normals,
UMat img, Vec3f lightLoc)
{
CV_TRACE_FUNCTION();
cv::String errorStr;
cv::String name = "render";
ocl::ProgramSource source = ocl::ptcloud::kinfu_frame_oclsrc;
cv::String options = "-cl-mad-enable";
ocl::Kernel k;
k.create(name.c_str(), source, options, &errorStr);
if(k.empty())
return false;
Vec4f lightPt(lightLoc[0], lightLoc[1], lightLoc[2]);
Size frameSize = points.size();
k.args(ocl::KernelArg::ReadOnlyNoSize(points),
ocl::KernelArg::ReadOnlyNoSize(normals),
ocl::KernelArg::WriteOnlyNoSize(img),
frameSize,
lightPt.val);
size_t globalSize[2];
globalSize[0] = (size_t)points.cols;
globalSize[1] = (size_t)points.rows;
return k.run(2, globalSize, NULL, true);
}
static bool ocl_makeFrameFromDepth(const UMat depth, OutputArrayOfArrays points, OutputArrayOfArrays normals,
const Intr intr, int levels, float depthFactor,
float sigmaDepth, float sigmaSpatial, int kernelSize,
float truncateThreshold)
{
CV_TRACE_FUNCTION();
// looks like OpenCV's bilateral filter works the same as KinFu's
UMat smooth;
//TODO: fix that
// until 32f isn't implemented in OpenCV in OpenCL, we should use our workarounds
//bilateralFilter(udepth, smooth, kernelSize, sigmaDepth*depthFactor, sigmaSpatial);
if(!customBilateralFilterGpu(depth, smooth, kernelSize, sigmaDepth*depthFactor, sigmaSpatial))
return false;
// depth truncation can be used in some scenes
UMat depthThreshold;
if(truncateThreshold > 0.f)
threshold(smooth, depthThreshold, truncateThreshold * depthFactor, 0.0, THRESH_TOZERO_INV);
else
depthThreshold = smooth;
UMat scaled = depthThreshold;
Size sz = smooth.size();
points.create(levels, 1, POINT_TYPE);
normals.create(levels, 1, POINT_TYPE);
for(int i = 0; i < levels; i++)
{
UMat& p = points.getUMatRef(i);
UMat& n = normals.getUMatRef(i);
p.create(sz, POINT_TYPE);
n.create(sz, POINT_TYPE);
if(!computePointsNormalsGpu(intr.scale(i), depthFactor, scaled, p, n))
return false;
if(i < levels - 1)
{
sz.width /= 2, sz.height /= 2;
UMat halfDepth(sz, DEPTH_TYPE);
pyrDownBilateralGpu(scaled, halfDepth, sigmaDepth*depthFactor);
scaled = halfDepth;
}
}
return true;
}
static bool ocl_buildPyramidPointsNormals(const UMat points, const UMat normals,
OutputArrayOfArrays pyrPoints, OutputArrayOfArrays pyrNormals,
int levels)
{
CV_TRACE_FUNCTION();
pyrPoints .create(levels, 1, POINT_TYPE);
pyrNormals.create(levels, 1, POINT_TYPE);
pyrPoints .getUMatRef(0) = points;
pyrNormals.getUMatRef(0) = normals;
Size sz = points.size();
for(int i = 1; i < levels; i++)
{
UMat p1 = pyrPoints .getUMat(i-1);
UMat n1 = pyrNormals.getUMat(i-1);
sz.width /= 2; sz.height /= 2;
UMat& p0 = pyrPoints .getUMatRef(i);
UMat& n0 = pyrNormals.getUMatRef(i);
p0.create(sz, POINT_TYPE);
n0.create(sz, POINT_TYPE);
if(!pyrDownPointsNormalsGpu(p1, n1, p0, n0))
return false;
}
return true;
}
#endif
namespace detail {
void renderPointsNormals(InputArray _points, InputArray _normals, OutputArray image, cv::Vec3f lightLoc)
{
CV_TRACE_FUNCTION();
CV_Assert(_points.size().area() > 0);
CV_Assert(_points.size() == _normals.size());
Size sz = _points.size();
image.create(sz, CV_8UC4);
CV_OCL_RUN(_points.isUMat() && _normals.isUMat() && image.isUMat(),
ocl_renderPointsNormals(_points.getUMat(),
_normals.getUMat(),
image.getUMat(), lightLoc))
Points points = _points.getMat();
Normals normals = _normals.getMat();
Mat_<Vec4b> img = image.getMat();
RenderInvoker ri(points, normals, img, lightLoc, sz);
Range range(0, sz.height);
const int nstripes = -1;
parallel_for_(range, ri, nstripes);
}
void renderPointsNormalsColors(InputArray _points, InputArray _normals, InputArray _colors, OutputArray image)
{
CV_TRACE_FUNCTION();
CV_Assert(_points.size().area() > 0);
CV_Assert(_points.size() == _normals.size());
Size sz = _points.size();
image.create(sz, CV_8UC4);
Points points = _points.getMat();
Normals normals = _normals.getMat();
Colors colors = _colors.getMat();
Mat_<Vec4b> img = image.getMat();
RenderColorInvoker ri(points, colors, img, sz);
Range range(0, sz.height);
const int nstripes = -1;
parallel_for_(range, ri, nstripes);
}
} // namespace detail
void makeFrameFromDepth(InputArray _depth,
OutputArray pyrPoints, OutputArray pyrNormals,
const Matx33f _intr, int levels, float depthFactor,
float sigmaDepth, float sigmaSpatial, int kernelSize,
float truncateThreshold)
{
CV_TRACE_FUNCTION();
CV_Assert(_depth.type() == DEPTH_TYPE);
Intr intr(_intr);
CV_OCL_RUN(_depth.isUMat() && pyrPoints.isUMatVector() && pyrNormals.isUMatVector(),
ocl_makeFrameFromDepth(_depth.getUMat(), pyrPoints, pyrNormals,
intr, levels, depthFactor,
sigmaDepth, sigmaSpatial, kernelSize,
truncateThreshold));
int kp = pyrPoints.kind(), kn = pyrNormals.kind();
// There can be UMats in the container (when OpenCL is off)
CV_Assert(kp == _InputArray::STD_ARRAY_MAT || kp == _InputArray::STD_VECTOR_MAT || kp == _InputArray::STD_VECTOR_UMAT);
CV_Assert(kn == _InputArray::STD_ARRAY_MAT || kn == _InputArray::STD_VECTOR_MAT || kp == _InputArray::STD_VECTOR_UMAT);
Depth depth = _depth.getMat();
// looks like OpenCV's bilateral filter works the same as KinFu's
Depth smooth;
Depth depthNoNans = depth.clone();
patchNaNs(depthNoNans);
bilateralFilter(depthNoNans, smooth, kernelSize, sigmaDepth * depthFactor, sigmaSpatial);
// depth truncation can be used in some scenes
Depth depthThreshold;
if(truncateThreshold > 0.f)
threshold(smooth, depthThreshold, truncateThreshold * depthFactor, 0.0, THRESH_TOZERO_INV);
else
depthThreshold = smooth;
// we don't need depth pyramid outside this method
// if we do, the code is to be refactored
Depth scaled = depthThreshold;
Size sz = smooth.size();
pyrPoints.create(levels, 1, POINT_TYPE);
pyrNormals.create(levels, 1, POINT_TYPE);
for(int i = 0; i < levels; i++)
{
pyrPoints .create(sz, POINT_TYPE, i);
pyrNormals.create(sz, POINT_TYPE, i);
// There can be UMats in the container (when OpenCL is off),
// getMatRef() is not applicable
Points p = pyrPoints. getMat(i);
Normals n = pyrNormals.getMat(i);
computePointsNormals(intr.scale(i), depthFactor, scaled, p, n);
if(i < levels - 1)
{
sz.width /= 2; sz.height /= 2;
scaled = pyrDownBilateral(scaled, sigmaDepth*depthFactor);
}
}
}
void buildPyramidPointsNormals(InputArray _points, InputArray _normals,
OutputArrayOfArrays pyrPoints, OutputArrayOfArrays pyrNormals,
int levels)
{
CV_TRACE_FUNCTION();
CV_Assert(_points.type() == POINT_TYPE);
CV_Assert(_points.type() == _normals.type());
CV_Assert(_points.size() == _normals.size());
CV_OCL_RUN(_points.isUMat() && _normals.isUMat() &&
pyrPoints.isUMatVector() && pyrNormals.isUMatVector(),
ocl_buildPyramidPointsNormals(_points.getUMat(), _normals.getUMat(),
pyrPoints, pyrNormals,
levels));
int kp = pyrPoints.kind(), kn = pyrNormals.kind();
CV_Assert(kp == _InputArray::STD_ARRAY_MAT || kp == _InputArray::STD_VECTOR_MAT);
CV_Assert(kn == _InputArray::STD_ARRAY_MAT || kn == _InputArray::STD_VECTOR_MAT);
Mat p0 = _points.getMat(), n0 = _normals.getMat();
pyrPoints .create(levels, 1, POINT_TYPE);
pyrNormals.create(levels, 1, POINT_TYPE);
pyrPoints .getMatRef(0) = p0;
pyrNormals.getMatRef(0) = n0;
Size sz = _points.size();
for(int i = 1; i < levels; i++)
{
Points p1 = pyrPoints .getMat(i-1);
Normals n1 = pyrNormals.getMat(i-1);
sz.width /= 2; sz.height /= 2;
pyrPoints .create(sz, POINT_TYPE, i);
pyrNormals.create(sz, POINT_TYPE, i);
Points pd = pyrPoints. getMatRef(i);
Normals nd = pyrNormals.getMatRef(i);
pyrDownPointsNormals(p1, n1, pd, nd);
}
}
} // namespace cv
+341
View File
@@ -0,0 +1,341 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#include "io_base.hpp"
#include "io_obj.hpp"
#include "io_ply.hpp"
#include "utils.hpp"
#include "opencv2/core/utils/filesystem.private.hpp"
#include <opencv2/core/utils/logger.hpp>
#include <memory>
namespace cv {
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
static PointCloudDecoder findDecoder(const String &filename)
{
auto file_ext = getExtension(filename);
if (file_ext == "obj" || file_ext == "OBJ")
{
return std::unique_ptr<ObjDecoder>(new ObjDecoder());
}
if (file_ext == "ply" || file_ext == "PLY")
{
return std::unique_ptr<PlyDecoder>(new PlyDecoder());
}
return nullptr;
}
static PointCloudEncoder findEncoder(const String &filename)
{
auto file_ext = getExtension(filename);
if (file_ext == "obj" || file_ext == "OBJ")
{
return std::unique_ptr<ObjEncoder>(new ObjEncoder());
}
if (file_ext == "ply" || file_ext == "PLY")
{
return std::unique_ptr<PlyEncoder>(new PlyEncoder());
}
return nullptr;
}
#endif
void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals, OutputArray rgb)
{
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
auto decoder = findDecoder(filename);
if (!decoder) {
String file_ext = getExtension(filename);
CV_LOG_ERROR(NULL, "File extension '" << file_ext << "' is not supported");
return;
}
decoder->setSource(filename);
std::vector<Point3f> vec_vertices, vec_normals, vec_rgb;
decoder->readData(vec_vertices, vec_normals, vec_rgb);
if (!vec_vertices.empty())
Mat(static_cast<int>(vec_vertices.size()), 1, CV_32FC3, vec_vertices.data()).copyTo(vertices);
if (!vec_normals.empty() && normals.needed())
Mat(static_cast<int>(vec_normals.size()), 1, CV_32FC3, vec_normals.data()).copyTo(normals);
if (!vec_rgb.empty() && rgb.needed())
Mat(static_cast<int>(vec_rgb.size()), 1, CV_32FC3, vec_rgb.data()).copyTo(rgb);
#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
CV_UNUSED(vertices);
CV_UNUSED(normals);
CV_UNUSED(rgb);
CV_LOG_WARNING(NULL, "File system support is disabled in this OpenCV build!");
#endif
}
void savePointCloud(const String &filename, InputArray vertices, InputArray normals, InputArray rgb)
{
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
if (vertices.empty()) {
CV_LOG_WARNING(NULL, "Have no vertices to save");
return;
};
auto encoder = findEncoder(filename);
if (!encoder) {
String file_ext = getExtension(filename);
CV_LOG_ERROR(NULL, "File extension '" << file_ext << "' is not supported");
return;
}
encoder->setDestination(filename);
std::vector<Point3f> vec_vertices(vertices.getMat()), vec_normals, vec_rgb;
if (!normals.empty())
{
vec_normals = normals.getMat();
}
if (!rgb.empty())
{
vec_rgb = rgb.getMat();
}
encoder->writeData(vec_vertices, vec_normals, vec_rgb);
#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
CV_UNUSED(vertices);
CV_UNUSED(normals);
CV_UNUSED(rgb);
CV_LOG_WARNING(NULL, "File system support is disabled in this OpenCV build!");
#endif
}
void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays indices,
OutputArray normals, OutputArray colors, OutputArray texCoords)
{
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_Assert(vertices.needed());
CV_Assert(indices.needed());
PointCloudDecoder decoder = findDecoder(filename);
String file_ext = getExtension(filename);
if (!decoder) {
CV_LOG_ERROR(NULL, "File extension '" << file_ext << "' is not supported");
return;
}
decoder->setSource(filename);
std::vector<Point3f> vec_vertices, vec_normals, vec_rgb;
std::vector<std::vector<int32_t>> vec_indices;
std::vector<Point3f> vec_texCoords;
int nTexCoords = 0;
decoder->readData(vec_vertices, vec_normals, vec_rgb, vec_texCoords, nTexCoords, vec_indices, 0);
if (!vec_vertices.empty())
{
Mat(1, static_cast<int>(vec_vertices.size()), CV_32FC3, vec_vertices.data()).copyTo(vertices);
}
if (normals.needed() && !vec_normals.empty())
{
Mat(1, static_cast<int>(vec_normals.size()), CV_32FC3, vec_normals.data()).copyTo(normals);
}
if (colors.needed() && !vec_rgb.empty())
{
Mat(1, static_cast<int>(vec_rgb.size()), CV_32FC3, vec_rgb.data()).copyTo(colors);
}
if (!vec_indices.empty())
{
_InputArray::KindFlag kind = indices.kind();
int vecsz = (int)vec_indices.size();
if (kind == _InputArray::KindFlag::STD_VECTOR_VECTOR)
{
CV_Assert(indices.depth() == CV_32S);
std::vector<std::vector<int32_t>>& vec = *(std::vector<std::vector<int32_t>>*)indices.getObj();
vec.resize(vecsz);
for (int i = 0; i < vecsz; ++i)
{
Mat(1, static_cast<int>(vec_indices[i].size()), CV_32SC1, vec_indices[i].data()).copyTo(vec[i]);
}
}
// std::array<Mat> has fixed size, unsupported
else if (kind == _InputArray::KindFlag::STD_VECTOR_MAT)
{
indices.create(vecsz, 1, CV_32S);
for (int i = 0; i < vecsz; i++)
{
std::vector<int> vi = vec_indices[i];
indices.create(1, (int)vi.size(), CV_32S, i);
Mat(vi).copyTo(indices.getMat(i));
}
}
else
{
std::vector<Vec3i> vec(vec_indices.size());
for (int i = 0; i < vecsz; ++i)
{
Vec3i tri;
size_t sz = vec_indices[i].size();
if (sz != 3)
{
CV_Error(Error::StsBadArg, "Face contains " + std::to_string(sz) + " vertices, can not put it into 3-channel indices array");
}
else
{
for (int j = 0; j < 3; j++)
{
tri[j] = vec_indices[i][j];
}
}
vec[i] = tri;
}
indices.create(1, (int)vec_indices.size(), CV_32SC3);
Mat(1, static_cast<int>(vec_indices.size()), CV_32SC3, vec.data()).copyTo(indices);
}
}
if (texCoords.needed())
{
if (nTexCoords)
{
CV_Assert(!texCoords.fixedType() || (texCoords.type() == CV_MAKE_TYPE(CV_32F, nTexCoords)));
Mat tex3(vec_texCoords);
if (nTexCoords == 3)
{
tex3.copyTo(texCoords);
}
else if (nTexCoords == 2)
{
// if texCoords is empty then channels() can be any number
bool has3ch = texCoords.channels() == 3;
int ch = has3ch ? 3 : 2;
std::vector<int> permut = has3ch ? std::vector<int>{ 0, 0, 1, 1, -1, 2 } : std::vector<int>{ 0, 0, 1, 1 };
texCoords.createSameSize(vec_texCoords, CV_MAKE_TYPE(CV_32F, ch));
Mat out = texCoords.getMat();
cv::mixChannels(tex3, out, permut);
}
}
else
{
texCoords.clear();
}
}
#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
CV_UNUSED(vertices);
CV_UNUSED(normals);
CV_UNUSED(colors);
CV_UNUSED(indices);
CV_UNUSED(texCoords);
CV_LOG_WARNING(NULL, "File system support is disabled in this OpenCV build!");
#endif
}
void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays indices,
InputArray normals, InputArray colors, InputArray texCoords)
{
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
if (vertices.empty()) {
CV_LOG_WARNING(NULL, "Have no vertices to save");
return;
}
auto encoder = findEncoder(filename);
String file_ext = getExtension(filename);
if (!encoder) {
CV_LOG_ERROR(NULL, "File extension '" << file_ext << "' is not supported");
return;
}
encoder->setDestination(filename);
std::vector<Point3f> vec_vertices(vertices.getMat()), vec_normals, vec_rgb;
if (!normals.empty())
{
vec_normals = normals.getMat();
}
if (!colors.empty())
{
vec_rgb = colors.getMat();
}
std::vector<std::vector<int32_t>> vec_indices;
CV_Assert(indices.depth() == CV_32S);
if (indices.kind() == _InputArray::KindFlag::STD_VECTOR_VECTOR ||
indices.kind() == _InputArray::KindFlag::STD_VECTOR_MAT)
{
std::vector<Mat> mat_indices;
indices.getMatVector(mat_indices);
vec_indices.resize(mat_indices.size());
for (size_t i = 0; i < mat_indices.size(); ++i)
{
mat_indices[i].copyTo(vec_indices[i]);
}
}
else
{
CV_Assert(indices.channels() == 3);
std::vector<Vec3i>& vec = *(std::vector<Vec3i>*)indices.getObj();
vec_indices.resize(vec.size());
for (size_t i = 0; i < vec.size(); ++i)
{
for (int j = 0; j < 3; j++)
{
vec_indices[i].push_back(vec[i][j]);
}
}
}
std::vector<Point3f> vec_texCoords;
int nTexCoords = 0;
if (!texCoords.empty())
{
nTexCoords = texCoords.channels();
}
if (nTexCoords == 2)
{
// extend by 3rd zero channel
vec_texCoords.resize(texCoords.total());
cv::mixChannels(texCoords, vec_texCoords, {0, 0, 1, 1, -1, 2});
}
if (nTexCoords == 3)
{
texCoords.copyTo(vec_texCoords);
}
encoder->writeData(vec_vertices, vec_normals, vec_rgb, vec_texCoords, nTexCoords, vec_indices);
#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
CV_UNUSED(vertices);
CV_UNUSED(colors);
CV_UNUSED(normals);
CV_UNUSED(indices);
CV_UNUSED(texCoords);
CV_LOG_WARNING(NULL, "File system support is disabled in this OpenCV build!");
#endif
}
}/* namespace cv */
+921
View File
@@ -0,0 +1,921 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
namespace cv
{
/** Just compute the norm of a vector
* @param vec a vector of size 3 and any type T
* @return
*/
template<typename T>
T inline norm_vec(const Vec<T, 3>& vec)
{
return std::sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
}
template<typename T>
T inline norm_vec(const Vec<T, 4>& vec)
{
return std::sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
}
/** Given 3d points, compute their distance to the origin
* @param points
* @return
*/
template<typename T>
Mat_<T> computeRadius(const Mat& points)
{
typedef Vec<T, 4> PointT;
// Compute the
Size size(points.cols, points.rows);
Mat_<T> r(size);
if (points.isContinuous())
size = Size(points.cols * points.rows, 1);
for (int y = 0; y < size.height; ++y)
{
const PointT* point = points.ptr < PointT >(y), * point_end = points.ptr < PointT >(y) + size.width;
T* row = r[y];
for (; point != point_end; ++point, ++row)
*row = norm_vec(*point);
}
return r;
}
// Compute theta and phi according to equation 3 of
// ``Fast and Accurate Computation of Surface Normals from Range Images``
// by H. Badino, D. Huber, Y. Park and T. Kanade
template<typename T>
void computeThetaPhi(int rows, int cols, const Matx<T, 3, 3>& K, Mat& cos_theta, Mat& sin_theta,
Mat& cos_phi, Mat& sin_phi)
{
// Create some bogus coordinates
Mat depth_image = K(0, 0) * Mat_<T> ::ones(rows, cols);
Mat points3d;
depthTo3d(depth_image, Mat(K), points3d);
//typedef Vec<T, 3> Vec3T;
typedef Vec<T, 4> Vec4T;
cos_theta = Mat_<T>(rows, cols);
sin_theta = Mat_<T>(rows, cols);
cos_phi = Mat_<T>(rows, cols);
sin_phi = Mat_<T>(rows, cols);
Mat r = computeRadius<T>(points3d);
for (int y = 0; y < rows; ++y)
{
T* row_cos_theta = cos_theta.ptr <T>(y), * row_sin_theta = sin_theta.ptr <T>(y);
T* row_cos_phi = cos_phi.ptr <T>(y), * row_sin_phi = sin_phi.ptr <T>(y);
const Vec4T* row_points = points3d.ptr <Vec4T>(y),
* row_points_end = points3d.ptr <Vec4T>(y) + points3d.cols;
const T* row_r = r.ptr < T >(y);
for (; row_points < row_points_end;
++row_cos_theta, ++row_sin_theta, ++row_cos_phi, ++row_sin_phi, ++row_points, ++row_r)
{
// In the paper, z goes away from the camera, y goes down, x goes right
// OpenCV has the same conventions
// Theta goes from z to x (and actually goes from -pi/2 to pi/2, phi goes from z to y
float theta = (float)std::atan2(row_points->val[0], row_points->val[2]);
*row_cos_theta = std::cos(theta);
*row_sin_theta = std::sin(theta);
float phi = (float)std::asin(row_points->val[1] / (*row_r));
*row_cos_phi = std::cos(phi);
*row_sin_phi = std::sin(phi);
}
}
}
/** Modify normals to make sure they point towards the camera
* @param normals
*/
template<typename T>
inline void signNormal(const Vec<T, 3>& normal_in, Vec<T, 3>& normal_out)
{
Vec<T, 3> res;
if (normal_in[2] > 0)
res = -normal_in / norm_vec(normal_in);
else
res = normal_in / norm_vec(normal_in);
normal_out[0] = res[0];
normal_out[1] = res[1];
normal_out[2] = res[2];
}
template<typename T>
inline void signNormal(const Vec<T, 3>& normal_in, Vec<T, 4>& normal_out)
{
Vec<T, 3> res;
if (normal_in[2] > 0)
res = -normal_in / norm_vec(normal_in);
else
res = normal_in / norm_vec(normal_in);
normal_out[0] = res[0];
normal_out[1] = res[1];
normal_out[2] = res[2];
normal_out[3] = 0;
}
/** Modify normals to make sure they point towards the camera
* @param normals
*/
template<typename T>
inline void signNormal(T a, T b, T c, Vec<T, 3>& normal)
{
T norm = 1 / std::sqrt(a * a + b * b + c * c);
if (c > 0)
{
normal[0] = -a * norm;
normal[1] = -b * norm;
normal[2] = -c * norm;
}
else
{
normal[0] = a * norm;
normal[1] = b * norm;
normal[2] = c * norm;
}
}
template<typename T>
inline void signNormal(T a, T b, T c, Vec<T, 4>& normal)
{
T norm = 1 / std::sqrt(a * a + b * b + c * c);
if (c > 0)
{
normal[0] = -a * norm;
normal[1] = -b * norm;
normal[2] = -c * norm;
normal[3] = 0;
}
else
{
normal[0] = a * norm;
normal[1] = b * norm;
normal[2] = c * norm;
normal[3] = 0;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename T>
class RgbdNormalsImpl : public RgbdNormals
{
public:
static const int dtype = cv::traits::Depth<T>::value;
RgbdNormalsImpl(int _rows, int _cols, int _windowSize, const Mat& _K, RgbdNormals::RgbdNormalsMethod _method) :
rows(_rows),
cols(_cols),
windowSize(_windowSize),
method(_method),
cacheIsDirty(true)
{
CV_Assert(_K.cols == 3 && _K.rows == 3);
_K.convertTo(K, dtype);
_K.copyTo(K_ori);
}
virtual ~RgbdNormalsImpl() CV_OVERRIDE
{ }
virtual int getDepth() const CV_OVERRIDE
{
return dtype;
}
virtual int getRows() const CV_OVERRIDE
{
return rows;
}
virtual void setRows(int val) CV_OVERRIDE
{
rows = val; cacheIsDirty = true;
}
virtual int getCols() const CV_OVERRIDE
{
return cols;
}
virtual void setCols(int val) CV_OVERRIDE
{
cols = val; cacheIsDirty = true;
}
virtual int getWindowSize() const CV_OVERRIDE
{
return windowSize;
}
virtual void setWindowSize(int val) CV_OVERRIDE
{
windowSize = val; cacheIsDirty = true;
}
virtual void getK(OutputArray val) const CV_OVERRIDE
{
K.copyTo(val);
}
virtual void setK(InputArray val) CV_OVERRIDE
{
K = val.getMat(); cacheIsDirty = true;
}
virtual RgbdNormalsMethod getMethod() const CV_OVERRIDE
{
return method;
}
virtual void compute(const Mat& in, Mat& normals) const = 0;
/** Given a set of 3d points in a depth image, compute the normals at each point
* @param points3d_in depth a float depth image. Or it can be rows x cols x 3 is they are 3d points
* @param normals a rows x cols x 3 matrix
*/
virtual void apply(InputArray points3d_in, OutputArray normals_out) const CV_OVERRIDE
{
Mat points3d_ori = points3d_in.getMat();
CV_Assert(points3d_ori.dims == 2);
// Either we have 3d points or a depth image
bool ptsAre4F = (points3d_ori.channels() == 4) && (points3d_ori.depth() == CV_32F || points3d_ori.depth() == CV_64F);
bool ptsAreDepth = (points3d_ori.channels() == 1) && (points3d_ori.depth() == CV_16U || points3d_ori.depth() == CV_32F || points3d_ori.depth() == CV_64F);
if (method == RGBD_NORMALS_METHOD_FALS || method == RGBD_NORMALS_METHOD_SRI || method == RGBD_NORMALS_METHOD_CROSS_PRODUCT)
{
if (!ptsAre4F)
{
CV_Error(Error::StsBadArg, "Input image should have 4 float-point channels");
}
}
else if (method == RGBD_NORMALS_METHOD_LINEMOD)
{
if (!ptsAre4F && !ptsAreDepth)
{
CV_Error(Error::StsBadArg, "Input image should have 4 float-point channels or have 1 ushort or float-point channel");
}
}
else
{
CV_Error(Error::StsInternal, "Unknown normal computer algorithm");
}
// Initialize the pimpl
cache();
// Precompute something for RGBD_NORMALS_METHOD_SRI and RGBD_NORMALS_METHOD_FALS
Mat points3d;
if (method != RGBD_NORMALS_METHOD_LINEMOD)
{
// Make the points have the right depth
if (points3d_ori.depth() == dtype)
points3d = points3d_ori;
else
points3d_ori.convertTo(points3d, dtype);
}
// Get the normals
normals_out.create(points3d_ori.size(), CV_MAKETYPE(dtype, 4));
if (points3d_ori.empty())
return;
Mat normals = normals_out.getMat();
if ((method == RGBD_NORMALS_METHOD_FALS) || (method == RGBD_NORMALS_METHOD_SRI))
{
// Compute the distance to the points
Mat radius = computeRadius<T>(points3d);
compute(radius, normals);
}
else if (method == RGBD_NORMALS_METHOD_LINEMOD)
{
compute(points3d_ori, normals);
}
else if (method == RGBD_NORMALS_METHOD_CROSS_PRODUCT)
{
compute(points3d, normals);
}
else
{
CV_Error(Error::StsInternal, "Unknown normal computer algorithm");
}
}
int rows, cols;
Mat K, K_ori;
int windowSize;
RgbdNormalsMethod method;
mutable bool cacheIsDirty;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** Given a set of 3d points in a depth image, compute the normals at each point
* using the FALS method described in
* ``Fast and Accurate Computation of Surface Normals from Range Images``
* by H. Badino, D. Huber, Y. Park and T. Kanade
*/
template<typename T>
class FALS : public RgbdNormalsImpl<T>
{
public:
typedef Matx<T, 3, 3> Mat33T;
typedef Vec<T, 9> Vec9T;
typedef Vec<T, 4> Vec4T;
typedef Vec<T, 3> Vec3T;
FALS(int _rows, int _cols, int _windowSize, const Mat& _K) :
RgbdNormalsImpl<T>(_rows, _cols, _windowSize, _K, RgbdNormals::RGBD_NORMALS_METHOD_FALS)
{ }
virtual ~FALS() CV_OVERRIDE
{ }
/** Compute cached data
*/
virtual void cache() const CV_OVERRIDE
{
if (!this->cacheIsDirty)
return;
// Compute theta and phi according to equation 3
Mat cos_theta, sin_theta, cos_phi, sin_phi;
computeThetaPhi<T>(this->rows, this->cols, this->K, cos_theta, sin_theta, cos_phi, sin_phi);
// Compute all the v_i for every points
std::vector<Mat> channels(3);
channels[0] = sin_theta.mul(cos_phi);
channels[1] = sin_phi;
channels[2] = cos_theta.mul(cos_phi);
merge(channels, V_);
// Compute M
Mat_<Vec9T> M(this->rows, this->cols);
Mat33T VVt;
const Vec3T* vec = V_[0];
Vec9T* M_ptr = M[0], * M_ptr_end = M_ptr + this->rows * this->cols;
for (; M_ptr != M_ptr_end; ++vec, ++M_ptr)
{
VVt = (*vec) * vec->t();
*M_ptr = Vec9T(VVt.val);
}
boxFilter(M, M, M.depth(), Size(this->windowSize, this->windowSize), Point(-1, -1), false);
// Compute M's inverse
Mat33T M_inv;
M_inv_.create(this->rows, this->cols);
Vec9T* M_inv_ptr = M_inv_[0];
for (M_ptr = &M(0); M_ptr != M_ptr_end; ++M_inv_ptr, ++M_ptr)
{
// We have a semi-definite matrix
invert(Mat33T(M_ptr->val), M_inv, DECOMP_CHOLESKY);
*M_inv_ptr = Vec9T(M_inv.val);
}
this->cacheIsDirty = false;
}
/** Compute the normals
* @param r
* @return
*/
virtual void compute(const Mat& r, Mat& normals) const CV_OVERRIDE
{
// Compute B
Mat_<Vec3T> B(this->rows, this->cols);
const T* row_r = r.ptr < T >(0), * row_r_end = row_r + this->rows * this->cols;
const Vec3T* row_V = V_[0];
Vec3T* row_B = B[0];
for (; row_r != row_r_end; ++row_r, ++row_B, ++row_V)
{
Vec3T val = (*row_V) / (*row_r);
if (cvIsInf(val[0]) || cvIsNaN(val[0]) ||
cvIsInf(val[1]) || cvIsNaN(val[1]) ||
cvIsInf(val[2]) || cvIsNaN(val[2]))
*row_B = Vec3T();
else
*row_B = val;
}
// Apply a box filter to B
boxFilter(B, B, B.depth(), Size(this->windowSize, this->windowSize), Point(-1, -1), false);
// compute the Minv*B products
row_r = r.ptr < T >(0);
const Vec3T* B_vec = B[0];
const Mat33T* M_inv = reinterpret_cast<const Mat33T*>(M_inv_.ptr(0));
//Vec3T* normal = normals.ptr<Vec3T>(0);
Vec4T* normal = normals.ptr<Vec4T>(0);
for (; row_r != row_r_end; ++row_r, ++B_vec, ++normal, ++M_inv)
if (cvIsNaN(*row_r))
{
(*normal)[0] = *row_r;
(*normal)[1] = *row_r;
(*normal)[2] = *row_r;
(*normal)[3] = 0;
}
else
{
Mat33T Mr = *M_inv;
Vec3T Br = *B_vec;
Vec3T MBr(Mr(0, 0) * Br[0] + Mr(0, 1) * Br[1] + Mr(0, 2) * Br[2],
Mr(1, 0) * Br[0] + Mr(1, 1) * Br[1] + Mr(1, 2) * Br[2],
Mr(2, 0) * Br[0] + Mr(2, 1) * Br[1] + Mr(2, 2) * Br[2]);
signNormal(MBr, *normal);
}
}
// Cached data
mutable Mat_<Vec3T> V_;
mutable Mat_<Vec9T> M_inv_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** Function that multiplies K_inv by a vector. It is just meant to speed up the product as we know
* that K_inv is upper triangular and K_inv(2,2)=1
* @param K_inv
* @param a
* @param b
* @param c
* @param res
*/
template<typename T, typename U>
void multiply_by_K_inv(const Matx<T, 3, 3>& K_inv, U a, U b, U c, Vec<T, 3>& res)
{
res[0] = (T)(K_inv(0, 0) * a + K_inv(0, 1) * b + K_inv(0, 2) * c);
res[1] = (T)(K_inv(1, 1) * b + K_inv(1, 2) * c);
res[2] = (T)c;
}
/** Given a depth image, compute the normals as detailed in the LINEMOD paper
* ``Gradient Response Maps for Real-Time Detection of Texture-Less Objects``
* by S. Hinterstoisser, C. Cagniart, S. Ilic, P. Sturm, N. Navab, P. Fua, and V. Lepetit
*/
template<typename T>
class LINEMOD : public RgbdNormalsImpl<T>
{
public:
typedef Vec<T, 4> Vec4T;
typedef Vec<T, 3> Vec3T;
typedef Matx<T, 3, 3> Mat33T;
LINEMOD(int _rows, int _cols, int _windowSize, const Mat& _K, double _diffThr = 50.0) :
RgbdNormalsImpl<T>(_rows, _cols, _windowSize, _K, RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD),
differenceThreshold(_diffThr)
{ }
/** Compute cached data
*/
virtual void cache() const CV_OVERRIDE
{
this->cacheIsDirty = false;
}
/** Compute the normals
* @param r
* @param normals the output normals
*/
virtual void compute(const Mat& points3d, Mat& normals) const CV_OVERRIDE
{
// Only focus on the depth image for LINEMOD
Mat depth_in;
//if (points3d.channels() == 3)
if (points3d.channels() == 4)
{
std::vector<Mat> channels;
split(points3d, channels);
depth_in = channels[2];
}
else
depth_in = points3d;
switch (depth_in.depth())
{
case CV_16U:
{
const Mat_<unsigned short>& d(depth_in);
computeImpl<unsigned short, long>(d, normals);
break;
}
case CV_32F:
{
const Mat_<float>& d(depth_in);
computeImpl<float, float>(d, normals);
break;
}
case CV_64F:
{
const Mat_<double>& d(depth_in);
computeImpl<double, double>(d, normals);
break;
}
}
}
/** Compute the normals
* @param r
* @return
*/
template<typename DepthDepth, typename ContainerDepth>
Mat computeImpl(const Mat_<DepthDepth>& depthIn, Mat& normals) const
{
const int r = 5; // used to be 7
const int sample_step = r;
const int square_size = ((2 * r / sample_step) + 1);
long offsets[square_size * square_size];
long offsets_x[square_size * square_size];
long offsets_y[square_size * square_size];
long offsets_x_x[square_size * square_size];
long offsets_x_y[square_size * square_size];
long offsets_y_y[square_size * square_size];
for (int j = -r, index = 0; j <= r; j += sample_step)
for (int i = -r; i <= r; i += sample_step, ++index)
{
offsets_x[index] = i;
offsets_y[index] = j;
offsets_x_x[index] = i * i;
offsets_x_y[index] = i * j;
offsets_y_y[index] = j * j;
offsets[index] = j * this->cols + i;
}
// Define K_inv by hand, just for higher accuracy
Mat33T K_inv = Matx<T, 3, 3>::eye(), kmat;
this->K.copyTo(kmat);
K_inv(0, 0) = 1.0f / kmat(0, 0);
K_inv(0, 1) = -kmat(0, 1) / (kmat(0, 0) * kmat(1, 1));
K_inv(0, 2) = (kmat(0, 1) * kmat(1, 2) - kmat(0, 2) * kmat(1, 1)) / (kmat(0, 0) * kmat(1, 1));
K_inv(1, 1) = 1 / kmat(1, 1);
K_inv(1, 2) = -kmat(1, 2) / kmat(1, 1);
Vec3T X1_minus_X, X2_minus_X;
ContainerDepth difference_threshold((ContainerDepth)differenceThreshold);
//TODO: fixit, difference threshold should not depend on input type
difference_threshold *= (ContainerDepth)(std::is_same<DepthDepth, ushort>::value ? 1000.0 : 1.0);
normals.setTo(std::numeric_limits<DepthDepth>::quiet_NaN());
for (int y = r; y < this->rows - r - 1; ++y)
{
const DepthDepth* p_line = reinterpret_cast<const DepthDepth*>(depthIn.ptr(y, r));
Vec4T* normal = normals.ptr<Vec4T>(y, r);
for (int x = r; x < this->cols - r - 1; ++x)
{
DepthDepth d = p_line[0];
// accum
long A[4];
A[0] = A[1] = A[2] = A[3] = 0;
ContainerDepth b[2];
b[0] = b[1] = 0;
for (unsigned int i = 0; i < square_size * square_size; ++i) {
// We need to cast to ContainerDepth in case we have unsigned DepthDepth
ContainerDepth delta = ContainerDepth(p_line[offsets[i]]) - ContainerDepth(d);
if (std::abs(delta) > difference_threshold)
continue;
A[0] += offsets_x_x[i];
A[1] += offsets_x_y[i];
A[3] += offsets_y_y[i];
b[0] += offsets_x[i] * delta;
b[1] += offsets_y[i] * delta;
}
// solve for the optimal gradient D of equation (8)
long det = A[0] * A[3] - A[1] * A[1];
// We should divide the following two by det, but instead, we multiply
// X1_minus_X and X2_minus_X by det (which does not matter as we normalize the normals)
// Therefore, no division is done: this is only for speedup
ContainerDepth dx = (A[3] * b[0] - A[1] * b[1]);
ContainerDepth dy = (-A[1] * b[0] + A[0] * b[1]);
// Compute the dot product
//Vec3T X = K_inv * Vec3T(x, y, 1) * depth(y, x);
//Vec3T X1 = K_inv * Vec3T(x + 1, y, 1) * (depth(y, x) + dx);
//Vec3T X2 = K_inv * Vec3T(x, y + 1, 1) * (depth(y, x) + dy);
//Vec3T nor = (X1 - X).cross(X2 - X);
multiply_by_K_inv(K_inv, d * det + (x + 1) * dx, y * dx, dx, X1_minus_X);
multiply_by_K_inv(K_inv, x * dy, d * det + (y + 1) * dy, dy, X2_minus_X);
Vec3T nor = X1_minus_X.cross(X2_minus_X);
signNormal(nor, *normal);
++p_line;
++normal;
}
}
return normals;
}
double differenceThreshold;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** Given a set of 3d points in a depth image, compute the normals at each point
* using the SRI method described in
* ``Fast and Accurate Computation of Surface Normals from Range Images``
* by H. Badino, D. Huber, Y. Park and T. Kanade
*/
template<typename T>
class SRI : public RgbdNormalsImpl<T>
{
public:
typedef Matx<T, 3, 3> Mat33T;
typedef Vec<T, 9> Vec9T;
typedef Vec<T, 4> Vec4T;
typedef Vec<T, 3> Vec3T;
SRI(int _rows, int _cols, int _windowSize, const Mat& _K) :
RgbdNormalsImpl<T>(_rows, _cols, _windowSize, _K, RgbdNormals::RGBD_NORMALS_METHOD_SRI),
phi_step_(0),
theta_step_(0)
{ }
/** Compute cached data
*/
virtual void cache() const CV_OVERRIDE
{
if (!this->cacheIsDirty)
return;
Mat_<T> cos_theta, sin_theta, cos_phi, sin_phi;
computeThetaPhi<T>(this->rows, this->cols, this->K, cos_theta, sin_theta, cos_phi, sin_phi);
// Create the derivative kernels
getDerivKernels(kx_dx_, ky_dx_, 1, 0, this->windowSize, true, this->dtype);
getDerivKernels(kx_dy_, ky_dy_, 0, 1, this->windowSize, true, this->dtype);
// Get the mapping function for SRI
float min_theta = (float)std::asin(sin_theta(0, 0)), max_theta = (float)std::asin(sin_theta(0, this->cols - 1));
float min_phi = (float)std::asin(sin_phi(0, this->cols / 2 - 1)), max_phi = (float)std::asin(sin_phi(this->rows - 1, this->cols / 2 - 1));
std::vector<Point3f> points3d(this->cols * this->rows);
R_hat_.create(this->rows, this->cols);
phi_step_ = float(max_phi - min_phi) / (this->rows - 1);
theta_step_ = float(max_theta - min_theta) / (this->cols - 1);
for (int phi_int = 0, k = 0; phi_int < this->rows; ++phi_int)
{
float phi = min_phi + phi_int * phi_step_;
float phi_sin = std::sin(phi), phi_cos = std::cos(phi);
for (int theta_int = 0; theta_int < this->cols; ++theta_int, ++k)
{
float theta = min_theta + theta_int * theta_step_;
float theta_sin = std::sin(theta), theta_cos = std::cos(theta);
// Store the 3d point to project it later
Point3f pp(theta_sin * phi_cos, phi_sin, theta_cos * phi_cos);
points3d[k] = pp;
// Cache the rotation matrix and negate it
Matx<T, 3, 3> mat = Matx<T, 3, 3> (0, 1, 0, 0, 0, 1, 1, 0, 0) *
Matx<T, 3, 3> (theta_cos, -theta_sin, 0, theta_sin, theta_cos, 0, 0, 0, 1) *
Matx<T, 3, 3> (phi_cos, 0, -phi_sin, 0, 1, 0, phi_sin, 0, phi_cos);
for (unsigned char i = 0; i < 3; ++i)
mat(i, 1) = mat(i, 1) / phi_cos;
// The second part of the matrix is never explained in the paper ... but look at the wikipedia normal article
mat(0, 0) = mat(0, 0) - 2 * pp.x;
mat(1, 0) = mat(1, 0) - 2 * pp.y;
mat(2, 0) = mat(2, 0) - 2 * pp.z;
R_hat_(phi_int, theta_int) = Vec9T(mat.val);
}
}
map_.create(this->rows, this->cols);
projectPoints(points3d, Mat(3, 1, CV_32FC1, Scalar::all(0.0f)), Mat(3, 1, CV_32FC1, Scalar::all(0.0f)), this->K, Mat(), map_);
map_ = map_.reshape(2, this->rows);
convertMaps(map_, Mat(), xy_, fxy_, CV_16SC2);
//map for converting from Spherical coordinate space to Euclidean space
euclideanMap_.create(this->rows, this->cols);
Matx<T, 3, 3> km(this->K);
float invFx = (float)(1.0f / km(0, 0)), cx = (float)(km(0, 2));
double invFy = 1.0f / (km(1, 1)), cy = km(1, 2);
for (int i = 0; i < this->rows; i++)
{
float y = (float)((i - cy) * invFy);
for (int j = 0; j < this->cols; j++)
{
float x = (j - cx) * invFx;
float theta = std::atan(x);
float phi = std::asin(y / std::sqrt(x * x + y * y + 1.0f));
euclideanMap_(i, j) = Vec2f((theta - min_theta) / theta_step_, (phi - min_phi) / phi_step_);
}
}
//convert map to 2 maps in short format for increasing speed in remap function
convertMaps(euclideanMap_, Mat(), invxy_, invfxy_, CV_16SC2);
// Update the kernels: the steps are due to the fact that derivatives will be computed on a grid where
// the step is not 1. Only need to do it on one dimension as it computes derivatives in only one direction
kx_dx_ /= theta_step_;
ky_dy_ /= phi_step_;
this->cacheIsDirty = false;
}
/** Compute the normals
* @param r
* @return
*/
virtual void compute(const Mat& in, Mat& normals_out) const CV_OVERRIDE
{
const Mat_<T>& r_non_interp = in;
// Interpolate the radial image to make derivatives meaningful
Mat_<T> r;
// higher quality remapping does not help here
remap(r_non_interp, r, xy_, fxy_, INTER_LINEAR);
// Compute the derivatives with respect to theta and phi
// TODO add bilateral filtering (as done in kinfu)
Mat_<T> r_theta, r_phi;
sepFilter2D(r, r_theta, r.depth(), kx_dx_, ky_dx_);
//current OpenCV version sometimes corrupts r matrix after second call of sepFilter2D
//it depends on resolution, be careful
sepFilter2D(r, r_phi, r.depth(), kx_dy_, ky_dy_);
// Fill the result matrix
Mat_<Vec4T> normals(this->rows, this->cols);
const T* r_theta_ptr = r_theta[0], * r_theta_ptr_end = r_theta_ptr + this->rows * this->cols;
const T* r_phi_ptr = r_phi[0];
const Mat33T* R = reinterpret_cast<const Mat33T*>(R_hat_[0]);
const T* r_ptr = r[0];
Vec4T* normal = normals[0];
for (; r_theta_ptr != r_theta_ptr_end; ++r_theta_ptr, ++r_phi_ptr, ++R, ++r_ptr, ++normal)
{
if (cvIsNaN(*r_ptr))
{
(*normal)[0] = *r_ptr;
(*normal)[1] = *r_ptr;
(*normal)[2] = *r_ptr;
(*normal)[3] = 0;
}
else
{
T r_theta_over_r = (*r_theta_ptr) / (*r_ptr);
T r_phi_over_r = (*r_phi_ptr) / (*r_ptr);
// R(1,1) is 0
signNormal((*R)(0, 0) + (*R)(0, 1) * r_theta_over_r + (*R)(0, 2) * r_phi_over_r,
(*R)(1, 0) + (*R)(1, 2) * r_phi_over_r,
(*R)(2, 0) + (*R)(2, 1) * r_theta_over_r + (*R)(2, 2) * r_phi_over_r, *normal);
}
}
remap(normals, normals_out, invxy_, invfxy_, INTER_LINEAR);
normal = normals_out.ptr<Vec4T>(0);
Vec4T* normal_end = normal + this->rows * this->cols;
for (; normal != normal_end; ++normal)
signNormal((*normal)[0], (*normal)[1], (*normal)[2], *normal);
}
// Cached data
/** Stores R */
mutable Mat_<Vec9T> R_hat_;
mutable float phi_step_, theta_step_;
/** Derivative kernels */
mutable Mat kx_dx_, ky_dx_, kx_dy_, ky_dy_;
/** mapping function to get an SRI image */
mutable Mat_<Vec2f> map_;
mutable Mat xy_, fxy_;
mutable Mat_<Vec2f> euclideanMap_;
mutable Mat invxy_, invfxy_;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* Uses the simpliest possible method for normals calculation: calculates cross product between two vectors
(pointAt(x+1, y) - pointAt(x, y)) and (pointAt(x, y+1) - pointAt(x, y)) */
template<typename DataType>
class CrossProduct : public RgbdNormalsImpl<DataType>
{
public:
typedef Vec<DataType, 3> Vec3T;
typedef Vec<DataType, 4> Vec4T;
typedef Point3_<DataType> Point3T;
CrossProduct(int _rows, int _cols, int _windowSize, const Mat& _K) :
RgbdNormalsImpl<DataType>(_rows, _cols, _windowSize, _K, RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT)
{ }
/** Compute cached data
*/
virtual void cache() const CV_OVERRIDE
{
this->cacheIsDirty = false;
}
static inline Point3T fromVec(Vec4T v)
{
return {v[0], v[1], v[2]};
}
static inline Vec4T toVec4(Point3T p)
{
return {p.x, p.y, p.z, 0};
}
static inline bool haveNaNs(Point3T p)
{
return cvIsNaN(p.x) || cvIsNaN(p.y) || cvIsNaN(p.z);
}
/** Compute the normals
* @param points reprojected depth points
* @param normals generated normals
* @return
*/
virtual void compute(const Mat& points, Mat& normals) const CV_OVERRIDE
{
for(int y = 0; y < this->rows; y++)
{
const Vec4T* ptsRow0 = points.ptr<Vec4T>(y);
const Vec4T* ptsRow1 = (y < this->rows - 1) ? points.ptr<Vec4T>(y + 1) : nullptr;
Vec4T *normRow = normals.ptr<Vec4T>(y);
for (int x = 0; x < this->cols; x++)
{
Point3T v00 = fromVec(ptsRow0[x]);
const float qnan = std::numeric_limits<float>::quiet_NaN();
Point3T n(qnan, qnan, qnan);
if ((x < this->cols - 1) && (y < this->rows - 1) && !haveNaNs(v00))
{
Point3T v01 = fromVec(ptsRow0[x + 1]);
Point3T v10 = fromVec(ptsRow1[x]);
if (!haveNaNs(v01) && !haveNaNs(v10))
{
Vec3T vec = (v10 - v00).cross(v01 - v00);
n = normalize(vec);
}
}
normRow[x] = toVec4(n);
}
}
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Ptr<RgbdNormals> RgbdNormals::create(int rows, int cols, int depth, InputArray K, int windowSize, float diffThreshold, RgbdNormalsMethod method)
{
CV_Assert(rows > 0 && cols > 0 && (depth == CV_32F || depth == CV_64F));
CV_Assert(windowSize == 1 || windowSize == 3 || windowSize == 5 || windowSize == 7);
CV_Assert(K.cols() == 3 && K.rows() == 3 && (K.depth() == CV_32F || K.depth() == CV_64F));
Mat mK = K.getMat();
Ptr<RgbdNormals> ptr;
switch (method)
{
case (RGBD_NORMALS_METHOD_FALS):
{
if (depth == CV_32F)
ptr = makePtr<FALS<float> >(rows, cols, windowSize, mK);
else
ptr = makePtr<FALS<double>>(rows, cols, windowSize, mK);
break;
}
case (RGBD_NORMALS_METHOD_LINEMOD):
{
CV_Assert(diffThreshold > 0);
if (depth == CV_32F)
ptr = makePtr<LINEMOD<float> >(rows, cols, windowSize, mK, diffThreshold);
else
ptr = makePtr<LINEMOD<double>>(rows, cols, windowSize, mK, diffThreshold);
break;
}
case RGBD_NORMALS_METHOD_SRI:
{
if (depth == CV_32F)
ptr = makePtr<SRI<float> >(rows, cols, windowSize, mK);
else
ptr = makePtr<SRI<double>>(rows, cols, windowSize, mK);
break;
}
case RGBD_NORMALS_METHOD_CROSS_PRODUCT:
{
if (depth == CV_32F)
ptr = makePtr<CrossProduct<float> >(rows, cols, windowSize, mK);
else
ptr = makePtr<CrossProduct<double>>(rows, cols, windowSize, mK);
break;
}
default:
CV_Error(Error::StsBadArg, "Unknown normals compute algorithm");
}
return ptr;
}
} // namespace cv
+695
View File
@@ -0,0 +1,695 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "octree.hpp"
#include "opencv2/geometry/3d.hpp"
namespace cv{
OctreeNode::OctreeNode() :
children(),
depth(0),
size(0),
origin(0,0,0),
neigh(),
parentIndex(-1)
{ }
OctreeNode::OctreeNode(int _depth, double _size, const Point3f &_origin, int _parentIndex) :
children(),
depth(_depth),
size(_size),
origin(_origin),
neigh(),
parentIndex(_parentIndex)
{ }
bool OctreeNode::empty() const
{
if(this->isLeaf)
{
if(this->pointList.empty())
return true;
else
return false;
}
else
{
for(size_t i = 0; i < 8; i++)
{
if(!this->children[i].empty())
{
return false;
}
}
return true;
}
}
bool OctreeNode::isPointInBound(const Point3f& _point) const
{
Point3f eps;
eps.x = std::max(std::abs(_point.x), std::abs(this->origin.x));
eps.y = std::max(std::abs(_point.y), std::abs(this->origin.y));
eps.z = std::max(std::abs(_point.z), std::abs(this->origin.z));
eps *= std::numeric_limits<float>::epsilon();
Point3f ptEps = _point + eps;
Point3f upPt = this->origin + eps + Point3f {(float)this->size, (float)this->size, (float)this->size};
return (ptEps.x >= this->origin.x) &&
(ptEps.y >= this->origin.y) &&
(ptEps.z >= this->origin.z) &&
(_point.x <= upPt.x) &&
(_point.y <= upPt.y) &&
(_point.z <= upPt.z);
}
struct Octree::Impl
{
public:
Impl() : Impl(0, 0, {0, 0, 0}, 0, false) { }
Impl(int _maxDepth, double _size, const Point3f& _origin, double _resolution,
bool _hasColor) :
maxDepth(_maxDepth),
size(_size),
origin(_origin),
resolution(_resolution),
hasColor(_hasColor)
{ }
~Impl() { }
void fill(bool useResolution, InputArray pointCloud, InputArray colorAttribute);
bool insertPoint(const Point3f& point, const Point3f &color);
// The pointer to Octree root node
Ptr <OctreeNode> rootNode = nullptr;
//! Max depth of the Octree
int maxDepth;
//! The size of the cube
double size;
//! The origin coordinate of root node
Point3f origin;
//! The size of the leaf node
double resolution;
//! Whether the point cloud has a color attribute
bool hasColor;
};
Octree::Octree() :
p(makePtr<Impl>())
{ }
Ptr<Octree> Octree::createWithDepth(int maxDepth, double size, const Point3f& origin, bool withColors)
{
CV_Assert(maxDepth > 0);
CV_Assert(size > 0);
Ptr<Octree> octree = makePtr<Octree>();
octree->p = makePtr<Impl>(maxDepth, size, origin, /*resolution*/ 0, withColors);
return octree;
}
Ptr<Octree> Octree::createWithDepth(int maxDepth, InputArray pointCloud, InputArray colors)
{
CV_Assert(maxDepth > 0);
Ptr<Octree> octree = makePtr<Octree>();
octree->p->maxDepth = maxDepth;
octree->p->fill(/* useResolution */ false, pointCloud, colors);
return octree;
}
Ptr<Octree> Octree::createWithResolution(double resolution, double size, const Point3f& origin, bool withColors)
{
CV_Assert(resolution > 0);
CV_Assert(size > 0);
Ptr<Octree> octree = makePtr<Octree>();
octree->p = makePtr<Impl>(/*maxDepth*/ 0, size, origin, resolution, withColors);
return octree;
}
Ptr<Octree> Octree::createWithResolution(double resolution, InputArray pointCloud, InputArray colors)
{
CV_Assert(resolution > 0);
Ptr<Octree> octree = makePtr<Octree>();
octree->p->resolution = resolution;
octree->p->fill(/* useResolution */ true, pointCloud, colors);
return octree;
}
Octree::~Octree() { }
bool Octree::insertPoint(const Point3f& point, const Point3f &color)
{
return p->insertPoint(point, color);
}
bool Octree::Impl::insertPoint(const Point3f& point, const Point3f &color)
{
size_t depthMask = (size_t)(1ULL << (this->maxDepth - 1));
if(this->rootNode.empty())
{
this->rootNode = new OctreeNode( 0, this->size, this->origin, -1);
}
bool pointInBoundFlag = this->rootNode->isPointInBound(point);
if(this->rootNode->depth == 0 && !pointInBoundFlag)
{
return false;
}
OctreeKey key((size_t)floor((point.x - this->origin.x) / this->resolution),
(size_t)floor((point.y - this->origin.y) / this->resolution),
(size_t)floor((point.z - this->origin.z) / this->resolution));
Ptr<OctreeNode> node = this->rootNode;
while (node->depth != maxDepth)
{
double childSize = node->size * 0.5;
// calculate the index and the origin of child.
size_t childIndex = key.findChildIdxByMask(depthMask);
size_t xIndex = (childIndex & 1) ? 1 : 0;
size_t yIndex = (childIndex & 2) ? 1 : 0;
size_t zIndex = (childIndex & 4) ? 1 : 0;
Point3f childOrigin = node->origin + Point3f(float(xIndex), float(yIndex), float(zIndex)) * float(childSize);
Ptr<OctreeNode> &childPtr = node->children[childIndex];
if (!childPtr)
{
childPtr = new OctreeNode(node->depth + 1, childSize, childOrigin, int(childIndex));
childPtr->parent = node;
}
node = childPtr;
depthMask = depthMask >> 1;
}
node->isLeaf = true;
node->pointList.push_back(point);
node->colorList.push_back(color);
return true;
}
static Vec6f getBoundingBox(const Mat& points)
{
const float mval = std::numeric_limits<float>::max();
Vec6f bb(mval, mval, mval, -mval, -mval, -mval);
for (int i = 0; i < (int)points.total(); i++)
{
Point3f pt = points.at<Point3f>(i);
bb[0] = min(bb[0], pt.x);
bb[1] = min(bb[1], pt.y);
bb[2] = min(bb[2], pt.z);
bb[3] = max(bb[3], pt.x);
bb[4] = max(bb[4], pt.y);
bb[5] = max(bb[5], pt.z);
}
return bb;
}
void Octree::Impl::fill(bool useResolution, InputArray _points, InputArray _colors)
{
CV_CheckFalse(_points.empty(), "No points provided");
Mat points, colors;
int nPoints = 0, nColors = 0;
int pointType = _points.type();
CV_Assert(pointType == CV_32FC1 || pointType == CV_32FC3);
points = _points.getMat();
// transform 3xN matrix to Nx3, except 3x3
if ((_points.channels() == 1) && (_points.rows() == 3) && (_points.cols() != 3))
{
points = points.t();
}
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
points = points.reshape(3, 1).t();
nPoints = (int)points.total();
if (!_colors.empty())
{
int colorType = _colors.type();
CV_Assert(colorType == CV_32FC1 || colorType == CV_32FC3);
colors = _colors.getMat();
// transform 3xN matrix to Nx3, except 3x3
if ((_colors.channels() == 1) && (_colors.rows() == 3) && (_colors.cols() != 3))
{
colors = colors.t();
}
colors = colors.reshape(3, 1).t();
nColors = (int)colors.total();
CV_Assert(nColors == nPoints);
this->hasColor = true;
}
Vec6f bbox = getBoundingBox(points);
Point3f minBound(bbox[0], bbox[1], bbox[2]);
Point3f maxBound(bbox[3], bbox[4], bbox[5]);
double maxSize = max(max(maxBound.x - minBound.x, maxBound.y - minBound.y), maxBound.z - minBound.z);
// Extend maxSize to the closest power of 2 that exceeds it for bit operations
maxSize = double(1 << int(ceil(log2(maxSize))));
// to calculate maxDepth from resolution or vice versa
if (useResolution)
{
this->maxDepth = (int)ceil(log2(maxSize / this->resolution));
}
else
{
this->resolution = (maxSize / (1 << (this->maxDepth + 1)));
}
this->size = (1 << this->maxDepth) * this->resolution;
this->origin = Point3f(float(floor(minBound.x / this->resolution) * this->resolution),
float(floor(minBound.y / this->resolution) * this->resolution),
float(floor(minBound.z / this->resolution) * this->resolution));
// Insert every point in PointCloud data.
for (int idx = 0; idx < nPoints; idx++)
{
Point3f pt = points.at<Point3f>(idx);
Point3f insertColor = this->hasColor ? colors.at<Point3f>(idx) : Point3f { };
if (!this->insertPoint(pt, insertColor))
{
CV_Error(Error::StsBadArg, "The point is out of boundary!");
}
}
}
void Octree::clear()
{
p = makePtr<Impl>();
}
bool Octree::empty() const
{
return p->rootNode.empty();
}
bool Octree::isPointInBound(const Point3f& _point) const
{
return p->rootNode->isPointInBound(_point);
}
bool Octree::deletePoint(const Point3f& point)
{
OctreeKey key = OctreeKey((size_t)floor((point.x - this->p->origin.x) / p->resolution),
(size_t)floor((point.y - this->p->origin.y) / p->resolution),
(size_t)floor((point.z - this->p->origin.z) / p->resolution));
size_t depthMask = (size_t)1 << (p->maxDepth - 1);
Ptr<OctreeNode> node = p->rootNode;
while(node)
{
if (node->empty())
{
node = nullptr;
}
else if (node->isLeaf)
{
const float eps = 1e-9f;
bool found = std::any_of(node->pointList.begin(), node->pointList.end(),
[point, eps](const Point3f& pt) -> bool
{
return abs(point.x - pt.x) < eps &&
abs(point.y - pt.y) < eps &&
abs(point.z - pt.z) < eps;
});
if (!found)
node = nullptr;
break;
}
else
{
node = node->children[key.findChildIdxByMask(depthMask)];
depthMask = depthMask >> 1;
}
}
if(!node)
return false;
const float eps = 1e-9f;
// we've found a leaf node and delete all verts equal to given one
size_t ctr = 0;
while (!node->pointList.empty() && ctr < node->pointList.size())
{
if (abs(point.x - node->pointList[ctr].x) < eps &&
abs(point.y - node->pointList[ctr].y) < eps &&
abs(point.z - node->pointList[ctr].z) < eps)
{
node->pointList.erase(node->pointList.begin() + ctr);
}
else
{
ctr++;
}
}
if (node->pointList.empty())
{
// empty node and its empty parents should be removed
OctreeNode *parentPtr = node->parent;
int parentdIdx = node->parentIndex;
while (parentPtr)
{
parentPtr->children[parentdIdx].release();
// check if all children were deleted
bool deleteFlag = true;
for (size_t i = 0; i < 8; i++)
{
if (!parentPtr->children[i].empty())
{
deleteFlag = false;
break;
}
}
if (deleteFlag)
{
// we're at empty node, going up
parentdIdx = parentPtr->parentIndex;
parentPtr = parentPtr->parent;
}
else
{
// reached first non-empty node, stopping
parentPtr = nullptr;
}
}
}
return true;
}
void Octree::getPointCloudByOctree(OutputArray restorePointCloud, OutputArray restoreColor)
{
Ptr<OctreeNode> root = p->rootNode;
double resolution = p->resolution;
std::vector<Point3f> outPts, outColors;
typedef std::tuple<Ptr<OctreeNode>, size_t, size_t, size_t> stack_element;
std::stack<stack_element> toCheck;
toCheck.push(stack_element(root, 0, 0, 0));
while (!toCheck.empty())
{
auto top = toCheck.top();
toCheck.pop();
Ptr<OctreeNode> node = std::get<0>(top);
size_t x_key = std::get<1>(top);
size_t y_key = std::get<2>(top);
size_t z_key = std::get<3>(top);
if (node->isLeaf)
{
outPts.emplace_back(
(float) (resolution * x_key) + (float) (resolution * 0.5) + p->origin.x,
(float) (resolution * y_key) + (float) (resolution * 0.5) + p->origin.y,
(float) (resolution * z_key) + (float) (resolution * 0.5) + p->origin.z);
if (p->hasColor)
{
Point3f avgColor { };
for (const auto& c : node->colorList)
{
avgColor += c;
}
avgColor *= (1.f/(float)node->colorList.size());
outColors.emplace_back(avgColor);
}
}
else
{
unsigned char x_mask = 1;
unsigned char y_mask = 2;
unsigned char z_mask = 4;
for (unsigned char i = 0; i < 8; i++)
{
size_t x_copy = x_key;
size_t y_copy = y_key;
size_t z_copy = z_key;
if (!node->children[i].empty())
{
size_t x_offSet = !!(x_mask & i);
size_t y_offSet = !!(y_mask & i);
size_t z_offSet = !!(z_mask & i);
x_copy = (x_copy << 1) | x_offSet;
y_copy = (y_copy << 1) | y_offSet;
z_copy = (z_copy << 1) | z_offSet;
toCheck.push(stack_element(node->children[i], x_copy, y_copy, z_copy));
}
}
}
}
if (restorePointCloud.needed())
{
Mat(outPts).copyTo(restorePointCloud);
}
if (restoreColor.needed())
{
Mat(outColors).copyTo(restoreColor);
}
}
static float SquaredDistance(const Point3f& query, const Point3f& origin)
{
Point3f diff = query - origin;
return diff.dot(diff);
}
bool OctreeNode::overlap(const Point3f& query, float squareRadius) const
{
float halfSize = float(this->size * 0.5);
Point3f center = this->origin + Point3f( halfSize, halfSize, halfSize );
float dist = SquaredDistance(center, query);
float temp = float(this->size) * float(this->size) * 3.0f;
return ( dist + dist * std::numeric_limits<float>::epsilon() ) <= float(temp * 0.25f + squareRadius + sqrt(temp * squareRadius)) ;
}
int Octree::radiusNNSearch(const Point3f& query, float radius, OutputArray pointSet, OutputArray squareDistSet) const
{
return this->radiusNNSearch(query, radius, pointSet, noArray(), squareDistSet);
}
int Octree::radiusNNSearch(const Point3f& query, float radius, OutputArray points, OutputArray colors, OutputArray squareDists) const
{
std::vector<Point3f> outPoints, outColors;
std::vector<float> outSqDists;
if (!p->rootNode.empty())
{
float squareRadius = radius * radius;
std::vector<std::tuple<float, Point3f, Point3f>> candidatePoints;
std::stack<Ptr<OctreeNode>> toCheck;
toCheck.push(p->rootNode);
while (!toCheck.empty())
{
Ptr<OctreeNode> node = toCheck.top();
toCheck.pop();
for(size_t i = 0; i < 8; i++)
{
Ptr<OctreeNode> child = node->children[i];
if( child && child->overlap(query, squareRadius))
{
if(child->isLeaf)
{
for(size_t j = 0; j < child->pointList.size(); j++)
{
Point3f pt = child->pointList[j];
Point3f col;
if (!child->colorList.empty())
{
col = child->colorList[j];
}
float dist = SquaredDistance(pt, query);
if(dist + dist * std::numeric_limits<float>::epsilon() <= squareRadius)
{
candidatePoints.emplace_back(dist, pt, col);
}
}
}
else
{
toCheck.push(child);
}
}
}
}
for (size_t i = 0; i < candidatePoints.size(); i++)
{
auto cp = candidatePoints[i];
outSqDists.push_back(std::get<0>(cp));
outPoints.push_back(std::get<1>(cp));
outColors.push_back(std::get<2>(cp));
}
}
if (points.needed())
{
Mat(outPoints).copyTo(points);
}
if (colors.needed())
{
CV_Assert(this->p->hasColor);
Mat(outColors).copyTo(colors);
}
if (squareDists.needed())
{
Mat(outSqDists).copyTo(squareDists);
}
return int(outPoints.size());
}
void OctreeNode::KNNSearchRecurse(const Point3f& query, const int K,
float& smallestDist, std::vector<std::tuple<float, Point3f, Point3f>>& candidatePoint) const
{
std::vector<std::pair<float, int>> priorityQue;
// Add the non-empty OctreeNode to priorityQue
for(size_t i = 0; i < 8; i++)
{
Ptr<OctreeNode> child = this->children[i];
if(child)
{
float halfSize = float(child->size * 0.5);
Point3f center = child->origin + Point3f(halfSize, halfSize, halfSize);
float dist = SquaredDistance(query, center);
priorityQue.emplace_back(dist, int(i));
}
}
std::sort(priorityQue.rbegin(), priorityQue.rend(),
[](const std::pair<float, int>& a, const std::pair<float, int>& b) -> bool
{
return std::get<0>(a) < std::get<0>(b);
});
Ptr<OctreeNode> child = this->children[std::get<1>(priorityQue.back())];
while (!priorityQue.empty() && child->overlap(query, smallestDist))
{
if (!child->isLeaf)
{
child->KNNSearchRecurse(query, K, smallestDist, candidatePoint);
}
else
{
for (size_t i = 0; i < child->pointList.size(); i++)
{
float dist = SquaredDistance(child->pointList[i], query);
if ( dist + dist * std::numeric_limits<float>::epsilon() <= smallestDist )
{
Point3f pt = child->pointList[i];
Point3f col { };
if (child->colorList.empty())
{
col = child->colorList[i];
}
candidatePoint.emplace_back(dist, pt, col);
}
}
std::sort(candidatePoint.begin(), candidatePoint.end(),
[](const std::tuple<float, Point3f, Point3f>& a, const std::tuple<float, Point3f, Point3f>& b) -> bool
{
return std::get<0>(a) < std::get<0>(b);
}
);
if (int(candidatePoint.size()) > K)
{
candidatePoint.resize(K);
}
if (int(candidatePoint.size()) == K)
{
smallestDist = std::get<0>(candidatePoint.back());
}
}
priorityQue.pop_back();
// To next child
if(!priorityQue.empty())
{
child = this->children[std::get<1>(priorityQue.back())];
}
}
}
void Octree::KNNSearch(const Point3f &query, const int K, OutputArray pointSet, OutputArray squareDistSet) const
{
this->KNNSearch(query, K, pointSet, noArray(), squareDistSet);
}
void Octree::KNNSearch(const Point3f &query, const int K, OutputArray points, OutputArray colors, OutputArray squareDists) const
{
std::vector<Point3f> outPoints, outColors;
std::vector<float> outSqDists;
if (!p->rootNode.empty())
{
std::vector<std::tuple<float, Point3f, Point3f>> candidatePoints;
float smallestDist = std::numeric_limits<float>::max();
p->rootNode->KNNSearchRecurse(query, K, smallestDist, candidatePoints);
for(size_t i = 0; i < candidatePoints.size(); i++)
{
auto cp = candidatePoints[i];
outSqDists.push_back(std::get<0>(cp));
outPoints.push_back(std::get<1>(cp));
outColors.push_back(std::get<2>(cp));
}
}
if (points.needed())
{
Mat(outPoints).copyTo(points);
}
if (colors.needed())
{
CV_Assert(this->p->hasColor);
Mat(outColors).copyTo(colors);
}
if (squareDists.needed())
{
Mat(outSqDists).copyTo(squareDists);
}
}
}
+181
View File
@@ -0,0 +1,181 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2021, Huawei Technologies Co., Ltd. All rights reserved.
// Third party copyrights are property of their respective owners.
//
// Author: Zihao Mu <zihaomu6@gmail.com>
// Liangqian Kong <chargerKong@126.com>
// Longbu Wang <riskiest@gmail.com>
#ifndef OPENCV_3D_SRC_OCTREE_HPP
#define OPENCV_3D_SRC_OCTREE_HPP
#include <vector>
#include <array>
#include "opencv2/core.hpp"
namespace cv
{
// Forward declaration
class OctreeKey;
/** @brief OctreeNode for Octree.
The class OctreeNode represents the node of the octree. Each node contains 8 children, which are used to divide the
space cube into eight parts. Each octree node represents a cube.
And these eight children will have a fixed order, the order is described as follows:
For illustration, assume,
rootNode: origin == (0, 0, 0), size == 2
Then,
children[0]: origin == (0, 0, 0), size == 1
children[1]: origin == (1, 0, 0), size == 1, along X-axis next to child 0
children[2]: origin == (0, 1, 0), size == 1, along Y-axis next to child 0
children[3]: origin == (1, 1, 0), size == 1, in X-Y plane
children[4]: origin == (0, 0, 1), size == 1, along Z-axis next to child 0
children[5]: origin == (1, 0, 1), size == 1, in X-Z plane
children[6]: origin == (0, 1, 1), size == 1, in Y-Z plane
children[7]: origin == (1, 1, 1), size == 1, furthest from child 0
There are two kinds of nodes in an octree, intermediate nodes and leaf nodes, which are distinguished by isLeaf.
Intermediate nodes are used to contain leaf nodes, and leaf nodes will contain pointers to all pointcloud data
within the node, which will be used for octree indexing and mapping from point clouds to octree. Note that,
in an octree, each leaf node contains at least one point cloud data. Similarly, every intermediate OctreeNode
contains at least one non-empty child pointer, except for the root node.
*/
class OctreeNode
{
public:
/**
* There are multiple constructors to create OctreeNode.
* */
OctreeNode();
/** @overload
*
* @param _depth The depth of the current node. The depth of the root node is 0, and the leaf node is equal
* to the depth of Octree.
* @param _size The length of the OctreeNode. In space, every OctreeNode represents a cube.
* @param _origin The absolute coordinates of the center of the cube.
* @param _parentIndex The serial number of the child of the current node in the parent node,
* the range is (-1~7). Among them, only the root node's _parentIndex is -1.
*/
OctreeNode(int _depth, double _size, const Point3f& _origin, int _parentIndex);
//! returns true if the rootNode is NULL.
bool empty() const;
bool isPointInBound(const Point3f& _point) const;
bool overlap(const Point3f& query, float squareRadius) const;
void KNNSearchRecurse(const Point3f& query, const int K, float& smallestDist, std::vector<std::tuple<float, Point3f, Point3f>>& candidatePoint) const;
//! Contains 8 pointers to its 8 children.
std::array<Ptr<OctreeNode>, 8> children;
//! Point to the parent node of the current node. The root node has no parent node and the value is NULL.
OctreeNode* parent;
//! The depth of the current node. The depth of the root node is 0, and the leaf node is equal to the depth of Octree.
int depth;
//! The length of the OctreeNode. In space, every OctreeNode represents a cube.
double size;
//! Absolute coordinates of the smallest point of the cube.
//! And the center of cube is `center = origin + Point3f(size/2, size/2, size/2)`.
Point3f origin;
//! RAHTCoefficient of octree node, used for color attribute compression.
Point3f RAHTCoefficient = { };
/** The list of 6 adjacent neighbor node.
* index mapping:
* +z [101]
* | | [110]
* | | /
* O-------- +x [001]----{000} ----[011]
* / / |
* / [010] |
* +y [100]
* index 000, 111 are reserved
*/
std::array<Ptr<OctreeNode>, 8> neigh;
/** The serial number of the child of the current node in the parent node,
* the range is (-1~7). Among them, only the root node's _parentIndex is -1.
*/
int parentIndex;
//! If the OctreeNode is LeafNode.
bool isLeaf = false;
//! Contains pointers to all point cloud data in this node.
std::vector<Point3f> pointList;
//! color attribute of octree node.
std::vector<Point3f> colorList;
};
/** @brief Key for pointCloud, used to compute the child node index through bit operations.
When building the octree, the point cloud data is firstly voxelized/discretized: by inserting
all the points into a voxel coordinate system. For example, when resolution is set to 0.01, a point
with coordinate Point3f(0.251,0.502,0.753) would be transformed to:(0.251/0.01,0.502/0.01,0.753/0.01)
=(25,50,75). And the OctreeKey will be (x_key:1_1001,y_key:11_0010,z_key:100_1011). Assume the Octree->depth
is 100_0000, It can quickly calculate the index of the child nodes at each layer.
layer Depth Mask x&Depth Mask y&Depth Mask z&Depth Mask Child Index(0-7)
1 100_0000 0 0 1 4
2 10_0000 0 1 0 2
3 1_0000 1 1 0 3
4 1000 1 0 1 5
5 100 0 0 0 0
6 10 0 1 1 6
7 1 1 0 1 5
*/
class OctreeKey
{
public:
size_t x_key;
size_t y_key;
size_t z_key;
public:
OctreeKey() : x_key(0), y_key(0), z_key(0) { }
OctreeKey(size_t x, size_t y, size_t z) : x_key(x), y_key(y), z_key(z) { }
/** @brief compute the child node index through bit operations.
*
* @param mask The mask of specify layer.
* @return the index of child(0-7)
*/
inline unsigned char findChildIdxByMask(size_t mask) const
{
return static_cast<unsigned char>((!!(z_key & mask))<<2) | ((!!(y_key & mask))<<1) | (!!(x_key & mask));
}
/** @brief get occupancy code from node.
*
* The occupancy code type is unsigned char that represents whether the eight child nodes of the octree node exist
* If a octree node has 3 child which indexes are 0,1,7, then the occupancy code of this node is 1000_0011
* @param node The octree node.
* @return the occupancy code(0000_0000-1111_1111)
*/
static inline unsigned char getBitPattern(OctreeNode &node)
{
unsigned char res = 0;
for (unsigned char i = 0; i < node.children.size(); i++)
{
res |= static_cast<unsigned char>((!node.children[i].empty()) << i);
}
return res;
}
};
}
#endif //OPENCV_3D_SRC_OCTREE_HPP
+529
View File
@@ -0,0 +1,529 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "utils.hpp"
#include "opencv2/ptcloud/odometry.hpp"
#include "odometry_functions.hpp"
namespace cv
{
class Odometry::Impl
{
public:
Impl() {};
virtual ~Impl() {};
virtual void prepareFrame(OdometryFrame& frame) const = 0;
virtual void prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const = 0;
virtual bool compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const = 0;
virtual bool compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const = 0;
virtual bool compute(InputArray srcDepth, InputArray srcRGB,
InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const = 0;
virtual Ptr<RgbdNormals> getNormalsComputer() const = 0;
};
class OdometryICP : public Odometry::Impl
{
private:
OdometrySettings settings;
OdometryAlgoType algtype;
mutable Ptr<RgbdNormals> normalsComputer;
public:
OdometryICP(OdometrySettings _settings, OdometryAlgoType _algtype) :
settings(_settings), algtype(_algtype), normalsComputer()
{ }
~OdometryICP() { }
virtual void prepareFrame(OdometryFrame& frame) const override;
virtual void prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const override;
virtual bool compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const override;
virtual bool compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const override;
virtual bool compute(InputArray srcDepth, InputArray srcRGB,
InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const override;
virtual Ptr<RgbdNormals> getNormalsComputer() const override;
};
Ptr<RgbdNormals> OdometryICP::getNormalsComputer() const
{
return this->normalsComputer;
}
void OdometryICP::prepareFrame(OdometryFrame& frame) const
{
prepareICPFrame(frame, frame, this->normalsComputer, this->settings, this->algtype);
}
void OdometryICP::prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const
{
prepareICPFrame(srcFrame, dstFrame, this->normalsComputer, this->settings, this->algtype);
}
bool OdometryICP::compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const
{
Matx33f cameraMatrix;
settings.getCameraMatrix(cameraMatrix);
std::vector<int> iterCounts;
settings.getIterCounts(iterCounts);
bool isCorrect = RGBDICPOdometryImpl(Rt, Mat(), srcFrame, dstFrame, cameraMatrix,
this->settings.getMaxDepthDiff(), this->settings.getAngleThreshold(),
iterCounts, this->settings.getMaxTranslation(),
this->settings.getMaxRotation(), settings.getSobelScale(),
OdometryType::DEPTH, OdometryTransformType::RIGID_TRANSFORMATION, this->algtype);
return isCorrect;
}
bool OdometryICP::compute(InputArray _srcDepth, InputArray _dstDepth, OutputArray Rt) const
{
OdometryFrame srcFrame(_srcDepth);
OdometryFrame dstFrame(_dstDepth);
prepareICPFrame(srcFrame, dstFrame, this->normalsComputer, this->settings, this->algtype);
bool isCorrect = compute(srcFrame, dstFrame, Rt);
return isCorrect;
}
bool OdometryICP::compute(InputArray srcDepth, InputArray srcRGB,
InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const
{
CV_UNUSED(srcDepth);
CV_UNUSED(srcRGB);
CV_UNUSED(dstDepth);
CV_UNUSED(dstRGB);
CV_UNUSED(Rt);
CV_Error(cv::Error::StsBadFunc, "This odometry does not work with rgb data");
}
class OdometryRGB : public Odometry::Impl
{
private:
OdometrySettings settings;
OdometryAlgoType algtype;
public:
OdometryRGB(OdometrySettings _settings, OdometryAlgoType _algtype) : settings(_settings), algtype(_algtype) { }
~OdometryRGB() { }
virtual void prepareFrame(OdometryFrame& frame) const override;
virtual void prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const override;
virtual bool compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const override;
virtual bool compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const override;
virtual bool compute(InputArray srcDepth, InputArray srcRGB,
InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const override;
virtual Ptr<RgbdNormals> getNormalsComputer() const override { return Ptr<RgbdNormals>(); }
};
void OdometryRGB::prepareFrame(OdometryFrame& frame) const
{
prepareRGBFrame(frame, frame, this->settings);
}
void OdometryRGB::prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const
{
prepareRGBFrame(srcFrame, dstFrame, this->settings);
}
bool OdometryRGB::compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const
{
Matx33f cameraMatrix;
settings.getCameraMatrix(cameraMatrix);
std::vector<int> iterCounts;
settings.getIterCounts(iterCounts);
bool isCorrect = RGBDICPOdometryImpl(Rt, Mat(), srcFrame, dstFrame, cameraMatrix,
this->settings.getMaxDepthDiff(), this->settings.getAngleThreshold(),
iterCounts, this->settings.getMaxTranslation(),
this->settings.getMaxRotation(), settings.getSobelScale(),
OdometryType::RGB, OdometryTransformType::RIGID_TRANSFORMATION, this->algtype);
return isCorrect;
}
bool OdometryRGB::compute(InputArray _srcDepth, InputArray _dstDepth, OutputArray Rt) const
{
CV_UNUSED(_srcDepth);
CV_UNUSED(_dstDepth);
CV_UNUSED(Rt);
CV_Error(cv::Error::StsBadFunc, "This odometry algorithm requires depth and rgb data simultaneously");
}
bool OdometryRGB::compute(InputArray srcDepth, InputArray srcRGB, InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const
{
OdometryFrame srcFrame(srcDepth, srcRGB);
OdometryFrame dstFrame(dstDepth, dstRGB);
prepareRGBFrame(srcFrame, dstFrame, this->settings);
return compute(srcFrame, dstFrame, Rt);
}
class OdometryRGBD : public Odometry::Impl
{
private:
OdometrySettings settings;
OdometryAlgoType algtype;
mutable Ptr<RgbdNormals> normalsComputer;
public:
OdometryRGBD(OdometrySettings _settings, OdometryAlgoType _algtype) : settings(_settings), algtype(_algtype), normalsComputer() { }
~OdometryRGBD() { }
virtual void prepareFrame(OdometryFrame& frame) const override;
virtual void prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const override;
virtual bool compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const override;
virtual bool compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const override;
virtual bool compute(InputArray srcDepth, InputArray srcRGB,
InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const override;
virtual Ptr<RgbdNormals> getNormalsComputer() const override;
};
Ptr<RgbdNormals> OdometryRGBD::getNormalsComputer() const
{
return normalsComputer;
}
void OdometryRGBD::prepareFrame(OdometryFrame& frame) const
{
prepareRGBDFrame(frame, frame, this->normalsComputer, this->settings, this->algtype);
}
void OdometryRGBD::prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const
{
prepareRGBDFrame(srcFrame, dstFrame, this->normalsComputer, this->settings, this->algtype);
}
bool OdometryRGBD::compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const
{
Matx33f cameraMatrix;
settings.getCameraMatrix(cameraMatrix);
std::vector<int> iterCounts;
settings.getIterCounts(iterCounts);
bool isCorrect = RGBDICPOdometryImpl(Rt, Mat(), srcFrame, dstFrame, cameraMatrix,
this->settings.getMaxDepthDiff(), this->settings.getAngleThreshold(),
iterCounts, this->settings.getMaxTranslation(),
this->settings.getMaxRotation(), settings.getSobelScale(),
OdometryType::RGB_DEPTH, OdometryTransformType::RIGID_TRANSFORMATION, this->algtype);
return isCorrect;
}
bool OdometryRGBD::compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const
{
CV_UNUSED(srcDepth);
CV_UNUSED(dstDepth);
CV_UNUSED(Rt);
CV_Error(cv::Error::StsBadFunc, "This odometry algorithm needs depth and rgb data simultaneously");
}
bool OdometryRGBD::compute(InputArray _srcDepth, InputArray _srcRGB,
InputArray _dstDepth, InputArray _dstRGB, OutputArray Rt) const
{
OdometryFrame srcFrame(_srcDepth, _srcRGB);
OdometryFrame dstFrame(_dstDepth, _dstRGB);
prepareRGBDFrame(srcFrame, dstFrame, this->normalsComputer, this->settings, this->algtype);
bool isCorrect = compute(srcFrame, dstFrame, Rt);
return isCorrect;
}
Odometry::Odometry()
{
OdometrySettings settings;
this->impl = makePtr<OdometryICP>(settings, OdometryAlgoType::COMMON);
}
Odometry::Odometry(OdometryType otype)
{
OdometrySettings settings;
switch (otype)
{
case OdometryType::DEPTH:
this->impl = makePtr<OdometryICP>(settings, OdometryAlgoType::FAST);
break;
case OdometryType::RGB:
this->impl = makePtr<OdometryRGB>(settings, OdometryAlgoType::COMMON);
break;
case OdometryType::RGB_DEPTH:
this->impl = makePtr<OdometryRGBD>(settings, OdometryAlgoType::COMMON);
break;
default:
CV_Error(Error::StsInternal,
"Incorrect OdometryType, you are able to use only { DEPTH = 0, RGB = 1, RGB_DEPTH = 2 }");
break;
}
}
Odometry::Odometry(OdometryType otype, const OdometrySettings& settings, OdometryAlgoType algtype)
{
switch (otype)
{
case OdometryType::DEPTH:
this->impl = makePtr<OdometryICP>(settings, algtype);
break;
case OdometryType::RGB:
this->impl = makePtr<OdometryRGB>(settings, algtype);
break;
case OdometryType::RGB_DEPTH:
this->impl = makePtr<OdometryRGBD>(settings, algtype);
break;
default:
CV_Error(Error::StsInternal,
"Incorrect OdometryType, you are able to use only { ICP, RGB, RGBD }");
break;
}
}
Odometry::~Odometry()
{
}
void Odometry::prepareFrame(OdometryFrame& frame) const
{
this->impl->prepareFrame(frame);
}
void Odometry::prepareFrames(OdometryFrame& srcFrame, OdometryFrame& dstFrame) const
{
this->impl->prepareFrames(srcFrame, dstFrame);
}
bool Odometry::compute(const OdometryFrame& srcFrame, const OdometryFrame& dstFrame, OutputArray Rt) const
{
return this->impl->compute(srcFrame, dstFrame, Rt);
}
bool Odometry::compute(InputArray srcDepth, InputArray dstDepth, OutputArray Rt) const
{
return this->impl->compute(srcDepth, dstDepth, Rt);
}
bool Odometry::compute(InputArray srcDepth, InputArray srcRGB,
InputArray dstDepth, InputArray dstRGB, OutputArray Rt) const
{
return this->impl->compute(srcDepth, srcRGB, dstDepth, dstRGB, Rt);
}
Ptr<RgbdNormals> Odometry::getNormalsComputer() const
{
return this->impl->getNormalsComputer();
}
void warpFrame(InputArray depth, InputArray image, InputArray mask,
InputArray Rt, InputArray cameraMatrix,
OutputArray warpedDepth, OutputArray warpedImage, OutputArray warpedMask)
{
CV_Assert(cameraMatrix.size() == Size(3, 3));
CV_Assert(cameraMatrix.depth() == CV_32F || cameraMatrix.depth() == CV_64F);
Matx33d K, Kinv;
cameraMatrix.getMat().convertTo(K, CV_64F);
std::vector<bool> camPlaces { /* fx */ true, false, /* cx */ true, false, /* fy */ true, /* cy */ true, false, false, /* 1 */ true};
for (int i = 0; i < 9; i++)
{
CV_Assert(camPlaces[i] == (K.val[i] > DBL_EPSILON));
}
Kinv = K.inv();
CV_Assert((Rt.cols() == 4) && (Rt.rows() == 3 || Rt.rows() == 4));
CV_Assert(Rt.depth() == CV_32F || Rt.depth() == CV_64F);
Mat rtmat;
Rt.getMat().convertTo(rtmat, CV_64F);
Affine3d rt(rtmat);
CV_Assert(!depth.empty());
CV_Assert(depth.channels() == 1);
double maxDepth = 0;
int depthDepth = depth.depth();
switch (depthDepth)
{
case CV_16U:
maxDepth = std::numeric_limits<unsigned short>::max();
break;
case CV_32F:
maxDepth = std::numeric_limits<float>::max();
break;
case CV_64F:
maxDepth = std::numeric_limits<double>::max();
break;
default:
CV_Error(Error::StsBadArg, "Unsupported depth data type");
}
Mat_<double> depthDbl;
depth.getMat().convertTo(depthDbl, CV_64F);
Size sz = depth.size();
Mat_<uchar> maskMat;
if (!mask.empty())
{
CV_Assert(mask.type() == CV_8UC1 || mask.type() == CV_8SC1 || mask.type() == CV_BoolC1);
CV_Assert(mask.size() == sz);
maskMat = mask.getMat();
}
int imageType = -1;
Mat imageMat;
if (!image.empty())
{
imageType = image.type();
CV_Assert(imageType == CV_8UC1 || imageType == CV_8UC3 || imageType == CV_8UC4);
CV_Assert(image.size() == sz);
CV_Assert(warpedImage.needed());
imageMat = image.getMat();
}
CV_Assert(warpedDepth.needed() || warpedImage.needed() || warpedMask.needed());
// Getting new coords for depth point
// see the explanation in the loop below
Matx33d krki = K * rt.rotation() * Kinv;
Matx32d krki_cols01 = krki.get_minor<3, 2>(0, 0);
Vec3d krki_col2(krki.col(2).val);
Vec3d ktmat = K * rt.translation();
Mat_<Vec3d> reprojBack(depth.size());
for (int y = 0; y < sz.height; y++)
{
const uchar* maskRow = maskMat.empty() ? nullptr : maskMat[y];
const double* depthRow = depthDbl[y];
Vec3d* reprojRow = reprojBack[y];
for (int x = 0; x < sz.width; x++)
{
double z = depthRow[x];
bool badz = cvIsNaN(z) || cvIsInf(z) || z <= 0 || z >= maxDepth || (maskRow && !maskRow[x]);
Vec3d v;
if (!badz)
{
// Reproject pixel (x, y) using known z, rotate+translate and project back
// getting new pixel in projective coordinates:
// v = K * Rt * K^-1 * ([x, y, 1] * z) = [new_x*new_z, new_y*new_z, new_z]
// v = K * (R * K^-1 * ([x, y, 1] * z) + t) =
// v = krki * [x, y, 1] * z + ktmat =
// v = (krki_cols01 * [x, y] + krki_col2) * z + K * t
v = (krki_cols01 * Vec2d(x, y) + krki_col2) * z + ktmat;
}
else
{
v = Vec3d();
}
reprojRow[x] = v;
}
}
// Draw new depth in z-buffer manner
Mat warpedImageMat;
if (warpedImage.needed())
{
warpedImage.create(sz, imageType);
warpedImage.setZero();
warpedImageMat = warpedImage.getMat();
}
const double infinity = std::numeric_limits<double>::max();
Mat zBuffer(sz, CV_32FC1, infinity);
const Rect rect = Rect(Point(), sz);
for (int y = 0; y < sz.height; y++)
{
uchar* imageRow1ch = nullptr;
Vec3b* imageRow3ch = nullptr;
Vec4b* imageRow4ch = nullptr;
switch (imageType)
{
case -1:
break;
case CV_8UC1:
imageRow1ch = imageMat.ptr<uchar>(y);
break;
case CV_8UC3:
imageRow3ch = imageMat.ptr<Vec3b>(y);
break;
case CV_8UC4:
imageRow4ch = imageMat.ptr<Vec4b>(y);
break;
default:
break;
}
const Vec3d* reprojRow = reprojBack[y];
for (int x = 0; x < sz.width; x++)
{
Vec3d v = reprojRow[x];
double z = v[2];
if (z > 0)
{
Point uv(cvFloor(v[0] / z), cvFloor(v[1] / z));
if (rect.contains(uv))
{
float oldz = zBuffer.at<float>(uv);
if (z < oldz)
{
zBuffer.at<float>(uv) = (float)z;
switch (imageType)
{
case -1:
break;
case CV_8UC1:
warpedImageMat.at<uchar>(uv) = imageRow1ch[x];
break;
case CV_8UC3:
warpedImageMat.at<Vec3b>(uv) = imageRow3ch[x];
break;
case CV_8UC4:
warpedImageMat.at<Vec4b>(uv) = imageRow4ch[x];
break;
default:
break;
}
}
}
}
}
}
if (warpedDepth.needed() || warpedMask.needed())
{
Mat goodMask = (zBuffer < infinity);
if (warpedDepth.needed())
{
warpedDepth.create(sz, depthDepth);
double badVal;
switch (depthDepth)
{
case CV_16U:
badVal = 0;
break;
case CV_32F:
badVal = std::numeric_limits<float>::quiet_NaN();
break;
case CV_64F:
badVal = std::numeric_limits<double>::quiet_NaN();
break;
default:
break;
}
zBuffer.convertTo(warpedDepth, depthDepth);
warpedDepth.setTo(badVal, ~goodMask);
}
if (warpedMask.needed())
{
warpedMask.create(sz, CV_8UC1);
goodMask.copyTo(warpedMask);
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include <opencv2/core/ocl.hpp>
#include "utils.hpp"
namespace cv
{
OdometryFrame::OdometryFrame(InputArray depth, InputArray image, InputArray mask, InputArray normals)
{
this->impl = makePtr<OdometryFrame::Impl>();
if (!image.empty())
{
image.copyTo(this->impl->image);
}
if (!depth.empty())
{
depth.copyTo(this->impl->depth);
}
if (!mask.empty())
{
mask.copyTo(this->impl->mask);
}
if (!normals.empty())
{
normals.copyTo(this->impl->normals);
}
}
void OdometryFrame::getImage(OutputArray image) const { this->impl->getImage(image); }
void OdometryFrame::getGrayImage(OutputArray image) const { this->impl->getGrayImage(image); }
void OdometryFrame::getDepth(OutputArray depth) const { this->impl->getDepth(depth); }
void OdometryFrame::getProcessedDepth(OutputArray depth) const { this->impl->getProcessedDepth(depth); }
void OdometryFrame::getMask(OutputArray mask) const { this->impl->getMask(mask); }
void OdometryFrame::getNormals(OutputArray normals) const { this->impl->getNormals(normals); }
int OdometryFrame::getPyramidLevels() const { return this->impl->getPyramidLevels(); }
void OdometryFrame::getPyramidAt(OutputArray img, OdometryFramePyramidType pyrType, size_t level) const
{
this->impl->getPyramidAt(img, pyrType, level);
}
void OdometryFrame::Impl::getImage(OutputArray _image) const
{
_image.assign(this->image);
}
void OdometryFrame::Impl::getGrayImage(OutputArray _image) const
{
_image.assign(this->imageGray);
}
void OdometryFrame::Impl::getDepth(OutputArray _depth) const
{
_depth.assign(this->depth);
}
void OdometryFrame::Impl::getProcessedDepth(OutputArray _depth) const
{
_depth.assign(this->scaledDepth);
}
void OdometryFrame::Impl::getMask(OutputArray _mask) const
{
_mask.assign(this->mask);
}
void OdometryFrame::Impl::getNormals(OutputArray _normals) const
{
_normals.assign(this->normals);
}
int OdometryFrame::Impl::getPyramidLevels() const
{
// all pyramids should have the same size
for (const auto& p : this->pyramids)
{
if (!p.empty())
return (int)(p.size());
}
return 0;
}
void OdometryFrame::Impl::getPyramidAt(OutputArray _img, OdometryFramePyramidType pyrType, size_t level) const
{
CV_Assert(pyrType < OdometryFramePyramidType::N_PYRAMIDS);
if (level < pyramids[pyrType].size())
_img.assign(pyramids[pyrType][level]);
else
_img.clear();
}
}
File diff suppressed because it is too large Load Diff
+200
View File
@@ -0,0 +1,200 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_ODOMETRY_FUNCTIONS_HPP
#define OPENCV_3D_ODOMETRY_FUNCTIONS_HPP
#include "utils.hpp"
#include <opencv2/imgproc.hpp>
namespace cv
{
enum class OdometryTransformType
{
// rotation, translation, rotation+translation
ROTATION = 1, TRANSLATION = 2, RIGID_TRANSFORMATION = 4
};
static inline int getTransformDim(OdometryTransformType transformType)
{
switch(transformType)
{
case OdometryTransformType::RIGID_TRANSFORMATION:
return 6;
case OdometryTransformType::ROTATION:
case OdometryTransformType::TRANSLATION:
return 3;
default:
CV_Error(Error::StsBadArg, "Incorrect transformation type");
}
}
static inline
Vec6d calcRgbdEquationCoeffs(double dIdx, double dIdy, const Point3d& p3d, double fx, double fy)
{
double invz = 1. / p3d.z,
v0 = dIdx * fx * invz,
v1 = dIdy * fy * invz,
v2 = -(v0 * p3d.x + v1 * p3d.y) * invz;
Point3d v(v0, v1, v2);
Point3d pxv = p3d.cross(v);
return Vec6d(pxv.x, pxv.y, pxv.z, v0, v1, v2);
}
static inline
Vec3d calcRgbdEquationCoeffsRotation(double dIdx, double dIdy, const Point3d& p3d, double fx, double fy)
{
double invz = 1. / p3d.z,
v0 = dIdx * fx * invz,
v1 = dIdy * fy * invz,
v2 = -(v0 * p3d.x + v1 * p3d.y) * invz;
Point3d v(v0, v1, v2);
Point3d pxv = p3d.cross(v);
return Vec3d(pxv);
}
static inline
Vec3d calcRgbdEquationCoeffsTranslation(double dIdx, double dIdy, const Point3d& p3d, double fx, double fy)
{
double invz = 1. / p3d.z,
v0 = dIdx * fx * invz,
v1 = dIdy * fy * invz,
v2 = -(v0 * p3d.x + v1 * p3d.y) * invz;
return Vec3d(v0, v1, v2);
}
static inline void rgbdCoeffsFunc(OdometryTransformType transformType,
double* C, double dIdx, double dIdy, const Point3d& p3d, double fx, double fy)
{
int dim = getTransformDim(transformType);
Vec6d ret;
switch(transformType)
{
case OdometryTransformType::RIGID_TRANSFORMATION:
{
ret = calcRgbdEquationCoeffs(dIdx, dIdy, p3d, fx, fy);
break;
}
case OdometryTransformType::ROTATION:
{
Vec3d r = calcRgbdEquationCoeffsRotation(dIdx, dIdy, p3d, fx, fy);
ret = Vec6d(r[0], r[1], r[2], 0, 0, 0);
break;
}
case OdometryTransformType::TRANSLATION:
{
Vec3d r = calcRgbdEquationCoeffsTranslation(dIdx, dIdy, p3d, fx, fy);
ret = Vec6d(r[0], r[1], r[2], 0, 0, 0);
break;
}
default:
CV_Error(Error::StsBadArg, "Incorrect transformation type");
}
for (int i = 0; i < dim; i++)
C[i] = ret[i];
}
static inline
Vec6d calcICPEquationCoeffs(const Point3d& psrc, const Vec3d& ndst)
{
Point3d pxv = psrc.cross(Point3d(ndst));
return Vec6d(pxv.x, pxv.y, pxv.z, ndst[0], ndst[1], ndst[2]);
}
static inline
Vec3d calcICPEquationCoeffsRotation(const Point3d& psrc, const Vec3d& ndst)
{
Point3d pxv = psrc.cross(Point3d(ndst));
return Vec3d(pxv);
}
static inline
Vec3d calcICPEquationCoeffsTranslation( const Point3d& /*p0*/, const Vec3d& ndst)
{
return Vec3d(ndst);
}
static inline
void icpCoeffsFunc(OdometryTransformType transformType, double* C, const Point3d& p0, const Point3d& /*p1*/, const Vec3d& n1)
{
int dim = getTransformDim(transformType);
Vec6d ret;
switch(transformType)
{
case OdometryTransformType::RIGID_TRANSFORMATION:
{
ret = calcICPEquationCoeffs(p0, n1);
break;
}
case OdometryTransformType::ROTATION:
{
Vec3d r = calcICPEquationCoeffsRotation(p0, n1);
ret = Vec6d(r[0], r[1], r[2], 0, 0, 0);
break;
}
case OdometryTransformType::TRANSLATION:
{
Vec3d r = calcICPEquationCoeffsTranslation(p0, n1);
ret = Vec6d(r[0], r[1], r[2], 0, 0, 0);
break;
}
default:
CV_Error(Error::StsBadArg, "Incorrect transformation type");
}
for (int i = 0; i < dim; i++)
C[i] = ret[i];
}
void prepareRGBDFrame(OdometryFrame& srcFrame, OdometryFrame& dstFrame, Ptr<RgbdNormals>& normalsComputer, const OdometrySettings settings, OdometryAlgoType algtype);
void prepareRGBFrame(OdometryFrame& srcFrame, OdometryFrame& dstFrame, OdometrySettings settings);
void prepareICPFrame(OdometryFrame& srcFrame, OdometryFrame& dstFrame, Ptr<RgbdNormals>& normalsComputer, const OdometrySettings settings, OdometryAlgoType algtype);
bool RGBDICPOdometryImpl(OutputArray _Rt, const Mat& initRt,
const OdometryFrame& srcFrame,
const OdometryFrame& dstFrame,
const Matx33f& cameraMatrix,
float maxDepthDiff, float angleThreshold, const std::vector<int>& iterCounts,
double maxTranslation, double maxRotation, double sobelScale,
OdometryType method, OdometryTransformType transfromType, OdometryAlgoType algtype);
void computeCorresps(const Matx33f& _K, const Mat& Rt,
const Mat& image0, const Mat& depth0, const Mat& validMask0,
const Mat& image1, const Mat& depth1, const Mat& selectMask1, float maxDepthDiff,
Mat& _corresps, Mat& _diffs, double& _sigma, OdometryType method);
void calcRgbdLsmMatrices(const Mat& cloud0, const Mat& Rt,
const Mat& dI_dx1, const Mat& dI_dy1,
const Mat& corresps, const Mat& diffs, const double sigma,
double fx, double fy, double sobelScaleIn,
Mat& AtA, Mat& AtB, OdometryTransformType transformType);
void calcICPLsmMatrices(const Mat& cloud0, const Mat& Rt,
const Mat& cloud1, const Mat& normals1,
const Mat& corresps,
Mat& AtA, Mat& AtB, OdometryTransformType transformType);
void calcICPLsmMatricesFast(Matx33f cameraMatrix, const UMat& oldPts, const UMat& oldNrm, const UMat& newPts, const UMat& newNrm,
cv::Affine3f pose, int level, float maxDepthDiff, float angleThreshold, cv::Matx66f& A, cv::Vec6f& b);
#ifdef HAVE_OPENCL
bool ocl_calcICPLsmMatricesFast(Matx33f cameraMatrix, const UMat& oldPts, const UMat& oldNrm, const UMat& newPts, const UMat& newNrm,
cv::Affine3f pose, int level, float maxDepthDiff, float angleThreshold, cv::Matx66f& A, cv::Vec6f& b);
#endif
void computeProjectiveMatrix(const Mat& ksi, Mat& Rt);
bool solveSystem(const Mat& AtA, const Mat& AtB, double detThreshold, Mat& x);
bool testDeltaTransformation(const Mat& deltaRt, double maxTranslation, double maxRotation);
}
#endif //OPENCV_3D_ODOMETRY_FUNCTIONS_HPP
@@ -0,0 +1,407 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "utils.hpp"
namespace cv
{
class OdometrySettings::Impl
{
public:
Impl() {};
virtual ~Impl() {};
virtual void setCameraMatrix(InputArray val) = 0;
virtual void getCameraMatrix(OutputArray val) const = 0;
virtual void setIterCounts(InputArray val) = 0;
virtual void getIterCounts(OutputArray val) const = 0;
virtual void setMinDepth(float val) = 0;
virtual float getMinDepth() const = 0;
virtual void setMaxDepth(float val) = 0;
virtual float getMaxDepth() const = 0;
virtual void setMaxDepthDiff(float val) = 0;
virtual float getMaxDepthDiff() const = 0;
virtual void setMaxPointsPart(float val) = 0;
virtual float getMaxPointsPart() const = 0;
virtual void setSobelSize(int val) = 0;
virtual int getSobelSize() const = 0;
virtual void setSobelScale(double val) = 0;
virtual double getSobelScale() const = 0;
virtual void setNormalWinSize(int val) = 0;
virtual int getNormalWinSize() const = 0;
virtual void setNormalDiffThreshold(float val) = 0;
virtual float getNormalDiffThreshold() const = 0;
virtual void setNormalMethod(RgbdNormals::RgbdNormalsMethod nm) = 0;
virtual RgbdNormals::RgbdNormalsMethod getNormalMethod() const = 0;
virtual void setAngleThreshold(float val) = 0;
virtual float getAngleThreshold() const = 0;
virtual void setMaxTranslation(float val) = 0;
virtual float getMaxTranslation() const = 0;
virtual void setMaxRotation(float val) = 0;
virtual float getMaxRotation() const = 0;
virtual void setMinGradientMagnitude(float val) = 0;
virtual float getMinGradientMagnitude() const = 0;
virtual void setMinGradientMagnitudes(InputArray val) = 0;
virtual void getMinGradientMagnitudes(OutputArray val) const = 0;
};
class OdometrySettingsImplCommon : public OdometrySettings::Impl
{
public:
OdometrySettingsImplCommon();
~OdometrySettingsImplCommon() {};
virtual void setCameraMatrix(InputArray val) override;
virtual void getCameraMatrix(OutputArray val) const override;
virtual void setIterCounts(InputArray val) override;
virtual void getIterCounts(OutputArray val) const override;
virtual void setMinDepth(float val) override;
virtual float getMinDepth() const override;
virtual void setMaxDepth(float val) override;
virtual float getMaxDepth() const override;
virtual void setMaxDepthDiff(float val) override;
virtual float getMaxDepthDiff() const override;
virtual void setMaxPointsPart(float val) override;
virtual float getMaxPointsPart() const override;
virtual void setSobelSize(int val) override;
virtual int getSobelSize() const override;
virtual void setSobelScale(double val) override;
virtual double getSobelScale() const override;
virtual void setNormalWinSize(int val) override;
virtual int getNormalWinSize() const override;
virtual void setNormalDiffThreshold(float val) override;
virtual float getNormalDiffThreshold() const override;
virtual void setNormalMethod(RgbdNormals::RgbdNormalsMethod nm) override;
virtual RgbdNormals::RgbdNormalsMethod getNormalMethod() const override;
virtual void setAngleThreshold(float val) override;
virtual float getAngleThreshold() const override;
virtual void setMaxTranslation(float val) override;
virtual float getMaxTranslation() const override;
virtual void setMaxRotation(float val) override;
virtual float getMaxRotation() const override;
virtual void setMinGradientMagnitude(float val) override;
virtual float getMinGradientMagnitude() const override;
virtual void setMinGradientMagnitudes(InputArray val) override;
virtual void getMinGradientMagnitudes(OutputArray val) const override;
private:
Matx33f cameraMatrix;
std::vector<int> iterCounts;
float minDepth;
float maxDepth;
float maxDepthDiff;
float maxPointsPart;
int sobelSize;
double sobelScale;
int normalWinSize;
float normalDiffThreshold;
RgbdNormals::RgbdNormalsMethod normalMethod;
float angleThreshold;
float maxTranslation;
float maxRotation;
float minGradientMagnitude;
std::vector<float> minGradientMagnitudes;
public:
class DefaultSets {
public:
static const int width = 640;
static const int height = 480;
static constexpr float fx = 525.f;
static constexpr float fy = 525.f;
static constexpr float cx = float(width) / 2.f - 0.5f;
static constexpr float cy = float(height) / 2.f - 0.5f;
const cv::Matx33f defaultCameraMatrix = { fx, 0, cx, 0, fy, cy, 0, 0, 1 };
const std::vector<int> defaultIterCounts = { 7, 7, 7, 10 };
static constexpr float defaultMinDepth = 0.f;
static constexpr float defaultMaxDepth = 4.f;
static constexpr float defaultMaxDepthDiff = 0.07f;
static constexpr float defaultMaxPointsPart = 0.07f;
static const int defaultSobelSize = 3;
static constexpr double defaultSobelScale = 1. / 8.;
static const int defaultNormalWinSize = 5;
static const RgbdNormals::RgbdNormalsMethod defaultNormalMethod = RgbdNormals::RGBD_NORMALS_METHOD_FALS;
static constexpr float defaultNormalDiffThreshold = 50.f;
static constexpr float defaultAngleThreshold = (float)(30. * CV_PI / 180.);
static constexpr float defaultMaxTranslation = 0.15f;
static constexpr float defaultMaxRotation = 15.f;
static constexpr float defaultMinGradientMagnitude = 10.f;
const std::vector<float> defaultMinGradientMagnitudes = std::vector<float>(defaultIterCounts.size(), 10.f /*defaultMinGradientMagnitude*/);
};
};
OdometrySettings::OdometrySettings()
{
this->impl = makePtr<OdometrySettingsImplCommon>();
}
OdometrySettings::OdometrySettings(const OdometrySettings& s)
{
this->impl = makePtr<OdometrySettingsImplCommon>(*s.impl.dynamicCast<OdometrySettingsImplCommon>());
}
OdometrySettings& OdometrySettings::operator=(const OdometrySettings& s)
{
this->impl = makePtr<OdometrySettingsImplCommon>(*s.impl.dynamicCast<OdometrySettingsImplCommon>());
return *this;
}
void OdometrySettings::setCameraMatrix(InputArray val) { this->impl->setCameraMatrix(val); }
void OdometrySettings::getCameraMatrix(OutputArray val) const { this->impl->getCameraMatrix(val); }
void OdometrySettings::setIterCounts(InputArray val) { this->impl->setIterCounts(val); }
void OdometrySettings::getIterCounts(OutputArray val) const { this->impl->getIterCounts(val); }
void OdometrySettings::setMinDepth(float val) { this->impl->setMinDepth(val); }
float OdometrySettings::getMinDepth() const { return this->impl->getMinDepth(); }
void OdometrySettings::setMaxDepth(float val) { this->impl->setMaxDepth(val); }
float OdometrySettings::getMaxDepth() const { return this->impl->getMaxDepth(); }
void OdometrySettings::setMaxDepthDiff(float val) { this->impl->setMaxDepthDiff(val); }
float OdometrySettings::getMaxDepthDiff() const { return this->impl->getMaxDepthDiff(); }
void OdometrySettings::setMaxPointsPart(float val) { this->impl->setMaxPointsPart(val); }
float OdometrySettings::getMaxPointsPart() const { return this->impl->getMaxPointsPart(); }
void OdometrySettings::setSobelSize(int val) { this->impl->setSobelSize(val); }
int OdometrySettings::getSobelSize() const { return this->impl->getSobelSize(); }
void OdometrySettings::setSobelScale(double val) { this->impl->setSobelScale(val); }
double OdometrySettings::getSobelScale() const { return this->impl->getSobelScale(); }
void OdometrySettings::setNormalWinSize(int val) { this->impl->setNormalWinSize(val); }
int OdometrySettings::getNormalWinSize() const { return this->impl->getNormalWinSize(); }
void OdometrySettings::setNormalDiffThreshold(float val) { this->impl->setNormalDiffThreshold(val); }
float OdometrySettings::getNormalDiffThreshold() const { return this->impl->getNormalDiffThreshold(); }
void OdometrySettings::setNormalMethod(RgbdNormals::RgbdNormalsMethod nm) { this->impl->setNormalMethod(nm); }
RgbdNormals::RgbdNormalsMethod OdometrySettings::getNormalMethod() const { return this->impl->getNormalMethod(); }
void OdometrySettings::setAngleThreshold(float val) { this->impl->setAngleThreshold(val); }
float OdometrySettings::getAngleThreshold() const { return this->impl->getAngleThreshold(); }
void OdometrySettings::setMaxTranslation(float val) { this->impl->setMaxTranslation(val); }
float OdometrySettings::getMaxTranslation() const { return this->impl->getMaxTranslation(); }
void OdometrySettings::setMaxRotation(float val) { this->impl->setMaxRotation(val); }
float OdometrySettings::getMaxRotation() const { return this->impl->getMaxRotation(); }
void OdometrySettings::setMinGradientMagnitude(float val) { this->impl->setMinGradientMagnitude(val); }
float OdometrySettings::getMinGradientMagnitude() const { return this->impl->getMinGradientMagnitude(); }
void OdometrySettings::setMinGradientMagnitudes(InputArray val) { this->impl->setMinGradientMagnitudes(val); }
void OdometrySettings::getMinGradientMagnitudes(OutputArray val) const { this->impl->getMinGradientMagnitudes(val); }
OdometrySettingsImplCommon::OdometrySettingsImplCommon()
{
DefaultSets ds;
this->cameraMatrix = ds.defaultCameraMatrix;
this->iterCounts = ds.defaultIterCounts;
this->minDepth = ds.defaultMinDepth;
this->maxDepth = ds.defaultMaxDepth;
this->maxDepthDiff = ds.defaultMaxDepthDiff;
this->maxPointsPart = ds.defaultMaxPointsPart;
this->sobelSize = ds.defaultSobelSize;
this->sobelScale = ds.defaultSobelScale;
this->normalWinSize = ds.defaultNormalWinSize;
this->normalDiffThreshold = ds.defaultNormalDiffThreshold;
this->normalMethod = ds.defaultNormalMethod;
this->angleThreshold = ds.defaultAngleThreshold;
this->maxTranslation = ds.defaultMaxTranslation;
this->maxRotation = ds.defaultMaxRotation;
this->minGradientMagnitude = ds.defaultMinGradientMagnitude;
this->minGradientMagnitudes = ds.defaultMinGradientMagnitudes;
}
void OdometrySettingsImplCommon::setCameraMatrix(InputArray val)
{
if (!val.empty())
{
CV_Assert(val.rows() == 3 && val.cols() == 3 && val.channels() == 1);
CV_Assert(val.type() == CV_32F);
val.copyTo(cameraMatrix);
}
else
{
DefaultSets ds;
this->cameraMatrix = ds.defaultCameraMatrix;
}
}
void OdometrySettingsImplCommon::getCameraMatrix(OutputArray val) const
{
Mat(this->cameraMatrix).copyTo(val);
}
void OdometrySettingsImplCommon::setIterCounts(InputArray val)
{
if (!val.empty())
{
size_t nLevels = val.size(-1).width;
std::vector<Mat> pyramids;
val.getMatVector(pyramids);
this->iterCounts.clear();
for (size_t i = 0; i < nLevels; i++)
this->iterCounts.push_back(pyramids[i].at<int>(0));
}
else
{
DefaultSets ds;
this->iterCounts = ds.defaultIterCounts;
}
}
void OdometrySettingsImplCommon::getIterCounts(OutputArray val) const
{
Mat(this->iterCounts).copyTo(val);
}
void OdometrySettingsImplCommon::setMinDepth(float val)
{
this->minDepth = val;
}
float OdometrySettingsImplCommon::getMinDepth() const
{
return this->minDepth;
}
void OdometrySettingsImplCommon::setMaxDepth(float val)
{
this->maxDepth = val;
}
float OdometrySettingsImplCommon::getMaxDepth() const
{
return this->maxDepth;
}
void OdometrySettingsImplCommon::setMaxDepthDiff(float val)
{
this->maxDepthDiff = val;
}
float OdometrySettingsImplCommon::getMaxDepthDiff() const
{
return this->maxDepthDiff;
}
void OdometrySettingsImplCommon::setMaxPointsPart(float val)
{
this->maxPointsPart = val;
}
float OdometrySettingsImplCommon::getMaxPointsPart() const
{
return this->maxPointsPart;
}
void OdometrySettingsImplCommon::setSobelSize(int val)
{
this->sobelSize = val;
}
int OdometrySettingsImplCommon::getSobelSize() const
{
return this->sobelSize;
}
void OdometrySettingsImplCommon::setSobelScale(double val)
{
this->sobelScale = val;
}
double OdometrySettingsImplCommon::getSobelScale() const
{
return this->sobelScale;
}
void OdometrySettingsImplCommon::setNormalWinSize(int val)
{
this->normalWinSize = val;
}
int OdometrySettingsImplCommon::getNormalWinSize() const
{
return this->normalWinSize;
}
void OdometrySettingsImplCommon::setNormalDiffThreshold(float val)
{
this->normalDiffThreshold = val;
}
float OdometrySettingsImplCommon::getNormalDiffThreshold() const
{
return this->normalDiffThreshold;
}
void OdometrySettingsImplCommon::setNormalMethod(RgbdNormals::RgbdNormalsMethod nm)
{
this->normalMethod = nm;
}
RgbdNormals::RgbdNormalsMethod OdometrySettingsImplCommon::getNormalMethod() const
{
return this->normalMethod;
}
void OdometrySettingsImplCommon::setAngleThreshold(float val)
{
this->angleThreshold = val;
}
float OdometrySettingsImplCommon::getAngleThreshold() const
{
return this->angleThreshold;
}
void OdometrySettingsImplCommon::setMaxTranslation(float val)
{
this->maxTranslation = val;
}
float OdometrySettingsImplCommon::getMaxTranslation() const
{
return this->maxTranslation;
}
void OdometrySettingsImplCommon::setMaxRotation(float val)
{
this->maxRotation = val;
}
float OdometrySettingsImplCommon::getMaxRotation() const
{
return this->maxRotation;
}
void OdometrySettingsImplCommon::setMinGradientMagnitude(float val)
{
this->minGradientMagnitude = val;
}
float OdometrySettingsImplCommon::getMinGradientMagnitude() const
{
return this->minGradientMagnitude;
}
void OdometrySettingsImplCommon::setMinGradientMagnitudes(InputArray val)
{
if (!val.empty())
{
size_t nLevels = val.size(-1).width;
std::vector<Mat> pyramids;
val.getMatVector(pyramids);
this->minGradientMagnitudes.clear();
for (size_t i = 0; i < nLevels; i++)
this->minGradientMagnitudes.push_back(pyramids[i].at<float>(0));
}
else
{
DefaultSets ds;
this->minGradientMagnitudes = ds.defaultMinGradientMagnitudes;
}
}
void OdometrySettingsImplCommon::getMinGradientMagnitudes(OutputArray val) const
{
Mat(this->minGradientMagnitudes).copyTo(val);
}
}
+653
View File
@@ -0,0 +1,653 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// This code is also subject to the license terms in the LICENSE_KinectFusion.md file found in this module's directory
#define USE_INTERPOLATION_IN_GETNORMAL 1
#define HASH_DIVISOR 32768
typedef char int8_t;
typedef uint int32_t;
typedef int8_t TsdfType;
typedef uchar WeightType;
struct TsdfVoxel
{
TsdfType tsdf;
WeightType weight;
};
static inline TsdfType floatToTsdf(float num)
{
int8_t res = (int8_t) ( (num * (-128)) );
res = res ? res : (num < 0 ? 1 : -1);
return res;
}
static inline float tsdfToFloat(TsdfType num)
{
return ( (float) num ) / (-128);
}
static uint calc_hash(int3 x)
{
unsigned int seed = 0;
unsigned int GOLDEN_RATIO = 0x9e3779b9;
seed ^= x.s0 + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
seed ^= x.s1 + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
seed ^= x.s2 + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
return seed;
}
//TODO: make hashDivisor a power of 2
//TODO: put it to this .cl file as a constant
static int custom_find(int3 idx, const int hashDivisor, __global const int* hashes,
__global const int4* data)
{
int hash = calc_hash(idx) % hashDivisor;
int place = hashes[hash];
// search a place
while (place >= 0)
{
if (all(data[place].s012 == idx))
break;
else
place = data[place].s3;
}
return place;
}
static void integrateVolumeUnit(
int x, int y,
__global const char * depthptr,
int depth_step, int depth_offset,
int depth_rows, int depth_cols,
__global struct TsdfVoxel * volumeptr,
const __global char * pixNormsPtr,
int pixNormsStep, int pixNormsOffset,
int pixNormsRows, int pixNormsCols,
const float16 vol2camMatrix,
const float voxelSize,
const int4 volResolution4,
const int4 volStrides4,
const float2 fxy,
const float2 cxy,
const float dfac,
const float truncDist,
const int maxWeight
)
{
const int3 volResolution = volResolution4.xyz;
if(x >= volResolution.x || y >= volResolution.y)
return;
// coord-independent constants
const int3 volStrides = volStrides4.xyz;
const float2 limits = (float2)(depth_cols-1, depth_rows-1);
const float4 vol2cam0 = vol2camMatrix.s0123;
const float4 vol2cam1 = vol2camMatrix.s4567;
const float4 vol2cam2 = vol2camMatrix.s89ab;
const float truncDistInv = 1.f/truncDist;
// optimization of camSpace transformation (vector addition instead of matmul at each z)
float4 inPt = (float4)(x*voxelSize, y*voxelSize, 0, 1);
float3 basePt = (float3)(dot(vol2cam0, inPt),
dot(vol2cam1, inPt),
dot(vol2cam2, inPt));
float3 camSpacePt = basePt;
// zStep == vol2cam*(float3(x, y, 1)*voxelSize) - basePt;
float3 zStep = ((float3)(vol2cam0.z, vol2cam1.z, vol2cam2.z))*voxelSize;
int volYidx = x*volStrides.x + y*volStrides.y;
int startZ, endZ;
if(fabs(zStep.z) > 1e-5f)
{
int baseZ = convert_int(-basePt.z / zStep.z);
if(zStep.z > 0)
{
startZ = baseZ;
endZ = volResolution.z;
}
else
{
startZ = 0;
endZ = baseZ;
}
}
else
{
if(basePt.z > 0)
{
startZ = 0; endZ = volResolution.z;
}
else
{
// z loop shouldn't be performed
startZ = endZ = 0;
}
}
startZ = max(0, startZ);
endZ = min(volResolution.z, endZ);
for(int z = startZ; z < endZ; z++)
{
// optimization of the following:
//float3 camSpacePt = vol2cam * ((float3)(x, y, z)*voxelSize);
camSpacePt += zStep;
if(camSpacePt.z <= 0)
continue;
float3 camPixVec = camSpacePt / camSpacePt.z;
float2 projected = mad(camPixVec.xy, fxy, cxy); // mad(a,b,c) = a * b + c
float v;
// bilinearly interpolate depth at projected
if(all(projected >= 0) && all(projected < limits))
{
float2 ip = floor(projected);
int xi = ip.x, yi = ip.y;
__global const float* row0 = (__global const float*)(depthptr + depth_offset +
(yi+0)*depth_step);
__global const float* row1 = (__global const float*)(depthptr + depth_offset +
(yi+1)*depth_step);
float v00 = row0[xi+0];
float v01 = row0[xi+1];
float v10 = row1[xi+0];
float v11 = row1[xi+1];
float4 vv = (float4)(v00, v01, v10, v11);
// assume correct depth is positive
if(all(vv > 0))
{
float2 t = projected - ip;
float2 vf = mix(vv.xz, vv.yw, t.x);
v = mix(vf.s0, vf.s1, t.y);
}
else
continue;
}
else
continue;
if(v == 0)
continue;
int2 projInt = convert_int2(projected);
float pixNorm = *(__global const float*)(pixNormsPtr + pixNormsOffset + projInt.y*pixNormsStep + projInt.x*sizeof(float));
//float pixNorm = length(camPixVec);
// difference between distances of point and of surface to camera
float sdf = pixNorm*(v*dfac - camSpacePt.z);
// possible alternative is:
// float sdf = length(camSpacePt)*(v*dfac/camSpacePt.z - 1.0);
if(sdf >= -truncDist)
{
float tsdf = fmin(1.0f, sdf * truncDistInv);
int volIdx = volYidx + z*volStrides.z;
struct TsdfVoxel voxel = volumeptr[volIdx];
float value = tsdfToFloat(voxel.tsdf);
int weight = voxel.weight;
// update TSDF
value = (value*weight + tsdf) / (weight + 1);
weight = min(weight + 1, maxWeight);
voxel.tsdf = floatToTsdf(value);
voxel.weight = weight;
volumeptr[volIdx] = voxel;
}
}
}
__kernel void integrateAllVolumeUnits(
// depth
__global const char * depthptr,
int depth_step, int depth_offset,
int depth_rows, int depth_cols,
// hashMap
__global const int* hashes,
__global const int4* data,
// volUnitsData
__global struct TsdfVoxel * allVolumePtr,
int table_step, int table_offset,
int table_rows, int table_cols,
// pixNorms
const __global char * pixNormsPtr,
int pixNormsStep, int pixNormsOffset,
int pixNormsRows, int pixNormsCols,
// isActiveFlags
__global const uchar* isActiveFlagsPtr,
int isActiveFlagsStep, int isActiveFlagsOffset,
int isActiveFlagsRows, int isActiveFlagsCols,
// cam matrix:
const float16 vol2cam,
// scalars:
const float voxelSize,
const int volUnitResolution,
const int4 volStrides4,
const float2 fxy,
const float2 cxy,
const float dfac,
const float truncDist,
const int maxWeight
)
{
const int hash_divisor = HASH_DIVISOR;
int i = get_global_id(0);
int j = get_global_id(1);
int row = get_global_id(2);
int3 idx = data[row].xyz;
const int4 volResolution4 = (int4)(volUnitResolution,
volUnitResolution,
volUnitResolution,
volUnitResolution);
int isActive = *(__global const uchar*)(isActiveFlagsPtr + isActiveFlagsOffset + row);
if (isActive)
{
int volCubed = volUnitResolution * volUnitResolution * volUnitResolution;
__global struct TsdfVoxel * volumeptr = (__global struct TsdfVoxel*)
(allVolumePtr + table_offset + row * volCubed);
// volUnit2cam = world2cam * volUnit2world =
// camPoseInv * volUnitPose = camPoseInv * (volPose + volPoseRot*(idx * volUnitSize)) =
// camPoseInv * (volPose + volPoseRot*(idx * volUnitResolution * voxelSize)) =
// camPoseInv * (volPose + volPoseRot*mulIdx) = camPoseInv * volPose + camPoseInv * volPoseRot * mulIdx =
// vol2cam + camPoseInv * volPoseRot * mulIdx
float3 mulIdx = convert_float3(idx * volUnitResolution) * voxelSize;
float16 volUnit2cam = vol2cam;
volUnit2cam.s37b += (float3)(dot(mulIdx, vol2cam.s012),
dot(mulIdx, vol2cam.s456),
dot(mulIdx, vol2cam.s89a));
integrateVolumeUnit(
i, j,
depthptr,
depth_step, depth_offset,
depth_rows, depth_cols,
volumeptr,
pixNormsPtr,
pixNormsStep, pixNormsOffset,
pixNormsRows, pixNormsCols,
volUnit2cam,
voxelSize,
volResolution4,
volStrides4,
fxy,
cxy,
dfac,
truncDist,
maxWeight
);
}
}
static struct TsdfVoxel at(int3 volumeIdx, int row, int volumeUnitDegree,
int3 volStrides, __global const struct TsdfVoxel * allVolumePtr, int table_offset)
{
//! Out of bounds
if (any(volumeIdx >= (1 << volumeUnitDegree)) ||
any(volumeIdx < 0))
{
struct TsdfVoxel dummy;
dummy.tsdf = floatToTsdf(1.0f);
dummy.weight = 0;
return dummy;
}
int volCubed = 1 << (volumeUnitDegree*3);
__global struct TsdfVoxel * volData = (__global struct TsdfVoxel*)
(allVolumePtr + table_offset + row * volCubed);
int3 ismul = volumeIdx * volStrides;
int coordBase = ismul.x + ismul.y + ismul.z;
return volData[coordBase];
}
static struct TsdfVoxel atVolumeUnit(int3 volumeIdx, int3 volumeUnitIdx, int row,
int volumeUnitDegree, int3 volStrides,
__global const struct TsdfVoxel * allVolumePtr, int table_offset)
{
//! Out of bounds
if (row < 0)
{
struct TsdfVoxel dummy;
dummy.tsdf = floatToTsdf(1.0f);
dummy.weight = 0;
return dummy;
}
int3 volUnitLocalIdx = volumeIdx - (volumeUnitIdx << volumeUnitDegree);
int volCubed = 1 << (volumeUnitDegree*3);
__global struct TsdfVoxel * volData = (__global struct TsdfVoxel*)
(allVolumePtr + table_offset + row * volCubed);
int3 ismul = volUnitLocalIdx * volStrides;
int coordBase = ismul.x + ismul.y + ismul.z;
return volData[coordBase];
}
inline float interpolate(float3 t, float8 vz)
{
float4 vy = mix(vz.s0246, vz.s1357, t.z);
float2 vx = mix(vy.s02, vy.s13, t.y);
return mix(vx.s0, vx.s1, t.x);
}
inline float3 getNormalVoxel(float3 ptVox, __global const struct TsdfVoxel* allVolumePtr,
int volumeUnitDegree,
const int hash_divisor,
__global const int* hashes,
__global const int4* data,
int3 volStrides, int table_offset)
{
float3 normal = (float3) (0.0f, 0.0f, 0.0f);
float3 fip = floor(ptVox);
int3 iptVox = convert_int3(fip);
// A small hash table to reduce a number of findRow() calls
// -2 and lower means not queried yet
// -1 means not found
// 0+ means found
int iterMap[8];
for (int i = 0; i < 8; i++)
{
iterMap[i] = -2;
}
#if !USE_INTERPOLATION_IN_GETNORMAL
int4 offsets[] = { (int4)( 1, 0, 0, 0), (int4)(-1, 0, 0, 0), (int4)( 0, 1, 0, 0), // 0-3
(int4)( 0, -1, 0, 0), (int4)( 0, 0, 1, 0), (int4)( 0, 0, -1, 0) // 4-7
};
const int nVals = 6;
float vals[6];
#else
int4 offsets[]={(int4)( 0, 0, 0, 0), (int4)( 0, 0, 1, 0), (int4)( 0, 1, 0, 0), (int4)( 0, 1, 1, 0), // 0-3
(int4)( 1, 0, 0, 0), (int4)( 1, 0, 1, 0), (int4)( 1, 1, 0, 0), (int4)( 1, 1, 1, 0), // 4-7
(int4)(-1, 0, 0, 0), (int4)(-1, 0, 1, 0), (int4)(-1, 1, 0, 0), (int4)(-1, 1, 1, 0), // 8-11
(int4)( 2, 0, 0, 0), (int4)( 2, 0, 1, 0), (int4)( 2, 1, 0, 0), (int4)( 2, 1, 1, 0), // 12-15
(int4)( 0, -1, 0, 0), (int4)( 0, -1, 1, 0), (int4)( 1, -1, 0, 0), (int4)( 1, -1, 1, 0), // 16-19
(int4)( 0, 2, 0, 0), (int4)( 0, 2, 1, 0), (int4)( 1, 2, 0, 0), (int4)( 1, 2, 1, 0), // 20-23
(int4)( 0, 0, -1, 0), (int4)( 0, 1, -1, 0), (int4)( 1, 0, -1, 0), (int4)( 1, 1, -1, 0), // 24-27
(int4)( 0, 0, 2, 0), (int4)( 0, 1, 2, 0), (int4)( 1, 0, 2, 0), (int4)( 1, 1, 2, 0), // 28-31
};
const int nVals = 32;
float vals[32];
#endif
for (int i = 0; i < nVals; i++)
{
int3 pt = iptVox + offsets[i].s012;
// VoxelToVolumeUnitIdx()
int3 volumeUnitIdx = pt >> volumeUnitDegree;
int3 vand = (volumeUnitIdx & 1);
int dictIdx = vand.s0 + vand.s1 * 2 + vand.s2 * 4;
int it = iterMap[dictIdx];
if (it < -1)
{
it = custom_find(volumeUnitIdx, hash_divisor, hashes, data);
iterMap[dictIdx] = it;
}
struct TsdfVoxel tmp = atVolumeUnit(pt, volumeUnitIdx, it, volumeUnitDegree, volStrides, allVolumePtr, table_offset);
vals[i] = tsdfToFloat( tmp.tsdf );
}
#if !USE_INTERPOLATION_IN_GETNORMAL
float3 pv, nv;
pv = (float3)(vals[0*2 ], vals[1*2 ], vals[2*2 ]);
nv = (float3)(vals[0*2+1], vals[1*2+1], vals[2*2+1]);
normal = pv - nv;
#else
float cxv[8], cyv[8], czv[8];
// How these numbers were obtained:
// 1. Take the basic interpolation sequence:
// 000, 001, 010, 011, 100, 101, 110, 111
// where each digit corresponds to shift by x, y, z axis respectively.
// 2. Add +1 for next or -1 for prev to each coordinate to corresponding axis
// 3. Search corresponding values in offsets
const int idxxn[8] = { 8, 9, 10, 11, 0, 1, 2, 3 };
const int idxxp[8] = { 4, 5, 6, 7, 12, 13, 14, 15 };
const int idxyn[8] = { 16, 17, 0, 1, 18, 19, 4, 5 };
const int idxyp[8] = { 2, 3, 20, 21, 6, 7, 22, 23 };
const int idxzn[8] = { 24, 0, 25, 2, 26, 4, 27, 6 };
const int idxzp[8] = { 1, 28, 3, 29, 5, 30, 7, 31 };
float vcxp[8], vcxn[8];
float vcyp[8], vcyn[8];
float vczp[8], vczn[8];
for (int i = 0; i < 8; i++)
{
vcxp[i] = vals[idxxp[i]]; vcxn[i] = vals[idxxn[i]];
vcyp[i] = vals[idxyp[i]]; vcyn[i] = vals[idxyn[i]];
vczp[i] = vals[idxzp[i]]; vczn[i] = vals[idxzn[i]];
}
float8 cxp = vload8(0, vcxp), cxn = vload8(0, vcxn);
float8 cyp = vload8(0, vcyp), cyn = vload8(0, vcyn);
float8 czp = vload8(0, vczp), czn = vload8(0, vczn);
float8 cx = cxp - cxn;
float8 cy = cyp - cyn;
float8 cz = czp - czn;
float3 tv = ptVox - fip;
normal.x = interpolate(tv, cx);
normal.y = interpolate(tv, cy);
normal.z = interpolate(tv, cz);
#endif
float norm = sqrt(dot(normal, normal));
return norm < 0.0001f ? nan((uint)0) : normal / norm;
}
typedef float4 ptype;
__kernel void raycast(
__global const int* hashes,
__global const int4* data,
__global char * pointsptr,
int points_step, int points_offset,
__global char * normalsptr,
int normals_step, int normals_offset,
const int2 frameSize,
__global const struct TsdfVoxel * allVolumePtr,
int table_step, int table_offset,
int table_rows, int table_cols,
float16 cam2volRotGPU,
float16 vol2camRotGPU,
float truncateThreshold,
const float2 fixy, const float2 cxy,
const float4 boxDown4, const float4 boxUp4,
const float tstep,
const float voxelSize,
const float voxelSizeInv,
float volumeUnitSize,
float truncDist,
int volumeUnitDegree,
int4 volStrides4
)
{
const int hash_divisor = HASH_DIVISOR;
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= frameSize.x || y >= frameSize.y)
return;
float3 point = nan((uint)0);
float3 normal = nan((uint)0);
const float3 camRot0 = cam2volRotGPU.s012;
const float3 camRot1 = cam2volRotGPU.s456;
const float3 camRot2 = cam2volRotGPU.s89a;
const float3 camTrans = cam2volRotGPU.s37b;
const float3 volRot0 = vol2camRotGPU.s012;
const float3 volRot1 = vol2camRotGPU.s456;
const float3 volRot2 = vol2camRotGPU.s89a;
const float3 volTrans = vol2camRotGPU.s37b;
float3 planed = (float3)(((float2)(x, y) - cxy)*fixy, 1.f);
planed = (float3)(dot(planed, camRot0),
dot(planed, camRot1),
dot(planed, camRot2));
float3 orig = (float3) (camTrans.s0, camTrans.s1, camTrans.s2);
float3 dir = fast_normalize(planed);
float3 origScaled = orig * voxelSizeInv;
float3 dirScaled = dir * voxelSizeInv;
float tmin = 0;
float tmax = truncateThreshold;
float tcurr = tmin;
float tprev = tcurr;
float prevTsdf = truncDist;
int3 volStrides = volStrides4.xyz;
while (tcurr < tmax)
{
float3 currRayPosVox = origScaled + tcurr * dirScaled;
// VolumeToVolumeUnitIdx()
int3 currVoxel = convert_int3(floor(currRayPosVox));
int3 currVolumeUnitIdx = currVoxel >> volumeUnitDegree;
int row = custom_find(currVolumeUnitIdx, hash_divisor, hashes, data);
float currTsdf = prevTsdf;
int currWeight = 0;
float stepSize = 0.5 * volumeUnitSize;
int3 volUnitLocalIdx;
if (row >= 0)
{
volUnitLocalIdx = currVoxel - (currVolumeUnitIdx << volumeUnitDegree);
struct TsdfVoxel currVoxel = at(volUnitLocalIdx, row, volumeUnitDegree, volStrides, allVolumePtr, table_offset);
currTsdf = tsdfToFloat(currVoxel.tsdf);
currWeight = currVoxel.weight;
stepSize = tstep;
}
if (prevTsdf > 0.f && currTsdf <= 0.f && currWeight > 0)
{
float tInterp = (tcurr * prevTsdf - tprev * currTsdf) / (prevTsdf - currTsdf);
if ( !isnan(tInterp) && !isinf(tInterp) )
{
float3 pvox = origScaled + tInterp * dirScaled;
float3 nv = getNormalVoxel( pvox, allVolumePtr, volumeUnitDegree,
hash_divisor, hashes, data,
volStrides, table_offset);
if(!any(isnan(nv)))
{
//convert pv and nv to camera space
normal = (float3)(dot(nv, volRot0),
dot(nv, volRot1),
dot(nv, volRot2));
// interpolation optimized a little
float3 pv = pvox * voxelSize;
point = (float3)(dot(pv, volRot0),
dot(pv, volRot1),
dot(pv, volRot2)) + volTrans;
}
}
break;
}
prevTsdf = currTsdf;
tprev = tcurr;
tcurr += stepSize;
}
__global float* pts = (__global float*)(pointsptr + points_offset + y*points_step + x*sizeof(ptype));
__global float* nrm = (__global float*)(normalsptr + normals_offset + y*normals_step + x*sizeof(ptype));
vstore4((float4)(point, 0), 0, pts);
vstore4((float4)(normal, 0), 0, nrm);
}
__kernel void markActive (
__global const int4* hashSetData,
__global char* isActiveFlagsPtr,
int isActiveFlagsStep, int isActiveFlagsOffset,
int isActiveFlagsRows, int isActiveFlagsCols,
__global char* lastVisibleIndicesPtr,
int lastVisibleIndicesStep, int lastVisibleIndicesOffset,
int lastVisibleIndicesRows, int lastVisibleIndicesCols,
const float16 vol2cam,
const float2 fxy,
const float2 cxy,
const int2 frameSz,
const float volumeUnitSize,
const int lastVolIndex,
const float truncateThreshold,
const int frameId
)
{
const int hash_divisor = HASH_DIVISOR;
int row = get_global_id(0);
if (row < lastVolIndex)
{
int3 idx = hashSetData[row].xyz;
float3 volumeUnitPos = convert_float3(idx) * volumeUnitSize;
float3 volUnitInCamSpace = (float3) (dot(volumeUnitPos, vol2cam.s012),
dot(volumeUnitPos, vol2cam.s456),
dot(volumeUnitPos, vol2cam.s89a)) + vol2cam.s37b;
if (volUnitInCamSpace.z < 0 || volUnitInCamSpace.z > truncateThreshold)
{
*(isActiveFlagsPtr + isActiveFlagsOffset + row * isActiveFlagsStep) = 0;
return;
}
float2 cameraPoint;
float invz = 1.f / volUnitInCamSpace.z;
cameraPoint = fxy * volUnitInCamSpace.xy * invz + cxy;
if (all(cameraPoint >= 0) && all(cameraPoint < convert_float2(frameSz)))
{
*(__global int*)(lastVisibleIndicesPtr + lastVisibleIndicesOffset + row * lastVisibleIndicesStep) = frameId;
*(isActiveFlagsPtr + isActiveFlagsOffset + row * isActiveFlagsStep) = 1;
}
}
}
+239
View File
@@ -0,0 +1,239 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
#define UTSIZE 27
typedef float4 ptype;
/*
Calculate an upper triangle of Ab matrix then reduce it across workgroup
*/
inline void calcAb7(__global const char * oldPointsptr,
int oldPoints_step, int oldPoints_offset,
__global const char * oldNormalsptr,
int oldNormals_step, int oldNormals_offset,
const int2 oldSize,
__global const char * newPointsptr,
int newPoints_step, int newPoints_offset,
__global const char * newNormalsptr,
int newNormals_step, int newNormals_offset,
const int2 newSize,
const float16 poseMatrix,
const float2 fxy,
const float2 cxy,
const float sqDistanceThresh,
const float minCos,
float* ab7
)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
if(x >= newSize.x || y >= newSize.y)
return;
// coord-independent constants
const float3 poseRot0 = poseMatrix.s012;
const float3 poseRot1 = poseMatrix.s456;
const float3 poseRot2 = poseMatrix.s89a;
const float3 poseTrans = poseMatrix.s37b;
const float2 oldEdge = (float2)(oldSize.x - 1, oldSize.y - 1);
__global const ptype* newPtsRow = (__global const ptype*)(newPointsptr +
newPoints_offset +
y*newPoints_step);
__global const ptype* newNrmRow = (__global const ptype*)(newNormalsptr +
newNormals_offset +
y*newNormals_step);
float3 newP = newPtsRow[x].xyz;
float3 newN = newNrmRow[x].xyz;
if( any(isnan(newP)) || any(isnan(newN)) ||
any(isinf(newP)) || any(isinf(newN)) )
return;
//transform to old coord system
newP = (float3)(dot(newP, poseRot0),
dot(newP, poseRot1),
dot(newP, poseRot2)) + poseTrans;
newN = (float3)(dot(newN, poseRot0),
dot(newN, poseRot1),
dot(newN, poseRot2));
//find correspondence by projecting the point
float2 oldCoords = (newP.xy/newP.z)*fxy+cxy;
if(!(all(oldCoords >= 0.f) && all(oldCoords < oldEdge)))
return;
// bilinearly interpolate oldPts and oldNrm under oldCoords point
float3 oldP, oldN;
float2 ip = floor(oldCoords);
float2 t = oldCoords - ip;
int xi = ip.x, yi = ip.y;
__global const ptype* prow0 = (__global const ptype*)(oldPointsptr +
oldPoints_offset +
(yi+0)*oldPoints_step);
__global const ptype* prow1 = (__global const ptype*)(oldPointsptr +
oldPoints_offset +
(yi+1)*oldPoints_step);
float3 p00 = prow0[xi+0].xyz;
float3 p01 = prow0[xi+1].xyz;
float3 p10 = prow1[xi+0].xyz;
float3 p11 = prow1[xi+1].xyz;
// NaN check is done later
__global const ptype* nrow0 = (__global const ptype*)(oldNormalsptr +
oldNormals_offset +
(yi+0)*oldNormals_step);
__global const ptype* nrow1 = (__global const ptype*)(oldNormalsptr +
oldNormals_offset +
(yi+1)*oldNormals_step);
float3 n00 = nrow0[xi+0].xyz;
float3 n01 = nrow0[xi+1].xyz;
float3 n10 = nrow1[xi+0].xyz;
float3 n11 = nrow1[xi+1].xyz;
// NaN check is done later
float3 p0 = mix(p00, p01, t.x);
float3 p1 = mix(p10, p11, t.x);
oldP = mix(p0, p1, t.y);
float3 n0 = mix(n00, n01, t.x);
float3 n1 = mix(n10, n11, t.x);
oldN = mix(n0, n1, t.y);
if( any(isnan(oldP)) || any(isnan(oldN)) ||
any(isinf(oldP)) || any(isinf(oldN)) )
return;
//filter by distance
float3 diff = newP - oldP;
if(dot(diff, diff) > sqDistanceThresh)
return;
//filter by angle
if(fabs(dot(newN, oldN)) < minCos)
return;
// build point-wise vector ab = [ A | b ]
float3 VxN = cross(newP, oldN);
float ab[7] = {VxN.x, VxN.y, VxN.z, oldN.x, oldN.y, oldN.z, -dot(oldN, diff)};
for(int i = 0; i < 7; i++)
ab7[i] = ab[i];
}
__kernel void getAb(__global const char * oldPointsptr,
int oldPoints_step, int oldPoints_offset,
__global const char * oldNormalsptr,
int oldNormals_step, int oldNormals_offset,
const int2 oldSize,
__global const char * newPointsptr,
int newPoints_step, int newPoints_offset,
__global const char * newNormalsptr,
int newNormals_step, int newNormals_offset,
const int2 newSize,
const float16 poseMatrix,
const float2 fxy,
const float2 cxy,
const float sqDistanceThresh,
const float minCos,
__local float * reducebuf,
__global char* groupedSumptr,
int groupedSum_step, int groupedSum_offset
)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
const int gx = get_group_id(0);
const int gy = get_group_id(1);
const int gw = get_num_groups(0);
const int gh = get_num_groups(1);
const int lx = get_local_id(0);
const int ly = get_local_id(1);
const int lw = get_local_size(0);
const int lh = get_local_size(1);
const int lsz = lw*lh;
const int lid = lx + ly*lw;
float ab[7];
for(int i = 0; i < 7; i++)
ab[i] = 0;
calcAb7(oldPointsptr,
oldPoints_step, oldPoints_offset,
oldNormalsptr,
oldNormals_step, oldNormals_offset,
oldSize,
newPointsptr,
newPoints_step, newPoints_offset,
newNormalsptr,
newNormals_step, newNormals_offset,
newSize,
poseMatrix,
fxy, cxy,
sqDistanceThresh,
minCos,
ab);
// build point-wise upper-triangle matrix [ab^T * ab] w/o last row
// which is [A^T*A | A^T*b]
// and gather sum
__local float* upperTriangle = reducebuf + lid*UTSIZE;
int pos = 0;
for(int i = 0; i < 6; i++)
{
for(int j = i; j < 7; j++)
{
upperTriangle[pos++] = ab[i]*ab[j];
}
}
// reduce upperTriangle to local mem
// maxStep = ctz(lsz), ctz isn't supported on CUDA devices
const int c = clz(lsz & -lsz);
const int maxStep = c ? 31 - c : c;
for(int nstep = 1; nstep <= maxStep; nstep++)
{
if(lid % (1 << nstep) == 0)
{
__local float* rto = reducebuf + UTSIZE*lid;
__local float* rfrom = reducebuf + UTSIZE*(lid+(1 << (nstep-1)));
for(int i = 0; i < UTSIZE; i++)
rto[i] += rfrom[i];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
// here group sum should be in reducebuf[0...UTSIZE]
if(lid == 0)
{
__global float* groupedRow = (__global float*)(groupedSumptr +
groupedSum_offset +
gy*groupedSum_step);
for(int i = 0; i < UTSIZE; i++)
groupedRow[gx*UTSIZE + i] = reducebuf[i];
}
}
+288
View File
@@ -0,0 +1,288 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
inline float3 reproject(float3 p, float2 fxyinv, float2 cxy)
{
float2 pp = p.z*(p.xy - cxy)*fxyinv;
return (float3)(pp, p.z);
}
typedef float4 ptype;
__kernel void computePointsNormals(__global char * pointsptr,
int points_step, int points_offset,
__global char * normalsptr,
int normals_step, int normals_offset,
__global const char * depthptr,
int depth_step, int depth_offset,
int depth_rows, int depth_cols,
const float2 fxyinv,
const float2 cxy,
const float dfac
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= depth_cols || y >= depth_rows)
return;
__global const float* row0 = (__global const float*)(depthptr + depth_offset +
(y+0)*depth_step);
__global const float* row1 = (__global const float*)(depthptr + depth_offset +
(y+1)*depth_step);
float d00 = row0[x];
float z00 = d00*dfac;
float3 p00 = (float3)(convert_float2((int2)(x, y)), z00);
float3 v00 = reproject(p00, fxyinv, cxy);
float3 p = nan((uint)0), n = nan((uint)0);
if(x < depth_cols - 1 && y < depth_rows - 1)
{
float d01 = row0[x+1];
float d10 = row1[x];
float z01 = d01*dfac;
float z10 = d10*dfac;
if(z00 != 0 && z01 != 0 && z10 != 0)
{
float3 p01 = (float3)(convert_float2((int2)(x+1, y+0)), z01);
float3 p10 = (float3)(convert_float2((int2)(x+0, y+1)), z10);
float3 v01 = reproject(p01, fxyinv, cxy);
float3 v10 = reproject(p10, fxyinv, cxy);
float3 vec = cross(v01 - v00, v10 - v00);
n = - normalize(vec);
p = v00;
}
}
__global float* pts = (__global float*)(pointsptr + points_offset + y*points_step + x*sizeof(ptype));
__global float* nrm = (__global float*)(normalsptr + normals_offset + y*normals_step + x*sizeof(ptype));
vstore4((ptype)(p, 0), 0, pts);
vstore4((ptype)(n, 0), 0, nrm);
}
__kernel void pyrDownBilateral(__global const char * depthptr,
int depth_step, int depth_offset,
int depth_rows, int depth_cols,
__global char * depthDownptr,
int depthDown_step, int depthDown_offset,
int depthDown_rows, int depthDown_cols,
const float sigma
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= depthDown_cols || y >= depthDown_rows)
return;
const float sigma3 = sigma*3;
const int D = 5;
__global const float* srcCenterRow = (__global const float*)(depthptr + depth_offset +
(2*y)*depth_step);
float center = srcCenterRow[2*x];
int sx = max(0, 2*x - D/2), ex = min(2*x - D/2 + D, depth_cols-1);
int sy = max(0, 2*y - D/2), ey = min(2*y - D/2 + D, depth_rows-1);
float sum = 0;
int count = 0;
for(int iy = sy; iy < ey; iy++)
{
__global const float* srcRow = (__global const float*)(depthptr + depth_offset +
(iy)*depth_step);
for(int ix = sx; ix < ex; ix++)
{
float val = srcRow[ix];
if(fabs(val - center) < sigma3)
{
sum += val; count++;
}
}
}
__global float* downRow = (__global float*)(depthDownptr + depthDown_offset +
y*depthDown_step + x*sizeof(float));
*downRow = (count == 0) ? 0 : sum/convert_float(count);
}
//TODO: remove bilateral when OpenCV performs 32f bilat with OpenCL
__kernel void customBilateral(__global const char * srcptr,
int src_step, int src_offset,
__global char * dstptr,
int dst_step, int dst_offset,
const int2 frameSize,
const int kernelSize,
const float sigma_spatial2_inv_half,
const float sigma_depth2_inv_half
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= frameSize.x || y >= frameSize.y)
return;
__global const float* srcCenterRow = (__global const float*)(srcptr + src_offset +
y*src_step);
float value = srcCenterRow[x];
int tx = min (x - kernelSize / 2 + kernelSize, frameSize.x - 1);
int ty = min (y - kernelSize / 2 + kernelSize, frameSize.y - 1);
float sum1 = 0;
float sum2 = 0;
for (int cy = max (y - kernelSize / 2, 0); cy < ty; ++cy)
{
__global const float* srcRow = (__global const float*)(srcptr + src_offset +
cy*src_step);
for (int cx = max (x - kernelSize / 2, 0); cx < tx; ++cx)
{
float depth = srcRow[cx];
float space2 = convert_float((x - cx) * (x - cx) + (y - cy) * (y - cy));
float color2 = (value - depth) * (value - depth);
float weight = native_exp (-(space2 * sigma_spatial2_inv_half +
color2 * sigma_depth2_inv_half));
sum1 += depth * weight;
sum2 += weight;
}
}
__global float* dst = (__global float*)(dstptr + dst_offset +
y*dst_step + x*sizeof(float));
*dst = sum1/sum2;
}
__kernel void pyrDownPointsNormals(__global const char * pptr,
int p_step, int p_offset,
__global const char * nptr,
int n_step, int n_offset,
__global char * pdownptr,
int pdown_step, int pdown_offset,
__global char * ndownptr,
int ndown_step, int ndown_offset,
const int2 downSize
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= downSize.x || y >= downSize.y)
return;
float3 point = nan((uint)0), normal = nan((uint)0);
__global const ptype* pUpRow0 = (__global const ptype*)(pptr + p_offset + (2*y )*p_step);
__global const ptype* pUpRow1 = (__global const ptype*)(pptr + p_offset + (2*y+1)*p_step);
float3 d00 = pUpRow0[2*x ].xyz;
float3 d01 = pUpRow0[2*x+1].xyz;
float3 d10 = pUpRow1[2*x ].xyz;
float3 d11 = pUpRow1[2*x+1].xyz;
if(!(any(isnan(d00)) || any(isnan(d01)) ||
any(isnan(d10)) || any(isnan(d11))))
{
point = (d00 + d01 + d10 + d11)*0.25f;
__global const ptype* nUpRow0 = (__global const ptype*)(nptr + n_offset + (2*y )*n_step);
__global const ptype* nUpRow1 = (__global const ptype*)(nptr + n_offset + (2*y+1)*n_step);
float3 n00 = nUpRow0[2*x ].xyz;
float3 n01 = nUpRow0[2*x+1].xyz;
float3 n10 = nUpRow1[2*x ].xyz;
float3 n11 = nUpRow1[2*x+1].xyz;
normal = (n00 + n01 + n10 + n11)*0.25f;
}
__global ptype* pts = (__global ptype*)(pdownptr + pdown_offset + y*pdown_step);
__global ptype* nrm = (__global ptype*)(ndownptr + ndown_offset + y*ndown_step);
pts[x] = (ptype)(point, 0);
nrm[x] = (ptype)(normal, 0);
}
typedef char4 pixelType;
// 20 is fixed power
float specPow20(float x)
{
float x2 = x*x;
float x5 = x2*x2*x;
float x10 = x5*x5;
float x20 = x10*x10;
return x20;
}
__kernel void render(__global const char * pointsptr,
int points_step, int points_offset,
__global const char * normalsptr,
int normals_step, int normals_offset,
__global char * imgptr,
int img_step, int img_offset,
const int2 frameSize,
const float4 lightPt
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= frameSize.x || y >= frameSize.y)
return;
__global const ptype* ptsRow = (__global const ptype*)(pointsptr + points_offset + y*points_step + x*sizeof(ptype));
__global const ptype* nrmRow = (__global const ptype*)(normalsptr + normals_offset + y*normals_step + x*sizeof(ptype));
float3 p = (*ptsRow).xyz;
float3 n = (*nrmRow).xyz;
pixelType color;
if(any(isnan(p)))
{
color = (pixelType)(0, 32, 0, 0);
}
else
{
const float Ka = 0.3f; //ambient coeff
const float Kd = 0.5f; //diffuse coeff
const float Ks = 0.2f; //specular coeff
//const int sp = 20; //specular power, fixed in specPow20()
const float Ax = 1.f; //ambient color, can be RGB
const float Dx = 1.f; //diffuse color, can be RGB
const float Sx = 1.f; //specular color, can be RGB
const float Lx = 1.f; //light color
float3 l = normalize(lightPt.xyz - p);
float3 v = normalize(-p);
float3 r = normalize(2.f*n*dot(n, l) - l);
float val = (Ax*Ka*Dx + Lx*Kd*Dx*max(0.f, dot(n, l)) +
Lx*Ks*Sx*specPow20(max(0.f, dot(r, v))));
uchar ix = convert_uchar(val*255.f);
color = (pixelType)(ix, ix, ix, 0);
}
__global char* imgRow = (__global char*)(imgptr + img_offset + y*img_step + x*sizeof(pixelType));
vstore4(color, 0, imgRow);
}
+856
View File
@@ -0,0 +1,856 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
typedef char int8_t;
typedef int8_t TsdfType;
typedef uchar WeightType;
struct TsdfVoxel
{
TsdfType tsdf;
WeightType weight;
};
static inline TsdfType floatToTsdf(float num)
{
int8_t res = (int8_t) ( (num * (-128)) );
res = res ? res : (num < 0 ? 1 : -1);
return res;
}
static inline float tsdfToFloat(TsdfType num)
{
return ( (float) num ) / (-128);
}
__kernel void integrate(__global const char * depthptr,
int depth_step, int depth_offset,
int depth_rows, int depth_cols,
__global struct TsdfVoxel * volumeptr,
__global const float * vol2camptr,
const float voxelSize,
const int4 volResolution4,
const int4 volDims4,
const float2 fxy,
const float2 cxy,
const float dfac,
const float truncDist,
const int maxWeight,
const __global float * pixNorms)
{
int x = get_global_id(0);
int y = get_global_id(1);
const int3 volResolution = volResolution4.xyz;
if(x >= volResolution.x || y >= volResolution.y)
return;
// coord-independent constants
const int3 volDims = volDims4.xyz;
const float2 limits = (float2)(depth_cols-1, depth_rows-1);
__global const float* vm = vol2camptr;
const float4 vol2cam0 = vload4(0, vm);
const float4 vol2cam1 = vload4(1, vm);
const float4 vol2cam2 = vload4(2, vm);
const float truncDistInv = 1.f/truncDist;
// optimization of camSpace transformation (vector addition instead of matmul at each z)
float4 inPt = (float4)(x*voxelSize, y*voxelSize, 0, 1);
float3 basePt = (float3)(dot(vol2cam0, inPt),
dot(vol2cam1, inPt),
dot(vol2cam2, inPt));
float3 camSpacePt = basePt;
// zStep == vol2cam*(float3(x, y, 1)*voxelSize) - basePt;
float3 zStep = ((float3)(vol2cam0.z, vol2cam1.z, vol2cam2.z))*voxelSize;
int volYidx = x*volDims.x + y*volDims.y;
int startZ, endZ;
if(fabs(zStep.z) > 1e-5f)
{
int baseZ = convert_int(-basePt.z / zStep.z);
if(zStep.z > 0)
{
startZ = baseZ;
endZ = volResolution.z;
}
else
{
startZ = 0;
endZ = baseZ;
}
}
else
{
if(basePt.z > 0)
{
startZ = 0; endZ = volResolution.z;
}
else
{
// z loop shouldn't be performed
//startZ = endZ = 0;
return;
}
}
startZ = max(0, startZ);
endZ = min(volResolution.z, endZ);
for(int z = startZ; z < endZ; z++)
{
// optimization of the following:
//float3 camSpacePt = vol2cam * ((float3)(x, y, z)*voxelSize);
camSpacePt += zStep;
if(camSpacePt.z <= 0)
continue;
float3 camPixVec = camSpacePt / camSpacePt.z;
float2 projected = mad(camPixVec.xy, fxy, cxy);
float v;
// bilinearly interpolate depth at projected
if(all(projected >= 0) && all(projected < limits))
{
float2 ip = floor(projected);
int xi = ip.x, yi = ip.y;
__global const float* row0 = (__global const float*)(depthptr + depth_offset +
(yi+0)*depth_step);
__global const float* row1 = (__global const float*)(depthptr + depth_offset +
(yi+1)*depth_step);
float v00 = row0[xi+0];
float v01 = row0[xi+1];
float v10 = row1[xi+0];
float v11 = row1[xi+1];
float4 vv = (float4)(v00, v01, v10, v11);
// assume correct depth is positive
if(all(vv > 0))
{
float2 t = projected - ip;
float2 vf = mix(vv.xz, vv.yw, t.x);
v = mix(vf.s0, vf.s1, t.y);
}
else
continue;
}
else
continue;
if(v == 0)
continue;
int idx = projected.y * depth_cols + projected.x;
float pixNorm = pixNorms[idx];
//float pixNorm = length(camPixVec);
// difference between distances of point and of surface to camera
float sdf = pixNorm*(v*dfac - camSpacePt.z);
// possible alternative is:
// float sdf = length(camSpacePt)*(v*dfac/camSpacePt.z - 1.0);
if(sdf >= -truncDist)
{
float tsdf = fmin(1.0f, sdf * truncDistInv);
int volIdx = volYidx + z*volDims.z;
struct TsdfVoxel voxel = volumeptr[volIdx];
float value = tsdfToFloat(voxel.tsdf);
int weight = voxel.weight;
// update TSDF
value = (value*weight + tsdf) / (weight + 1);
weight = min(weight + 1, maxWeight);
voxel.tsdf = floatToTsdf(value);
voxel.weight = weight;
volumeptr[volIdx] = voxel;
}
}
}
inline float interpolateVoxel(float3 p, __global const struct TsdfVoxel* volumePtr,
int3 volDims, int8 neighbourCoords)
{
float3 fip = floor(p);
int3 ip = convert_int3(fip);
float3 t = p - fip;
int3 cmul = volDims*ip;
int coordBase = cmul.x + cmul.y + cmul.z;
int nco[8];
vstore8(neighbourCoords + coordBase, 0, nco);
float vaz[8];
for(int i = 0; i < 8; i++)
vaz[i] = tsdfToFloat(volumePtr[nco[i]].tsdf);
float8 vz = vload8(0, vaz);
float4 vy = mix(vz.s0246, vz.s1357, t.z);
float2 vx = mix(vy.s02, vy.s13, t.y);
return mix(vx.s0, vx.s1, t.x);
}
inline float3 getNormalVoxel(float3 p, __global const struct TsdfVoxel* volumePtr,
int3 volResolution, int3 volDims, int8 neighbourCoords)
{
if(any(p < 1) || any(p >= convert_float3(volResolution - 2)))
return nan((uint)0);
float3 fip = floor(p);
int3 ip = convert_int3(fip);
float3 t = p - fip;
int3 cmul = volDims*ip;
int coordBase = cmul.x + cmul.y + cmul.z;
int nco[8];
vstore8(neighbourCoords + coordBase, 0, nco);
int arDims[3];
vstore3(volDims, 0, arDims);
float an[3];
for(int c = 0; c < 3; c++)
{
int dim = arDims[c];
float vaz[8];
for(int i = 0; i < 8; i++)
vaz[i] = tsdfToFloat(volumePtr[nco[i] + dim].tsdf) -
tsdfToFloat(volumePtr[nco[i] - dim].tsdf);
float8 vz = vload8(0, vaz);
float4 vy = mix(vz.s0246, vz.s1357, t.z);
float2 vx = mix(vy.s02, vy.s13, t.y);
an[c] = mix(vx.s0, vx.s1, t.x);
}
//gradientDeltaFactor is fixed at 1.0 of voxel size
float3 n = vload3(0, an);
float Norm = sqrt(n.x*n.x + n.y*n.y + n.z*n.z);
return Norm < 0.0001f ? nan((uint)0) : n / Norm;
//return fast_normalize(vload3(0, an));
}
typedef float4 ptype;
__kernel void raycast(__global char * pointsptr,
int points_step, int points_offset,
__global char * normalsptr,
int normals_step, int normals_offset,
const int2 frameSize,
__global const struct TsdfVoxel * volumeptr,
__global const float * vol2camptr,
__global const float * cam2volptr,
const float2 fixy,
const float2 cxy,
const float4 boxDown4,
const float4 boxUp4,
const float tstep,
const float voxelSize,
const int4 volResolution4,
const int4 volDims4,
const int8 neighbourCoords
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= frameSize.x || y >= frameSize.y)
return;
// coordinate-independent constants
__global const float* cm = cam2volptr;
const float3 camRot0 = vload4(0, cm).xyz;
const float3 camRot1 = vload4(1, cm).xyz;
const float3 camRot2 = vload4(2, cm).xyz;
const float3 camTrans = (float3)(cm[3], cm[7], cm[11]);
__global const float* vm = vol2camptr;
const float3 volRot0 = vload4(0, vm).xyz;
const float3 volRot1 = vload4(1, vm).xyz;
const float3 volRot2 = vload4(2, vm).xyz;
const float3 volTrans = (float3)(vm[3], vm[7], vm[11]);
const float3 boxDown = boxDown4.xyz;
const float3 boxUp = boxUp4.xyz;
const int3 volDims = volDims4.xyz;
const int3 volResolution = volResolution4.xyz;
const float invVoxelSize = native_recip(voxelSize);
// kernel itself
float3 point = nan((uint)0);
float3 normal = nan((uint)0);
float3 orig = camTrans;
// get direction through pixel in volume space:
// 1. reproject (x, y) on projecting plane where z = 1.f
float3 planed = (float3)(((float2)(x, y) - cxy)*fixy, 1.f);
// 2. rotate to volume space
planed = (float3)(dot(planed, camRot0),
dot(planed, camRot1),
dot(planed, camRot2));
// 3. normalize
float3 dir = fast_normalize(planed);
// compute intersection of ray with all six bbox planes
float3 rayinv = native_recip(dir);
float3 tbottom = rayinv*(boxDown - orig);
float3 ttop = rayinv*(boxUp - orig);
// re-order intersections to find smallest and largest on each axis
float3 minAx = min(ttop, tbottom);
float3 maxAx = max(ttop, tbottom);
// near clipping plane
const float clip = 0.f;
float tmin = max(max(max(minAx.x, minAx.y), max(minAx.x, minAx.z)), clip);
float tmax = min(min(maxAx.x, maxAx.y), min(maxAx.x, maxAx.z));
// precautions against getting coordinates out of bounds
tmin = tmin + tstep;
tmax = tmax - tstep;
if(tmin < tmax)
{
// interpolation optimized a little
orig *= invVoxelSize;
dir *= invVoxelSize;
float3 rayStep = dir*tstep;
float3 next = (orig + dir*tmin);
float f = interpolateVoxel(next, volumeptr, volDims, neighbourCoords);
float fnext = f;
// raymarch
int steps = 0;
int nSteps = floor(native_divide(tmax - tmin, tstep));
bool stop = false;
for(int i = 0; i < nSteps; i++)
{
// fix for wrong steps counting
if(!stop)
{
next += rayStep;
// fetch voxel
int3 ip = convert_int3(round(next));
int3 cmul = ip*volDims;
int idx = cmul.x + cmul.y + cmul.z;
fnext = tsdfToFloat(volumeptr[idx].tsdf);
if(fnext != f)
{
fnext = interpolateVoxel(next, volumeptr, volDims, neighbourCoords);
// when ray crosses a surface
if(signbit(f) != signbit(fnext))
{
stop = true; continue;
}
f = fnext;
}
steps++;
}
}
// if ray penetrates a surface from outside
// linearly interpolate t between two f values
if(f > 0 && fnext < 0)
{
float3 tp = next - rayStep;
float ft = interpolateVoxel(tp, volumeptr, volDims, neighbourCoords);
float ftdt = interpolateVoxel(next, volumeptr, volDims, neighbourCoords);
// float t = tmin + steps*tstep;
// float ts = t - tstep*ft/(ftdt - ft);
float ts = tmin + tstep*(steps - native_divide(ft, ftdt - ft));
// avoid division by zero
if(!isnan(ts) && !isinf(ts))
{
float3 pv = orig + dir*ts;
float3 nv = getNormalVoxel(pv, volumeptr, volResolution, volDims, neighbourCoords);
if(!any(isnan(nv)))
{
//convert pv and nv to camera space
normal = (float3)(dot(nv, volRot0),
dot(nv, volRot1),
dot(nv, volRot2));
// interpolation optimized a little
pv *= voxelSize;
point = (float3)(dot(pv, volRot0),
dot(pv, volRot1),
dot(pv, volRot2)) + volTrans;
}
}
}
}
__global float* pts = (__global float*)(pointsptr + points_offset + y*points_step + x*sizeof(ptype));
__global float* nrm = (__global float*)(normalsptr + normals_offset + y*normals_step + x*sizeof(ptype));
vstore4((float4)(point, 0), 0, pts);
vstore4((float4)(normal, 0), 0, nrm);
}
__kernel void getNormals(__global const char * pointsptr,
int points_step, int points_offset,
__global char * normalsptr,
int normals_step, int normals_offset,
const int2 frameSize,
__global const struct TsdfVoxel* volumeptr,
__global const float * volPoseptr,
__global const float * invPoseptr,
const float voxelSizeInv,
const int4 volResolution4,
const int4 volDims4,
const int8 neighbourCoords
)
{
int x = get_global_id(0);
int y = get_global_id(1);
if(x >= frameSize.x || y >= frameSize.y)
return;
// coordinate-independent constants
__global const float* vp = volPoseptr;
const float3 volRot0 = vload4(0, vp).xyz;
const float3 volRot1 = vload4(1, vp).xyz;
const float3 volRot2 = vload4(2, vp).xyz;
const float3 volTrans = (float3)(vp[3], vp[7], vp[11]);
__global const float* iv = invPoseptr;
const float3 invRot0 = vload4(0, iv).xyz;
const float3 invRot1 = vload4(1, iv).xyz;
const float3 invRot2 = vload4(2, iv).xyz;
const float3 invTrans = (float3)(iv[3], iv[7], iv[11]);
const int3 volResolution = volResolution4.xyz;
const int3 volDims = volDims4.xyz;
// kernel itself
__global const ptype* ptsRow = (__global const ptype*)(pointsptr +
points_offset +
y*points_step);
float3 p = ptsRow[x].xyz;
float3 n = nan((uint)0);
if(!any(isnan(p)))
{
float3 voxPt = (float3)(dot(p, invRot0),
dot(p, invRot1),
dot(p, invRot2)) + invTrans;
voxPt = voxPt * voxelSizeInv;
n = getNormalVoxel(voxPt, volumeptr, volResolution, volDims, neighbourCoords);
n = (float3)(dot(n, volRot0),
dot(n, volRot1),
dot(n, volRot2));
}
__global float* nrm = (__global float*)(normalsptr +
normals_offset +
y*normals_step +
x*sizeof(ptype));
vstore4((float4)(n, 0), 0, nrm);
}
#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics:enable
struct CoordReturn
{
bool result;
float3 point;
float3 normal;
};
inline struct CoordReturn coord(int x, int y, int z, float3 V, float v0, int axis,
__global const struct TsdfVoxel* volumeptr,
int3 volResolution, int3 volDims,
int8 neighbourCoords,
float voxelSize, float voxelSizeInv,
const float3 volRot0,
const float3 volRot1,
const float3 volRot2,
const float3 volTrans,
bool needNormals,
bool scan
)
{
struct CoordReturn cr;
// 0 for x, 1 for y, 2 for z
bool limits = false;
int3 shift;
float Vc = 0.f;
if(axis == 0)
{
shift = (int3)(1, 0, 0);
limits = (x + 1 < volResolution.x);
Vc = V.x;
}
if(axis == 1)
{
shift = (int3)(0, 1, 0);
limits = (y + 1 < volResolution.y);
Vc = V.y;
}
if(axis == 2)
{
shift = (int3)(0, 0, 1);
limits = (z + 1 < volResolution.z);
Vc = V.z;
}
if(limits)
{
int3 ip = ((int3)(x, y, z)) + shift;
int3 cmul = ip*volDims;
int idx = cmul.x + cmul.y + cmul.z;
struct TsdfVoxel voxel = volumeptr[idx];
float vd = tsdfToFloat(voxel.tsdf);
int weight = voxel.weight;
if(weight != 0 && vd != 1.f)
{
if((v0 > 0 && vd < 0) || (v0 < 0 && vd > 0))
{
// calc actual values or estimate amount of space
if(!scan)
{
// linearly interpolate coordinate
float Vn = Vc + voxelSize;
float dinv = 1.f/(fabs(v0)+fabs(vd));
float inter = (Vc*fabs(vd) + Vn*fabs(v0))*dinv;
float3 p = (float3)(shift.x ? inter : V.x,
shift.y ? inter : V.y,
shift.z ? inter : V.z);
cr.point = (float3)(dot(p, volRot0),
dot(p, volRot1),
dot(p, volRot2)) + volTrans;
if(needNormals)
{
float3 nv = getNormalVoxel(p * voxelSizeInv,
volumeptr, volResolution, volDims, neighbourCoords);
cr.normal = (float3)(dot(nv, volRot0),
dot(nv, volRot1),
dot(nv, volRot2));
}
}
cr.result = true;
return cr;
}
}
}
cr.result = false;
return cr;
}
__kernel void scanSize(__global const struct TsdfVoxel* volumeptr,
const int4 volResolution4,
const int4 volDims4,
const int8 neighbourCoords,
__global const float * volPoseptr,
const float voxelSize,
const float voxelSizeInv,
__local int* reducebuf,
__global char* groupedSumptr,
int groupedSum_slicestep,
int groupedSum_step, int groupedSum_offset
)
{
const int3 volDims = volDims4.xyz;
const int3 volResolution = volResolution4.xyz;
int x = get_global_id(0);
int y = get_global_id(1);
int z = get_global_id(2);
bool validVoxel = true;
if(x >= volResolution.x || y >= volResolution.y || z >= volResolution.z)
validVoxel = false;
const int gx = get_group_id(0);
const int gy = get_group_id(1);
const int gz = get_group_id(2);
const int lx = get_local_id(0);
const int ly = get_local_id(1);
const int lz = get_local_id(2);
const int lw = get_local_size(0);
const int lh = get_local_size(1);
const int ld = get_local_size(2);
const int lsz = lw*lh*ld;
const int lid = lx + ly*lw + lz*lw*lh;
// coordinate-independent constants
__global const float* vp = volPoseptr;
const float3 volRot0 = vload4(0, vp).xyz;
const float3 volRot1 = vload4(1, vp).xyz;
const float3 volRot2 = vload4(2, vp).xyz;
const float3 volTrans = (float3)(vp[3], vp[7], vp[11]);
// kernel itself
int npts = 0;
if(validVoxel)
{
int3 ip = (int3)(x, y, z);
int3 cmul = ip*volDims;
int idx = cmul.x + cmul.y + cmul.z;
struct TsdfVoxel voxel = volumeptr[idx];
float value = tsdfToFloat(voxel.tsdf);
int weight = voxel.weight;
// if voxel is not empty
if(weight != 0 && value != 1.f)
{
float3 V = (((float3)(x, y, z)) + 0.5f)*voxelSize;
#pragma unroll
for(int i = 0; i < 3; i++)
{
struct CoordReturn cr;
cr = coord(x, y, z, V, value, i,
volumeptr, volResolution, volDims,
neighbourCoords,
voxelSize, voxelSizeInv,
volRot0, volRot1, volRot2, volTrans,
false, true);
if(cr.result)
{
npts++;
}
}
}
}
// reducebuf keeps counters for each thread
reducebuf[lid] = npts;
// reduce counter to local mem
// maxStep = ctz(lsz), ctz isn't supported on CUDA devices
const int c = clz(lsz & -lsz);
const int maxStep = c ? 31 - c : c;
for(int nstep = 1; nstep <= maxStep; nstep++)
{
if(lid % (1 << nstep) == 0)
{
int rto = lid;
int rfrom = lid + (1 << (nstep-1));
reducebuf[rto] += reducebuf[rfrom];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
if(lid == 0)
{
__global int* groupedRow = (__global int*)(groupedSumptr +
groupedSum_offset +
gy*groupedSum_step +
gz*groupedSum_slicestep);
groupedRow[gx] = reducebuf[0];
}
}
__kernel void fillPtsNrm(__global const struct TsdfVoxel* volumeptr,
const int4 volResolution4,
const int4 volDims4,
const int8 neighbourCoords,
__global const float * volPoseptr,
const float voxelSize,
const float voxelSizeInv,
const int needNormals,
__local float* localbuf,
volatile __global int* atomicCtr,
__global const char* groupedSumptr,
int groupedSum_slicestep,
int groupedSum_step, int groupedSum_offset,
__global char * pointsptr,
int points_step, int points_offset,
__global char * normalsptr,
int normals_step, int normals_offset
)
{
const int3 volDims = volDims4.xyz;
const int3 volResolution = volResolution4.xyz;
int x = get_global_id(0);
int y = get_global_id(1);
int z = get_global_id(2);
bool validVoxel = true;
if(x >= volResolution.x || y >= volResolution.y || z >= volResolution.z)
validVoxel = false;
const int gx = get_group_id(0);
const int gy = get_group_id(1);
const int gz = get_group_id(2);
__global int* groupedRow = (__global int*)(groupedSumptr +
groupedSum_offset +
gy*groupedSum_step +
gz*groupedSum_slicestep);
// this group contains 0 pts, skip it
int nptsGroup = groupedRow[gx];
if(nptsGroup == 0)
return;
const int lx = get_local_id(0);
const int ly = get_local_id(1);
const int lz = get_local_id(2);
const int lw = get_local_size(0);
const int lh = get_local_size(1);
const int ld = get_local_size(2);
const int lsz = lw*lh*ld;
const int lid = lx + ly*lw + lz*lw*lh;
// coordinate-independent constants
__global const float* vp = volPoseptr;
const float3 volRot0 = vload4(0, vp).xyz;
const float3 volRot1 = vload4(1, vp).xyz;
const float3 volRot2 = vload4(2, vp).xyz;
const float3 volTrans = (float3)(vp[3], vp[7], vp[11]);
// kernel itself
int npts = 0;
float3 parr[3], narr[3];
if(validVoxel)
{
int3 ip = (int3)(x, y, z);
int3 cmul = ip*volDims;
int idx = cmul.x + cmul.y + cmul.z;
struct TsdfVoxel voxel = volumeptr[idx];
float value = tsdfToFloat(voxel.tsdf);
int weight = voxel.weight;
// if voxel is not empty
if(weight != 0 && value != 1.f)
{
float3 V = (((float3)(x, y, z)) + 0.5f)*voxelSize;
#pragma unroll
for(int i = 0; i < 3; i++)
{
struct CoordReturn cr;
cr = coord(x, y, z, V, value, i,
volumeptr, volResolution, volDims,
neighbourCoords,
voxelSize, voxelSizeInv,
volRot0, volRot1, volRot2, volTrans,
needNormals, false);
if(cr.result)
{
parr[npts] = cr.point;
narr[npts] = cr.normal;
npts++;
}
}
}
}
// 4 floats per point or normal
const int elemStep = 4;
__local float* normAddr;
__local int localCtr;
if(lid == 0)
localCtr = 0;
// push all pts (and nrm) from private array to local mem
int privateCtr = 0;
barrier(CLK_LOCAL_MEM_FENCE);
privateCtr = atomic_add(&localCtr, npts);
barrier(CLK_LOCAL_MEM_FENCE);
for(int i = 0; i < npts; i++)
{
__local float* addr = localbuf + (privateCtr+i)*elemStep;
vstore4((float4)(parr[i], 0), 0, addr);
}
if(needNormals)
{
normAddr = localbuf + localCtr*elemStep;
for(int i = 0; i < npts; i++)
{
__local float* addr = normAddr + (privateCtr+i)*elemStep;
vstore4((float4)(narr[i], 0), 0, addr);
}
}
// debugging purposes
if(lid == 0)
{
if(localCtr != nptsGroup)
{
printf("!!! fetchPointsNormals result may be incorrect, npts != localCtr at %3d %3d %3d: %3d vs %3d\n",
gx, gy, gz, localCtr, nptsGroup);
}
}
// copy local buffer to global mem
__local int whereToWrite;
if(lid == 0)
whereToWrite = atomic_add(atomicCtr, localCtr);
barrier(CLK_GLOBAL_MEM_FENCE);
event_t ev[2];
int evn = 0;
// points and normals are 1-column matrices
__global float* pts = (__global float*)(pointsptr +
points_offset +
whereToWrite*points_step);
ev[evn++] = async_work_group_copy(pts, localbuf, localCtr*elemStep, 0);
if(needNormals)
{
__global float* nrm = (__global float*)(normalsptr +
normals_offset +
whereToWrite*normals_step);
ev[evn++] = async_work_group_copy(nrm, normAddr, localCtr*elemStep, 0);
}
wait_group_events(evn, ev);
}
+586
View File
@@ -0,0 +1,586 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
/** 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
{
/** 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 CV_OVERRIDE
{
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 CV_OVERRIDE
{
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_<Vec4f>& 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 Vec4f* vec = points3d.ptr < Vec4f >(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 < Vec4f >(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 += Vec3f((*vec)[0], (*vec)[1], (*vec)[2]);
++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_<Vec4f>& points3d, const Mat_<Vec4f>& 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 Vec4f* point = points3d_.ptr < Vec4f >(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 Vec4f* normal = normals_.ptr < Vec4f >(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
Vec3f _p = Vec3f((*point)[0], (*point)[1], (*point)[2]);
if (plane->distance(_p) < err_)
{
// make sure the normals are similar to the plane
Vec3f _n = Vec3f((*normal)[0], (*normal)[1], (*normal)[2]);
if (std::abs(plane->n().dot(_n)) > 0.3)
{
// The point now belongs to the plane
plane->UpdateStatistics(_p, *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
Vec3f _p = Vec3f((*point)[0], (*point)[1], (*point)[2]);
if (plane->distance(_p) < err_)
{
// The point now belongs to the plane
plane->UpdateStatistics(_p, *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_<Vec4f>& points3d_;
const Mat_<Vec4f>& normals_;
unsigned char plane_index_;
/** THe block size as defined in the main algorithm */
int block_size_;
const InlierFinder& operator = (const InlierFinder&);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void findPlanes(InputArray points3d_in, InputArray normals_in, OutputArray mask_out, OutputArray plane_coefficients_out,
int block_size, int min_size, double threshold, double sensor_error_a, double sensor_error_b, double sensor_error_c,
RgbdPlaneMethod method)
{
CV_Assert(method == RGBD_PLANE_METHOD_DEFAULT);
Mat_<Vec4f> 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(divUp(points3d.rows, block_size),
divUp(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];
}
} // namespace cv
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
#ifndef __OPENCV_PRECOMP_H__
#define __OPENCV_PRECOMP_H__
#ifdef _MSC_VER
# define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
# define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
#endif
#include <vector>
#include <set>
#include <map>
#include <list>
#include <unordered_set>
#include <unordered_map>
#include <stack>
#include "opencv2/core.hpp"
#include "opencv2/core/types.hpp"
#include "opencv2/core/base.hpp"
#include "opencv2/core/utils/logger.hpp"
#include "opencv2/core/utils/trace.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/core/hal/intrin.hpp"
#include "opencv2/ptcloud.hpp"
#include "opencv2/geometry.hpp"
#include "opencv2/imgproc.hpp"
#endif
+412
View File
@@ -0,0 +1,412 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
namespace cv {
TriangleRasterizeSettings::TriangleRasterizeSettings()
{
shadingType = RASTERIZE_SHADING_SHADED;
cullingMode = RASTERIZE_CULLING_CW;
glCompatibleMode = RASTERIZE_COMPAT_DISABLED;
}
static void drawTriangle(Vec4f verts[3], Vec3f colors[3], Mat& depthBuf, Mat& colorBuf,
TriangleRasterizeSettings settings)
{
// this will be useful during refactoring
// if there's gonna be more supported data types
CV_DbgAssert(depthBuf.empty() || depthBuf.type() == CV_32FC1);
CV_DbgAssert(colorBuf.empty() || colorBuf.type() == CV_32FC3);
// any of buffers can be empty
int width = std::max(colorBuf.cols, depthBuf.cols);
int height = std::max(colorBuf.rows, depthBuf.rows);
Point minPt(width, height), maxPt(0, 0);
for (int i = 0; i < 3; i++)
{
// round down to cover the whole pixel
int x = (int)(verts[i][0]), y = (int)(verts[i][1]);
minPt.x = std::min( x, minPt.x);
minPt.y = std::min( y, minPt.y);
maxPt.x = std::max(x + 1, maxPt.x);
maxPt.y = std::max(y + 1, maxPt.y);
}
minPt.x = std::max(minPt.x, 0); maxPt.x = std::min(maxPt.x, width);
minPt.y = std::max(minPt.y, 0); maxPt.y = std::min(maxPt.y, height);
Point2f a(verts[0][0], verts[0][1]), b(verts[1][0], verts[1][1]), c(verts[2][0], verts[2][1]);
Point2f bc = b - c, ac = a - c;
float d = ac.x*bc.y - ac.y*bc.x;
// culling and degenerated triangle removal
if ((settings.cullingMode == RASTERIZE_CULLING_CW && d <= 0) ||
(settings.cullingMode == RASTERIZE_CULLING_CCW && d >= 0) ||
(abs(d) < 1e-6))
{
return;
}
float invd = 1.f / d;
Vec3f zinv { verts[0][2], verts[1][2], verts[2][2] };
Vec3f w { verts[0][3], verts[1][3], verts[2][3] };
for (int y = minPt.y; y < maxPt.y; y++)
{
for (int x = minPt.x; x < maxPt.x; x++)
{
Point2f p(x + 0.5f, y + 0.5f), pc = p - c;
// barycentric coordinates
Vec3f f;
f[0] = ( pc.x * bc.y - pc.y * bc.x) * invd;
f[1] = ( pc.y * ac.x - pc.x * ac.y) * invd;
f[2] = 1.f - f[0] - f[1];
// if inside the triangle
if ((f[0] >= 0) && (f[1] >= 0) && (f[2] >= 0))
{
bool update = false;
if (!depthBuf.empty())
{
float zCurrent = depthBuf.at<float>(height - 1 - y, x);
float zNew = f[0] * zinv[0] + f[1] * zinv[1] + f[2] * zinv[2];
if (zNew < zCurrent)
{
update = true;
depthBuf.at<float>(height - 1 - y, x) = zNew;
}
}
else // RASTERIZE_SHADING_WHITE
{
update = true;
}
if (!colorBuf.empty() && update)
{
Vec3f color;
if (settings.shadingType == RASTERIZE_SHADING_WHITE)
{
color = { 1.f, 1.f, 1.f };
}
else if (settings.shadingType == RASTERIZE_SHADING_FLAT)
{
color = colors[0];
}
else // TriangleShadingType::Shaded
{
float zInter = 1.0f / (f[0] * w[0] + f[1] * w[1] + f[2] * w[2]);
color = { 0, 0, 0 };
for (int j = 0; j < 3; j++)
{
color += (f[j] * w[j]) * colors[j];
}
color *= zInter;
}
colorBuf.at<Vec3f>(height - 1 - y, x) = color;
}
}
}
}
}
// values outside of [zNear, zFar] have to be restored
// [0, 1] -> [zNear, zFar]
static void linearizeDepth(const Mat& inbuf, const Mat& validMask, Mat outbuf, double zFar, double zNear)
{
CV_Assert(inbuf.type() == CV_32FC1);
CV_Assert(validMask.type() == CV_8UC1 || validMask.type() == CV_8SC1 || validMask.type() == CV_BoolC1);
CV_Assert(outbuf.type() == CV_32FC1);
CV_Assert(outbuf.size() == inbuf.size());
float scaleNear = (float)(1.0 / zNear);
float scaleFar = (float)(1.0 / zFar);
for (int y = 0; y < inbuf.rows; y++)
{
const float* inp = inbuf.ptr<float>(y);
const uchar * validPtr = validMask.ptr<uchar>(y);
float * outp = outbuf.ptr<float>(y);
for (int x = 0; x < inbuf.cols; x++)
{
if (validPtr[x])
{
float d = inp[x];
// precision-optimized version of this:
//float z = - zFar * zNear / (d * (zFar - zNear) - zFar);
float z = 1.f / ((1.f - d) * scaleNear + d * scaleFar );
outp[x] = z;
}
}
}
}
// [zNear, zFar] -> [0, 1]
static void invertDepth(const Mat& inbuf, Mat& outbuf, Mat& validMask, double zNear, double zFar)
{
CV_Assert(inbuf.type() == CV_32FC1);
outbuf.create(inbuf.size(), CV_32FC1);
validMask.create(inbuf.size(), CV_8UC1);
float fNear = (float)zNear, fFar = (float)zFar;
float zadd = (float)(zFar / (zFar - zNear));
float zmul = (float)(-zNear * zFar / (zFar - zNear));
for (int y = 0; y < inbuf.rows; y++)
{
const float * inp = inbuf.ptr<float>(y);
float * outp = outbuf.ptr<float>(y);
uchar * validPtr = validMask.ptr<uchar>(y);
for (int x = 0; x < inbuf.cols; x++)
{
float z = inp[x];
uchar m = (z >= fNear) && (z <= fFar);
z = std::max(std::min(z, fFar), fNear);
// precision-optimized version of this:
// outp[x] = (z - zNear) / z * zFar / (zFar - zNear);
outp[x] = zadd + zmul / z;
validPtr[x] = m;
}
}
}
static void triangleRasterizeInternal(InputArray _vertices, InputArray _indices, InputArray _colors,
Mat& colorBuf, Mat& depthBuf,
InputArray world2cam, double fovyRadians, double zNear, double zFar,
const TriangleRasterizeSettings& settings)
{
CV_Assert(world2cam.type() == CV_32FC1 || world2cam.type() == CV_64FC1);
CV_Assert((world2cam.size() == Size {4, 3}) || (world2cam.size() == Size {4, 4}));
CV_Assert((fovyRadians > 0) && (fovyRadians < CV_PI));
CV_Assert(zNear > 0);
CV_Assert(zFar > zNear);
Mat cpMat;
world2cam.getMat().convertTo(cpMat, CV_64FC1);
Matx44d camPoseMat = Matx44d::eye();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
camPoseMat(i, j) = cpMat.at<double>(i, j);
}
}
if(_indices.empty())
{
return;
}
CV_CheckFalse(_vertices.empty(), "No vertices provided along with indices array");
Mat vertices, colors, triangles;
int nVerts = 0, nColors = 0, nTriangles = 0;
int vertexType = _vertices.type();
CV_Assert(vertexType == CV_32FC1 || vertexType == CV_32FC3);
vertices = _vertices.getMat();
// transform 3xN matrix to Nx3, except 3x3
if ((_vertices.channels() == 1) && (_vertices.rows() == 3) && (_vertices.cols() != 3))
{
vertices = vertices.t();
}
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
vertices = vertices.reshape(3, 1).t();
nVerts = (int)vertices.total();
int indexType = _indices.type();
CV_Assert(indexType == CV_32SC1 || indexType == CV_32SC3);
triangles = _indices.getMat();
// transform 3xN matrix to Nx3, except 3x3
if ((_indices.channels() == 1) && (_indices.rows() == 3) && (_indices.cols() != 3))
{
triangles = triangles.t();
}
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
triangles = triangles.reshape(3, 1).t();
nTriangles = (int)triangles.total();
if (!_colors.empty())
{
int colorType = _colors.type();
CV_Assert(colorType == CV_32FC1 || colorType == CV_32FC3);
colors = _colors.getMat();
// transform 3xN matrix to Nx3, except 3x3
if ((_colors.channels() == 1) && (_colors.rows() == 3) && (_colors.cols() != 3))
{
colors = colors.t();
}
colors = colors.reshape(3, 1).t();
nColors = (int)colors.total();
CV_Assert(nColors == nVerts);
}
// any of buffers can be empty
Size imgSize {std::max(colorBuf.cols, depthBuf.cols), std::max(colorBuf.rows, depthBuf.rows)};
// world-to-camera coord system
Matx44d lookAtMatrix = camPoseMat;
double ys = 1.0 / std::tan(fovyRadians / 2);
double xs = ys / (double)imgSize.width * (double)imgSize.height;
double zz = (zNear + zFar) / (zNear - zFar);
double zw = 2.0 * zFar * zNear / (zNear - zFar);
// camera to NDC: [-1, 1]^3
Matx44d perspectMatrix (xs, 0, 0, 0,
0, ys, 0, 0,
0, 0, zz, zw,
0, 0, -1, 0);
Matx44f mvpMatrix = perspectMatrix * lookAtMatrix;
// vertex transform stage
Mat screenVertices(vertices.size(), CV_32FC4);
for (int i = 0; i < nVerts; i++)
{
Vec3f vglobal = vertices.at<Vec3f>(i);
float x_num = std::fma(mvpMatrix(0,0), vglobal[0],
std::fma(mvpMatrix(0,1), vglobal[1],
std::fma(mvpMatrix(0,2), vglobal[2], mvpMatrix(0,3))));
float y_num = std::fma(mvpMatrix(1,0), vglobal[0],
std::fma(mvpMatrix(1,1), vglobal[1],
std::fma(mvpMatrix(1,2), vglobal[2], mvpMatrix(1,3))));
float z_num = std::fma(mvpMatrix(2,0), vglobal[0],
std::fma(mvpMatrix(2,1), vglobal[1],
std::fma(mvpMatrix(2,2), vglobal[2], mvpMatrix(2,3))));
float w_num = std::fma(mvpMatrix(3,0), vglobal[0],
std::fma(mvpMatrix(3,1), vglobal[1],
std::fma(mvpMatrix(3,2), vglobal[2], mvpMatrix(3,3))));
float invw = 1.f / w_num;
// [-1, 1]^3 => [0, width] x [0, height] x [0, 1]
Vec4f vscreen = {
std::fma(x_num * invw, 0.5f * (float)imgSize.width, 0.5f * (float)imgSize.width),
std::fma(y_num * invw, 0.5f * (float)imgSize.height, 0.5f * (float)imgSize.height),
std::fma(z_num * invw, 0.5f, 0.5f),
invw
};
screenVertices.at<Vec4f>(i) = vscreen;
}
// draw stage
for (int t = 0; t < nTriangles; t++)
{
Vec3i tri = triangles.at<Vec3i>(t);
Vec3f col[3];
Vec4f ver[3];
for (int i = 0; i < 3; i++)
{
int idx = tri[i];
CV_DbgAssert(idx >= 0 && idx < nVerts);
col[i] = colors.empty() ? Vec3f::all(0) : colors.at<Vec3f>(idx);
ver[i] = screenVertices.at<Vec4f>(idx);
}
drawTriangle(ver, col, depthBuf, colorBuf, settings);
}
}
void triangleRasterizeDepth(InputArray _vertices, InputArray _indices, InputOutputArray _depthBuf,
InputArray world2cam, double fovY, double zNear, double zFar,
const TriangleRasterizeSettings& settings)
{
CV_Assert(!_depthBuf.empty());
CV_Assert(_depthBuf.type() == CV_32FC1);
Mat emptyColorBuf;
// out-of-range values from user-provided depthBuf should not be altered, let's mark them
Mat_<uchar> validMask;
Mat depthBuf;
if (settings.glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH)
{
depthBuf = _depthBuf.getMat();
}
else // RASTERIZE_COMPAT_DISABLED
{
invertDepth(_depthBuf.getMat(), depthBuf, validMask, zNear, zFar);
}
triangleRasterizeInternal(_vertices, _indices, noArray(), emptyColorBuf, depthBuf, world2cam, fovY, zNear, zFar, settings);
if (settings.glCompatibleMode == RASTERIZE_COMPAT_DISABLED)
{
linearizeDepth(depthBuf, validMask, _depthBuf.getMat(), zFar, zNear);
}
}
void triangleRasterizeColor(InputArray _vertices, InputArray _indices, InputArray _colors, InputOutputArray _colorBuf,
InputArray world2cam, double fovY, double zNear, double zFar,
const TriangleRasterizeSettings& settings)
{
CV_Assert(!_colorBuf.empty());
CV_Assert(_colorBuf.type() == CV_32FC3);
Mat colorBuf = _colorBuf.getMat();
Mat depthBuf;
if (_colors.empty())
{
// full white shading does not require depth test
CV_Assert(settings.shadingType == RASTERIZE_SHADING_WHITE);
}
else
{
// internal depth buffer is not exposed outside
depthBuf.create(_colorBuf.size(), CV_32FC1);
depthBuf.setTo(1.0);
}
triangleRasterizeInternal(_vertices, _indices, _colors, colorBuf, depthBuf, world2cam, fovY, zNear, zFar, settings);
}
void triangleRasterize(InputArray _vertices, InputArray _indices, InputArray _colors,
InputOutputArray _colorBuffer, InputOutputArray _depthBuffer,
InputArray world2cam, double fovyRadians, double zNear, double zFar,
const TriangleRasterizeSettings& settings)
{
if (_colors.empty())
{
CV_Assert(settings.shadingType == RASTERIZE_SHADING_WHITE);
}
CV_Assert(!_colorBuffer.empty());
CV_Assert(_colorBuffer.type() == CV_32FC3);
CV_Assert(!_depthBuffer.empty());
CV_Assert(_depthBuffer.type() == CV_32FC1);
CV_Assert(_depthBuffer.size() == _colorBuffer.size());
Mat colorBuf = _colorBuffer.getMat();
// out-of-range values from user-provided depthBuf should not be altered, let's mark them
Mat_<uchar> validMask;
Mat depthBuf;
if (settings.glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH)
{
depthBuf = _depthBuffer.getMat();
}
else // RASTERIZE_COMPAT_DISABLED
{
invertDepth(_depthBuffer.getMat(), depthBuf, validMask, zNear, zFar);
}
triangleRasterizeInternal(_vertices, _indices, _colors, colorBuf, depthBuf, world2cam, fovyRadians, zNear, zFar, settings);
if (settings.glCompatibleMode == RASTERIZE_COMPAT_DISABLED)
{
linearizeDepth(depthBuf, validMask, _depthBuffer.getMat(), zFar, zNear);
}
}
} // namespace cv
+223
View File
@@ -0,0 +1,223 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_SPARSE_BLOCK_MATRIX_HPP
#define OPENCV_3D_SPARSE_BLOCK_MATRIX_HPP
#include "precomp.hpp"
#if defined(HAVE_EIGEN)
#include <Eigen/Core>
#include <Eigen/Sparse>
#include <Eigen/SparseCholesky>
#include "opencv2/core/eigen.hpp"
#endif
namespace cv
{
/*!
* \class BlockSparseMat
* Naive implementation of Sparse Block Matrix
*/
template<typename _Tp, size_t blockM, size_t blockN>
struct BlockSparseMat
{
struct Point2iHash
{
size_t operator()(const cv::Point2i& point) const noexcept
{
size_t seed = 0;
constexpr uint32_t GOLDEN_RATIO = 0x9e3779b9;
seed ^= std::hash<int>()(point.x) + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
seed ^= std::hash<int>()(point.y) + GOLDEN_RATIO + (seed << 6) + (seed >> 2);
return seed;
}
};
typedef Matx<_Tp, blockM, blockN> MatType;
typedef std::unordered_map<Point2i, MatType, Point2iHash> IDtoBlockValueMap;
BlockSparseMat(size_t _nBlocks) : nBlocks(_nBlocks), ijValue() {}
void clear()
{
ijValue.clear();
}
inline MatType& refBlock(size_t i, size_t j)
{
Point2i p((int)i, (int)j);
auto it = ijValue.find(p);
if (it == ijValue.end())
{
it = ijValue.insert({ p, MatType::zeros() }).first;
}
return it->second;
}
inline _Tp& refElem(size_t i, size_t j)
{
Point2i ib((int)(i / blockM), (int)(j / blockN));
Point2i iv((int)(i % blockM), (int)(j % blockN));
return refBlock(ib.x, ib.y)(iv.x, iv.y);
}
inline MatType valBlock(size_t i, size_t j) const
{
Point2i p((int)i, (int)j);
auto it = ijValue.find(p);
if (it == ijValue.end())
return MatType::zeros();
else
return it->second;
}
inline _Tp valElem(size_t i, size_t j) const
{
Point2i ib((int)(i / blockM), (int)(j / blockN));
Point2i iv((int)(i % blockM), (int)(j % blockN));
return valBlock(ib.x, ib.y)(iv.x, iv.y);
}
Mat diagonal() const
{
// Diagonal max length is the number of columns in the sparse matrix
int diagLength =int( blockN * nBlocks );
cv::Mat diag = cv::Mat::zeros(diagLength, 1, cv::DataType<_Tp>::type);
for (int i = 0; i < diagLength; i++)
{
diag.at<_Tp>(i, 0) = valElem(i, i);
}
return diag;
}
#if defined(HAVE_EIGEN)
Eigen::SparseMatrix<_Tp> toEigen() const
{
std::vector<Eigen::Triplet<_Tp>> tripletList;
tripletList.reserve(ijValue.size() * blockM * blockN);
for (const auto& ijv : ijValue)
{
int xb = ijv.first.x, yb = ijv.first.y;
MatType vblock = ijv.second;
for (size_t i = 0; i < blockM; i++)
{
for (size_t j = 0; j < blockN; j++)
{
_Tp val = vblock((int)i, (int)j);
if (abs(val) >= NON_ZERO_VAL_THRESHOLD)
{
tripletList.push_back(Eigen::Triplet<_Tp>((int)(blockM * xb + i), (int)(blockN * yb + j), val));
}
}
}
}
Eigen::SparseMatrix<_Tp> EigenMat(blockM * nBlocks, blockN * nBlocks);
EigenMat.setFromTriplets(tripletList.begin(), tripletList.end());
EigenMat.makeCompressed();
return EigenMat;
}
#endif
inline size_t nonZeroBlocks() const { return ijValue.size(); }
BlockSparseMat<_Tp, blockM, blockN>& operator+=(const BlockSparseMat<_Tp, blockM, blockN>& other)
{
for (const auto& oijv : other.ijValue)
{
Point2i p = oijv.first;
MatType vblock = oijv.second;
this->refBlock(p.x, p.y) += vblock;
}
return *this;
}
#if defined(HAVE_EIGEN)
// Decomposes matrix for further solution
// Sometimes it's required to consequently solve A*x = b then A*x = c then A*x = d...
// Splitting the solution procedure into two parts let us reuse the matrix' decomposition
struct Decomposition
{
Eigen::SparseMatrix<_Tp> bigA;
Eigen::SimplicialLDLT<Eigen::SparseMatrix<_Tp>> solver;
};
bool decompose(Decomposition& d, bool checkSymmetry = true) const
{
d.bigA = this->toEigen();
Eigen::SparseMatrix<_Tp> bigAtranspose = d.bigA.transpose();
if (checkSymmetry && !d.bigA.isApprox(bigAtranspose))
{
CV_Error(Error::StsBadArg, "H matrix is not symmetrical");
return false;
}
d.solver.compute(d.bigA);
bool r = (d.solver.info() == Eigen::Success);
if (!r)
{
CV_LOG_INFO(NULL, "Failed to eigen-decompose");
}
return r;
}
static bool solveDecomposed(const Decomposition& d, InputArray B, OutputArray X, OutputArray predB = cv::noArray())
{
Mat mb = B.getMat();
mb = mb.cols == 1 ? mb : mb.t();
Eigen::Matrix<_Tp, -1, 1> bigB;
cv2eigen(mb, bigB);
Eigen::Matrix<_Tp, -1, 1> solutionX = d.solver.solve(bigB);
if (d.solver.info() != Eigen::Success)
{
CV_LOG_INFO(NULL, "Failed to eigen-solve");
return false;
}
else
{
eigen2cv(solutionX, X);
if (predB.needed())
{
Eigen::Matrix<_Tp, -1, 1> predBEigen = d.bigA * solutionX;
eigen2cv(predBEigen, predB);
}
return true;
}
}
#else
struct Decomposition { };
bool decompose(Decomposition& /*_d*/, bool /*checkSymmetry*/ = true) const
{
CV_Error(Error::StsNotImplemented, "Eigen library required for matrix solve, dense solver is not implemented");
}
bool solveDecomposed(const Decomposition& /*d*/, InputArray /*B*/, OutputArray /*X*/, OutputArray /*predB*/ = cv::noArray()) const
{
CV_Error(Error::StsNotImplemented, "Eigen library required for matrix solve, dense solver is not implemented");
}
#endif
//! Function to solve a sparse linear system of equations HX = B
bool sparseSolve(InputArray B, OutputArray X, bool checkSymmetry = true, OutputArray predB = cv::noArray()) const
{
Decomposition d;
return decompose(d, checkSymmetry) && solveDecomposed(d, B, X, predB);
}
static constexpr _Tp NON_ZERO_VAL_THRESHOLD = _Tp(0.0001);
size_t nBlocks;
IDtoBlockValueMap ijValue;
};
} // namespace cv
#endif // include guard
File diff suppressed because it is too large Load Diff
+211
View File
@@ -0,0 +1,211 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Partially rewritten from https://github.com/Nerei/kinfu_remake
// Copyright(c) 2012, Anatoly Baksheev. All rights reserved.
#ifndef OPENCV_3D_TSDF_FUNCTIONS_HPP
#define OPENCV_3D_TSDF_FUNCTIONS_HPP
#include "precomp.hpp"
#include "utils.hpp"
namespace cv
{
typedef int8_t TsdfType;
typedef uchar WeightType;
struct TsdfVoxel
{
TsdfVoxel(TsdfType _tsdf, WeightType _weight) :
tsdf(_tsdf), weight(_weight)
{ }
TsdfType tsdf;
WeightType weight;
};
typedef Vec<uchar, sizeof(TsdfVoxel)> VecTsdfVoxel;
typedef short int ColorType;
struct RGBTsdfVoxel
{
RGBTsdfVoxel(TsdfType _tsdf, WeightType _weight, ColorType _r, ColorType _g, ColorType _b) :
tsdf(_tsdf), weight(_weight), r(_r), g(_g), b(_b)
{ }
TsdfType tsdf;
WeightType weight;
ColorType r, g, b;
};
typedef Vec<uchar, sizeof(RGBTsdfVoxel)> VecRGBTsdfVoxel;
#if CV_SIMD128
inline v_float32x4 tsdfToFloat_INTR(const v_int32x4& num)
{
v_float32x4 num128 = v_setall_f32(-1.f / 128.f);
return v_mul(v_cvt_f32(num), num128);
}
#endif
inline TsdfType floatToTsdf(float num)
{
//CV_Assert(-1 < num <= 1);
int8_t res = int8_t(num * (-128.f));
res = res ? res : (num < 0 ? 1 : -1);
return res;
}
inline float tsdfToFloat(TsdfType num)
{
return float(num) * (-1.f / 128.f);
}
inline void colorFix(ColorType& r, ColorType& g, ColorType&b)
{
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
}
inline void colorFix(Point3f& c)
{
if (c.x > 255) c.x = 255;
if (c.y > 255) c.y = 255;
if (c.z > 255) c.z = 255;
}
void preCalculationPixNorm(Size size, const Intr& intrinsics, Mat& pixNorm);
#ifdef HAVE_OPENCL
void ocl_preCalculationPixNorm(Size size, const Intr& intrinsics, UMat& pixNorm);
#endif
inline depthType bilinearDepth(const Depth& m, cv::Point2f pt)
{
const bool fixMissingData = false;
const depthType defaultValue = qnan;
if (pt.x < 0 || pt.x >= m.cols - 1 ||
pt.y < 0 || pt.y >= m.rows - 1)
return defaultValue;
int xi = cvFloor(pt.x), yi = cvFloor(pt.y);
const depthType* row0 = m[yi + 0];
const depthType* row1 = m[yi + 1];
depthType v00 = row0[xi + 0];
depthType v01 = row0[xi + 1];
depthType v10 = row1[xi + 0];
depthType v11 = row1[xi + 1];
// assume correct depth is positive
bool b00 = v00 > 0;
bool b01 = v01 > 0;
bool b10 = v10 > 0;
bool b11 = v11 > 0;
if (!fixMissingData)
{
if (!(b00 && b01 && b10 && b11))
return defaultValue;
else
{
float tx = pt.x - xi, ty = pt.y - yi;
depthType v0 = v00 + tx * (v01 - v00);
depthType v1 = v10 + tx * (v11 - v10);
return v0 + ty * (v1 - v0);
}
}
else
{
int nz = b00 + b01 + b10 + b11;
if (nz == 0)
{
return defaultValue;
}
if (nz == 1)
{
if (b00) return v00;
if (b01) return v01;
if (b10) return v10;
if (b11) return v11;
}
if (nz == 2)
{
if (b00 && b10) v01 = v00, v11 = v10;
if (b01 && b11) v00 = v01, v10 = v11;
if (b00 && b01) v10 = v00, v11 = v01;
if (b10 && b11) v00 = v10, v01 = v11;
if (b00 && b11) v01 = v10 = (v00 + v11) * 0.5f;
if (b01 && b10) v00 = v11 = (v01 + v10) * 0.5f;
}
if (nz == 3)
{
if (!b00) v00 = v10 + v01 - v11;
if (!b01) v01 = v00 + v11 - v10;
if (!b10) v10 = v00 + v11 - v01;
if (!b11) v11 = v01 + v10 - v00;
}
float tx = pt.x - xi, ty = pt.y - yi;
depthType v0 = v00 + tx * (v01 - v00);
depthType v1 = v10 + tx * (v11 - v10);
return v0 + ty * (v1 - v0);
}
}
void _integrateVolumeUnit(
float truncDist, float voxelSize, int maxWeight,
cv::Matx44f _pose, Point3i volResolution, Vec4i volStrides,
InputArray _depth, float depthFactor, const cv::Matx44f& cameraPose,
const cv::Intr& intrinsics, InputArray _pixNorms, InputArray _volume);
void _integrateRGBVolumeUnit(
float truncDist, float voxelSize, int maxWeight,
cv::Matx44f _pose, Point3i volResolution, Vec4i volStrides,
InputArray _depth, InputArray _rgb, float depthFactor, const cv::Matx44f& cameraPose,
const cv::Intr& depth_intrinsics, const cv::Intr& rgb_intrinsics, InputArray _pixNorms, InputArray _volume);
void integrateTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose,
InputArray _depth, InputArray _pixNorms, InputArray _volume);
void integrateTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& volumePose, const Matx44f& cameraPose,
InputArray _depth, InputArray _pixNorms, InputArray _volume);
void raycastTsdfVolumeUnit(const VolumeSettings& settings, const Matx44f& cameraPose, int height, int width, InputArray intr,
InputArray _volume, OutputArray _points, OutputArray _normals);
void fetchNormalsFromTsdfVolumeUnit(const VolumeSettings& settings, InputArray _volume,
InputArray _points, OutputArray _normals);
void fetchPointsNormalsFromTsdfVolumeUnit(const VolumeSettings& settings, InputArray _volume,
OutputArray points, OutputArray normals);
#ifdef HAVE_OPENCL
void ocl_integrateTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose,
InputArray _depth, InputArray _pixNorms, InputArray _volume);
void ocl_raycastTsdfVolumeUnit(
const VolumeSettings& settings, const Matx44f& cameraPose, int height, int width, InputArray intr,
InputArray _volume, OutputArray _points, OutputArray _normals);
void ocl_fetchNormalsFromTsdfVolumeUnit(
const VolumeSettings& settings, InputArray _volume,
InputArray _points, OutputArray _normals);
void ocl_fetchPointsNormalsFromTsdfVolumeUnit(
const VolumeSettings& settings, InputArray _volume,
OutputArray _points, OutputArray _normals);
#endif
} // namespace cv
#endif // include guard
+21
View File
@@ -0,0 +1,21 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "utils.hpp"
namespace cv {
std::vector<std::string> split(const std::string &s, char delimiter)
{
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
} /* namespace cv */
+319
View File
@@ -0,0 +1,319 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef _CODERS_UTILS_H_
#define _CODERS_UTILS_H_
#include "precomp.hpp"
#include <string>
#include <vector>
#include <sstream>
#include <array>
#include <algorithm>
#include <cstdint>
namespace cv {
std::vector<std::string> split(const std::string &s, char delimiter);
inline bool startsWith(const std::string &s1, const std::string &s2)
{
return s1.compare(0, s2.length(), s2) == 0;
}
inline std::string trimSpaces(const std::string &input)
{
size_t start = 0;
while (start < input.size() && input[start] == ' ')
{
start++;
}
size_t end = input.size();
while (end > start && (input[end - 1] == ' ' || input[end - 1] == '\n' || input[end - 1] == '\r'))
{
end--;
}
return input.substr(start, end - start);
}
inline std::string getExtension(const std::string& filename)
{
auto pos = filename.find_last_of('.');
if (pos == std::string::npos)
{
return "";
}
return filename.substr( pos + 1);
}
template <typename T>
void swapEndian(T &val)
{
union U
{
T val;
std::array<std::uint8_t, sizeof(T)> raw;
} src, dst;
src.val = val;
std::reverse_copy(src.raw.begin(), src.raw.end(), dst.raw.begin());
val = dst.val;
}
/** Checks if the value is a valid depth. For CV_16U or CV_16S, the convention is to be invalid if it is
* a limit. For a float/double, we just check if it is a NaN
* @param depth the depth to check for validity
*/
inline bool isValidDepth(const float& depth)
{
return !cvIsNaN(depth);
}
inline bool isValidDepth(const double& depth)
{
return !cvIsNaN(depth);
}
inline bool isValidDepth(const short int& depth)
{
return (depth != std::numeric_limits<short int>::min()) &&
(depth != std::numeric_limits<short int>::max());
}
inline bool isValidDepth(const unsigned short int& depth)
{
return (depth != std::numeric_limits<unsigned short int>::min()) &&
(depth != std::numeric_limits<unsigned short int>::max());
}
inline bool isValidDepth(const int& depth)
{
return (depth != std::numeric_limits<int>::min()) &&
(depth != std::numeric_limits<int>::max());
}
inline bool isValidDepth(const unsigned int& depth)
{
return (depth != std::numeric_limits<unsigned int>::min()) &&
(depth != std::numeric_limits<unsigned int>::max());
}
// One place to turn intrinsics on and off
#define USE_INTRINSICS CV_SIMD128
typedef float depthType;
const float qnan = std::numeric_limits<float>::quiet_NaN();
const cv::Vec3f nan3(qnan, qnan, qnan);
#if USE_INTRINSICS
const cv::v_float32x4 nanv(qnan, qnan, qnan, qnan);
#endif
inline bool isNaN(cv::Point3f p)
{
return (cvIsNaN(p.x) || cvIsNaN(p.y) || cvIsNaN(p.z));
}
#if USE_INTRINSICS
static inline bool isNaN(const cv::v_float32x4& p)
{
return cv::v_check_any(v_ne(p, p));
}
#endif
inline size_t roundDownPow2(size_t x)
{
size_t shift = 0;
while(x != 0)
{
shift++; x >>= 1;
}
return (size_t)(1ULL << (shift-1));
}
template<> class DataType<cv::Point3f>
{
public:
typedef float value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum { generic_type = 0,
depth = CV_32F,
channels = 3,
fmt = (int)'f',
type = CV_MAKETYPE(depth, channels)
};
};
template<> class DataType<cv::Vec3f>
{
public:
typedef float value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum { generic_type = 0,
depth = CV_32F,
channels = 3,
fmt = (int)'f',
type = CV_MAKETYPE(depth, channels)
};
};
template<> class DataType<cv::Vec4f>
{
public:
typedef float value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum { generic_type = 0,
depth = CV_32F,
channels = 4,
fmt = (int)'f',
type = CV_MAKETYPE(depth, channels)
};
};
typedef cv::Vec4f ptype;
inline cv::Vec3f fromPtype(const ptype& x)
{
return cv::Vec3f(x[0], x[1], x[2]);
}
inline ptype toPtype(const cv::Vec3f& x)
{
return ptype(x[0], x[1], x[2], 0);
}
enum
{
DEPTH_TYPE = DataType<depthType>::type,
POINT_TYPE = DataType<ptype >::type,
COLOR_TYPE = DataType<ptype >::type
};
typedef cv::Mat_< ptype > Points;
typedef Points Normals;
typedef Points Colors;
typedef cv::Point3f _ptype;
typedef cv::Mat_< _ptype > _Points;
typedef _Points _Normals;
typedef _Points _Colors;
enum
{
_DEPTH_TYPE = DataType<depthType>::type,
_POINT_TYPE = DataType<_ptype >::type,
_COLOR_TYPE = DataType<_ptype >::type
};
typedef cv::Mat_< depthType > Depth;
void makeFrameFromDepth(InputArray depth, OutputArray pyrPoints, OutputArray pyrNormals,
const Matx33f intr, int levels, float depthFactor,
float sigmaDepth, float sigmaSpatial, int kernelSize,
float truncateThreshold);
void buildPyramidPointsNormals(InputArray _points, InputArray _normals,
OutputArrayOfArrays pyrPoints, OutputArrayOfArrays pyrNormals,
int levels);
struct Intr
{
/** @brief Camera intrinsics */
/** Reprojects screen point to camera space given z coord. */
struct Reprojector
{
Reprojector() {}
inline Reprojector(Intr intr)
{
fxinv = 1.f/intr.fx, fyinv = 1.f/intr.fy;
cx = intr.cx, cy = intr.cy;
}
template<typename T>
inline cv::Point3_<T> operator()(cv::Point3_<T> p) const
{
T x = p.z * (p.x - cx) * fxinv;
T y = p.z * (p.y - cy) * fyinv;
return cv::Point3_<T>(x, y, p.z);
}
float fxinv, fyinv, cx, cy;
};
/** Projects camera space vector onto screen */
struct Projector
{
inline Projector(Intr intr) : fx(intr.fx), fy(intr.fy), cx(intr.cx), cy(intr.cy) { }
template<typename T>
inline cv::Point_<T> operator()(cv::Point3_<T> p) const
{
T invz = T(1)/p.z;
T x = fx*(p.x*invz) + cx;
T y = fy*(p.y*invz) + cy;
return cv::Point_<T>(x, y);
}
template<typename T>
inline cv::Point_<T> operator()(cv::Point3_<T> p, cv::Point3_<T>& pixVec) const
{
T invz = T(1)/p.z;
pixVec = cv::Point3_<T>(p.x*invz, p.y*invz, 1);
T x = fx*pixVec.x + cx;
T y = fy*pixVec.y + cy;
return cv::Point_<T>(x, y);
}
float fx, fy, cx, cy;
};
Intr() : fx(), fy(), cx(), cy() { }
Intr(float _fx, float _fy, float _cx, float _cy) : fx(_fx), fy(_fy), cx(_cx), cy(_cy) { }
Intr(cv::Matx33f m) : fx(m(0, 0)), fy(m(1, 1)), cx(m(0, 2)), cy(m(1, 2)) { }
// scale intrinsics to pyramid level
inline Intr scale(int pyr) const
{
float factor = (1.f /(1 << pyr));
return Intr(fx*factor, fy*factor, cx*factor, cy*factor);
}
inline Reprojector makeReprojector() const { return Reprojector(*this); }
inline Projector makeProjector() const { return Projector(*this); }
inline cv::Matx33f getMat() const { return Matx33f(fx, 0, cx, 0, fy, cy, 0, 0, 1); }
float fx, fy, cx, cy;
};
class OdometryFrame::Impl
{
public:
Impl() : pyramids(OdometryFramePyramidType::N_PYRAMIDS) { }
virtual ~Impl() {}
virtual void getImage(OutputArray image) const ;
virtual void getGrayImage(OutputArray image) const ;
virtual void getDepth(OutputArray depth) const ;
virtual void getProcessedDepth(OutputArray depth) const ;
virtual void getMask(OutputArray mask) const ;
virtual void getNormals(OutputArray normals) const ;
virtual int getPyramidLevels() const ;
virtual void getPyramidAt(OutputArray img,
OdometryFramePyramidType pyrType, size_t level) const ;
UMat imageGray;
UMat image;
UMat depth;
UMat scaledDepth;
UMat mask;
UMat normals;
std::vector< std::vector<UMat> > pyramids;
};
} /* namespace cv */
#endif
+596
View File
@@ -0,0 +1,596 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include <iostream>
#include "volume_impl.hpp"
#include "tsdf_functions.hpp"
#include "hash_tsdf_functions.hpp"
#include "color_tsdf_functions.hpp"
#include "opencv2/imgproc.hpp"
namespace cv
{
Volume::Impl::Impl(const VolumeSettings& _settings) :
settings(_settings)
#ifdef HAVE_OPENCL
, useGPU(ocl::useOpenCL())
#endif
{}
// TSDF
TsdfVolume::TsdfVolume(const VolumeSettings& _settings) :
Volume::Impl(_settings)
{
Vec3i volResolution;
settings.getVolumeResolution(volResolution);
#ifndef HAVE_OPENCL
volume = Mat(1, volResolution[0] * volResolution[1] * volResolution[2], rawType<TsdfVoxel>());
#else
if (useGPU)
gpu_volume = UMat(1, volResolution[0] * volResolution[1] * volResolution[2], rawType<TsdfVoxel>());
else
cpu_volume = Mat(1, volResolution[0] * volResolution[1] * volResolution[2], rawType<TsdfVoxel>());
#endif
reset();
}
TsdfVolume::~TsdfVolume() {}
void TsdfVolume::integrate(const OdometryFrame& frame, InputArray _cameraPose)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENCL
Mat depth;
#else
UMat depth;
#endif
frame.getDepth(depth);
integrate(depth, _cameraPose);
}
void TsdfVolume::integrate(InputArray _depth, InputArray _cameraPose)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENCL
Mat depth = _depth.getMat();
#else
UMat depth = _depth.getUMat();
#endif
CV_Assert(!depth.empty());
Matx33f intr;
settings.getCameraIntegrateIntrinsics(intr);
Intr intrinsics(intr);
Vec6f newParams((float)depth.rows, (float)depth.cols,
intrinsics.fx, intrinsics.fy,
intrinsics.cx, intrinsics.cy);
if (!(frameParams == newParams))
{
frameParams = newParams;
#ifndef HAVE_OPENCL
preCalculationPixNorm(depth.size(), intrinsics, pixNorms);
#else
if (useGPU)
ocl_preCalculationPixNorm(depth.size(), intrinsics, gpu_pixNorms);
else
preCalculationPixNorm(depth.size(), intrinsics, cpu_pixNorms);
#endif
}
const Matx44f cameraPose = _cameraPose.getMat();
#ifndef HAVE_OPENCL
integrateTsdfVolumeUnit(settings, cameraPose, depth, pixNorms, volume);
#else
if (useGPU)
ocl_integrateTsdfVolumeUnit(settings, cameraPose, depth, gpu_pixNorms, gpu_volume);
else
integrateTsdfVolumeUnit(settings, cameraPose, depth, cpu_pixNorms, cpu_volume);
#endif
}
void TsdfVolume::integrate(InputArray, InputArray, InputArray)
{
CV_Error(cv::Error::StsBadFunc, "This volume doesn't support vertex colors");
}
void TsdfVolume::raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const
{
Matx33f intr;
settings.getCameraRaycastIntrinsics(intr);
raycast(cameraPose, settings.getRaycastHeight(), settings.getRaycastWidth(), intr, points, normals, colors);
}
void TsdfVolume::raycast(InputArray _cameraPose, int height, int width, InputArray intr, OutputArray _points, OutputArray _normals, OutputArray _colors) const
{
if (_colors.needed())
CV_Error(cv::Error::StsBadFunc, "This volume doesn't support vertex colors");
CV_Assert(height > 0);
CV_Assert(width > 0);
const Matx44f cameraPose = _cameraPose.getMat();
#ifndef HAVE_OPENCL
raycastTsdfVolumeUnit(settings, cameraPose, height, width, intr, volume, _points, _normals);
#else
if (useGPU)
ocl_raycastTsdfVolumeUnit(settings, cameraPose, height, width, intr, gpu_volume, _points, _normals);
else
raycastTsdfVolumeUnit(settings, cameraPose, height, width, intr, cpu_volume, _points, _normals);
#endif
}
void TsdfVolume::fetchNormals(InputArray points, OutputArray normals) const
{
#ifndef HAVE_OPENCL
fetchNormalsFromTsdfVolumeUnit(settings, volume, points, normals);
#else
if (useGPU)
ocl_fetchNormalsFromTsdfVolumeUnit(settings, gpu_volume, points, normals);
else
fetchNormalsFromTsdfVolumeUnit(settings, cpu_volume, points, normals);
#endif
}
void TsdfVolume::fetchPointsNormals(OutputArray points, OutputArray normals) const
{
#ifndef HAVE_OPENCL
fetchPointsNormalsFromTsdfVolumeUnit(settings, volume, points, normals);
#else
if (useGPU)
ocl_fetchPointsNormalsFromTsdfVolumeUnit(settings, gpu_volume, points, normals);
else
fetchPointsNormalsFromTsdfVolumeUnit(settings, cpu_volume, points, normals);
#endif
}
void TsdfVolume::fetchPointsNormalsColors(OutputArray, OutputArray, OutputArray) const
{
CV_Error(cv::Error::StsBadFunc, "This volume doesn't support vertex colors");
}
void TsdfVolume::reset()
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENCL
//TODO: use setTo(Scalar(0, 0))
volume.forEach<VecTsdfVoxel>([](VecTsdfVoxel& vv, const int* /* position */)
{
TsdfVoxel& v = reinterpret_cast<TsdfVoxel&>(vv);
v.tsdf = floatToTsdf(0.0f); v.weight = 0;
});
#else
if (useGPU)
gpu_volume.setTo(Scalar(floatToTsdf(0.0f), 0));
else
//TODO: use setTo(Scalar(0, 0))
cpu_volume.forEach<VecTsdfVoxel>([](VecTsdfVoxel& vv, const int* /* position */)
{
TsdfVoxel& v = reinterpret_cast<TsdfVoxel&>(vv);
v.tsdf = floatToTsdf(0.0f); v.weight = 0;
});
#endif
}
int TsdfVolume::getVisibleBlocks() const { return 1; }
size_t TsdfVolume::getTotalVolumeUnits() const { return 1; }
void TsdfVolume::getBoundingBox(OutputArray bb, int precision) const
{
if (precision == Volume::BoundingBoxPrecision::VOXEL)
{
CV_Error(Error::StsNotImplemented, "Voxel mode is not implemented yet");
}
else
{
float sz = this->settings.getVoxelSize();
Vec3f res;
this->settings.getVolumeResolution(res);
Vec3f volSize = res * sz;
Vec6f(0, 0, 0, volSize[0], volSize[1], volSize[2]).copyTo(bb);
}
}
void TsdfVolume::setEnableGrowth(bool /*v*/) { }
bool TsdfVolume::getEnableGrowth() const
{
return false;
}
// HASH_TSDF
HashTsdfVolume::HashTsdfVolume(const VolumeSettings& _settings) :
Volume::Impl(_settings)
{
Vec3i resolution;
settings.getVolumeResolution(resolution);
const Point3i volResolution = Point3i(resolution);
volumeUnitDegree = calcVolumeUnitDegree(volResolution);
#ifndef HAVE_OPENCL
volUnitsData = cv::Mat(VOLUMES_SIZE, resolution[0] * resolution[1] * resolution[2], rawType<TsdfVoxel>());
reset();
#else
if (useGPU)
{
reset();
}
else
{
cpu_volUnitsData = cv::Mat(VOLUMES_SIZE, resolution[0] * resolution[1] * resolution[2], rawType<TsdfVoxel>());
reset();
}
#endif
}
HashTsdfVolume::~HashTsdfVolume() {}
void HashTsdfVolume::integrate(const OdometryFrame& frame, InputArray _cameraPose)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENCL
Mat depth;
#else
UMat depth;
#endif
frame.getDepth(depth);
integrate(depth, _cameraPose);
}
void HashTsdfVolume::integrate(InputArray _depth, InputArray _cameraPose)
{
#ifndef HAVE_OPENCL
Mat depth = _depth.getMat();
#else
UMat depth = _depth.getUMat();
#endif
const Matx44f cameraPose = _cameraPose.getMat();
Matx33f intr;
settings.getCameraIntegrateIntrinsics(intr);
Intr intrinsics(intr);
Vec6f newParams((float)depth.rows, (float)depth.cols,
intrinsics.fx, intrinsics.fy,
intrinsics.cx, intrinsics.cy);
if (!(frameParams == newParams))
{
frameParams = newParams;
#ifndef HAVE_OPENCL
preCalculationPixNorm(depth.size(), intrinsics, pixNorms);
#else
if (useGPU)
ocl_preCalculationPixNorm(depth.size(), intrinsics, gpu_pixNorms);
else
preCalculationPixNorm(depth.size(), intrinsics, cpu_pixNorms);
#endif
}
#ifndef HAVE_OPENCL
integrateHashTsdfVolumeUnit(settings, cameraPose, lastVolIndex, lastFrameId, volumeUnitDegree, enableGrowth, depth, pixNorms, volUnitsData, volumeUnits);
lastFrameId++;
#else
if (useGPU)
{
ocl_integrateHashTsdfVolumeUnit(settings, cameraPose, lastVolIndex, lastFrameId, bufferSizeDegree, volumeUnitDegree, enableGrowth, depth, gpu_pixNorms,
lastVisibleIndices, volUnitsDataCopy, gpu_volUnitsData, hashTable, isActiveFlags);
}
else
{
integrateHashTsdfVolumeUnit(settings, cameraPose, lastVolIndex, lastFrameId, volumeUnitDegree, enableGrowth, depth,
cpu_pixNorms, cpu_volUnitsData, cpu_volumeUnits);
lastFrameId++;
}
#endif
}
void HashTsdfVolume::integrate(InputArray, InputArray, InputArray)
{
CV_Error(cv::Error::StsBadFunc, "This volume doesn't support vertex colors");
}
void HashTsdfVolume::raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const
{
Matx33f intr;
settings.getCameraRaycastIntrinsics(intr);
raycast(cameraPose, settings.getRaycastHeight(), settings.getRaycastWidth(), intr, points, normals, colors);
}
void HashTsdfVolume::raycast(InputArray _cameraPose, int height, int width, InputArray intr, OutputArray _points, OutputArray _normals, OutputArray _colors) const
{
if (_colors.needed())
CV_Error(cv::Error::StsBadFunc, "This volume doesn't support vertex colors");
const Matx44f cameraPose = _cameraPose.getMat();
#ifndef HAVE_OPENCL
raycastHashTsdfVolumeUnit(settings, cameraPose, height, width, intr, volumeUnitDegree, volUnitsData, volumeUnits, _points, _normals);
#else
if (useGPU)
ocl_raycastHashTsdfVolumeUnit(settings, cameraPose, height, width, intr, volumeUnitDegree, hashTable, gpu_volUnitsData, _points, _normals);
else
raycastHashTsdfVolumeUnit(settings, cameraPose, height, width, intr, volumeUnitDegree, cpu_volUnitsData, cpu_volumeUnits, _points, _normals);
#endif
}
void HashTsdfVolume::fetchNormals(InputArray points, OutputArray normals) const
{
#ifndef HAVE_OPENCL
fetchNormalsFromHashTsdfVolumeUnit(settings, volUnitsData, volumeUnits, volumeUnitDegree, points, normals);
#else
if (useGPU)
ocl_fetchNormalsFromHashTsdfVolumeUnit(settings, volumeUnitDegree, gpu_volUnitsData, volUnitsDataCopy, hashTable, points, normals);
else
fetchNormalsFromHashTsdfVolumeUnit(settings, cpu_volUnitsData, cpu_volumeUnits, volumeUnitDegree, points, normals);
#endif
}
void HashTsdfVolume::fetchPointsNormals(OutputArray points, OutputArray normals) const
{
#ifndef HAVE_OPENCL
fetchPointsNormalsFromHashTsdfVolumeUnit(settings, volUnitsData, volumeUnits, volumeUnitDegree, points, normals);
#else
if (useGPU)
ocl_fetchPointsNormalsFromHashTsdfVolumeUnit(settings, volumeUnitDegree, gpu_volUnitsData, volUnitsDataCopy, hashTable, points, normals);
else
fetchPointsNormalsFromHashTsdfVolumeUnit(settings, cpu_volUnitsData, cpu_volumeUnits, volumeUnitDegree, points, normals);
#endif
}
void HashTsdfVolume::fetchPointsNormalsColors(OutputArray, OutputArray, OutputArray) const
{
CV_Error(cv::Error::StsBadFunc, "This volume doesn't support vertex colors");
};
void HashTsdfVolume::reset()
{
CV_TRACE_FUNCTION();
lastVolIndex = 0;
lastFrameId = 0;
enableGrowth = true;
#ifndef HAVE_OPENCL
volUnitsData.forEach<VecTsdfVoxel>([](VecTsdfVoxel& vv, const int* /* position */)
{
TsdfVoxel& v = reinterpret_cast<TsdfVoxel&>(vv);
v.tsdf = floatToTsdf(0.0f); v.weight = 0;
});
volumeUnits = VolumeUnitIndexes();
#else
if (useGPU)
{
Vec3i resolution;
settings.getVolumeResolution(resolution);
bufferSizeDegree = 15;
int buff_lvl = (int)(1 << bufferSizeDegree);
int volCubed = resolution[0] * resolution[1] * resolution[2];
volUnitsDataCopy = cv::Mat(buff_lvl, volCubed, rawType<TsdfVoxel>());
gpu_volUnitsData = cv::UMat(buff_lvl, volCubed, CV_8UC2);
lastVisibleIndices = cv::UMat(buff_lvl, 1, CV_32S);
isActiveFlags = cv::UMat(buff_lvl, 1, CV_8U);
hashTable = CustomHashSet();
frameParams = Vec6f();
gpu_pixNorms = UMat();
}
else
{
cpu_volUnitsData.forEach<VecTsdfVoxel>([](VecTsdfVoxel& vv, const int* /* position */)
{
TsdfVoxel& v = reinterpret_cast<TsdfVoxel&>(vv);
v.tsdf = floatToTsdf(0.0f); v.weight = 0;
});
cpu_volumeUnits = VolumeUnitIndexes();
}
#endif
}
int HashTsdfVolume::getVisibleBlocks() const { return 1; }
size_t HashTsdfVolume::getTotalVolumeUnits() const { return 1; }
void HashTsdfVolume::setEnableGrowth(bool v)
{
enableGrowth = v;
}
bool HashTsdfVolume::getEnableGrowth() const
{
return enableGrowth;
}
void HashTsdfVolume::getBoundingBox(OutputArray boundingBox, int precision) const
{
if (precision == Volume::BoundingBoxPrecision::VOXEL)
{
CV_Error(Error::StsNotImplemented, "Voxel mode is not implemented yet");
}
else
{
Vec3i res;
this->settings.getVolumeResolution(res);
float voxelSize = this->settings.getVoxelSize();
float side = res[0] * voxelSize;
std::vector<Vec3i> vi;
#ifndef HAVE_OPENCL
for (const auto& keyvalue : volumeUnits)
{
vi.push_back(keyvalue.first);
}
#else
if (useGPU)
{
for (int row = 0; row < hashTable.last; row++)
{
Vec4i idx4 = hashTable.data[row];
vi.push_back(Vec3i(idx4[0], idx4[1], idx4[2]));
}
}
else
{
for (const auto& keyvalue : cpu_volumeUnits)
{
vi.push_back(keyvalue.first);
}
}
#endif
if (vi.empty())
{
boundingBox.setZero();
}
else
{
std::vector<Point3f> pts;
for (Vec3i idx : vi)
{
Point3f base = Point3f((float)idx[0], (float)idx[1], (float)idx[2]) * side;
pts.push_back(base);
pts.push_back(base + Point3f(side, 0, 0));
pts.push_back(base + Point3f(0, side, 0));
pts.push_back(base + Point3f(0, 0, side));
pts.push_back(base + Point3f(side, side, 0));
pts.push_back(base + Point3f(side, 0, side));
pts.push_back(base + Point3f(0, side, side));
pts.push_back(base + Point3f(side, side, side));
}
const float mval = std::numeric_limits<float>::max();
Vec6f bb(mval, mval, mval, -mval, -mval, -mval);
for (auto p : pts)
{
// pt in local coords
Point3f pg = p;
bb[0] = min(bb[0], pg.x);
bb[1] = min(bb[1], pg.y);
bb[2] = min(bb[2], pg.z);
bb[3] = max(bb[3], pg.x);
bb[4] = max(bb[4], pg.y);
bb[5] = max(bb[5], pg.z);
}
bb.copyTo(boundingBox);
}
}
}
// COLOR_TSDF
ColorTsdfVolume::ColorTsdfVolume(const VolumeSettings& _settings) :
Volume::Impl(_settings)
{
Vec3i volResolution;
settings.getVolumeResolution(volResolution);
volume = Mat(1, volResolution[0] * volResolution[1] * volResolution[2], rawType<RGBTsdfVoxel>());
reset();
}
ColorTsdfVolume::~ColorTsdfVolume() {}
void ColorTsdfVolume::integrate(const OdometryFrame& frame, InputArray cameraPose)
{
CV_TRACE_FUNCTION();
Mat depth;
frame.getDepth(depth);
Mat rgb;
frame.getImage(rgb);
integrate(depth, rgb, cameraPose);
}
void ColorTsdfVolume::integrate(InputArray, InputArray)
{
CV_Error(cv::Error::StsBadFunc, "Color data should be passed for this volume type");
}
void ColorTsdfVolume::integrate(InputArray _depth, InputArray _image, InputArray _cameraPose)
{
Mat depth = _depth.getMat();
Colors image = _image.getMat();
const Matx44f cameraPose = _cameraPose.getMat();
Matx33f intr;
settings.getCameraIntegrateIntrinsics(intr);
Intr intrinsics(intr);
Vec6f newParams((float)depth.rows, (float)depth.cols,
intrinsics.fx, intrinsics.fy,
intrinsics.cx, intrinsics.cy);
if (!(frameParams == newParams))
{
frameParams = newParams;
preCalculationPixNorm(depth.size(), intrinsics, pixNorms);
}
integrateColorTsdfVolumeUnit(settings, cameraPose, depth, image, pixNorms, volume);
}
void ColorTsdfVolume::raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const
{
Matx33f intr;
settings.getCameraRaycastIntrinsics(intr);
raycast(cameraPose, settings.getRaycastHeight(), settings.getRaycastWidth(), intr, points, normals, colors);
}
void ColorTsdfVolume::raycast(InputArray _cameraPose, int height, int width, InputArray intr, OutputArray _points, OutputArray _normals, OutputArray _colors) const
{
const Matx44f cameraPose = _cameraPose.getMat();
raycastColorTsdfVolumeUnit(settings, cameraPose, height, width, intr, volume, _points, _normals, _colors);
}
void ColorTsdfVolume::fetchNormals(InputArray points, OutputArray normals) const
{
fetchNormalsFromColorTsdfVolumeUnit(settings, volume, points, normals);
}
void ColorTsdfVolume::fetchPointsNormals(OutputArray points, OutputArray normals) const
{
fetchPointsNormalsFromColorTsdfVolumeUnit(settings, volume, points, normals);
}
void ColorTsdfVolume::fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const
{
fetchPointsNormalsColorsFromColorTsdfVolumeUnit(settings, volume, points, normals, colors);
}
void ColorTsdfVolume::reset()
{
CV_TRACE_FUNCTION();
volume.forEach<VecRGBTsdfVoxel>([](VecRGBTsdfVoxel& vv, const int* /* position */)
{
RGBTsdfVoxel& v = reinterpret_cast<RGBTsdfVoxel&>(vv);
v.tsdf = floatToTsdf(0.0f); v.weight = 0;
v.r = v.g = v.b = 0;
});
}
int ColorTsdfVolume::getVisibleBlocks() const { return 1; }
size_t ColorTsdfVolume::getTotalVolumeUnits() const { return 1; }
void ColorTsdfVolume::getBoundingBox(OutputArray bb, int precision) const
{
if (precision == Volume::BoundingBoxPrecision::VOXEL)
{
CV_Error(Error::StsNotImplemented, "Voxel mode is not implemented yet");
}
else
{
float sz = this->settings.getVoxelSize();
Vec3f res;
this->settings.getVolumeResolution(res);
Vec3f volSize = res * sz;
Vec6f(0, 0, 0, volSize[0], volSize[1], volSize[2]).copyTo(bb);
}
}
void ColorTsdfVolume::setEnableGrowth(bool /*v*/) { }
bool ColorTsdfVolume::getEnableGrowth() const
{
return false;
}
}
+257
View File
@@ -0,0 +1,257 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_3D_VOLUME_IMPL_HPP
#define OPENCV_3D_VOLUME_IMPL_HPP
#include <iostream>
#include "precomp.hpp"
#include "hash_tsdf_functions.hpp"
namespace cv
{
class Volume::Impl
{
private:
// TODO: make debug function, which show histogram of volume points values
// make this function run with debug lvl == 10
public:
Impl(const VolumeSettings& settings);
virtual ~Impl() {};
virtual void integrate(const OdometryFrame& frame, InputArray pose) = 0;
virtual void integrate(InputArray depth, InputArray pose) = 0;
virtual void integrate(InputArray depth, InputArray image, InputArray pose) = 0;
virtual void raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const = 0;
virtual void raycast(InputArray cameraPose, int height, int width, InputArray intr, OutputArray points, OutputArray normals, OutputArray colors) const = 0;
virtual void fetchNormals(InputArray points, OutputArray normals) const = 0;
virtual void fetchPointsNormals(OutputArray points, OutputArray normals) const = 0;
virtual void fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const = 0;
virtual void reset() = 0;
virtual int getVisibleBlocks() const = 0;
virtual size_t getTotalVolumeUnits() const = 0;
virtual void getBoundingBox(OutputArray bb, int precision) const = 0;
virtual void setEnableGrowth(bool v) = 0;
virtual bool getEnableGrowth() const = 0;
public:
VolumeSettings settings;
#ifdef HAVE_OPENCL
const bool useGPU;
#endif
};
class TsdfVolume : public Volume::Impl
{
public:
TsdfVolume(const VolumeSettings& settings);
~TsdfVolume();
virtual void integrate(const OdometryFrame& frame, InputArray pose) override;
virtual void integrate(InputArray depth, InputArray pose) override;
virtual void integrate(InputArray depth, InputArray image, InputArray pose) override;
virtual void raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void raycast(InputArray cameraPose, int height, int width, InputArray intr, OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void fetchNormals(InputArray points, OutputArray normals) const override;
virtual void fetchPointsNormals(OutputArray points, OutputArray normals) const override;
virtual void fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void reset() override;
virtual int getVisibleBlocks() const override;
virtual size_t getTotalVolumeUnits() const override;
// Gets bounding box in volume coordinates with given precision:
// VOLUME_UNIT - up to volume unit
// VOXEL - up to voxel
// returns (min_x, min_y, min_z, max_x, max_y, max_z) in volume coordinates
virtual void getBoundingBox(OutputArray bb, int precision) const override;
// Enabels or disables new volume unit allocation during integration
// Applicable for HashTSDF only
virtual void setEnableGrowth(bool v) override;
// Returns if new volume units are allocated during integration or not
// Applicable for HashTSDF only
virtual bool getEnableGrowth() const override;
public:
Vec6f frameParams;
#ifndef HAVE_OPENCL
Mat pixNorms;
// See zFirstMemOrder arg of parent class constructor
// for the array layout info
// Consist of Voxel elements
Mat volume;
#else
//temporary solution
Mat cpu_pixNorms;
Mat cpu_volume;
UMat gpu_pixNorms;
UMat gpu_volume;
#endif
};
typedef std::unordered_set<cv::Vec3i, tsdf_hash> VolumeUnitIndexSet;
typedef std::unordered_map<cv::Vec3i, VolumeUnit, tsdf_hash> VolumeUnitIndexes;
class HashTsdfVolume : public Volume::Impl
{
public:
HashTsdfVolume(const VolumeSettings& settings);
~HashTsdfVolume();
virtual void integrate(const OdometryFrame& frame, InputArray pose) override;
virtual void integrate(InputArray depth, InputArray pose) override;
virtual void integrate(InputArray depth, InputArray image, InputArray pose) override;
virtual void raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void raycast(InputArray cameraPose, int height, int width, InputArray intr, OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void fetchNormals(InputArray points, OutputArray normals) const override;
virtual void fetchPointsNormals(OutputArray points, OutputArray normals) const override;
virtual void fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void reset() override;
virtual int getVisibleBlocks() const override;
virtual size_t getTotalVolumeUnits() const override;
// Enabels or disables new volume unit allocation during integration
// Applicable for HashTSDF only
virtual void setEnableGrowth(bool v) override;
// Returns if new volume units are allocated during integration or not
// Applicable for HashTSDF only
virtual bool getEnableGrowth() const override;
// Gets bounding box in volume coordinates with given precision:
// VOLUME_UNIT - up to volume unit
// VOXEL - up to voxel
// returns (min_x, min_y, min_z, max_x, max_y, max_z) in volume coordinates
virtual void getBoundingBox(OutputArray bb, int precision) const override;
public:
int lastVolIndex;
int lastFrameId;
Vec6f frameParams;
int volumeUnitDegree;
bool enableGrowth;
#ifndef HAVE_OPENCL
Mat volUnitsData;
Mat pixNorms;
VolumeUnitIndexes volumeUnits;
#else
VolumeUnitIndexes cpu_volumeUnits;
Mat cpu_volUnitsData;
Mat cpu_pixNorms;
UMat gpu_volUnitsData;
UMat gpu_pixNorms;
int bufferSizeDegree;
// per-volume-unit data
UMat lastVisibleIndices;
UMat isActiveFlags;
//TODO: remove it when there's no CPU parts
Mat volUnitsDataCopy;
//TODO: move indexes.volumes to GPU
CustomHashSet hashTable;
#endif
};
class ColorTsdfVolume : public Volume::Impl
{
public:
ColorTsdfVolume(const VolumeSettings& settings);
~ColorTsdfVolume();
virtual void integrate(const OdometryFrame& frame, InputArray pose) override;
virtual void integrate(InputArray depth, InputArray pose) override;
virtual void integrate(InputArray depth, InputArray image, InputArray pose) override;
virtual void raycast(InputArray cameraPose, OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void raycast(InputArray cameraPose, int height, int width, InputArray intr, OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void fetchNormals(InputArray points, OutputArray normals) const override;
virtual void fetchPointsNormals(OutputArray points, OutputArray normals) const override;
virtual void fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const override;
virtual void reset() override;
virtual int getVisibleBlocks() const override;
virtual size_t getTotalVolumeUnits() const override;
// Gets bounding box in volume coordinates with given precision:
// VOLUME_UNIT - up to volume unit
// VOXEL - up to voxel
// returns (min_x, min_y, min_z, max_x, max_y, max_z) in volume coordinates
virtual void getBoundingBox(OutputArray bb, int precision) const override;
// Enabels or disables new volume unit allocation during integration
// Applicable for HashTSDF only
virtual void setEnableGrowth(bool v) override;
// Returns if new volume units are allocated during integration or not
// Applicable for HashTSDF only
virtual bool getEnableGrowth() const override;
private:
Vec4i volStrides;
Vec6f frameParams;
Mat pixNorms;
// See zFirstMemOrder arg of parent class constructor
// for the array layout info
// Consist of Voxel elements
Mat volume;
};
Volume::Volume(VolumeType vtype, const VolumeSettings& settings)
{
switch (vtype)
{
case VolumeType::TSDF:
this->impl = makePtr<TsdfVolume>(settings);
break;
case VolumeType::HashTSDF:
this->impl = makePtr<HashTsdfVolume>(settings);
break;
case VolumeType::ColorTSDF:
this->impl = makePtr<ColorTsdfVolume>(settings);
break;
default:
CV_Error(Error::StsInternal, "Incorrect OdometryType, you are able to use only { ICP, RGB, RGBD }");
break;
}
}
Volume::~Volume() {}
void Volume::integrate(const OdometryFrame& frame, InputArray pose) { this->impl->integrate(frame, pose); }
void Volume::integrate(InputArray depth, InputArray pose) { this->impl->integrate(depth, pose); }
void Volume::integrate(InputArray depth, InputArray image, InputArray pose) { this->impl->integrate(depth, image, pose); }
void Volume::raycast(InputArray cameraPose, OutputArray _points, OutputArray _normals) const { this->impl->raycast(cameraPose, _points, _normals, noArray()); }
void Volume::raycast(InputArray cameraPose, OutputArray _points, OutputArray _normals, OutputArray _colors) const { this->impl->raycast(cameraPose, _points, _normals, _colors); }
void Volume::raycast(InputArray cameraPose, int height, int width, InputArray _intr, OutputArray _points, OutputArray _normals) const { this->impl->raycast(cameraPose, height, width, _intr, _points, _normals, noArray()); }
void Volume::raycast(InputArray cameraPose, int height, int width, InputArray _intr, OutputArray _points, OutputArray _normals, OutputArray _colors) const { this->impl->raycast(cameraPose, height, width, _intr, _points, _normals, _colors); }
void Volume::fetchNormals(InputArray points, OutputArray normals) const { this->impl->fetchNormals(points, normals); }
void Volume::fetchPointsNormals(OutputArray points, OutputArray normals) const { this->impl->fetchPointsNormals(points, normals); }
void Volume::fetchPointsNormalsColors(OutputArray points, OutputArray normals, OutputArray colors) const { this->impl->fetchPointsNormalsColors(points, normals, colors); };
void Volume::reset() { this->impl->reset(); }
int Volume::getVisibleBlocks() const { return this->impl->getVisibleBlocks(); }
size_t Volume::getTotalVolumeUnits() const { return this->impl->getTotalVolumeUnits(); }
void Volume::getBoundingBox(OutputArray bb, int precision) const { this->impl->getBoundingBox(bb, precision); }
void Volume::setEnableGrowth(bool v) { this->impl->setEnableGrowth(v); }
bool Volume::getEnableGrowth() const { return this->impl->getEnableGrowth(); }
}
#endif // !OPENCV_3D_VOLUME_IMPL_HPP
@@ -0,0 +1,535 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
namespace cv
{
static Vec4i calcVolumeStrides(Point3i volumeResolution, bool ZFirstMemOrder)
{
// (xRes*yRes*zRes) array
// Depending on zFirstMemOrder arg:
// &elem(x, y, z) = data + x*zRes*yRes + y*zRes + z;
// &elem(x, y, z) = data + x + y*xRes + z*xRes*yRes;
int xdim, ydim, zdim;
if (ZFirstMemOrder)
{
xdim = volumeResolution.z * volumeResolution.y;
ydim = volumeResolution.z;
zdim = 1;
}
else
{
xdim = 1;
ydim = volumeResolution.x;
zdim = volumeResolution.x * volumeResolution.y;
}
return Vec4i(xdim, ydim, zdim);
}
class VolumeSettings::Impl
{
public:
Impl() {};
virtual ~Impl() {};
virtual void setIntegrateWidth(int val) = 0;
virtual int getIntegrateWidth() const = 0;
virtual void setIntegrateHeight(int val) = 0;
virtual int getIntegrateHeight() const = 0;
virtual void setRaycastWidth(int val) = 0;
virtual int getRaycastWidth() const = 0;
virtual void setRaycastHeight(int val) = 0;
virtual int getRaycastHeight() const = 0;
virtual void setDepthFactor(float val) = 0;
virtual float getDepthFactor() const = 0;
virtual void setVoxelSize(float val) = 0;
virtual float getVoxelSize() const = 0;
virtual void setTsdfTruncateDistance(float val) = 0;
virtual float getTsdfTruncateDistance() const = 0;
virtual void setMaxDepth(float val) = 0;
virtual float getMaxDepth() const = 0;
virtual void setMaxWeight(int val) = 0;
virtual int getMaxWeight() const = 0;
virtual void setRaycastStepFactor(float val) = 0;
virtual float getRaycastStepFactor() const = 0;
virtual void setVolumePose(InputArray val) = 0;
virtual void getVolumePose(OutputArray val) const = 0;
virtual void setVolumeResolution(InputArray val) = 0;
virtual void getVolumeResolution(OutputArray val) const = 0;
virtual void getVolumeStrides(OutputArray val) const = 0;
virtual void setCameraIntegrateIntrinsics(InputArray val) = 0;
virtual void getCameraIntegrateIntrinsics(OutputArray val) const = 0;
virtual void setCameraRaycastIntrinsics(InputArray val) = 0;
virtual void getCameraRaycastIntrinsics(OutputArray val) const = 0;
};
class VolumeSettingsImpl : public VolumeSettings::Impl
{
public:
VolumeSettingsImpl();
VolumeSettingsImpl(VolumeType volumeType);
~VolumeSettingsImpl();
virtual void setIntegrateWidth(int val) override;
virtual int getIntegrateWidth() const override;
virtual void setIntegrateHeight(int val) override;
virtual int getIntegrateHeight() const override;
virtual void setRaycastWidth(int val) override;
virtual int getRaycastWidth() const override;
virtual void setRaycastHeight(int val) override;
virtual int getRaycastHeight() const override;
virtual void setDepthFactor(float val) override;
virtual float getDepthFactor() const override;
virtual void setVoxelSize(float val) override;
virtual float getVoxelSize() const override;
virtual void setTsdfTruncateDistance(float val) override;
virtual float getTsdfTruncateDistance() const override;
virtual void setMaxDepth(float val) override;
virtual float getMaxDepth() const override;
virtual void setMaxWeight(int val) override;
virtual int getMaxWeight() const override;
virtual void setRaycastStepFactor(float val) override;
virtual float getRaycastStepFactor() const override;
virtual void setVolumePose(InputArray val) override;
virtual void getVolumePose(OutputArray val) const override;
virtual void setVolumeResolution(InputArray val) override;
virtual void getVolumeResolution(OutputArray val) const override;
virtual void getVolumeStrides(OutputArray val) const override;
virtual void setCameraIntegrateIntrinsics(InputArray val) override;
virtual void getCameraIntegrateIntrinsics(OutputArray val) const override;
virtual void setCameraRaycastIntrinsics(InputArray val) override;
virtual void getCameraRaycastIntrinsics(OutputArray val) const override;
private:
VolumeType volumeType;
int integrateWidth;
int integrateHeight;
int raycastWidth;
int raycastHeight;
float depthFactor;
float voxelSize;
float tsdfTruncateDistance;
float maxDepth;
int maxWeight;
float raycastStepFactor;
bool zFirstMemOrder;
Matx44f volumePose;
Point3i volumeResolution;
Vec4i volumeStrides;
Matx33f cameraIntegrateIntrinsics;
Matx33f cameraRaycastIntrinsics;
public:
// duplicate classes for all volumes
class DefaultTsdfSets {
public:
static const int integrateWidth = 640;
static const int integrateHeight = 480;
float ifx = 525.f; // focus point x axis
float ify = 525.f; // focus point y axis
float icx = float(integrateWidth) / 2.f - 0.5f; // central point x axis
float icy = float(integrateHeight) / 2.f - 0.5f; // central point y axis
const Matx33f cameraIntegrateIntrinsics = Matx33f(ifx, 0, icx, 0, ify, icy, 0, 0, 1); // camera settings
static const int raycastWidth = 640;
static const int raycastHeight = 480;
float rfx = 525.f; // focus point x axis
float rfy = 525.f; // focus point y axis
float rcx = float(raycastWidth) / 2.f - 0.5f; // central point x axis
float rcy = float(raycastHeight) / 2.f - 0.5f; // central point y axis
const Matx33f cameraRaycastIntrinsics = Matx33f(rfx, 0, rcx, 0, rfy, rcy, 0, 0, 1); // camera settings
static constexpr float depthFactor = 5000.f; // 5000 for the 16-bit PNG files, 1 for the 32-bit float images in the ROS bag files
static constexpr float volumeSize = 3.f; // meters
static constexpr float voxelSize = volumeSize / 128.f; //meters
static constexpr float tsdfTruncateDistance = 2 * voxelSize;
static constexpr float maxDepth = 0.f;
static const int maxWeight = 64; // number of frames
static constexpr float raycastStepFactor = 0.75f;
static const bool zFirstMemOrder = true; // order of voxels in volume
const Affine3f volumePose = Affine3f().translate(Vec3f(-volumeSize / 2.f, -volumeSize / 2.f, 0.5f));
const Matx44f volumePoseMatrix = volumePose.matrix;
// Unlike original code, this should work with any volume size
// Not only when (x,y,z % 32) == 0
const Point3i volumeResolution = Vec3i::all(128); //number of voxels
};
class DefaultHashTsdfSets {
public:
static const int integrateWidth = 640;
static const int integrateHeight = 480;
float ifx = 525.f; // focus point x axis
float ify = 525.f; // focus point y axis
float icx = float(integrateWidth) / 2.f - 0.5f; // central point x axis
float icy = float(integrateHeight) / 2.f - 0.5f; // central point y axis
const Matx33f cameraIntegrateIntrinsics = Matx33f(ifx, 0, icx, 0, ify, icy, 0, 0, 1); // camera settings
static const int raycastWidth = 640;
static const int raycastHeight = 480;
float rfx = 525.f; // focus point x axis
float rfy = 525.f; // focus point y axis
float rcx = float(raycastWidth) / 2.f - 0.5f; // central point x axis
float rcy = float(raycastHeight) / 2.f - 0.5f; // central point y axis
const Matx33f cameraRaycastIntrinsics = Matx33f(rfx, 0, rcx, 0, rfy, rcy, 0, 0, 1); // camera settings
static constexpr float depthFactor = 5000.f; // 5000 for the 16-bit PNG files, 1 for the 32-bit float images in the ROS bag files
static constexpr float volumeSize = 3.f; // meters
static constexpr float voxelSize = volumeSize / 512.f; //meters
static constexpr float tsdfTruncateDistance = 7 * voxelSize;
static constexpr float maxDepth = 4.f;
static const int maxWeight = 64; // number of frames
static constexpr float raycastStepFactor = 0.25f;
static const bool zFirstMemOrder = true; // order of voxels in volume
const Affine3f volumePose = Affine3f().translate(Vec3f(-volumeSize / 2.f, -volumeSize / 2.f, 0.5f));
const Matx44f volumePoseMatrix = volumePose.matrix;
// Unlike original code, this should work with any volume size
// Not only when (x,y,z % 32) == 0
const Point3i volumeResolution = Vec3i::all(16); //number of voxels
};
class DefaultColorTsdfSets {
public:
static const int integrateWidth = 640;
static const int integrateHeight = 480;
float ifx = 525.f; // focus point x axis
float ify = 525.f; // focus point y axis
float icx = float(integrateWidth) / 2.f - 0.5f; // central point x axis
float icy = float(integrateHeight) / 2.f - 0.5f; // central point y axis
float rgb_ifx = 525.f; // focus point x axis
float rgb_ify = 525.f; // focus point y axis
float rgb_icx = float(integrateWidth) / 2.f - 0.5f; // central point x axis
float rgb_icy = float(integrateHeight) / 2.f - 0.5f; // central point y axis
const Matx33f cameraIntegrateIntrinsics = Matx33f(ifx, 0, icx, 0, ify, icy, 0, 0, 1); // camera settings
static const int raycastWidth = 640;
static const int raycastHeight = 480;
float rfx = 525.f; // focus point x axis
float rfy = 525.f; // focus point y axis
float rcx = float(raycastWidth) / 2.f - 0.5f; // central point x axis
float rcy = float(raycastHeight) / 2.f - 0.5f; // central point y axis
float rgb_rfx = 525.f; // focus point x axis
float rgb_rfy = 525.f; // focus point y axis
float rgb_rcx = float(raycastWidth) / 2.f - 0.5f; // central point x axis
float rgb_rcy = float(raycastHeight) / 2.f - 0.5f; // central point y axis
const Matx33f cameraRaycastIntrinsics = Matx33f(rfx, 0, rcx, 0, rfy, rcy, 0, 0, 1); // camera settings
static constexpr float depthFactor = 5000.f; // 5000 for the 16-bit PNG files, 1 for the 32-bit float images in the ROS bag files
static constexpr float volumeSize = 3.f; // meters
static constexpr float voxelSize = volumeSize / 128.f; //meters
static constexpr float tsdfTruncateDistance = 2 * voxelSize;
static constexpr float maxDepth = 0.f;
static const int maxWeight = 64; // number of frames
static constexpr float raycastStepFactor = 0.75f;
static const bool zFirstMemOrder = true; // order of voxels in volume
const Affine3f volumePose = Affine3f().translate(Vec3f(-volumeSize / 2.f, -volumeSize / 2.f, 0.5f));
const Matx44f volumePoseMatrix = volumePose.matrix;
// Unlike original code, this should work with any volume size
// Not only when (x,y,z % 32) == 0
const Point3i volumeResolution = Vec3i::all(128); //number of voxels
};
};
VolumeSettings::VolumeSettings(VolumeType volumeType)
{
this->impl = makePtr<VolumeSettingsImpl>(volumeType);
}
VolumeSettings::VolumeSettings(const VolumeSettings& vs)
{
this->impl = makePtr<VolumeSettingsImpl>(*vs.impl.dynamicCast<VolumeSettingsImpl>());
}
VolumeSettings& VolumeSettings::operator=(const VolumeSettings& vs)
{
this->impl = makePtr<VolumeSettingsImpl>(*vs.impl.dynamicCast<VolumeSettingsImpl>());
return *this;
}
VolumeSettings::~VolumeSettings() {}
void VolumeSettings::setIntegrateWidth(int val) { this->impl->setIntegrateWidth(val); };
int VolumeSettings::getIntegrateWidth() const { return this->impl->getIntegrateWidth(); };
void VolumeSettings::setIntegrateHeight(int val) { this->impl->setIntegrateHeight(val); };
int VolumeSettings::getIntegrateHeight() const { return this->impl->getIntegrateHeight(); };
void VolumeSettings::setRaycastWidth(int val) { this->impl->setRaycastWidth(val); };
int VolumeSettings::getRaycastWidth() const { return this->impl->getRaycastWidth(); };
void VolumeSettings::setRaycastHeight(int val) { this->impl->setRaycastHeight(val); };
int VolumeSettings::getRaycastHeight() const { return this->impl->getRaycastHeight(); };
void VolumeSettings::setVoxelSize(float val) { this->impl->setVoxelSize(val); };
float VolumeSettings::getVoxelSize() const { return this->impl->getVoxelSize(); };
void VolumeSettings::setRaycastStepFactor(float val) { this->impl->setRaycastStepFactor(val); };
float VolumeSettings::getRaycastStepFactor() const { return this->impl->getRaycastStepFactor(); };
void VolumeSettings::setTsdfTruncateDistance(float val) { this->impl->setTsdfTruncateDistance(val); };
float VolumeSettings::getTsdfTruncateDistance() const { return this->impl->getTsdfTruncateDistance(); };
void VolumeSettings::setMaxDepth(float val) { this->impl->setMaxDepth(val); };
float VolumeSettings::getMaxDepth() const { return this->impl->getMaxDepth(); };
void VolumeSettings::setDepthFactor(float val) { this->impl->setDepthFactor(val); };
float VolumeSettings::getDepthFactor() const { return this->impl->getDepthFactor(); };
void VolumeSettings::setMaxWeight(int val) { this->impl->setMaxWeight(val); };
int VolumeSettings::getMaxWeight() const { return this->impl->getMaxWeight(); };
void VolumeSettings::setVolumePose(InputArray val) { this->impl->setVolumePose(val); };
void VolumeSettings::getVolumePose(OutputArray val) const { this->impl->getVolumePose(val); };
void VolumeSettings::setVolumeResolution(InputArray val) { this->impl->setVolumeResolution(val); };
void VolumeSettings::getVolumeResolution(OutputArray val) const { this->impl->getVolumeResolution(val); };
void VolumeSettings::getVolumeStrides(OutputArray val) const { this->impl->getVolumeStrides(val); };
void VolumeSettings::setCameraIntegrateIntrinsics(InputArray val) { this->impl->setCameraIntegrateIntrinsics(val); };
void VolumeSettings::getCameraIntegrateIntrinsics(OutputArray val) const { this->impl->getCameraIntegrateIntrinsics(val); };
void VolumeSettings::setCameraRaycastIntrinsics(InputArray val) { this->impl->setCameraRaycastIntrinsics(val); };
void VolumeSettings::getCameraRaycastIntrinsics(OutputArray val) const { this->impl->getCameraRaycastIntrinsics(val); };
VolumeSettingsImpl::VolumeSettingsImpl()
: VolumeSettingsImpl(VolumeType::TSDF)
{
}
VolumeSettingsImpl::VolumeSettingsImpl(VolumeType _volumeType)
{
volumeType = _volumeType;
if (volumeType == VolumeType::TSDF)
{
DefaultTsdfSets ds = DefaultTsdfSets();
this->integrateWidth = ds.integrateWidth;
this->integrateHeight = ds.integrateHeight;
this->raycastWidth = ds.raycastWidth;
this->raycastHeight = ds.raycastHeight;
this->depthFactor = ds.depthFactor;
this->voxelSize = ds.voxelSize;
this->tsdfTruncateDistance = ds.tsdfTruncateDistance;
this->maxDepth = ds.maxDepth;
this->maxWeight = ds.maxWeight;
this->raycastStepFactor = ds.raycastStepFactor;
this->zFirstMemOrder = ds.zFirstMemOrder;
this->volumePose = ds.volumePoseMatrix;
this->volumeResolution = ds.volumeResolution;
this->volumeStrides = calcVolumeStrides(ds.volumeResolution, ds.zFirstMemOrder);
this->cameraIntegrateIntrinsics = ds.cameraIntegrateIntrinsics;
this->cameraRaycastIntrinsics = ds.cameraRaycastIntrinsics;
}
else if (volumeType == VolumeType::HashTSDF)
{
DefaultHashTsdfSets ds = DefaultHashTsdfSets();
this->integrateWidth = ds.integrateWidth;
this->integrateHeight = ds.integrateHeight;
this->raycastWidth = ds.raycastWidth;
this->raycastHeight = ds.raycastHeight;
this->depthFactor = ds.depthFactor;
this->voxelSize = ds.voxelSize;
this->tsdfTruncateDistance = ds.tsdfTruncateDistance;
this->maxDepth = ds.maxDepth;
this->maxWeight = ds.maxWeight;
this->raycastStepFactor = ds.raycastStepFactor;
this->zFirstMemOrder = ds.zFirstMemOrder;
this->volumePose = ds.volumePoseMatrix;
this->volumeResolution = ds.volumeResolution;
this->volumeStrides = calcVolumeStrides(ds.volumeResolution, ds.zFirstMemOrder);
this->cameraIntegrateIntrinsics = ds.cameraIntegrateIntrinsics;
this->cameraRaycastIntrinsics = ds.cameraRaycastIntrinsics;
}
else if (volumeType == VolumeType::ColorTSDF)
{
DefaultColorTsdfSets ds = DefaultColorTsdfSets();
this->integrateWidth = ds.integrateWidth;
this->integrateHeight = ds.integrateHeight;
this->raycastWidth = ds.raycastWidth;
this->raycastHeight = ds.raycastHeight;
this->depthFactor = ds.depthFactor;
this->voxelSize = ds.voxelSize;
this->tsdfTruncateDistance = ds.tsdfTruncateDistance;
this->maxDepth = ds.maxDepth;
this->maxWeight = ds.maxWeight;
this->raycastStepFactor = ds.raycastStepFactor;
this->zFirstMemOrder = ds.zFirstMemOrder;
this->volumePose = ds.volumePoseMatrix;
this->volumeResolution = ds.volumeResolution;
this->volumeStrides = calcVolumeStrides(ds.volumeResolution, ds.zFirstMemOrder);
this->cameraIntegrateIntrinsics = ds.cameraIntegrateIntrinsics;
this->cameraRaycastIntrinsics = ds.cameraRaycastIntrinsics;
}
}
VolumeSettingsImpl::~VolumeSettingsImpl() {}
void VolumeSettingsImpl::setIntegrateWidth(int val)
{
this->integrateWidth = val;
}
int VolumeSettingsImpl::getIntegrateWidth() const
{
return this->integrateWidth;
}
void VolumeSettingsImpl::setIntegrateHeight(int val)
{
this->integrateHeight = val;
}
int VolumeSettingsImpl::getIntegrateHeight() const
{
return this->integrateHeight;
}
void VolumeSettingsImpl::setRaycastWidth(int val)
{
this->raycastWidth = val;
}
int VolumeSettingsImpl::getRaycastWidth() const
{
return this->raycastWidth;
}
void VolumeSettingsImpl::setRaycastHeight(int val)
{
this->raycastHeight = val;
}
int VolumeSettingsImpl::getRaycastHeight() const
{
return this->raycastHeight;
}
void VolumeSettingsImpl::setDepthFactor(float val)
{
this->depthFactor = val;
}
float VolumeSettingsImpl::getDepthFactor() const
{
return this->depthFactor;
}
void VolumeSettingsImpl::setVoxelSize(float val)
{
this->voxelSize = val;
}
float VolumeSettingsImpl::getVoxelSize() const
{
return this->voxelSize;
}
void VolumeSettingsImpl::setTsdfTruncateDistance(float val)
{
this->tsdfTruncateDistance = val;
}
float VolumeSettingsImpl::getTsdfTruncateDistance() const
{
return this->tsdfTruncateDistance;
}
void VolumeSettingsImpl::setMaxDepth(float val)
{
this->maxDepth = val;
}
float VolumeSettingsImpl::getMaxDepth() const
{
return this->maxDepth;
}
void VolumeSettingsImpl::setMaxWeight(int val)
{
this->maxWeight = val;
}
int VolumeSettingsImpl::getMaxWeight() const
{
return this->maxWeight;
}
void VolumeSettingsImpl::setRaycastStepFactor(float val)
{
this->raycastStepFactor = val;
}
float VolumeSettingsImpl::getRaycastStepFactor() const
{
return this->raycastStepFactor;
}
void VolumeSettingsImpl::setVolumePose(InputArray val)
{
if (!val.empty())
{
val.copyTo(this->volumePose);
}
}
void VolumeSettingsImpl::getVolumePose(OutputArray val) const
{
Mat(this->volumePose).copyTo(val);
}
void VolumeSettingsImpl::setVolumeResolution(InputArray val)
{
if (!val.empty())
{
this->volumeResolution = Point3i(val.getMat());
this->volumeStrides = calcVolumeStrides(this->volumeResolution, this->zFirstMemOrder);
}
}
void VolumeSettingsImpl::getVolumeResolution(OutputArray val) const
{
Mat(this->volumeResolution).copyTo(val);
}
void VolumeSettingsImpl::getVolumeStrides(OutputArray val) const
{
Mat(this->volumeStrides).copyTo(val);
}
void VolumeSettingsImpl::setCameraIntegrateIntrinsics(InputArray val)
{
if (!val.empty())
{
this->cameraIntegrateIntrinsics = Matx33f(val.getMat());
}
}
void VolumeSettingsImpl::getCameraIntegrateIntrinsics(OutputArray val) const
{
Mat(this->cameraIntegrateIntrinsics).copyTo(val);
}
void VolumeSettingsImpl::setCameraRaycastIntrinsics(InputArray val)
{
if (!val.empty())
{
this->cameraRaycastIntrinsics = Matx33f(val.getMat());
}
}
void VolumeSettingsImpl::getCameraRaycastIntrinsics(OutputArray val) const
{
Mat(this->cameraRaycastIntrinsics).copyTo(val);
}
}
+10
View File
@@ -0,0 +1,10 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#if defined(HAVE_HPX)
#include <hpx/hpx_main.hpp>
#endif
CV_TEST_MAIN("")
+725
View File
@@ -0,0 +1,725 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
#include <opencv2/geometry.hpp>
#include <opencv2/core/quaternion.hpp>
using namespace cv;
namespace opencv_test { namespace {
const int W = 640;
const int H = 480;
//int window_size = 5;
float focal_length = 525;
float cx = W / 2.f + 0.5f;
float cy = H / 2.f + 0.5f;
static Mat K() { static Mat res = (Mat_<double>(3, 3) << focal_length, 0, cx, 0, focal_length, cy, 0, 0, 1); return res; }
static Mat Kinv() { static Mat res = K().inv(); return res; }
void points3dToDepth16U(const Mat_<Vec4f>& points3d, Mat& depthMap);
void points3dToDepth16U(const Mat_<Vec4f>& points3d, Mat& depthMap)
{
std::vector<Point3f> points3dvec;
for (int i = 0; i < H; i++)
for (int j = 0; j < W; j++)
points3dvec.push_back(Point3f(points3d(i, j)[0], points3d(i, j)[1], points3d(i, j)[2]));
std::vector<Point2f> img_points;
depthMap = Mat::zeros(H, W, CV_32F);
Vec3f R(0.0, 0.0, 0.0);
Vec3f T(0.0, 0.0, 0.0);
cv::projectPoints(points3dvec, R, T, K(), Mat(), img_points);
float maxv = 0.f;
int index = 0;
for (int i = 0; i < H; i++)
{
for (int j = 0; j < W; j++)
{
float value = (points3d(i, j))[2]; // value is the z
depthMap.at<float>(cvRound(img_points[index].y), cvRound(img_points[index].x)) = value;
maxv = std::max(maxv, value);
index++;
}
}
double scale = ((1 << 16) - 1) / maxv;
depthMap.convertTo(depthMap, CV_16U, scale);
}
struct Plane
{
public:
Vec4d nd;
Plane() : nd(1, 0, 0, 0) { }
static Plane generate(RNG& rng)
{
// Gaussian 3D distribution is separable and spherically symmetrical
// Being normalized, its points represent uniformly distributed points on a sphere (i.e. normal directions)
double sigma = 1.0;
Vec3d ngauss;
ngauss[0] = rng.gaussian(sigma);
ngauss[1] = rng.gaussian(sigma);
ngauss[2] = rng.gaussian(sigma);
ngauss = ngauss * (1.0 / cv::norm(ngauss));
double d = rng.uniform(-2.0, 2.0);
Plane p;
p.nd = Vec4d(ngauss[0], ngauss[1], ngauss[2], d);
return p;
}
Vec3d pixelIntersection(double u, double v, const Matx33d& K_inv)
{
Vec3d uv1(u, v, 1);
// pixel reprojected to camera space
Matx31d pspace = K_inv * uv1;
double d = this->nd[3];
double dotp = pspace.ddot({this->nd[0], this->nd[1], this->nd[2]});
double d_over_dotp = d / dotp;
if (std::fabs(dotp) <= 1e-9)
{
d_over_dotp = 1.0;
CV_LOG_INFO(NULL, "warning, dotp nearly 0! " << dotp);
}
Matx31d pmeet = pspace * (- d_over_dotp);
return {pmeet(0, 0), pmeet(1, 0), pmeet(2, 0)};
}
};
void gen_points_3d(std::vector<Plane>& planes_out, Mat_<unsigned char> &plane_mask, Mat& points3d, Mat& normals,
int n_planes, float scale, RNG& rng)
{
const double minGoodZ = 0.0001;
const double maxGoodZ = 1000.0;
std::vector<Plane> planes;
for (int i = 0; i < n_planes; i++)
{
bool found = false;
for (int j = 0; j < 100; j++)
{
Plane px = Plane::generate(rng);
// Check that area corners have good z values
// So that they won't break rendering
double x0 = double(i) * double(W) / double(n_planes);
double x1 = double(i+1) * double(W) / double(n_planes);
std::vector<Point2d> corners = {{x0, 0}, {x0, H - 1}, {x1, 0}, {x1, H - 1}};
double minz = std::numeric_limits<double>::max();
double maxz = 0.0;
for (auto p : corners)
{
Vec3d v = px.pixelIntersection(p.x, p.y, Kinv());
minz = std::min(minz, v[2]);
maxz = std::max(maxz, v[2]);
}
if (minz > minGoodZ && maxz < maxGoodZ)
{
planes.push_back(px);
found = true;
break;
}
}
ASSERT_TRUE(found) << "Failed to generate proper random plane" << std::endl;
}
Mat_ < Vec4f > outp(H, W);
Mat_ < Vec4f > outn(H, W);
plane_mask.create(H, W);
// n ( r - r_0) = 0
// n * r_0 = d
//
// r_0 = (0,0,0)
// r[0]
for (int v = 0; v < H; v++)
{
for (int u = 0; u < W; u++)
{
unsigned int plane_index = (unsigned int)((u / float(W)) * planes.size());
Plane plane = planes[plane_index];
Vec3f pt = Vec3f(plane.pixelIntersection((double)u, (double)v, Kinv()) * scale);
outp(v, u) = {pt[0], pt[1], pt[2], 0};
outn(v, u) = {(float)plane.nd[0], (float)plane.nd[1], (float)plane.nd[2], 0};
plane_mask(v, u) = (uchar)plane_index;
}
}
planes_out = planes;
points3d = outp;
normals = outn;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CV_ENUM(NormalComputers, RgbdNormals::RGBD_NORMALS_METHOD_FALS,
RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD,
RgbdNormals::RGBD_NORMALS_METHOD_SRI,
RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT);
typedef std::tuple<MatDepth, NormalComputers, bool, double, double, double, double, double> NormalsTestData;
typedef std::tuple<NormalsTestData, int> NormalsTestParams;
const double threshold3d1d = 1e-12;
// Right angle is the maximum angle possible between two normals
const double hpi = CV_PI / 2.0;
const int nTestCasesNormals = 5;
class NormalsRandomPlanes : public ::testing::TestWithParam<NormalsTestParams>
{
protected:
void SetUp() override
{
p = GetParam();
depth = std::get<0>(std::get<0>(p));
alg = static_cast<RgbdNormals::RgbdNormalsMethod>(int(std::get<1>(std::get<0>(p))));
scale = std::get<2>(std::get<0>(p));
idx = std::get<1>(p);
float diffThreshold = scale ? 100000.f : 50.f;
normalsComputer = RgbdNormals::create(H, W, depth, K(), 5, diffThreshold, alg);
normalsComputer->cache();
}
struct NormalsCompareResult
{
double meanErr;
double maxErr;
};
static NormalsCompareResult checkNormals(Mat_<Vec4f> normals, Mat_<Vec4f> ground_normals)
{
double meanErr = 0, maxErr = 0;
for (int y = 0; y < normals.rows; ++y)
{
for (int x = 0; x < normals.cols; ++x)
{
Vec4f vec1 = normals(y, x), vec2 = ground_normals(y, x);
vec1 = vec1 / cv::norm(vec1);
vec2 = vec2 / cv::norm(vec2);
double dot = vec1.ddot(vec2);
// Just for rounding errors
double err = std::abs(dot) < 1.0 ? std::min(std::acos(dot), std::acos(-dot)) : 0.0;
meanErr += err;
maxErr = std::max(maxErr, err);
}
}
meanErr /= normals.rows * normals.cols;
return { meanErr, maxErr };
}
void runCase(bool scaleUp, int nPlanes, bool makeDepth,
double meanThreshold, double maxThreshold, double threshold3d)
{
RNG& rng = cv::theRNG();
rng.state += idx + nTestCasesNormals*int(scale) + alg*16 + depth*64;
std::vector<Plane> plane_params;
Mat_<unsigned char> plane_mask;
Mat points3d, ground_normals;
gen_points_3d(plane_params, plane_mask, points3d, ground_normals, nPlanes, scaleUp ? 5000.f : 1.f, rng);
Mat in;
if (makeDepth)
{
points3dToDepth16U(points3d, in);
}
else
{
in = points3d;
}
TickMeter tm;
tm.start();
Mat in_normals, normals3d;
//TODO: check other methods when 16U input is implemented for them
if (normalsComputer->getMethod() == RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD && in.channels() == 3)
{
std::vector<Mat> channels;
split(in, channels);
normalsComputer->apply(channels[2], in_normals);
normalsComputer->apply(in, normals3d);
}
else
normalsComputer->apply(in, in_normals);
tm.stop();
CV_LOG_INFO(NULL, "Speed: " << tm.getTimeMilli() << " ms");
Mat_<Vec4f> normals;
in_normals.convertTo(normals, CV_32FC4);
NormalsCompareResult res = checkNormals(normals, ground_normals);
double err3d = 0.0;
if (!normals3d.empty())
{
Mat_<Vec4f> cvtNormals3d;
normals3d.convertTo(cvtNormals3d, CV_32FC4);
err3d = checkNormals(cvtNormals3d, ground_normals).maxErr;
}
EXPECT_LE(res.meanErr, meanThreshold);
EXPECT_LE(res.maxErr, maxThreshold);
EXPECT_LE(err3d, threshold3d);
}
NormalsTestParams p;
int depth;
RgbdNormals::RgbdNormalsMethod alg;
bool scale;
int idx;
Ptr<RgbdNormals> normalsComputer;
};
//TODO Test NaNs in data
TEST_P(NormalsRandomPlanes, check1plane)
{
double meanErr = std::get<3>(std::get<0>(p));
double maxErr = std::get<4>(std::get<0>(p));
// 1 plane, continuous scene, very low error..
runCase(scale, 1, false, meanErr, maxErr, threshold3d1d);
}
TEST_P(NormalsRandomPlanes, check3planes)
{
double meanErr = std::get<5>(std::get<0>(p));
double maxErr = hpi;
// 3 discontinuities, more error expected
runCase(scale, 3, false, meanErr, maxErr, threshold3d1d);
}
TEST_P(NormalsRandomPlanes, check1plane16u)
{
// TODO: check other algos as soon as they support 16U depth inputs
if (alg == RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD && scale)
{
double meanErr = std::get<6>(std::get<0>(p));
double maxErr = hpi;
runCase(false, 1, true, meanErr, maxErr, threshold3d1d);
}
else
{
throw SkipTestException("Not implemented for anything except LINEMOD with scale");
}
}
TEST_P(NormalsRandomPlanes, check3planes16u)
{
// TODO: check other algos as soon as they support 16U depth inputs
if (alg == RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD && scale)
{
double meanErr = std::get<7>(std::get<0>(p));
double maxErr = hpi;
runCase(false, 3, true, meanErr, maxErr, threshold3d1d);
}
else
{
throw SkipTestException("Not implemented for anything except LINEMOD with scale");
}
}
INSTANTIATE_TEST_CASE_P(RGBD_Normals, NormalsRandomPlanes,
::testing::Combine(::testing::Values(
// 3 normal computer params + 5 thresholds:
//depth, alg, scale, 1plane mean, 1plane max, 3planes mean, 1plane16u mean, 3planes16 mean
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_FALS, true, 0.00362, 0.08881, 0.02175, 0, 0},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_FALS, false, 0.00374, 0.10309, 0.02, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_FALS, true, 0.00023, 0.00037, 0.01805, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_FALS, false, 0.00023, 0.00037, 0.01805, 0, 0},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD, true, 0.00186, 0.08974, 0.04528, 0.21220, 0.17314},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD, false, 0.00157, 0.01225, 0.04528, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD, true, 0.00160, 0.06526, 0.04371, 0.28837, 0.28918},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD, false, 0.00154, 0.06877, 0.04323, 0, 0},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_SRI, true, 0.01987, hpi, 0.036, 0, 0},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_SRI, false, 0.01962, hpi, 0.037, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_SRI, true, 0.01958, hpi, 0.037, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_SRI, false, 0.01995, hpi, 0.036, 0, 0},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT, true, 0.000230, 0.00038, 0.00450, 0, 0},
NormalsTestData {CV_32F, RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT, false, 0.000230, 0.00038, 0.00478, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT, true, 0.000221, 0.00038, 0.00469, 0, 0},
NormalsTestData {CV_64F, RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT, false, 0.000238, 0.00038, 0.00477, 0, 0}
), ::testing::Range(0, nTestCasesNormals)));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef std::tuple<NormalComputers, std::pair<double, double>> NormalComputerThresholds;
struct RenderedNormals: public ::testing::TestWithParam<std::tuple<MatDepth, NormalComputerThresholds, bool>>
{
static Mat readYaml(std::string fname)
{
Mat img;
FileStorage fs(fname, FileStorage::Mode::READ);
if (fs.isOpened() && fs.getFirstTopLevelNode().name() == "testImg")
{
fs["testImg"] >> img;
}
return img;
};
static Mat nanMask(Mat img)
{
int depth = img.depth();
Mat mask(img.size(), CV_8U);
for (int y = 0; y < img.rows; y++)
{
uchar* maskRow = mask.ptr<uchar>(y);
if (depth == CV_32F)
{
Vec3f *imgrow = img.ptr<Vec3f>(y);
for (int x = 0; x < img.cols; x++)
{
maskRow[x] = (imgrow[x] == imgrow[x])*255;
}
}
else if (depth == CV_64F)
{
Vec3d *imgrow = img.ptr<Vec3d>(y);
for (int x = 0; x < img.cols; x++)
{
maskRow[x] = (imgrow[x] == imgrow[x])*255;
}
}
}
return mask;
}
template<typename VT>
static Mat flipAxesT(Mat pts, int flip)
{
Mat flipped(pts.size(), pts.type());
for (int y = 0; y < pts.rows; y++)
{
VT *inrow = pts.ptr<VT>(y);
VT *outrow = flipped.ptr<VT>(y);
for (int x = 0; x < pts.cols; x++)
{
VT n = inrow[x];
n[0] = (flip & FLIP_X) ? -n[0] : n[0];
n[1] = (flip & FLIP_Y) ? -n[1] : n[1];
n[2] = (flip & FLIP_Z) ? -n[2] : n[2];
outrow[x] = n;
}
}
return flipped;
}
static const int FLIP_X = 1;
static const int FLIP_Y = 2;
static const int FLIP_Z = 4;
static Mat flipAxes(Mat pts, int flip)
{
int depth = pts.depth();
if (depth == CV_32F)
{
return flipAxesT<Vec3f>(pts, flip);
}
else if (depth == CV_64F)
{
return flipAxesT<Vec3d>(pts, flip);
}
else
{
return Mat();
}
}
template<typename VT>
static Mat_<typename VT::value_type> normalsErrorT(Mat_<VT> srcNormals, Mat_<VT> dstNormals)
{
typedef typename VT::value_type Val;
Mat out(srcNormals.size(), cv::traits::Depth<Val>::value, Scalar(0));
for (int y = 0; y < srcNormals.rows; y++)
{
VT *srcrow = srcNormals[y];
VT *dstrow = dstNormals[y];
Val *outrow = out.ptr<Val>(y);
for (int x = 0; x < srcNormals.cols; x++)
{
VT sn = srcrow[x];
VT dn = dstrow[x];
Val dot = sn.dot(dn);
Val v(0.0);
// Just for rounding errors
if (std::abs(dot) < 1)
v = std::min(std::acos(dot), std::acos(-dot));
outrow[x] = v;
}
}
return out;
}
static Mat normalsError(Mat srcNormals, Mat dstNormals)
{
int depth = srcNormals.depth();
int channels = srcNormals.channels();
if (depth == CV_32F)
{
if (channels == 3)
{
return normalsErrorT<Vec3f>(srcNormals, dstNormals);
}
else if (channels == 4)
{
return normalsErrorT<Vec4f>(srcNormals, dstNormals);
}
}
else if (depth == CV_64F)
{
if (channels == 3)
{
return normalsErrorT<Vec3d>(srcNormals, dstNormals);
}
else if (channels == 4)
{
return normalsErrorT<Vec4d>(srcNormals, dstNormals);
}
}
else
{
CV_Error(Error::StsInternal, "This type is unsupported");
}
return Mat();
}
};
TEST_P(RenderedNormals, check)
{
auto p = GetParam();
int depth = std::get<0>(p);
auto alg = static_cast<RgbdNormals::RgbdNormalsMethod>(int(std::get<0>(std::get<1>(p))));
bool scale = std::get<2>(p);
std::string dataPath = cvtest::TS::ptr()->get_data_path();
// The depth rendered from scene OPENCV_TEST_DATA_PATH + "/cv/rgbd/normals_check/normals_scene.blend"
std::string srcDepthFilename = dataPath + "/cv/rgbd/normals_check/depth.yaml.gz";
std::string srcNormalsFilename = dataPath + "/cv/rgbd/normals_check/normals%d.yaml.gz";
Mat srcDepth = readYaml(srcDepthFilename);
ASSERT_FALSE(srcDepth.empty()) << "Failed to load depth data";
Size depthSize = srcDepth.size();
Mat srcNormals;
std::array<Mat, 3> srcNormalsCh;
for (int i = 0; i < 3; i++)
{
Mat m = readYaml(cv::format(srcNormalsFilename.c_str(), i));
ASSERT_FALSE(m.empty()) << "Failed to load normals data";
if (depth == CV_64F)
{
Mat c;
m.convertTo(c, CV_64F);
m = c;
}
srcNormalsCh[i] = m;
}
cv::merge(srcNormalsCh, srcNormals);
// Convert saved normals from [0; 1] range to [-1; 1]
srcNormals = srcNormals * 2.0 - 1.0;
// Data obtained from Blender scene
Matx33f intr(666.6667f, 0.f, 320.f,
0.f, 666.6667f, 240.f,
0.f, 0.f, 1.f);
// Inverted camera rotation
Matx33d rotm = cv::Quatd(0.7805, 0.4835, 0.2087, 0.3369).conjugate().toRotMat3x3();
cv::transform(srcNormals, srcNormals, rotm);
Mat srcMask = srcDepth > 0;
float diffThreshold = 50.f;
if (scale)
{
srcDepth = srcDepth * 5000.0;
diffThreshold = 100000.f;
}
Mat srcCloud;
// The function with mask produces 1x(w*h) vector, this is not what we need
// depthTo3d(srcDepth, intr, srcCloud, srcMask);
depthTo3d(srcDepth, intr, srcCloud);
Scalar qnan = Scalar::all(std::numeric_limits<double>::quiet_NaN());
srcCloud.setTo(qnan, ~srcMask);
srcDepth.setTo(qnan, ~srcMask);
// For further result comparison
srcNormals.setTo(qnan, ~srcMask);
Ptr<RgbdNormals> normalsComputer = RgbdNormals::create(depthSize.height, depthSize.width, depth, intr, 5, diffThreshold, alg);
normalsComputer->cache();
Mat dstNormals, dstNormalsOrig, dstNormalsDepth;
normalsComputer->apply(srcCloud, dstNormals);
//TODO: add for other methods too when it's implemented
if (alg == RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD)
{
normalsComputer->apply(srcDepth, dstNormalsDepth);
dstNormalsOrig = dstNormals.clone();
}
// Remove 4th channel from dstNormals
Mat newDstNormals;
std::vector<Mat> dstNormalsCh;
split(dstNormals, dstNormalsCh);
dstNormalsCh.resize(3);
merge(dstNormalsCh, newDstNormals);
dstNormals = newDstNormals;
Mat dstMask = nanMask(dstNormals);
// div by 8 because uchar is 8-bit
double maskl2 = cv::norm(dstMask, srcMask, NORM_HAMMING) / 8;
// Flipping Y and Z to correspond to srcNormals
Mat flipped = flipAxes(dstNormals, FLIP_Y | FLIP_Z);
dstNormals = flipped;
Mat absdot = normalsError(srcNormals, dstNormals);
Mat cmpMask = srcMask & dstMask;
EXPECT_GT(countNonZero(cmpMask), 0);
double nrml2 = cv::norm(absdot, NORM_L2, cmpMask);
if (!dstNormalsDepth.empty())
{
Mat abs3d = normalsError(dstNormalsOrig, dstNormalsDepth);
double errInf = cv::norm(abs3d, NORM_INF, cmpMask);
double errL2 = cv::norm(abs3d, NORM_L2, cmpMask);
EXPECT_LE(errInf, 0.00085);
EXPECT_LE(errL2, 0.07718);
}
auto th = std::get<1>(std::get<1>(p));
EXPECT_LE(nrml2, th.first);
EXPECT_LE(maskl2, th.second);
}
INSTANTIATE_TEST_CASE_P(RGBD_Normals, RenderedNormals, ::testing::Combine(::testing::Values(CV_32F, CV_64F),
::testing::Values(
NormalComputerThresholds { RgbdNormals::RGBD_NORMALS_METHOD_FALS, { 81.8213, 0}},
NormalComputerThresholds { RgbdNormals::RGBD_NORMALS_METHOD_LINEMOD, { 107.2710, 29168}},
NormalComputerThresholds { RgbdNormals::RGBD_NORMALS_METHOD_SRI, { 73.2027, 17693}},
NormalComputerThresholds { RgbdNormals::RGBD_NORMALS_METHOD_CROSS_PRODUCT, { 57.9832, 2531}}),
::testing::Values(true, false)));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class RgbdPlaneGenerate : public ::testing::TestWithParam<std::tuple<int, bool, int>>
{
protected:
void SetUp() override
{
auto p = GetParam();
idx = std::get<0>(p);
checkNormals = std::get<1>(p);
nPlanes = std::get<2>(p);
}
int idx;
bool checkNormals;
int nPlanes;
};
TEST_P(RgbdPlaneGenerate, compute)
{
RNG &rng = cvtest::TS::ptr()->get_rng();
rng.state += idx;
std::vector<Plane> planes;
Mat points3d, ground_normals;
Mat_<unsigned char> gt_plane_mask;
gen_points_3d(planes, gt_plane_mask, points3d, ground_normals, nPlanes, 1.f, rng);
Mat plane_mask;
std::vector<Vec4f> plane_coefficients;
Mat normals;
if (checkNormals)
{
// First, get the normals
int depth = CV_32F;
Ptr<RgbdNormals> normalsComputer = RgbdNormals::create(H, W, depth, K(), 5, 50.f, RgbdNormals::RGBD_NORMALS_METHOD_FALS);
normalsComputer->apply(points3d, normals);
}
findPlanes(points3d, normals, plane_mask, plane_coefficients);
// Compare each found plane to each ground truth plane
int n_planes = (int)plane_coefficients.size();
int n_gt_planes = (int)planes.size();
Mat_<int> matching(n_gt_planes, n_planes);
for (int j = 0; j < n_gt_planes; ++j)
{
Mat gt_mask = gt_plane_mask == j;
int n_gt = countNonZero(gt_mask);
int n_max = 0, i_max = 0;
for (int i = 0; i < n_planes; ++i)
{
Mat dst;
bitwise_and(gt_mask, plane_mask == i, dst);
matching(j, i) = countNonZero(dst);
if (matching(j, i) > n_max)
{
n_max = matching(j, i);
i_max = i;
}
}
// Get the best match
ASSERT_LE(float(n_max - n_gt) / n_gt, 0.001);
// Compare the normals
Vec3d normal(plane_coefficients[i_max][0], plane_coefficients[i_max][1], plane_coefficients[i_max][2]);
Vec4d nd = planes[j].nd;
ASSERT_GE(std::abs(Vec3d(nd[0], nd[1], nd[2]).dot(normal)), 0.95);
}
}
// 1 plane, continuous scene, very low error
// 3 planes, 3 discontinuities, more error expected
INSTANTIATE_TEST_CASE_P(RGBD_Plane, RgbdPlaneGenerate, ::testing::Combine(::testing::Range(0, 10),
::testing::Values(false, true),
::testing::Values(1, 3)));
TEST(RGBD_Plane, regression2309ValgrindCheck)
{
Mat points(640, 480, CV_32FC3, Scalar::all(0));
// Note, 640%9 is 1 and 480%9 is 3
int blockSize = 9;
Mat mask;
std::vector<cv::Vec4f> planes;
// Will corrupt memory; valgrind gets triggered
findPlanes(points, noArray(), mask, planes, blockSize);
}
}} // namespace
+158
View File
@@ -0,0 +1,158 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
namespace opencv_test { namespace {
using namespace cv;
class OctreeTest: public testing::Test
{
protected:
void SetUp() override
{
pointCloudSize = 1000;
resolution = 0.0001;
int scale;
Point3i pmin, pmax;
scale = 1 << 20;
pmin = Point3i(-scale, -scale, -scale);
pmax = Point3i(scale, scale, scale);
RNG& rng_Point = theRNG(); // set random seed for fixing output 3D point.
// Generate 3D PointCloud
for(size_t i = 0; i < pointCloudSize; i++)
{
float _x = 10 * (float)rng_Point.uniform(pmin.x, pmax.x)/scale;
float _y = 10 * (float)rng_Point.uniform(pmin.y, pmax.y)/scale;
float _z = 10 * (float)rng_Point.uniform(pmin.z, pmax.z)/scale;
pointCloud.push_back(Point3f(_x, _y, _z));
}
// Generate Octree From PointCloud
treeTest = Octree::createWithResolution(resolution, pointCloud);
// Randomly generate another 3D point.
float _x = 10 * (float)rng_Point.uniform(pmin.x, pmax.x)/scale;
float _y = 10 * (float)rng_Point.uniform(pmin.y, pmax.y)/scale;
float _z = 10 * (float)rng_Point.uniform(pmin.z, pmax.z)/scale;
restPoint = Point3f(_x, _y, _z);
}
public:
//Origin point cloud data
std::vector<Point3f> pointCloud;
//Point cloud data from octree
std::vector<Point3f> restorePointCloudData;
//Color attribute of pointCloud from octree
std::vector<Point3f> restorePointCloudColor;
size_t pointCloudSize;
Point3f restPoint;
Ptr<Octree> treeTest;
double resolution;
};
TEST_F(OctreeTest, BasicFunctionTest)
{
// Check if the point in Bound.
EXPECT_TRUE(treeTest->isPointInBound(restPoint));
EXPECT_FALSE(treeTest->isPointInBound(restPoint + Point3f(60, 60, 60)));
// insert, delete Test.
EXPECT_FALSE(treeTest->deletePoint(restPoint));
EXPECT_FALSE(treeTest->insertPoint(restPoint + Point3f(60, 60, 60)));
EXPECT_TRUE(treeTest->insertPoint(restPoint));
EXPECT_TRUE(treeTest->deletePoint(restPoint));
EXPECT_FALSE(treeTest->empty());
EXPECT_NO_THROW(treeTest->clear());
EXPECT_TRUE(treeTest->empty());
}
TEST_F(OctreeTest, RadiusSearchTest)
{
float radius = 2.0f;
std::vector<Point3f> outputPoints;
std::vector<float> outputSquareDist;
EXPECT_NO_THROW(treeTest->radiusNNSearch(restPoint, radius, outputPoints, outputSquareDist));
EXPECT_EQ(outputPoints.size(),(unsigned int)5);
// The output is unsorted, so let's sort it before checking
std::map<float, Point3f> sortResults;
for (int i = 0; i < (int)outputPoints.size(); i++)
{
sortResults[outputSquareDist[i]] = outputPoints[i];
}
std::vector<Point3f> goldVals = {
{-8.1184864044189f, -0.528564453125f, 0.f},
{-8.405818939208f, -2.991247177124f, 0.f},
{-8.88461112976f, -1.881799697875f, 0.f},
{-6.551313400268f, -0.708484649658f, 0.f}
};
auto it = sortResults.begin();
for (int i = 0; i < (int)goldVals.size(); i++, it++)
{
Point3f p = it->second;
EXPECT_FLOAT_EQ(goldVals[i].x, p.x);
EXPECT_FLOAT_EQ(goldVals[i].y, p.y);
}
}
TEST_F(OctreeTest, KNNSearchTest)
{
int K = 10;
std::vector<Point3f> outputPoints;
std::vector<float> outputSquareDist;
EXPECT_NO_THROW(treeTest->KNNSearch(restPoint, K, outputPoints, outputSquareDist));
// The output is unsorted, so let's sort it before checking
std::map<float, Point3f> sortResults;
for (int i = 0; i < (int)outputPoints.size(); i++)
{
sortResults[outputSquareDist[i]] = outputPoints[i];
}
std::vector<Point3f> goldVals = {
{ -8.118486404418f, -0.528564453125f, 0.f },
{ -8.405818939208f, -2.991247177124f, 0.f },
{ -8.884611129760f, -1.881799697875f, 0.f },
{ -6.551313400268f, -0.708484649658f, 0.f }
};
auto it = sortResults.begin();
for (int i = 0; i < (int)goldVals.size(); i++, it++)
{
Point3f p = it->second;
EXPECT_FLOAT_EQ(goldVals[i].x, p.x);
EXPECT_FLOAT_EQ(goldVals[i].y, p.y);
}
}
TEST_F(OctreeTest, restoreTest) {
//restore the pointCloud data from octree.
EXPECT_NO_THROW(treeTest->getPointCloudByOctree(restorePointCloudData,restorePointCloudColor));
//The points in same leaf node will be seen as the same point. So if the resolution is small,
//it will work as a downSampling function.
EXPECT_LE(restorePointCloudData.size(),pointCloudSize);
//The distance between the restore point cloud data and origin data should less than resolution.
std::vector<Point3f> outputPoints;
std::vector<float> outputSquareDist;
EXPECT_NO_THROW(treeTest->getPointCloudByOctree(restorePointCloudData,restorePointCloudColor));
EXPECT_NO_THROW(treeTest->KNNSearch(restorePointCloudData[0], 1, outputPoints, outputSquareDist));
EXPECT_LE(abs(outputPoints[0].x - restorePointCloudData[0].x), resolution);
EXPECT_LE(abs(outputPoints[0].y - restorePointCloudData[0].y), resolution);
EXPECT_LE(abs(outputPoints[0].z - restorePointCloudData[0].z), resolution);
}
} // namespace
} // opencv_test
+833
View File
@@ -0,0 +1,833 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
namespace opencv_test { namespace {
static
void dilateFrame(Mat& image, Mat& depth)
{
CV_Assert(!image.empty());
CV_Assert(image.type() == CV_8UC1);
CV_Assert(!depth.empty());
CV_Assert(depth.type() == CV_32FC1);
CV_Assert(depth.size() == image.size());
Mat mask;
inRange(depth, FLT_EPSILON, 10, mask);
image.setTo(255, ~mask);
Mat minImage;
erode(image, minImage, Mat());
image.setTo(0, ~mask);
Mat maxImage;
dilate(image, maxImage, Mat());
depth.setTo(FLT_MAX, ~mask);
Mat minDepth;
erode(depth, minDepth, Mat());
depth.setTo(0, ~mask);
Mat maxDepth;
dilate(depth, maxDepth, Mat());
Mat dilatedMask;
dilate(mask, dilatedMask, Mat(), Point(-1,-1), 1);
for(int y = 0; y < depth.rows; y++)
for(int x = 0; x < depth.cols; x++)
if(!mask.at<uchar>(y,x) && dilatedMask.at<uchar>(y,x))
{
image.at<uchar>(y,x) = static_cast<uchar>(0.5f * (static_cast<float>(minImage.at<uchar>(y,x)) +
static_cast<float>(maxImage.at<uchar>(y,x))));
depth.at<float>(y,x) = 0.5f * (minDepth.at<float>(y,x) + maxDepth.at<float>(y,x));
}
}
class OdometryTest
{
public:
OdometryTest(OdometryType _otype,
OdometryAlgoType _algtype,
double _maxError1,
double _maxError5,
double _idError = DBL_EPSILON) :
otype(_otype),
algtype(_algtype),
maxError1(_maxError1),
maxError5(_maxError5),
idError(_idError)
{ }
void readData(Mat& image, Mat& depth) const;
static Mat getCameraMatrix()
{
float fx = 525.0f, // default
fy = 525.0f,
cx = 319.5f,
cy = 239.5f;
Matx33f K(fx, 0, cx,
0, fy, cy,
0, 0, 1);
return Mat(K);
}
static void generateRandomTransformation(Mat& R, Mat& t);
void run();
void checkUMats();
void prepareFrameCheck();
void processedDepthCheck();
OdometryType otype;
OdometryAlgoType algtype;
double maxError1;
double maxError5;
double idError;
};
void OdometryTest::readData(Mat& image, Mat& depth) const
{
std::string dataPath = cvtest::TS::ptr()->get_data_path();
std::string imageFilename = dataPath + "/cv/rgbd/rgb.png";
std::string depthFilename = dataPath + "/cv/rgbd/depth.png";
image = imread(imageFilename, 0);
depth = imread(depthFilename, -1);
ASSERT_FALSE(image.empty()) << "Image " << imageFilename.c_str() << " can not be read" << std::endl;
ASSERT_FALSE(depth.empty()) << "Depth " << depthFilename.c_str() << "can not be read" << std::endl;
CV_DbgAssert(image.type() == CV_8UC1);
CV_DbgAssert(depth.type() == CV_16UC1);
{
Mat depth_flt;
depth.convertTo(depth_flt, CV_32FC1, 1.f/5000.f);
depth_flt.setTo(std::numeric_limits<float>::quiet_NaN(), depth_flt < FLT_EPSILON);
depth = depth_flt;
}
}
void OdometryTest::generateRandomTransformation(Mat& rvec, Mat& tvec)
{
const float maxRotation = (float)(3.f / 180.f * CV_PI); //rad
const float maxTranslation = 0.02f; //m
RNG& rng = theRNG();
rvec.create(3, 1, CV_64FC1);
tvec.create(3, 1, CV_64FC1);
randu(rvec, Scalar(-1000), Scalar(1000));
normalize(rvec, rvec, rng.uniform(0.007f, maxRotation));
randu(tvec, Scalar(-1000), Scalar(1000));
normalize(tvec, tvec, rng.uniform(0.008f, maxTranslation));
}
void OdometryTest::checkUMats()
{
Mat K = getCameraMatrix();
Mat image, depth;
readData(image, depth);
UMat uimage, udepth;
image.copyTo(uimage);
depth.copyTo(udepth);
OdometrySettings ods;
ods.setCameraMatrix(K);
Odometry odometry = Odometry(otype, ods, algtype);
OdometryFrame odf(udepth, uimage);
Mat calcRt;
uimage.release();
udepth.release();
odometry.prepareFrame(odf);
bool isComputed = odometry.compute(odf, odf, calcRt);
ASSERT_TRUE(isComputed);
double diff = cv::norm(calcRt, Mat::eye(4, 4, CV_64FC1));
ASSERT_LE(diff, idError) << "Incorrect transformation between the same frame (not the identity matrix)" << std::endl;
}
void OdometryTest::run()
{
Mat K = getCameraMatrix();
Mat image, depth;
readData(image, depth);
OdometrySettings ods;
ods.setCameraMatrix(K);
Odometry odometry = Odometry(otype, ods, algtype);
OdometryFrame odf(depth, image);
Mat calcRt;
// 1. Try to find Rt between the same frame (try masks also).
Mat mask(image.size(), CV_8UC1, Scalar(255));
odometry.prepareFrame(odf);
bool isComputed = odometry.compute(odf, odf, calcRt);
ASSERT_TRUE(isComputed) << "Can not find Rt between the same frame" << std::endl;
double ndiff = cv::norm(calcRt, Mat::eye(4,4,CV_64FC1));
ASSERT_LE(ndiff, idError) << "Incorrect transformation between the same frame (not the identity matrix)" << std::endl;
// 2. Generate random rigid body motion in some ranges several times (iterCount).
// On each iteration an input frame is warped using generated transformation.
// Odometry is run on the following pair: the original frame and the warped one.
// Comparing a computed transformation with an applied one we compute 2 errors:
// better_1time_count - count of poses which error is less than ground truth pose,
// better_5times_count - count of poses which error is 5 times less than ground truth pose.
int iterCount = 100;
int better_1time_count = 0;
int better_5times_count = 0;
for (int iter = 0; iter < iterCount; iter++)
{
Mat rvec, tvec;
generateRandomTransformation(rvec, tvec);
Affine3d rt(rvec, tvec);
Mat warpedImage, warpedDepth;
warpFrame(depth, image, noArray(), rt.matrix, K, warpedDepth, warpedImage);
dilateFrame(warpedImage, warpedDepth); // due to inaccuracy after warping
OdometryFrame odfSrc(depth, image);
OdometryFrame odfDst(warpedDepth, warpedImage);
odometry.prepareFrames(odfSrc, odfDst);
isComputed = odometry.compute(odfSrc, odfDst, calcRt);
if (!isComputed)
{
CV_LOG_INFO(NULL, "Iter " << iter << "; Odometry compute returned false");
continue;
}
Mat calcR = calcRt(Rect(0, 0, 3, 3)), calcRvec;
cv::Rodrigues(calcR, calcRvec);
calcRvec = calcRvec.reshape(rvec.channels(), rvec.rows);
Mat calcTvec = calcRt(Rect(3,0,1,3));
if (cvtest::debugLevel >= 10)
{
imshow("image", image);
imshow("warpedImage", warpedImage);
Mat resultImage, resultDepth;
warpFrame(depth, image, noArray(), calcRt, K, resultDepth, resultImage);
imshow("resultImage", resultImage);
waitKey(100);
}
// compare rotation
double possibleError = algtype == OdometryAlgoType::COMMON ? 0.02f : 0.02f;
Affine3f src = Affine3f(Vec3f(rvec), Vec3f(tvec));
Affine3f res = Affine3f(Vec3f(calcRvec), Vec3f(calcTvec));
Affine3f src_inv = src.inv();
Affine3f diff = res * src_inv;
double rdiffnorm = cv::norm(diff.rvec());
double tdiffnorm = cv::norm(diff.translation());
if (rdiffnorm < possibleError && tdiffnorm < possibleError)
better_1time_count++;
if (5. * rdiffnorm < possibleError && 5 * tdiffnorm < possibleError)
better_5times_count++;
CV_LOG_INFO(NULL, "Iter " << iter);
CV_LOG_INFO(NULL, "rdiff: " << Vec3f(diff.rvec()) << "; rdiffnorm: " << rdiffnorm);
CV_LOG_INFO(NULL, "tdiff: " << Vec3f(diff.translation()) << "; tdiffnorm: " << tdiffnorm);
CV_LOG_INFO(NULL, "better_1time_count " << better_1time_count << "; better_5time_count " << better_5times_count);
}
if(static_cast<double>(better_1time_count) < maxError1 * static_cast<double>(iterCount))
{
FAIL() << "Incorrect count of accurate poses [1st case]: "
<< static_cast<double>(better_1time_count) << " / "
<< maxError1 * static_cast<double>(iterCount) << std::endl;
}
if(static_cast<double>(better_5times_count) < maxError5 * static_cast<double>(iterCount))
{
FAIL() << "Incorrect count of accurate poses [2nd case]: "
<< static_cast<double>(better_5times_count) << " / "
<< maxError5 * static_cast<double>(iterCount) << std::endl;
}
}
void OdometryTest::prepareFrameCheck()
{
Mat K = getCameraMatrix();
Mat gtImage, gtDepth;
readData(gtImage, gtDepth);
OdometrySettings ods;
ods.setCameraMatrix(K);
Odometry odometry = Odometry(otype, ods, algtype);
OdometryFrame odf(gtDepth, gtImage);
odometry.prepareFrame(odf);
std::vector<int> iters;
ods.getIterCounts(iters);
size_t nlevels = iters.size();
Mat points, mask, depth, gray, rgb, scaled;
odf.getMask(mask);
int masknz = countNonZero(mask);
ASSERT_GT(masknz, 0);
odf.getDepth(depth);
Mat patchedDepth = depth.clone();
patchNaNs(patchedDepth, 0);
int depthnz = countNonZero(patchedDepth);
double depthNorm = cv::norm(depth, gtDepth, NORM_INF, mask);
ASSERT_LE(depthNorm, 0.0);
Mat gtGray;
if (otype == OdometryType::RGB || otype == OdometryType::RGB_DEPTH)
{
odf.getGrayImage(gray);
odf.getImage(rgb);
double rgbNorm = cv::norm(rgb, gtImage);
ASSERT_LE(rgbNorm, 0.0);
if (gtImage.channels() == 3)
cvtColor(gtImage, gtGray, COLOR_BGR2GRAY);
else
gtGray = gtImage;
gtGray.convertTo(gtGray, CV_8U);
double grayNorm = cv::norm(gray, gtGray);
ASSERT_LE(grayNorm, 0.0);
}
odf.getProcessedDepth(scaled);
int scalednz = countNonZero(scaled);
EXPECT_EQ(scalednz, depthnz);
std::vector<Mat> gtPyrDepth, gtPyrMask;
//TODO: this depth calculation would become incorrect when we implement bilateral filtering, fixit
buildPyramid(gtDepth, gtPyrDepth, (int)nlevels - 1);
for (const auto& gd : gtPyrDepth)
{
Mat pm = (gd > ods.getMinDepth()) & (gd < ods.getMaxDepth());
gtPyrMask.push_back(pm);
}
size_t npyr = odf.getPyramidLevels();
ASSERT_EQ(npyr, nlevels);
Matx33f levelK = K;
for (size_t i = 0; i < nlevels; i++)
{
Mat depthi, cloudi, maski;
odf.getPyramidAt(maski, OdometryFramePyramidType::PYR_MASK, i);
ASSERT_FALSE(maski.empty());
double mnorm = cv::norm(maski, gtPyrMask[i]);
EXPECT_LE(mnorm, 16 * 255.0) << "Mask diff is too big at pyr level " << i;
odf.getPyramidAt(depthi, OdometryFramePyramidType::PYR_DEPTH, i);
ASSERT_FALSE(depthi.empty());
double dnorm = cv::norm(depthi, gtPyrDepth[i], NORM_INF, maski);
EXPECT_LE(dnorm, 8.e-7) << "Depth diff norm is too big at pyr level " << i;
odf.getPyramidAt(cloudi, OdometryFramePyramidType::PYR_CLOUD, i);
ASSERT_FALSE(cloudi.empty());
Mat gtCloud;
depthTo3d(depthi, levelK, gtCloud);
double cnorm = cv::norm(cloudi, gtCloud, NORM_INF, maski);
EXPECT_LE(cnorm, 0.0) << "Cloud diff norm is too big at pyr level " << i;
// downscale camera matrix for next pyramid level
levelK = 0.5f * levelK;
levelK(2, 2) = 1.f;
}
if (otype == OdometryType::RGB || otype == OdometryType::RGB_DEPTH)
{
std::vector<Mat> gtPyrImage;
buildPyramid(gtGray, gtPyrImage, (int)nlevels - 1);
for (size_t i = 0; i < nlevels; i++)
{
Mat rgbi, texi, dixi, diyi, maski;
odf.getPyramidAt(maski, OdometryFramePyramidType::PYR_MASK, i);
odf.getPyramidAt(rgbi, OdometryFramePyramidType::PYR_IMAGE, i);
ASSERT_FALSE(rgbi.empty());
double rnorm = cv::norm(rgbi, gtPyrImage[i], NORM_INF);
EXPECT_LE(rnorm, 1.0) << "RGB diff is too big at pyr level " << i;
odf.getPyramidAt(texi, OdometryFramePyramidType::PYR_TEXMASK, i);
ASSERT_FALSE(texi.empty());
int tnz = countNonZero(texi);
EXPECT_GE(tnz, 1000) << "Texture mask has too few valid pixels at pyr level " << i;
Mat gtDixi, gtDiyi;
Sobel(rgbi, gtDixi, CV_16S, 1, 0, ods.getSobelSize());
odf.getPyramidAt(dixi, OdometryFramePyramidType::PYR_DIX, i);
ASSERT_FALSE(dixi.empty());
double dixnorm = cv::norm(dixi, gtDixi, NORM_INF, maski);
EXPECT_LE(dixnorm, 0) << "dI/dx diff is too big at pyr level " << i;
Sobel(rgbi, gtDiyi, CV_16S, 0, 1, ods.getSobelSize());
odf.getPyramidAt(diyi, OdometryFramePyramidType::PYR_DIY, i);
ASSERT_FALSE(diyi.empty());
double diynorm = cv::norm(diyi, gtDiyi, NORM_INF, maski);
EXPECT_LE(diynorm, 0) << "dI/dy diff is too big at pyr level " << i;
}
}
if (otype == OdometryType::DEPTH || otype == OdometryType::RGB_DEPTH)
{
Ptr<RgbdNormals> normalComputer = odometry.getNormalsComputer();
ASSERT_FALSE(normalComputer.empty());
Mat normals;
odf.getNormals(normals);
std::vector<Mat> gtPyrNormals;
buildPyramid(normals, gtPyrNormals, (int)nlevels - 1);
for (size_t i = 0; i < nlevels; i++)
{
Mat gtNormal = gtPyrNormals[i];
CV_Assert(gtNormal.type() == CV_32FC4);
for (int y = 0; y < gtNormal.rows; y++)
{
Vec4f *normals_row = gtNormal.ptr<Vec4f>(y);
for (int x = 0; x < gtNormal.cols; x++)
{
Vec4f n4 = normals_row[x];
Point3f n(n4[0], n4[1], n4[2]);
double nrm = cv::norm(n);
n *= 1.f / nrm;
normals_row[x] = Vec4f(n.x, n.y, n.z, 0);
}
}
Mat normmaski;
odf.getPyramidAt(normmaski, OdometryFramePyramidType::PYR_NORMMASK, i);
ASSERT_FALSE(normmaski.empty());
int nnm = countNonZero(normmaski);
EXPECT_GE(nnm, 1000) << "Normals mask has too few valid pixels at pyr level " << i;
Mat ptsi;
odf.getPyramidAt(ptsi, OdometryFramePyramidType::PYR_CLOUD, i);
Mat normi;
odf.getPyramidAt(normi, OdometryFramePyramidType::PYR_NORM, i);
ASSERT_FALSE(normi.empty());
double nnorm = cv::norm(normi, gtNormal, NORM_INF, normmaski);
EXPECT_LE(nnorm, 3.3e-7) << "Normals diff is too big at pyr level " << i;
if (i == 0)
{
double pnnorm = cv::norm(normals, normi, NORM_INF, normmaski);
EXPECT_GE(pnnorm, 0);
}
}
}
}
void OdometryTest::processedDepthCheck()
{
Mat K = getCameraMatrix();
Mat gtImage, gtDepth;
readData(gtImage, gtDepth);
gtDepth *= 5000.0;
OdometrySettings ods;
ods.setCameraMatrix(K);
Odometry odometry = Odometry(otype, ods, algtype);
OdometryFrame odf(gtDepth, gtImage);
odometry.prepareFrame(odf);
Mat scaled;
odf.getProcessedDepth(scaled);
//TODO: remove this check when depth rescaling is removed
double pmax;
cv::minMaxLoc(scaled, nullptr, &pmax);
EXPECT_LT(pmax, 10.0);
}
/****************************************************************************************\
* Tests registrations *
\****************************************************************************************/
TEST(RGBD_Odometry_Rgb, algorithmic)
{
OdometryTest test(OdometryType::RGB, OdometryAlgoType::COMMON, 0.99, 0.99);
test.run();
}
TEST(RGBD_Odometry_ICP, algorithmic)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.run();
}
TEST(RGBD_Odometry_RgbdICP, algorithmic)
{
OdometryTest test(OdometryType::RGB_DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.run();
}
TEST(RGBD_Odometry_FastICP, algorithmic)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::FAST, 0.99, 0.87, 1.84e-5);
test.run();
}
TEST(RGBD_Odometry_Rgb, UMats)
{
OdometryTest test(OdometryType::RGB, OdometryAlgoType::COMMON, 0.99, 0.99);
test.checkUMats();
}
TEST(RGBD_Odometry_ICP, UMats)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.checkUMats();
}
TEST(RGBD_Odometry_RgbdICP, UMats)
{
OdometryTest test(OdometryType::RGB_DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.checkUMats();
}
TEST(RGBD_Odometry_FastICP, UMats)
{
// OpenCL version has slightly less accuracy than CPU version
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::FAST, 0.99, 0.99, 1.84e-5);
test.checkUMats();
}
TEST(RGBD_Odometry_Rgb, prepareFrame)
{
OdometryTest test(OdometryType::RGB, OdometryAlgoType::COMMON, 0.99, 0.99);
test.prepareFrameCheck();
}
TEST(RGBD_Odometry_ICP, prepareFrame)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.prepareFrameCheck();
}
TEST(RGBD_Odometry_RgbdICP, prepareFrame)
{
OdometryTest test(OdometryType::RGB_DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.prepareFrameCheck();
}
TEST(RGBD_Odometry_FastICP, prepareFrame)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::FAST, 0.99, 0.99, FLT_EPSILON);
test.prepareFrameCheck();
}
TEST(RGBD_Odometry_Rgb, processedDepth)
{
OdometryTest test(OdometryType::RGB, OdometryAlgoType::COMMON, 0.99, 0.99);
test.processedDepthCheck();
}
TEST(RGBD_Odometry_ICP, processedDepth)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.processedDepthCheck();
}
TEST(RGBD_Odometry_RgbdICP, processedDepth)
{
OdometryTest test(OdometryType::RGB_DEPTH, OdometryAlgoType::COMMON, 0.99, 0.99);
test.processedDepthCheck();
}
TEST(RGBD_Odometry_FastICP, processedDepth)
{
OdometryTest test(OdometryType::DEPTH, OdometryAlgoType::FAST, 0.99, 0.99, FLT_EPSILON);
test.processedDepthCheck();
}
struct WarpFrameTest
{
WarpFrameTest() :
srcDepth(), srcRgb(), srcMask(),
dstDepth(), dstRgb(), dstMask(),
warpedDepth(), warpedRgb(), warpedMask()
{}
void run(bool needRgb, bool scaleDown, bool checkMask, bool identityTransform, int depthType, int imageType);
Mat srcDepth, srcRgb, srcMask;
Mat dstDepth, dstRgb, dstMask;
Mat warpedDepth, warpedRgb, warpedMask;
};
void WarpFrameTest::run(bool needRgb, bool scaleDown, bool checkMask, bool identityTransform, int depthType, int rgbType)
{
std::string dataPath = cvtest::TS::ptr()->get_data_path();
std::string srcDepthFilename = dataPath + "/cv/rgbd/depth.png";
std::string srcRgbFilename = dataPath + "/cv/rgbd/rgb.png";
// The depth was generated using the script at testdata/cv/rgbd/warped_depth_generator/warp_test.py
std::string warpedDepthFilename = dataPath + "/cv/rgbd/warpedDepth.png";
std::string warpedRgbFilename = dataPath + "/cv/rgbd/warpedRgb.png";
srcDepth = imread(srcDepthFilename, IMREAD_UNCHANGED);
ASSERT_FALSE(srcDepth.empty()) << "Depth " << srcDepthFilename.c_str() << "can not be read" << std::endl;
if (identityTransform)
{
warpedDepth = srcDepth;
}
else
{
warpedDepth = imread(warpedDepthFilename, IMREAD_UNCHANGED);
ASSERT_FALSE(warpedDepth.empty()) << "Depth " << warpedDepthFilename.c_str() << "can not be read" << std::endl;
}
ASSERT_TRUE(srcDepth.type() == CV_16UC1);
ASSERT_TRUE(warpedDepth.type() == CV_16UC1);
Mat epsSrc = srcDepth > 0, epsWarped = warpedDepth > 0;
const double depthFactor = 5000.0;
// scale float types only
double depthScaleCoeff = scaleDown ? ( depthType == CV_16U ? 1. : 1./depthFactor ) : 1.;
double transScaleCoeff = scaleDown ? ( depthType == CV_16U ? depthFactor : 1. ) : depthFactor;
Mat srcDepthCvt, warpedDepthCvt;
srcDepth.convertTo(srcDepthCvt, depthType, depthScaleCoeff);
srcDepth = srcDepthCvt;
warpedDepth.convertTo(warpedDepthCvt, depthType, depthScaleCoeff);
warpedDepth = warpedDepthCvt;
Scalar badVal;
switch (depthType)
{
case CV_16U:
badVal = 0;
break;
case CV_32F:
badVal = std::numeric_limits<float>::quiet_NaN();
break;
case CV_64F:
badVal = std::numeric_limits<double>::quiet_NaN();
break;
default:
CV_Error(Error::StsBadArg, "Unsupported depth data type");
}
srcDepth.setTo(badVal, ~epsSrc);
warpedDepth.setTo(badVal, ~epsWarped);
if (checkMask)
{
srcMask = epsSrc; warpedMask = epsWarped;
}
else
{
srcMask = Mat(); warpedMask = Mat();
}
if (needRgb)
{
srcRgb = imread(srcRgbFilename, rgbType == CV_8UC1 ? IMREAD_GRAYSCALE : IMREAD_COLOR);
ASSERT_FALSE(srcRgb.empty()) << "Image " << srcRgbFilename.c_str() << "can not be read" << std::endl;
if (identityTransform)
{
srcRgb.copyTo(warpedRgb, epsSrc);
}
else
{
warpedRgb = imread(warpedRgbFilename, rgbType == CV_8UC1 ? IMREAD_GRAYSCALE : IMREAD_COLOR);
ASSERT_FALSE (warpedRgb.empty()) << "Image " << warpedRgbFilename.c_str() << "can not be read" << std::endl;
}
if (rgbType == CV_8UC4)
{
Mat newSrcRgb, newWarpedRgb;
cvtColor(srcRgb, newSrcRgb, COLOR_RGB2RGBA);
srcRgb = newSrcRgb;
// let's keep alpha channel
std::vector<Mat> warpedRgbChannels;
split(warpedRgb, warpedRgbChannels);
warpedRgbChannels.push_back(epsWarped);
merge(warpedRgbChannels, newWarpedRgb);
warpedRgb = newWarpedRgb;
}
ASSERT_TRUE(srcRgb.type() == rgbType);
ASSERT_TRUE(warpedRgb.type() == rgbType);
}
else
{
srcRgb = Mat(); warpedRgb = Mat();
}
// test data used to generate warped depth and rgb
// the script used to generate is in opencv_extra repo
// at testdata/cv/rgbd/warped_depth_generator/warp_test.py
double fx = 525.0, fy = 525.0,
cx = 319.5, cy = 239.5;
Matx33d K(fx, 0, cx,
0, fy, cy,
0, 0, 1);
cv::Affine3d rt;
cv::Vec3d tr(-0.04, 0.05, 0.6);
rt = identityTransform ? cv::Affine3d() : cv::Affine3d(cv::Vec3d(0.1, 0.2, 0.3), tr * transScaleCoeff);
warpFrame(srcDepth, srcRgb, srcMask, rt.matrix, K, dstDepth, dstRgb, dstMask);
}
typedef std::pair<int, int> WarpFrameInputTypes;
typedef testing::TestWithParam<WarpFrameInputTypes> WarpFrameInputs;
TEST_P(WarpFrameInputs, checkTypes)
{
const double shortl2diff = 233.983;
const double shortlidiff = 1;
const double floatl2diff = 0.038209;
const double floatlidiff = 0.00020004;
int depthType = GetParam().first;
int rgbType = GetParam().second;
WarpFrameTest w;
// scale down does not happen on CV_16U
// to avoid integer overflow
w.run(/* needRgb */ true, /* scaleDown*/ true,
/* checkMask */ true, /* identityTransform */ false, depthType, rgbType);
double rgbDiff = cv::norm(w.dstRgb, w.warpedRgb, NORM_L2);
double maskDiff = cv::norm(w.dstMask, w.warpedMask, NORM_L2);
EXPECT_EQ(0, maskDiff);
EXPECT_EQ(0, rgbDiff);
double l2diff = cv::norm(w.dstDepth, w.warpedDepth, NORM_L2, w.warpedMask);
double lidiff = cv::norm(w.dstDepth, w.warpedDepth, NORM_INF, w.warpedMask);
double l2threshold = depthType == CV_16U ? shortl2diff : floatl2diff;
double lithreshold = depthType == CV_16U ? shortlidiff : floatlidiff;
EXPECT_LE(l2diff, l2threshold);
EXPECT_LE(lidiff, lithreshold);
}
INSTANTIATE_TEST_CASE_P(RGBD_Odometry, WarpFrameInputs, ::testing::Values(
WarpFrameInputTypes { CV_16U, CV_8UC3 },
WarpFrameInputTypes { CV_32F, CV_8UC3 },
WarpFrameInputTypes { CV_64F, CV_8UC3 },
WarpFrameInputTypes { CV_32F, CV_8UC1 },
WarpFrameInputTypes { CV_32F, CV_8UC4 }));
TEST(RGBD_Odometry_WarpFrame, identity)
{
WarpFrameTest w;
w.run(/* needRgb */ true, /* scaleDown*/ true, /* checkMask */ true, /* identityTransform */ true, CV_32F, CV_8UC3);
double rgbDiff = cv::norm(w.dstRgb, w.warpedRgb, NORM_L2);
double maskDiff = cv::norm(w.dstMask, w.warpedMask, NORM_L2);
ASSERT_EQ(0, rgbDiff);
ASSERT_EQ(0, maskDiff);
double depthDiff = cv::norm(w.dstDepth, w.warpedDepth, NORM_L2, w.dstMask);
ASSERT_LE(depthDiff, DBL_EPSILON);
}
TEST(RGBD_Odometry_WarpFrame, noRgb)
{
WarpFrameTest w;
w.run(/* needRgb */ false, /* scaleDown*/ true, /* checkMask */ true, /* identityTransform */ false, CV_32F, CV_8UC3);
double maskDiff = cv::norm(w.dstMask, w.warpedMask, NORM_L2);
ASSERT_EQ(0, maskDiff);
double l2diff = cv::norm(w.dstDepth, w.warpedDepth, NORM_L2, w.warpedMask);
double lidiff = cv::norm(w.dstDepth, w.warpedDepth, NORM_INF, w.warpedMask);
ASSERT_LE(l2diff, 0.038209);
ASSERT_LE(lidiff, 0.00020004);
}
TEST(RGBD_Odometry_WarpFrame, nansAreMasked)
{
WarpFrameTest w;
w.run(/* needRgb */ true, /* scaleDown*/ true, /* checkMask */ false, /* identityTransform */ false, CV_32F, CV_8UC3);
double rgbDiff = cv::norm(w.dstRgb, w.warpedRgb, NORM_L2);
ASSERT_EQ(0, rgbDiff);
Mat goodVals;
finiteMask(w.warpedDepth, goodVals);
double l2diff = cv::norm(w.dstDepth, w.warpedDepth, NORM_L2, goodVals);
double lidiff = cv::norm(w.dstDepth, w.warpedDepth, NORM_INF, goodVals);
ASSERT_LE(l2diff, 0.038209);
ASSERT_LE(lidiff, 0.00020004);
}
TEST(RGBD_Odometry_WarpFrame, bigScale)
{
WarpFrameTest w;
w.run(/* needRgb */ true, /* scaleDown*/ false, /* checkMask */ true, /* identityTransform */ false, CV_32F, CV_8UC3);
double rgbDiff = cv::norm(w.dstRgb, w.warpedRgb, NORM_L2);
double maskDiff = cv::norm(w.dstMask, w.warpedMask, NORM_L2);
ASSERT_EQ(0, maskDiff);
ASSERT_EQ(0, rgbDiff);
double l2diff = cv::norm(w.dstDepth, w.warpedDepth, NORM_L2, w.warpedMask);
double lidiff = cv::norm(w.dstDepth, w.warpedDepth, NORM_INF, w.warpedMask);
ASSERT_LE(l2diff, 191.026565);
ASSERT_LE(lidiff, 0.99951172);
}
TEST(RGBD_DepthTo3D, mask)
{
std::string dataPath = cvtest::TS::ptr()->get_data_path();
std::string srcDepthFilename = dataPath + "/cv/rgbd/depth.png";
Mat srcDepth = imread(srcDepthFilename, IMREAD_UNCHANGED);
ASSERT_FALSE(srcDepth.empty()) << "Depth " << srcDepthFilename.c_str() << "can not be read" << std::endl;
ASSERT_TRUE(srcDepth.type() == CV_16UC1);
Mat srcMask = srcDepth > 0;
// test data used to generate warped depth and rgb
// the script used to generate is in opencv_extra repo
// at testdata/cv/rgbd/warped_depth_generator/warp_test.py
double fx = 525.0, fy = 525.0,
cx = 319.5, cy = 239.5;
Matx33d intr(fx, 0, cx,
0, fy, cy,
0, 0, 1);
Mat srcCloud;
depthTo3d(srcDepth, intr, srcCloud, srcMask);
size_t npts = countNonZero(srcMask);
ASSERT_EQ(npts, srcCloud.total());
}
}} // namespace
+299
View File
@@ -0,0 +1,299 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include <opencv2/core.hpp>
#include <vector>
#include <cstdio>
#include "test_precomp.hpp"
#include "opencv2/ts.hpp"
namespace opencv_test { namespace {
struct OriginalObjGoldValues
{
OriginalObjGoldValues()
{
std::array<float, 6> vals = { -5.93915f, -0.13257f, 2.55837f, 1.86743f, -1.16339f, 0.399941f };
points =
{
{ vals[0], vals[1], vals[2] },
{ vals[0], vals[3], vals[2] },
{ vals[0], vals[1], vals[4] },
{ vals[0], vals[3], vals[4] },
{ vals[5], vals[1], vals[2] },
{ vals[5], vals[3], vals[2] },
{ vals[5], vals[1], vals[4] },
{ vals[5], vals[3], vals[4] },
};
normals =
{
{-1.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, -1.0f},
{ 1.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 1.0f},
{ 0.0f, -1.0f, 0.0f},
{ 0.0f, 1.0f, 0.0f}
};
rgb =
{
{0.0756f, 0.5651f, 0.5829f},
{0.8596f, 0.1105f, 0.8455f},
{0.8534f, 0.6143f, 0.3950f},
{0.0438f, 0.6308f, 0.3065f},
{0.9716f, 0.7170f, 0.8378f},
{0.2472f, 0.7701f, 0.0234f},
{0.6472f, 0.7467f, 0.5981f},
{0.3502f, 0.7954f, 0.0443f}
};
std::vector<std::pair<int, int>> tcvals =
{
{ 3, 0 },
{ 5, 0 },
{ 5, 2 },
{ 3, 2 },
{ 5, 4 },
{ 3, 4 },
{ 5, 6 },
{ 3, 6 },
{ 5, 8 },
{ 3, 8 },
{ 1, 4 },
{ 1, 6 },
{ 7, 4 },
{ 7, 6 },
};
for (const auto& p : tcvals)
{
texCoords.push_back({p.first * 0.125f, p.second * 0.125f});
}
// mesh data is duplicated for each face
std::vector<std::array<int, 9>> fileIndices =
{
{ 1, 1, 1, /**/ 2, 2, 1, /**/ 4, 3, 1 },
{ 3, 4, 2, /**/ 4, 3, 2, /**/ 8, 5, 2 },
{ 7, 6, 3, /**/ 8, 5, 3, /**/ 6, 7, 3 },
{ 5, 8, 4, /**/ 6, 7, 4, /**/ 2, 9, 4 },
{ 3, 11, 5, /**/ 7, 6, 5, /**/ 5, 8, 5 },
{ 8, 5, 6, /**/ 4, 13, 6, /**/ 2, 14, 6 },
};
for (const auto& fi : fileIndices)
{
pointsMesh.push_back(points.at(fi[0] - 1));
pointsMesh.push_back(points.at(fi[3] - 1));
pointsMesh.push_back(points.at(fi[6] - 1));
rgbMesh.push_back(rgb.at(fi[0] - 1));
rgbMesh.push_back(rgb.at(fi[3] - 1));
rgbMesh.push_back(rgb.at(fi[6] - 1));
texCoordsMesh.push_back(texCoords.at(fi[1] - 1));
texCoordsMesh.push_back(texCoords.at(fi[4] - 1));
texCoordsMesh.push_back(texCoords.at(fi[7] - 1));
normalsMesh.push_back(normals.at(fi[2] - 1));
normalsMesh.push_back(normals.at(fi[5] - 1));
normalsMesh.push_back(normals.at(fi[8] - 1));
}
indices =
{
{ 0, 1, 2},
{ 3, 4, 5},
{ 6, 7, 8},
{ 9, 10, 11},
{12, 13, 14},
{15, 16, 17},
};
}
std::vector<Point3f> points, pointsMesh, normals, normalsMesh, rgb, rgbMesh;
std::vector<Point2f> texCoords, texCoordsMesh;
std::vector<std::vector<int32_t>> indices;
};
OriginalObjGoldValues origGold;
TEST(PointCloud, LoadPointCloudObj)
{
std::vector<cv::Point3f> points, normals, rgb;
auto folder = cvtest::TS::ptr()->get_data_path();
cv::loadPointCloud(folder + "pointcloudio/orig.obj", points, normals, rgb);
EXPECT_EQ(origGold.points, points);
EXPECT_EQ(origGold.rgb, rgb);
EXPECT_EQ(origGold.normals, normals);
}
TEST(PointCloud, LoadObjNoNormals)
{
std::vector<cv::Point3f> points, normals;
auto folder = cvtest::TS::ptr()->get_data_path();
cv::loadPointCloud(folder + "pointcloudio/orig_no_norms.obj", points, normals);
EXPECT_EQ(origGold.points, points);
EXPECT_TRUE(normals.empty());
}
TEST(PointCloud, SaveObj)
{
std::vector<cv::Point3f> points_gold, normals_gold, rgb_gold;
auto folder = cvtest::TS::ptr()->get_data_path();
auto new_path = tempfile("new.obj");
cv::loadPointCloud(folder + "pointcloudio/orig.obj", points_gold, normals_gold, rgb_gold);
cv::savePointCloud(new_path, points_gold, normals_gold, rgb_gold);
std::vector<cv::Point3f> points, normals, rgb;
cv::loadPointCloud(new_path, points, normals, rgb);
EXPECT_EQ(normals, normals_gold);
EXPECT_EQ(points, points_gold);
EXPECT_EQ(rgb, rgb_gold);
std::remove(new_path.c_str());
}
TEST(PointCloud, LoadSavePly)
{
std::vector<cv::Point3f> points, normals, rgb;
auto folder = cvtest::TS::ptr()->get_data_path();
std::string new_path = tempfile("new.ply");
cv::loadPointCloud(folder + "pointcloudio/orig.ply", points, normals, rgb);
cv::savePointCloud(new_path, points, normals, rgb);
std::vector<cv::Point3f> points_gold, normals_gold, rgb_gold;
cv::loadPointCloud(new_path, points_gold, normals_gold, rgb_gold);
EXPECT_EQ(normals_gold, normals);
EXPECT_EQ(points_gold, points);
EXPECT_EQ(rgb_gold, rgb);
std::remove(new_path.c_str());
}
TEST(PointCloud, LoadSaveMeshObj)
{
std::vector<cv::Point3f> points, normals, colors;
std::vector<cv::Point2f> texCoords;
std::vector<std::vector<int32_t>> indices;
auto folder = cvtest::TS::ptr()->get_data_path();
std::string new_path = tempfile("new_mesh.obj");
cv::loadMesh(folder + "pointcloudio/orig.obj", points, indices, normals, colors, texCoords);
EXPECT_EQ(origGold.pointsMesh, points);
EXPECT_EQ(origGold.indices, indices);
EXPECT_EQ(origGold.normalsMesh, normals);
EXPECT_EQ(origGold.rgbMesh, colors);
EXPECT_EQ(origGold.texCoordsMesh, texCoords);
cv::saveMesh(new_path, points, indices, normals, colors, texCoords);
std::vector<cv::Point3f> points_gold, normals_gold, colors_gold;
std::vector<cv::Point2f> texCoords_gold;
std::vector<std::vector<int32_t>> indices_gold;
cv::loadMesh(new_path, points_gold, indices_gold, normals_gold, colors_gold, texCoords_gold);
EXPECT_FALSE(points_gold.empty());
EXPECT_FALSE(indices_gold.empty());
EXPECT_FALSE(normals_gold.empty());
EXPECT_FALSE(colors_gold.empty());
EXPECT_FALSE(texCoords_gold.empty());
EXPECT_EQ(normals_gold, normals);
EXPECT_EQ(points_gold, points);
EXPECT_EQ(indices_gold, indices);
EXPECT_TRUE(!indices.empty());
std::remove(new_path.c_str());
}
typedef std::string PlyTestParamsType;
typedef testing::TestWithParam<PlyTestParamsType> PlyTest;
TEST_P(PlyTest, LoadSaveMesh)
{
std::string fname = GetParam();
std::vector<cv::Point3f> points_gold, normals_gold, colors_gold;
std::vector<cv::Vec3i> indices_gold;
auto folder = cvtest::TS::ptr()->get_data_path();
std::string new_path = tempfile("new_mesh.ply");
cv::loadMesh(folder + fname, points_gold, indices_gold, normals_gold, colors_gold);
size_t truePts, trueFaces;
if (fname.find("/dragon.ply") != fname.npos)
{
truePts = 50000; trueFaces = 100000;
}
else
{
truePts = 8; trueFaces = 12;
}
EXPECT_EQ(points_gold.size(), truePts);
EXPECT_EQ(indices_gold.size(), trueFaces);
cv::saveMesh(new_path, points_gold, indices_gold, normals_gold, colors_gold);
std::vector<cv::Point3f> points, normals, colors;
std::vector<cv::Vec3i> indices;
cv::loadMesh(new_path, points, indices, normals, colors);
if (!normals.empty())
{
EXPECT_LE(cv::norm(normals_gold, normals, NORM_INF), 0);
}
EXPECT_LE(cv::norm(points_gold, points, NORM_INF), 0);
EXPECT_LE(cv::norm(colors_gold, colors, NORM_INF), 0);
EXPECT_EQ(indices_gold, indices);
std::remove(new_path.c_str());
}
INSTANTIATE_TEST_CASE_P(PointCloud, PlyTest,
::testing::Values("pointcloudio/orig.ply", "pointcloudio/orig_ascii_fidx.ply", "pointcloudio/orig_bin_fidx.ply",
"pointcloudio/orig_ascii_vidx.ply", "pointcloudio/orig_bin.ply", "viz/dragon.ply"));
TEST(PointCloud, NonexistentFile)
{
std::vector<cv::Point3f> points;
std::vector<cv::Point3f> normals;
auto folder = cvtest::TS::ptr()->get_data_path();
cv::loadPointCloud(folder + "pointcloudio/fake.obj", points, normals);
EXPECT_TRUE(points.empty());
EXPECT_TRUE(normals.empty());
}
TEST(PointCloud, LoadBadExtension)
{
std::vector<cv::Point3f> points;
std::vector<cv::Point3f> normals;
auto folder = cvtest::TS::ptr()->get_data_path();
cv::loadPointCloud(folder + "pointcloudio/fake.fake", points, normals);
EXPECT_TRUE(points.empty());
EXPECT_TRUE(normals.empty());
}
TEST(PointCloud, SaveBadExtension)
{
std::vector<cv::Point3f> points;
std::vector<cv::Point3f> normals;
auto folder = cvtest::TS::ptr()->get_data_path();
cv::savePointCloud(folder + "pointcloudio/fake.fake", points, normals);
}
}} /* namespace opencv_test */
+562
View File
@@ -0,0 +1,562 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
#include <opencv2/ptcloud/detail/pose_graph.hpp>
#include <opencv2/core/dualquaternion.hpp>
namespace opencv_test { namespace {
using namespace cv;
#ifdef HAVE_EIGEN
static Affine3d readAffine(std::istream& input)
{
Vec3d p;
Vec4d q;
input >> p[0] >> p[1] >> p[2];
input >> q[1] >> q[2] >> q[3] >> q[0];
// Normalize the quaternion to account for precision loss due to
// serialization.
return Affine3d(Quatd(q).toRotMat3x3(), p);
};
// Rewritten from Ceres pose graph demo: https://ceres-solver.org/
static Ptr<detail::PoseGraph> readG2OFile(const std::string& g2oFileName)
{
Ptr<detail::PoseGraph> pg = detail::PoseGraph::create();
// for debugging purposes
size_t minId = 0, maxId = 1 << 30;
std::ifstream infile(g2oFileName.c_str());
if (!infile)
{
CV_Error(cv::Error::StsError, "failed to open file");
}
while (infile.good())
{
std::string data_type;
// Read whether the type is a node or a constraint
infile >> data_type;
if (data_type == "VERTEX_SE3:QUAT")
{
size_t id;
infile >> id;
Affine3d pose = readAffine(infile);
if (id < minId || id >= maxId)
continue;
bool fixed = (id == minId);
// Ensure we don't have duplicate poses
if (pg->isNodeExist(id))
{
CV_LOG_INFO(NULL, "duplicated node, id=" << id);
}
pg->addNode(id, pose, fixed);
}
else if (data_type == "EDGE_SE3:QUAT")
{
size_t startId, endId;
infile >> startId >> endId;
Affine3d pose = readAffine(infile);
Matx66d info;
for (int i = 0; i < 6 && infile.good(); ++i)
{
for (int j = i; j < 6 && infile.good(); ++j)
{
infile >> info(i, j);
if (i != j)
{
info(j, i) = info(i, j);
}
}
}
if ((startId >= minId && startId < maxId) && (endId >= minId && endId < maxId))
{
pg->addEdge(startId, endId, pose, info);
}
}
else
{
CV_Error(cv::Error::StsError, "unknown tag");
}
// Clear any trailing whitespace from the line
infile >> std::ws;
}
return pg;
}
TEST(PoseGraph, sphereG2O)
{
// Test takes 15+ sec in Release mode and 400+ sec in Debug mode
applyTestTag(CV_TEST_TAG_LONG, CV_TEST_TAG_DEBUG_VERYLONG);
// The dataset was taken from here: https://lucacarlone.mit.edu/datasets/
// Connected paper:
// L.Carlone, R.Tron, K.Daniilidis, and F.Dellaert.
// Initialization Techniques for 3D SLAM : a Survey on Rotation Estimation and its Use in Pose Graph Optimization.
// In IEEE Intl.Conf.on Robotics and Automation(ICRA), pages 4597 - 4604, 2015.
std::string filename = cvtest::TS::ptr()->get_data_path() + "/cv/rgbd/sphere_bignoise_vertex3.g2o";
Ptr<detail::PoseGraph> pg = readG2OFile(filename);
// You may change logging level to view detailed optimization report
// For example, set env. variable like this: OPENCV_LOG_LEVEL=INFO
// geoScale=1 is experimental, not guaranteed to work on other problems
// the rest are default params
pg->createOptimizer(LevMarq::Settings().setGeoScale(1.0)
.setMaxIterations(100)
.setCheckRelEnergyChange(true)
.setRelEnergyDeltaTolerance(1e-6)
.setGeodesic(true));
auto r = pg->optimize();
EXPECT_TRUE(r.found);
EXPECT_LE(r.iters, 20); // should converge in 31 iterations
EXPECT_LE(r.energy, 1.47723e+06); // should converge to 1.47722e+06 or less
// Add the "--test_debug" to arguments to see resulting pose graph nodes positions
if (cvtest::debugLevel > 0)
{
// Write edge-only model of how nodes are located in space
std::string fname = "pgout.obj";
std::fstream of(fname, std::fstream::out);
std::vector<size_t> ids = pg->getNodesIds();
for (const size_t& id : ids)
{
Point3d d = pg->getNodePose(id).translation();
of << "v " << d.x << " " << d.y << " " << d.z << std::endl;
}
size_t esz = pg->getNumEdges();
for (size_t i = 0; i < esz; i++)
{
size_t sid = pg->getEdgeStart(i), tid = pg->getEdgeEnd(i);
of << "l " << sid + 1 << " " << tid + 1 << std::endl;
}
of.close();
}
}
TEST(PoseGraphMST, optimization)
{
applyTestTag(CV_TEST_TAG_LONG, CV_TEST_TAG_DEBUG_VERYLONG);
// The dataset was taken from here: https://lucacarlone.mit.edu/datasets/
// Connected paper:
// L.Carlone, R.Tron, K.Daniilidis, and F.Dellaert.
// Initialization Techniques for 3D SLAM : a Survey on Rotation Estimation and its Use in Pose Graph Optimization.
// In IEEE Intl.Conf.on Robotics and Automation(ICRA), pages 4597 - 4604, 2015.
std::string filename = cvtest::TS::ptr()->get_data_path() + "/cv/rgbd/sphere_bignoise_vertex3.g2o";
Ptr<detail::PoseGraph> pgOptimizerOnly = readG2OFile(filename);
Ptr<detail::PoseGraph> pgWithMSTAndOptimizer = readG2OFile(filename);
Ptr<detail::PoseGraph> init = readG2OFile(filename);
double lambda = 0.485;
pgWithMSTAndOptimizer->initializePosesWithMST(lambda);
// You may change logging level to view detailed optimization report
// For example, set env. variable like this: OPENCV_LOG_LEVEL=INFO
// geoScale=1 is experimental, not guaranteed to work on other problems
// the rest are default params
pgOptimizerOnly->createOptimizer(LevMarq::Settings().setGeoScale(1.0)
.setMaxIterations(100)
.setCheckRelEnergyChange(true)
.setRelEnergyDeltaTolerance(1e-6)
.setGeodesic(true));
pgWithMSTAndOptimizer->createOptimizer(LevMarq::Settings().setGeoScale(1.0)
.setMaxIterations(100)
.setCheckRelEnergyChange(true)
.setRelEnergyDeltaTolerance(1e-6)
.setGeodesic(true));
auto r1 = pgWithMSTAndOptimizer->optimize();
auto r2 = pgOptimizerOnly->optimize();
EXPECT_TRUE(r1.found);
EXPECT_TRUE(r2.found);
EXPECT_LE(r2.energy, 1.47723e+06);
// Allow small tolerance due to optimization differences; final energy/iterations are effectively the same
EXPECT_LE(std::abs(r1.energy - r2.energy), 1e-2);
ASSERT_LE(std::abs(r1.iters - r2.iters), 1);
// Add the "--test_debug" to arguments to see resulting pose graph nodes positions
if (cvtest::debugLevel > 0)
{
// Note:
// A custom .obj writer is used here instead of cv::saveMesh because saveMesh expects faces
// (i.e., polygons with 3 or more vertices) and writes them using the "f i j k..." syntax in
// the .obj file. Since pose graphs consist of edges rather than polygonal faces, we represent
// them using line segments ("l i j").
// As saveMesh does not support writing "l" lines, it is not suitable in this context.
std::string fname;
std::fstream of;
std::vector<size_t> ids;
size_t esz;
// Write OBJ for MST-initialized pose graph with optimizer
fname = "pg_with_mst_and_optimizer.obj";
of.open(fname, std::fstream::out);
ids = pgWithMSTAndOptimizer->getNodesIds();
for (const size_t& id : ids)
{
Point3d d = pgWithMSTAndOptimizer->getNodePose(id).translation();
of << "v " << d.x << " " << d.y << " " << d.z << std::endl;
}
esz = pgWithMSTAndOptimizer->getNumEdges();
for (size_t i = 0; i < esz; i++)
{
size_t sid = pgWithMSTAndOptimizer->getEdgeStart(i), tid = pgWithMSTAndOptimizer->getEdgeEnd(i);
of << "l " << sid + 1 << " " << tid + 1 << std::endl;
}
of.close();
// Write OBJ for optimizer-only pose graph
fname = "pg_optimizer_only.obj";
of.open(fname, std::fstream::out);
ids = pgOptimizerOnly->getNodesIds();
for (const size_t& id : ids)
{
Point3d d = pgOptimizerOnly->getNodePose(id).translation();
of << "v " << d.x << " " << d.y << " " << d.z << std::endl;
}
esz = pgOptimizerOnly->getNumEdges();
for (size_t i = 0; i < esz; i++)
{
size_t sid = pgOptimizerOnly->getEdgeStart(i), tid = pgOptimizerOnly->getEdgeEnd(i);
of << "l " << sid + 1 << " " << tid + 1 << std::endl;
}
of.close();
// Write OBJ for initial pose graph
fname = "pg_init.obj";
of.open(fname, std::fstream::out);
ids = init->getNodesIds();
for (const size_t& id : ids)
{
Point3d d = init->getNodePose(id).translation();
of << "v " << d.x << " " << d.y << " " << d.z << std::endl;
}
esz = init->getNumEdges();
for (size_t i = 0; i < esz; i++)
{
size_t sid = init->getEdgeStart(i), tid = init->getEdgeEnd(i);
of << "l " << sid + 1 << " " << tid + 1 << std::endl;
}
of.close();
}
}
// ------------------------------------------------------------------------------------------
// Wireframe meshes for debugging visualization purposes
struct Mesh
{
std::vector<Point3f> pts;
std::vector<Vec2i> lines;
Mesh join(const Mesh& m2) const
{
Mesh mo;
size_t sz1 = this->pts.size();
std::copy(this->pts.begin(), this->pts.end(), std::back_inserter(mo.pts));
std::copy(m2.pts.begin(), m2.pts.end(), std::back_inserter(mo.pts));
std::copy(this->lines.begin(), this->lines.end(), std::back_inserter(mo.lines));
std::transform(m2.lines.begin(), m2.lines.end(), std::back_inserter(mo.lines),
[sz1](Vec2i ab) { return Vec2i(ab[0] + (int)sz1, ab[1] + (int)sz1); });
return mo;
}
Mesh transform(Affine3f a, float scale = 1.f) const
{
Mesh out;
out.lines = this->lines;
for (Point3f p : this->pts)
{
out.pts.push_back(a * (p * scale));
}
return out;
}
// 0-2 - min, 3-5 - max
Vec6f getBoundingBox() const
{
float maxv = std::numeric_limits<float>::max();
Vec3f xmin(maxv, maxv, maxv), xmax(-maxv, -maxv, -maxv);
for (Point3f p : this->pts)
{
xmin[0] = min(p.x, xmin[0]); xmin[1] = min(p.y, xmin[1]); xmin[2] = min(p.z, xmin[2]);
xmax[0] = max(p.x, xmax[0]); xmax[1] = max(p.y, xmax[1]); xmax[2] = max(p.z, xmax[2]);
}
return Vec6f(xmin[0], xmin[1], xmin[2], xmax[0], xmax[1], xmax[2]);
}
};
Mesh seg7(int d)
{
const std::vector<Point3f> pt = { {0, 0, 0}, {0, 1, 0},
{1, 0, 0}, {1, 1, 0},
{2, 0, 0}, {2, 1, 0} };
std::vector<Mesh> seg(7);
seg[0].pts = { pt[0], pt[1] };
seg[1].pts = { pt[1], pt[3] };
seg[2].pts = { pt[3], pt[5] };
seg[3].pts = { pt[5], pt[4] };
seg[4].pts = { pt[4], pt[2] };
seg[5].pts = { pt[2], pt[0] };
seg[6].pts = { pt[2], pt[3] };
for (int i = 0; i < 7; i++)
seg[i].lines = { {0, 1} };
vector<Mesh> digits = {
seg[0].join(seg[1]).join(seg[2]).join(seg[3]).join(seg[4]).join(seg[5]), // 0
seg[1].join(seg[2]), // 1
seg[0].join(seg[1]).join(seg[3]).join(seg[4]).join(seg[6]), // 2
seg[0].join(seg[1]).join(seg[2]).join(seg[3]).join(seg[6]), // 3
seg[1].join(seg[2]).join(seg[5]).join(seg[6]), // 4
seg[0].join(seg[2]).join(seg[3]).join(seg[5]).join(seg[6]), // 5
seg[0].join(seg[2]).join(seg[3]).join(seg[4]).join(seg[5]).join(seg[6]), // 6
seg[0].join(seg[1]).join(seg[2]), // 7
seg[0].join(seg[1]).join(seg[2]).join(seg[3]).join(seg[4]).join(seg[5]).join(seg[6]), // 8
seg[0].join(seg[1]).join(seg[2]).join(seg[3]).join(seg[5]).join(seg[6]), // 9
seg[6], // -
};
return digits[d];
}
Mesh drawId(size_t x)
{
vector<int> digits;
do
{
digits.push_back(x % 10);
x /= 10;
}
while (x > 0);
float spacing = 0.2f;
Mesh m;
for (size_t i = 0; i < digits.size(); i++)
{
Mesh digit = seg7(digits[digits.size() - 1 - i]);
Vec6f bb = digit.getBoundingBox();
digit = digit.transform(Affine3f().translate(-Vec3f(0, bb[1], 0)));
Vec3f tr;
if (m.pts.empty())
tr = Vec3f();
else
tr = Vec3f(0, (m.getBoundingBox()[4] + spacing), 0);
m = m.join(digit.transform( Affine3f().translate(tr) ));
}
return m;
}
Mesh drawFromTo(size_t f, size_t t)
{
Mesh m;
Mesh df = drawId(f);
Mesh dp = seg7(10);
Mesh dt = drawId(t);
float spacing = 0.2f;
m = m.join(df).join(dp.transform(Affine3f().translate(Vec3f(0, df.getBoundingBox()[4] + spacing, 0))))
.join(dt.transform(Affine3f().translate(Vec3f(0, df.getBoundingBox()[4] + 2*spacing + 1, 0))));
return m;
}
Mesh drawPoseGraph(Ptr<detail::PoseGraph> pg)
{
Mesh marker;
marker.pts = { {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {1, 1, 0} };
marker.lines = { {0, 1}, {0, 2}, {0, 3}, {1, 4} };
Mesh allMeshes;
Affine3f margin = Affine3f().translate(Vec3f(0.1f, 0.1f, 0));
std::vector<size_t> ids = pg->getNodesIds();
for (const size_t& id : ids)
{
Affine3f pose = pg->getNodePose(id);
Mesh m = marker.join(drawId(id).transform(margin, 0.25f)).transform(pose);
allMeshes = allMeshes.join(m);
}
// edges
margin = Affine3f().translate(Vec3f(0.05f, 0.05f, 0));
for (size_t i = 0; i < pg->getNumEdges(); i++)
{
Affine3f pose = pg->getEdgePose(i);
size_t sid = pg->getEdgeStart(i);
size_t did = pg->getEdgeEnd(i);
Affine3f spose = pg->getNodePose(sid);
Affine3f dpose = spose * pose;
Mesh m = marker.join(drawFromTo(sid, did).transform(margin, 0.125f)).transform(dpose);
allMeshes = allMeshes.join(m);
}
return allMeshes;
}
void writeObj(const std::string& fname, const Mesh& m)
{
// Write edge-only model of how nodes are located in space
std::fstream of(fname, std::fstream::out);
for (const Point3f& d : m.pts)
{
of << "v " << d.x << " " << d.y << " " << d.z << std::endl;
}
for (const Vec2i& v : m.lines)
{
of << "l " << v[0] + 1 << " " << v[1] + 1 << std::endl;
}
of.close();
}
TEST(PoseGraph, simple)
{
Ptr<detail::PoseGraph> pg = detail::PoseGraph::create();
DualQuatf true0(1, 0, 0, 0, 0, 0, 0, 0);
DualQuatf true1 = DualQuatf::createFromPitch((float)CV_PI / 3.0f, 10.0f, Vec3f(1, 1.5f, 1.2f), Vec3f());
DualQuatf pose0 = true0;
vector<DualQuatf> noise(7);
for (size_t i = 0; i < noise.size(); i++)
{
float angle = cv::theRNG().uniform(-1.f, 1.f);
float shift = cv::theRNG().uniform(-2.f, 2.f);
Matx31f axis = Vec3f::randu(0.f, 1.f), moment = Vec3f::randu(0.f, 1.f);
noise[i] = DualQuatf::createFromPitch(angle, shift,
Vec3f(axis(0), axis(1), axis(2)),
Vec3f(moment(0), moment(1), moment(2)));
}
DualQuatf pose1 = noise[0] * true1;
DualQuatf diff = true1 * true0.inv();
vector<DualQuatf> cfrom = { diff, diff * noise[1], noise[2] * diff };
DualQuatf diffInv = diff.inv();
vector<DualQuatf> cto = { diffInv, diffInv * noise[3], noise[4] * diffInv };
pg->addNode(123, pose0.toAffine3(), true);
pg->addNode(456, pose1.toAffine3(), false);
Matx66f info = Matx66f::eye();
for (int i = 0; i < 3; i++)
{
pg->addEdge(123, 456, cfrom[i].toAffine3(), info);
pg->addEdge(456, 123, cto[i].toAffine3(), info);
}
Mesh allMeshes = drawPoseGraph(pg);
// Add the "--test_debug" to arguments to see resulting pose graph nodes positions
if (cvtest::debugLevel > 0)
{
writeObj("pg_simple_in.obj", allMeshes);
}
auto r = pg->optimize();
Mesh after = drawPoseGraph(pg);
// Add the "--test_debug" to arguments to see resulting pose graph nodes positions
if (cvtest::debugLevel > 0)
{
writeObj("pg_simple_out.obj", after);
}
EXPECT_TRUE(r.found);
}
#else
TEST(PoseGraph, sphereG2O)
{
throw SkipTestException("Build with Eigen required for pose graph optimization");
}
TEST(PoseGraphMST, optimization)
{
throw SkipTestException("Build with Eigen required for pose graph optimization");
}
TEST(PoseGraph, simple)
{
throw SkipTestException("Build with Eigen required for pose graph optimization");
}
#endif
TEST(LevMarq, Rosenbrock)
{
auto f = [](double x, double y) -> double
{
return (1.0 - x) * (1.0 - x) + 100.0 * (y - x * x) * (y - x * x);
};
auto j = [](double x, double y) -> Matx12d
{
return {/*dx*/ -2.0 + 2.0 * x - 400.0 * x * y + 400.0 * x*x*x,
/*dy*/ 200.0 * y - 200.0 * x*x,
};
};
LevMarq solver(2, [f, j](InputOutputArray param, OutputArray err, OutputArray jv) -> bool
{
Vec2d v = param.getMat();
double x = v[0], y = v[1];
err.create(1, 1, CV_64F);
err.getMat().at<double>(0) = f(x, y);
if (jv.needed())
{
jv.create(1, 2, CV_64F);
Mat(j(x, y)).copyTo(jv);
}
return true;
},
LevMarq::Settings().setGeodesic(true));
Mat_<double> x (Vec2d(1, 3));
auto r = solver.run(x);
EXPECT_TRUE(r.found);
EXPECT_LT(r.energy, 0.035);
EXPECT_LE(r.iters, 17);
}
}} // namespace
+21
View File
@@ -0,0 +1,21 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_TEST_PRECOMP_HPP__
#define __OPENCV_TEST_PRECOMP_HPP__
#include <functional>
#include <numeric>
#include "opencv2/ts.hpp"
#include "opencv2/geometry.hpp"
#include "opencv2/ptcloud.hpp"
#include <opencv2/core/utils/logger.hpp>
#include "opencv2/ptcloud/depth.hpp"
#include "opencv2/ptcloud/odometry.hpp"
#ifdef HAVE_OPENCL
#include <opencv2/core/ocl.hpp>
#endif
#endif
+107
View File
@@ -0,0 +1,107 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// This code is also subject to the license terms in the LICENSE_WillowGarage.md file found in this module's directory
#include "test_precomp.hpp"
namespace opencv_test { namespace {
class RgbdDepthRegistrationTest
{
public:
RgbdDepthRegistrationTest() { }
~RgbdDepthRegistrationTest() { }
void run()
{
// Test all three input types for no-op registrations (where a depth image is registered to itself)
noOpRandomRegistrationTest<unsigned short>(100, 2500);
noOpRandomRegistrationTest<float>(0.1f, 2.5f);
noOpRandomRegistrationTest<double>(0.1, 2.5);
// Test sentinel value handling, occlusion, and dilation
{
// K from a VGA Kinect
Mat K = (Mat_<float>(3, 3) << 525., 0., 319.5, 0., 525., 239.5, 0., 0., 1.);
int width = 640, height = 480;
// All elements are zero except for first two along the diagonal
Mat_<unsigned short> vgaDepth(height, width, (unsigned short)0);
vgaDepth(0, 0) = 1001;
vgaDepth(1, 1) = 1000;
Mat_<unsigned short> registeredDepth;
registerDepth(K, K, Mat(), Matx44f::eye(), vgaDepth, Size(width, height), registeredDepth, true);
// We expect the closer depth of 1000 to occlude the more distant depth and occupy the
// upper four left pixels in the depth image because of dilation
Mat_<unsigned short> expectedResult(height, width, (unsigned short)0);
expectedResult(0, 0) = 1000;
expectedResult(0, 1) = 1000;
expectedResult(1, 0) = 1000;
expectedResult(1, 1) = 1000;
Mat ad;
absdiff(registeredDepth, expectedResult, ad);
ASSERT_GT(std::numeric_limits<double>::min(), abs(sum(ad)[0])) << "Dilation and occlusion";
}
}
template <class DepthDepth>
void noOpRandomRegistrationTest(DepthDepth minDepth, DepthDepth maxDepth)
{
// K from a VGA Kinect
Mat K = (Mat_<float>(3, 3) << 525., 0., 319.5, 0., 525., 239.5, 0., 0., 1.);
// Create a random depth image
RNG rng;
Mat_<DepthDepth> randomVGADepth(480, 640);
rng.fill(randomVGADepth, RNG::UNIFORM, minDepth, maxDepth);
Mat registeredDepth;
registerDepth(K, K, Mat(), Matx44f::eye(), randomVGADepth, Size(640, 480), registeredDepth);
// Check per-pixel relative difference
Mat ad;
absdiff(registeredDepth, randomVGADepth, ad);
Mat mmin;
cv::min(registeredDepth, randomVGADepth, mmin);
Mat rel = ad / mmin;
double maxDiff = cv::norm(rel, NORM_INF);
ASSERT_GT(1e-07, maxDiff) << "No-op registration";
}
};
TEST(RGBD_DepthRegistration, compute)
{
RgbdDepthRegistrationTest test;
test.run();
}
TEST(RGBD_DepthRegistration, issue_2234)
{
Matx33f intrinsicsDepth(100, 0, 50, 0, 100, 50, 0, 0, 1);
Matx33f intrinsicsColor(100, 0, 200, 0, 100, 50, 0, 0, 1);
Mat_<float> depthMat(100, 100, (float)0.);
for(int i = 1; i <= 100; i++)
{
for(int j = 1; j <= 100; j++)
depthMat(i-1,j-1) = (float)j;
}
Mat registeredDepth;
registerDepth(intrinsicsDepth, intrinsicsColor, Mat(), Matx44f::eye(), depthMat, Size(400, 100), registeredDepth);
Rect roi( 150, 0, 100, 100 );
Mat subM(registeredDepth,roi);
EXPECT_EQ(0, cvtest::norm(subM, depthMat, NORM_INF));
}
}} // namespace
+946
View File
@@ -0,0 +1,946 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
namespace opencv_test { namespace {
using namespace cv;
// that was easier than using CV_ENUM() macro
namespace
{
using namespace cv;
struct CullingModeEnum
{
static const std::array<TriangleCullingMode, 3> vals;
static const std::array<std::string, 3> svals;
CullingModeEnum(TriangleCullingMode v = RASTERIZE_CULLING_NONE) : val(v) {}
operator TriangleCullingMode() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < (int)vals.size())
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<CullingModeEnum> all()
{
return ::testing::Values(CullingModeEnum(vals[0]),
CullingModeEnum(vals[1]),
CullingModeEnum(vals[2]));
}
private:
TriangleCullingMode val;
};
const std::array<TriangleCullingMode, 3> CullingModeEnum::vals
{
RASTERIZE_CULLING_NONE,
RASTERIZE_CULLING_CW,
RASTERIZE_CULLING_CCW
};
const std::array<std::string, 3> CullingModeEnum::svals
{
std::string("None"),
std::string("CW"),
std::string("CCW")
};
static inline void PrintTo(const CullingModeEnum &t, std::ostream *os) { t.PrintTo(os); }
}
// that was easier than using CV_ENUM() macro
namespace
{
using namespace cv;
struct ShadingTypeEnum
{
static const std::array<TriangleShadingType, 3> vals;
static const std::array<std::string, 3> svals;
ShadingTypeEnum(TriangleShadingType v = RASTERIZE_SHADING_WHITE) : val(v) {}
operator TriangleShadingType() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < (int)vals.size())
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<ShadingTypeEnum> all()
{
return ::testing::Values(ShadingTypeEnum(vals[0]),
ShadingTypeEnum(vals[1]),
ShadingTypeEnum(vals[2]));
}
private:
TriangleShadingType val;
};
const std::array<TriangleShadingType, 3> ShadingTypeEnum::vals
{
RASTERIZE_SHADING_WHITE,
RASTERIZE_SHADING_FLAT,
RASTERIZE_SHADING_SHADED
};
const std::array<std::string, 3> ShadingTypeEnum::svals
{
std::string("White"),
std::string("Flat"),
std::string("Shaded")
};
static inline void PrintTo(const ShadingTypeEnum &t, std::ostream *os) { t.PrintTo(os); }
}
enum class ModelType
{
Empty = 0,
File = 1,
Clipping = 2,
Color = 3,
Centered = 4
};
// that was easier than using CV_ENUM() macro
namespace
{
using namespace cv;
struct ModelTypeEnum
{
static const std::array<ModelType, 5> vals;
static const std::array<std::string, 5> svals;
ModelTypeEnum(ModelType v = ModelType::Empty) : val(v) {}
operator ModelType() const { return val; }
void PrintTo(std::ostream *os) const
{
int v = int(val);
if (v >= 0 && v < (int)vals.size())
{
*os << svals[v];
}
else
{
*os << "UNKNOWN";
}
}
static ::testing::internal::ParamGenerator<ModelTypeEnum> all()
{
return ::testing::Values(ModelTypeEnum(vals[0]),
ModelTypeEnum(vals[1]),
ModelTypeEnum(vals[2]),
ModelTypeEnum(vals[3]),
ModelTypeEnum(vals[4]));
}
private:
ModelType val;
};
const std::array<ModelType, 5> ModelTypeEnum::vals
{
ModelType::Empty,
ModelType::File,
ModelType::Clipping,
ModelType::Color,
ModelType::Centered
};
const std::array<std::string, 5> ModelTypeEnum::svals
{
std::string("Empty"),
std::string("File"),
std::string("Clipping"),
std::string("Color"),
std::string("Centered")
};
static inline void PrintTo(const ModelTypeEnum &t, std::ostream *os) { t.PrintTo(os); }
}
template<typename T>
std::string printEnum(T v)
{
std::ostringstream ss;
v.PrintTo(&ss);
return ss.str();
}
static Matx44d lookAtMatrixCal(const Vec3d& position, const Vec3d& lookat, const Vec3d& upVector)
{
Vec3d w = cv::normalize(position - lookat);
Vec3d u = cv::normalize(upVector.cross(w));
Vec3d v = w.cross(u);
Matx44d res(u[0], u[1], u[2], 0,
v[0], v[1], v[2], 0,
w[0], w[1], w[2], 0,
0, 0, 0, 1.0);
Matx44d translate(1.0, 0, 0, -position[0],
0, 1.0, 0, -position[1],
0, 0, 1.0, -position[2],
0, 0, 0, 1.0);
res = res * translate;
return res;
}
static void generateNormals(const std::vector<Vec3f>& points, const std::vector<std::vector<int>>& indices,
std::vector<Vec3f>& normals)
{
std::vector<std::vector<Vec3f>> preNormals(points.size(), std::vector<Vec3f>());
for (const auto& tri : indices)
{
Vec3f p0 = points[tri[0]];
Vec3f p1 = points[tri[1]];
Vec3f p2 = points[tri[2]];
Vec3f cross = cv::normalize((p1 - p0).cross(p2 - p0));
for (int i = 0; i < 3; i++)
{
preNormals[tri[i]].push_back(cross);
}
}
normals.reserve(points.size());
for (const auto& pn : preNormals)
{
Vec3f sum { };
for (const auto& n : pn)
{
sum += n;
}
normals.push_back(cv::normalize(sum));
}
}
// load model once and keep it in static memory
static void getModelOnce(const std::string& objectPath, std::vector<Vec3f>& vertices,
std::vector<Vec3i>& indices, std::vector<Vec3f>& colors)
{
static bool load = false;
static std::vector<Vec3f> vert, col;
static std::vector<Vec3i> ind;
if (!load)
{
std::vector<vector<int>> indvec;
// using per-vertex normals as colors
loadMesh(objectPath, vert, indvec);
generateNormals(vert, indvec, col);
for (const auto &vec : indvec)
{
ind.push_back({vec[0], vec[1], vec[2]});
}
for (auto &color : col)
{
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2]));
}
load = true;
}
vertices = vert;
colors = col;
indices = ind;
}
class ModelData
{
public:
ModelData(ModelType type = ModelType::Empty)
{
switch (type)
{
case ModelType::Empty:
{
position = Vec3d(0.0, 0.0, 0.0);
lookat = Vec3d(0.0, 0.0, 0.0);
upVector = Vec3d(0.0, 1.0, 0.0);
fovy = 45.0;
vertices = std::vector<Vec3f>(4, {2.0f, 0, -2.0f});
colors = std::vector<Vec3f>(4, {0, 0, 1.0f});
indices = { };
}
break;
case ModelType::File:
{
string objectPath = findDataFile("viz/dragon.ply");
position = Vec3d( 1.9, 0.4, 1.3);
lookat = Vec3d( 0.0, 0.0, 0.0);
upVector = Vec3d( 0.0, 1.0, 0.0);
fovy = 45.0;
getModelOnce(objectPath, vertices, indices, colors);
}
break;
case ModelType::Clipping:
{
position = Vec3d(0.0, 0.0, 5.0);
lookat = Vec3d(0.0, 0.0, 0.0);
upVector = Vec3d(0.0, 1.0, 0.0);
fovy = 45.0;
vertices =
{
{ 2.0, 0.0, -2.0}, { 0.0, -6.0, -2.0}, {-2.0, 0.0, -2.0},
{ 3.5, -1.0, -5.0}, { 2.5, -2.5, -5.0}, {-1.0, 1.0, -5.0},
{-6.5, -1.0, -3.0}, {-2.5, -2.0, -3.0}, { 1.0, 1.0, -5.0},
};
indices = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} };
Vec3f col1(217.0, 238.0, 185.0);
Vec3f col2(185.0, 217.0, 238.0);
Vec3f col3(150.0, 10.0, 238.0);
col1 *= (1.f / 255.f);
col2 *= (1.f / 255.f);
col3 *= (1.f / 255.f);
colors =
{
col1, col2, col3,
col2, col3, col1,
col3, col1, col2,
};
}
break;
case ModelType::Centered:
{
position = Vec3d(0.0, 0.0, 5.0);
lookat = Vec3d(0.0, 0.0, 0.0);
upVector = Vec3d(0.0, 1.0, 0.0);
fovy = 45.0;
vertices =
{
{ 2.0, 0.0, -2.0}, { 0.0, -2.0, -2.0}, {-2.0, 0.0, -2.0},
{ 3.5, -1.0, -5.0}, { 2.5, -1.5, -5.0}, {-1.0, 0.5, -5.0},
};
indices = { {0, 1, 2}, {3, 4, 5} };
Vec3f col1(217.0, 238.0, 185.0);
Vec3f col2(185.0, 217.0, 238.0);
col1 *= (1.f / 255.f);
col2 *= (1.f / 255.f);
colors =
{
col1, col2, col1,
col2, col1, col2,
};
}
break;
case ModelType::Color:
{
position = Vec3d(0.0, 0.0, 5.0);
lookat = Vec3d(0.0, 0.0, 0.0);
upVector = Vec3d(0.0, 1.0, 0.0);
fovy = 60.0;
vertices =
{
{ 2.0, 0.0, -2.0},
{ 0.0, 2.0, -3.0},
{-2.0, 0.0, -2.0},
{ 0.0, -2.0, 1.0},
};
indices = { {0, 1, 2}, {0, 2, 3} };
colors =
{
{ 0.0f, 0.0f, 1.0f},
{ 0.0f, 1.0f, 0.0f},
{ 1.0f, 0.0f, 0.0f},
{ 0.0f, 1.0f, 0.0f},
};
}
break;
default:
CV_Error(Error::StsBadArg, "Unknown model type");
break;
}
}
Vec3d position;
Vec3d lookat;
Vec3d upVector;
double fovy;
std::vector<Vec3f> vertices;
std::vector<Vec3i> indices;
std::vector<Vec3f> colors;
};
void compareDepth(const cv::Mat& gt, const cv::Mat& mat, cv::Mat& diff, double zFar, double scale,
double maskThreshold, double normInfThreshold, double normL2Threshold)
{
ASSERT_EQ(CV_16UC1, gt.type());
ASSERT_EQ(CV_16UC1, mat.type());
ASSERT_EQ(gt.size(), mat.size());
Mat gtMask = gt < zFar*scale;
Mat matMask = mat < zFar*scale;
Mat diffMask = gtMask != matMask;
int nzDepthDiff = cv::countNonZero(diffMask);
EXPECT_LE(nzDepthDiff, maskThreshold);
Mat jointMask = gtMask & matMask;
int nzJointMask = cv::countNonZero(jointMask);
double normInfDepth = cv::norm(gt, mat, cv::NORM_INF, jointMask);
EXPECT_LE(normInfDepth, normInfThreshold);
double normL2Depth = nzJointMask ? (cv::norm(gt, mat, cv::NORM_L2, jointMask) / nzJointMask) : 0;
EXPECT_LE(normL2Depth, normL2Threshold);
// add --test_debug to output differences
if (debugLevel > 0)
{
std::cout << "nzDepthDiff: " << nzDepthDiff << " vs " << maskThreshold << std::endl;
std::cout << "normInfDepth: " << normInfDepth << " vs " << normInfThreshold << std::endl;
std::cout << "normL2Depth: " << normL2Depth << " vs " << normL2Threshold << std::endl;
}
diff = (gt - mat) + (1 << 15);
}
void compareRGB(const cv::Mat& gt, const cv::Mat& mat, cv::Mat& diff, double normInfThreshold, double normL2Threshold)
{
ASSERT_EQ(CV_32FC3, gt.type());
ASSERT_EQ(CV_32FC3, mat.type());
ASSERT_EQ(gt.size(), mat.size());
double normInfRgb = cv::norm(gt, mat, cv::NORM_INF);
EXPECT_LE(normInfRgb, normInfThreshold);
double normL2Rgb = cv::norm(gt, mat, cv::NORM_L2) / gt.total();
EXPECT_LE(normL2Rgb, normL2Threshold);
// add --test_debug to output differences
if (debugLevel > 0)
{
std::cout << "normInfRgb: " << normInfRgb << " vs " << normInfThreshold << std::endl;
std::cout << "normL2Rgb: " << normL2Rgb << " vs " << normL2Threshold << std::endl;
}
diff = (gt - mat) * 0.5 + 0.5;
}
struct RenderTestThresholds
{
RenderTestThresholds(
double _rgbInfThreshold,
double _rgbL2Threshold,
double _depthMaskThreshold,
double _depthInfThreshold,
double _depthL2Threshold) :
rgbInfThreshold(_rgbInfThreshold),
rgbL2Threshold(_rgbL2Threshold),
depthMaskThreshold(_depthMaskThreshold),
depthInfThreshold(_depthInfThreshold),
depthL2Threshold(_depthL2Threshold)
{ }
double rgbInfThreshold;
double rgbL2Threshold;
double depthMaskThreshold;
double depthInfThreshold;
double depthL2Threshold;
};
// resolution, shading type, culling mode, model type, float type, index type
typedef std::tuple<std::tuple<int, int>, ShadingTypeEnum, CullingModeEnum, ModelTypeEnum, MatDepth, MatDepth> RenderTestParamType;
class RenderingTest : public ::testing::TestWithParam<RenderTestParamType>
{
protected:
void SetUp() override
{
params = GetParam();
auto wh = std::get<0>(params);
width = std::get<0>(wh);
height = std::get<1>(wh);
shadingType = std::get<1>(params);
cullingMode = std::get<2>(params);
modelType = std::get<3>(params);
modelData = ModelData(modelType);
ftype = std::get<4>(params);
itype = std::get<5>(params);
zNear = 0.1, zFar = 50.0;
depthScale = 1000.0;
depth_buf = Mat(height, width, ftype, zFar);
color_buf = Mat(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0));
cameraPose = lookAtMatrixCal(modelData.position, modelData.lookat, modelData.upVector);
fovYradians = modelData.fovy * (CV_PI / 180.0);
verts = Mat(modelData.vertices);
verts.convertTo(verts, ftype);
if (shadingType != RASTERIZE_SHADING_WHITE)
{
// let vertices be in BGR format to avoid later color conversions
// mixChannels() does not support in-place operation
colors = Mat(modelData.colors);
colors.convertTo(colors, ftype);
cv::mixChannels(colors.clone(), colors, {0, 2, 1, 1, 2, 0});
}
indices = Mat(modelData.indices);
if (itype != CV_32S)
{
indices.convertTo(indices, itype);
}
settings = TriangleRasterizeSettings().setCullingMode(cullingMode).setShadingType(shadingType);
triangleRasterize(verts, indices, colors, color_buf, depth_buf,
cameraPose, fovYradians, zNear, zFar, settings);
}
public:
RenderTestParamType params;
int width, height;
double zNear, zFar, depthScale;
Mat depth_buf, color_buf;
Mat verts, colors, indices;
Matx44d cameraPose;
double fovYradians;
TriangleRasterizeSettings settings;
ModelData modelData;
ModelTypeEnum modelType;
ShadingTypeEnum shadingType;
CullingModeEnum cullingMode;
int ftype, itype;
};
// depth-only or RGB-only rendering should produce the same result as usual rendering
TEST_P(RenderingTest, noArrays)
{
Mat depthOnly(height, width, ftype, zFar);
Mat colorOnly(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0));
triangleRasterizeDepth(verts, indices, depthOnly, cameraPose, fovYradians, zNear, zFar, settings);
triangleRasterizeColor(verts, indices, colors, colorOnly, cameraPose, fovYradians, zNear, zFar, settings);
Mat rgbDiff, depthDiff;
compareRGB(color_buf, colorOnly, rgbDiff, 0, 0);
depth_buf.convertTo(depth_buf, CV_16U, depthScale);
depthOnly.convertTo(depthOnly, CV_16U, depthScale);
compareDepth(depth_buf, depthOnly, depthDiff, zFar, depthScale, 0, 0, 0);
// add --test_debug to output resulting images
if (debugLevel > 0)
{
std::string modelName = printEnum(modelType);
std::string shadingName = printEnum(shadingType);
std::string cullingName = printEnum(cullingMode);
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str());
std::string outColorPath = "noarray_color_image_" + suffix + "_" + shadingName + ".png";
std::string outDepthPath = "noarray_depth_image_" + suffix + "_" + shadingName + ".png";
imwrite(outColorPath, color_buf * 255.f);
imwrite(outDepthPath, depth_buf);
imwrite("diff_" + outColorPath, rgbDiff * 255.f);
imwrite("diff_" + outDepthPath, depthDiff);
}
}
// passing the same parameters in float should give the same result
TEST_P(RenderingTest, floatParams)
{
Mat depth_buf2(height, width, ftype, zFar);
Mat color_buf2(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0));
// cameraPose can also be float, checking it
triangleRasterize(verts, indices, colors, color_buf2, depth_buf2,
Matx44f(cameraPose), (float)fovYradians, (float)zNear, (float)zFar, settings);
RenderTestThresholds thr(0, 0, 0, 0, 0);
switch (modelType)
{
case ModelType::Empty: break;
case ModelType::Color: break;
case ModelType::Clipping:
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CW)
{
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.00127;
}
else if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_NONE)
{
thr.rgbInfThreshold = 3e-7;
thr.rgbL2Threshold = 1.86e-10;
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.000406;
}
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW)
{
thr.rgbInfThreshold = 2.39e-07;
thr.rgbL2Threshold = 1.86e-10;
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.0016;
}
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW)
{
thr.rgbInfThreshold = 0.934;
thr.rgbL2Threshold = 0.000102;
thr.depthMaskThreshold = 21;
}
else if (width == 640 && height == 480 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE)
{
thr.rgbL2Threshold = 1;
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.000248;
}
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW)
{
thr.rgbInfThreshold = 0.934;
thr.rgbL2Threshold = 3.18e-5;
thr.depthMaskThreshold = 114;
}
break;
case ModelType::File:
thr.depthInfThreshold = 1;
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CCW)
{
thr.rgbInfThreshold = 0.000229;
thr.rgbL2Threshold = 6.37e-09;
thr.depthL2Threshold = 0.00043;
}
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW)
{
thr.rgbInfThreshold = 0.000277;
thr.rgbL2Threshold = 1.8e-09;
thr.depthL2Threshold = 0.000124;
}
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE)
{
thr.depthL2Threshold = 0.000124;
}
break;
case ModelType::Centered:
if (shadingType == RASTERIZE_SHADING_SHADED && cullingMode != RASTERIZE_CULLING_CW)
{
thr.rgbInfThreshold = 3.58e-07;
thr.rgbL2Threshold = 1.51e-10;
}
break;
}
Mat rgbDiff, depthDiff;
compareRGB(color_buf, color_buf2, rgbDiff, thr.rgbInfThreshold, thr.rgbL2Threshold);
depth_buf.convertTo(depth_buf, CV_16U, depthScale);
depth_buf2.convertTo(depth_buf2, CV_16U, depthScale);
compareDepth(depth_buf, depth_buf2, depthDiff, zFar, depthScale, thr.depthMaskThreshold, thr.depthInfThreshold, thr.depthL2Threshold);
// add --test_debug to output resulting images
if (debugLevel > 0)
{
std::string modelName = printEnum(modelType);
std::string shadingName = printEnum(shadingType);
std::string cullingName = printEnum(cullingMode);
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str());
std::string outColorPath = "float_color_image_" + suffix + "_" + shadingName + ".png";
std::string outDepthPath = "float_depth_image_" + suffix + "_" + shadingName + ".png";
imwrite(outColorPath, color_buf * 255.f);
imwrite(outDepthPath, depth_buf);
imwrite("diff_" + outColorPath, rgbDiff * 255.f);
imwrite("diff_" + outDepthPath, depthDiff);
}
}
// some culling options produce the same pictures, let's join them
TriangleCullingMode findSameCulling(ModelType modelType, TriangleShadingType shadingType, TriangleCullingMode cullingMode, bool forRgb)
{
TriangleCullingMode sameCullingMode = cullingMode;
if ((modelType == ModelType::Centered && cullingMode == RASTERIZE_CULLING_CCW) ||
(modelType == ModelType::Color && cullingMode == RASTERIZE_CULLING_CW) ||
(modelType == ModelType::File && shadingType == RASTERIZE_SHADING_WHITE && forRgb) ||
(modelType == ModelType::File && cullingMode == RASTERIZE_CULLING_CW))
{
sameCullingMode = RASTERIZE_CULLING_NONE;
}
return sameCullingMode;
}
// compare rendering results to the ones produced by samples/opengl/opengl_testdata_generator app
TEST_P(RenderingTest, accuracy)
{
depth_buf.convertTo(depth_buf, CV_16U, depthScale);
if (modelType == ModelType::Empty ||
(modelType == ModelType::Centered && cullingMode == RASTERIZE_CULLING_CW) ||
(modelType == ModelType::Color && cullingMode == RASTERIZE_CULLING_CCW))
{
// empty image case
EXPECT_EQ(0, cv::norm(color_buf, NORM_INF));
Mat depthDiff;
absdiff(depth_buf, Scalar(zFar * depthScale), depthDiff);
EXPECT_EQ(0, cv::norm(depthDiff, cv::NORM_INF));
}
else
{
RenderTestThresholds thr(0, 0, 0, 0, 0);
switch (modelType)
{
case ModelType::Centered:
if (shadingType == RASTERIZE_SHADING_SHADED)
{
thr.rgbInfThreshold = 0.00218;
thr.rgbL2Threshold = 2.85e-06;
}
break;
case ModelType::Clipping:
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CW)
{
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.00163;
}
else if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_NONE)
{
thr.rgbInfThreshold = 0.934;
thr.rgbL2Threshold = 8.03E-05;
thr.depthMaskThreshold = 23;
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.000555;
}
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW)
{
thr.rgbInfThreshold = 0.0022;
thr.rgbL2Threshold = 2.54E-06;
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.00175;
}
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW)
{
thr.rgbInfThreshold = 0.934;
thr.rgbL2Threshold = 0.000102;
thr.depthMaskThreshold = 21;
}
else if (width == 640 && height == 480 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE)
{
thr.rgbInfThreshold = 1;
thr.rgbL2Threshold = 3.95E-05;
thr.depthMaskThreshold = 49;
thr.depthInfThreshold = 1;
thr.depthL2Threshold = 0.000269;
}
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW)
{
thr.rgbInfThreshold = 0.934;
thr.rgbL2Threshold = 3.27e-5;
thr.depthMaskThreshold = 121;
}
break;
case ModelType::Color:
thr.depthInfThreshold = 1;
if (width == 320 && height == 240)
{
thr.depthL2Threshold = 0.00103;
}
else if (width == 256 && height == 256)
{
thr.depthL2Threshold = 0.000785;
}
if (shadingType == RASTERIZE_SHADING_SHADED)
{
thr.rgbInfThreshold = 0.0022;
thr.rgbL2Threshold = 3.13e-06;
}
break;
case ModelType::File:
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CCW)
{
thr.rgbInfThreshold = 0.836;
thr.rgbL2Threshold = 2.08e-05;
thr.depthMaskThreshold = 1;
thr.depthInfThreshold = 99;
thr.depthL2Threshold = 0.00544;
}
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW)
{
thr.rgbInfThreshold = 0.973;
thr.rgbL2Threshold = 5.2e-06;
thr.depthMaskThreshold = 4;
thr.depthInfThreshold = 258;
thr.depthL2Threshold = 0.00228;
}
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE)
{
thr.rgbInfThreshold = 1;
thr.rgbL2Threshold = 7.07e-06;
thr.depthMaskThreshold = 4;
thr.depthInfThreshold = 258;
thr.depthL2Threshold = 0.00228;
}
break;
default:
break;
}
CullingModeEnum cullingModeRgb = findSameCulling(modelType, shadingType, cullingMode, true);
CullingModeEnum cullingModeDepth = findSameCulling(modelType, shadingType, cullingMode, false);
std::string modelName = printEnum(modelType);
std::string shadingName = printEnum(shadingType);
std::string cullingName = printEnum(cullingMode);
std::string cullingRgbName = printEnum(cullingModeRgb);
std::string cullingDepthName = printEnum(cullingModeDepth);
std::string path = findDataDirectory("rendering");
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str());
std::string suffixRgb = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingRgbName.c_str());
std::string suffixDepth = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingDepthName.c_str());
std::string gtPathColor = path + "/example_image_" + suffixRgb + "_" + shadingName + ".png";
std::string gtPathDepth = path + "/depth_image_" + suffixDepth + ".png";
Mat rgbDiff, depthDiff;
Mat groundTruthColor = imread(gtPathColor);
groundTruthColor.convertTo(groundTruthColor, CV_32F, (1.f / 255.f));
compareRGB(groundTruthColor, color_buf, rgbDiff, thr.rgbInfThreshold, thr.rgbL2Threshold);
Mat groundTruthDepth = imread(gtPathDepth, cv::IMREAD_GRAYSCALE | cv::IMREAD_ANYDEPTH);
compareDepth(groundTruthDepth, depth_buf, depthDiff, zFar, depthScale, thr.depthMaskThreshold, thr.depthInfThreshold, thr.depthL2Threshold);
// add --test_debug to output resulting images
if (debugLevel > 0)
{
std::string outColorPath = "color_image_" + suffix + "_" + shadingName + ".png";
std::string outDepthPath = "depth_image_" + suffix + "_" + shadingName + ".png";
imwrite(outColorPath, color_buf * 255.f);
imwrite(outDepthPath, depth_buf);
imwrite("diff_" + outColorPath, rgbDiff * 255.f);
imwrite("diff_" + outDepthPath, depthDiff);
}
}
}
// drawing model as a whole or as two halves should give the same result
TEST_P(RenderingTest, keepDrawnData)
{
if (modelType != ModelType::Empty)
{
Mat depth_buf2(height, width, ftype, zFar);
Mat color_buf2(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0));
Mat idx1, idx2;
int nTriangles = (int)indices.total();
idx1 = indices.reshape(3, 1)(Range::all(), Range(0, nTriangles / 2));
idx2 = indices.reshape(3, 1)(Range::all(), Range(nTriangles / 2, nTriangles));
triangleRasterize(verts, idx1, colors, color_buf2, depth_buf2, cameraPose, fovYradians, zNear, zFar, settings);
triangleRasterize(verts, idx2, colors, color_buf2, depth_buf2, cameraPose, fovYradians, zNear, zFar, settings);
Mat rgbDiff, depthDiff;
compareRGB(color_buf, color_buf2, rgbDiff, 0, 0);
depth_buf.convertTo(depth_buf, CV_16U, depthScale);
depth_buf2.convertTo(depth_buf2, CV_16U, depthScale);
compareDepth(depth_buf, depth_buf2, depthDiff, zFar, depthScale, 0, 0, 0);
}
}
TEST_P(RenderingTest, glCompatibleDepth)
{
Mat depth_buf2(height, width, ftype, 1.0);
triangleRasterizeDepth(verts, indices, depth_buf2, cameraPose, fovYradians, zNear, zFar,
settings.setGlCompatibleMode(RASTERIZE_COMPAT_INVDEPTH));
Mat convertedDepth(height, width, ftype);
// map from [0, 1] to [zNear, zFar]
double scaleNear = (1.0 / zNear);
double scaleFar = (1.0 / zFar);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double z = (double)depth_buf2.at<float>(y, x);
convertedDepth.at<float>(y, x) = (float)(1.0 / ((1.0 - z) * scaleNear + z * scaleFar ));
}
}
double normL2Diff = cv::norm(depth_buf, convertedDepth, cv::NORM_L2) / (height * width);
const double normL2Threshold = 5.53e-10;
EXPECT_LE(normL2Diff, normL2Threshold);
// add --test_debug to output differences
if (debugLevel > 0)
{
std::cout << "normL2Diff: " << normL2Diff << " vs " << normL2Threshold << std::endl;
}
}
INSTANTIATE_TEST_CASE_P(Rendering, RenderingTest, ::testing::Values(
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(640, 480), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CW, ModelType::Color, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CW, ModelType::Clipping, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Clipping, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CCW, ModelType::Clipping, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CW, ModelType::Clipping, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(640, 480), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Clipping, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CCW, ModelType::Clipping, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CCW, ModelType::File, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CW, ModelType::File, CV_32F, CV_32S },
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::File, CV_32F, CV_32S }
));
} // namespace ::
} // namespace opencv_test
File diff suppressed because it is too large Load Diff