Merge pull request #2566 from akashsharma02:master
[GSoC] Add new Hashtable based TSDF volume to RGBD module * - Add HashTSDF class - Implement Integrate function (untested) * Integration seems to be working, raycasting does not * Update integration code * Integration and Raycasting fixes, (both work now) * - Format code - Clean up comments and few fixes * Add Kinect Fusion backup file * - Add interpolation for vertices and normals (slow and unreliable!) - Format code - Delete kinfu_back.cpp * Bug fix for integration and noisy odometry * - Create volume abstract class - Address Review comments * - Add getPoints and getNormals function - Fix formatting according to comments - Move volume abstract class to include/opencv2/rgbd/ - Write factory method for creating TSDFVolumes - Small bug fixes - Minor fixes according to comments * - Add tests for hashTSDF - Fix raycasting bug causing to loop forever - Suppress warnings by explicit conversion - Disable hashTsdf test until we figure out memory leak - style changes - Add missing license in a few files, correct precomp.hpp usagepull/2595/head
parent
52200a82e7
commit
468345511f
15 changed files with 1250 additions and 394 deletions
@ -0,0 +1,81 @@ |
||||
// 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_RGBD_INTRINSICS_HPP__ |
||||
#define __OPENCV_RGBD_INTRINSICS_HPP__ |
||||
|
||||
#include "opencv2/core/matx.hpp" |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace kinfu |
||||
{ |
||||
|
||||
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; |
||||
}; |
||||
|
||||
} // namespace rgbd
|
||||
} // namespace cv
|
||||
|
||||
#endif |
@ -0,0 +1,58 @@ |
||||
// 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
|
||||
|
||||
#ifndef __OPENCV_RGBD_VOLUME_H__ |
||||
#define __OPENCV_RGBD_VOLUME_H__ |
||||
|
||||
#include "intrinsics.hpp" |
||||
#include "opencv2/core/affine.hpp" |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace kinfu |
||||
{ |
||||
class Volume |
||||
{ |
||||
public: |
||||
Volume(float _voxelSize, cv::Affine3f _pose, float _raycastStepFactor) |
||||
: voxelSize(_voxelSize), |
||||
voxelSizeInv(1.0f / voxelSize), |
||||
pose(_pose), |
||||
raycastStepFactor(_raycastStepFactor) |
||||
{ |
||||
} |
||||
|
||||
virtual ~Volume(){}; |
||||
|
||||
virtual void integrate(InputArray _depth, float depthFactor, const cv::Affine3f& cameraPose, |
||||
const cv::kinfu::Intr& intrinsics) = 0; |
||||
virtual void raycast(const cv::Affine3f& cameraPose, const cv::kinfu::Intr& intrinsics, |
||||
cv::Size frameSize, cv::OutputArray points, |
||||
cv::OutputArray normals) const = 0; |
||||
virtual void fetchNormals(cv::InputArray points, cv::OutputArray _normals) const = 0; |
||||
virtual void fetchPointsNormals(cv::OutputArray points, cv::OutputArray normals) const = 0; |
||||
virtual void reset() = 0; |
||||
|
||||
public: |
||||
const float voxelSize; |
||||
const float voxelSizeInv; |
||||
const cv::Affine3f pose; |
||||
const float raycastStepFactor; |
||||
}; |
||||
|
||||
enum class VolumeType |
||||
{ |
||||
TSDF = 0, |
||||
HASHTSDF = 1 |
||||
}; |
||||
|
||||
cv::Ptr<Volume> makeVolume(VolumeType _volumeType, float _voxelSize, cv::Affine3f _pose, |
||||
float _raycastStepFactor, float _truncDist, int _maxWeight, |
||||
float _truncateThreshold, Point3i _resolution); |
||||
} // namespace kinfu
|
||||
} // namespace cv
|
||||
#endif |
@ -0,0 +1,578 @@ |
||||
// 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 "hash_tsdf.hpp" |
||||
|
||||
#include <atomic> |
||||
#include <functional> |
||||
#include <iostream> |
||||
#include <limits> |
||||
#include <vector> |
||||
|
||||
#include "kinfu_frame.hpp" |
||||
#include "opencv2/core/cvstd.hpp" |
||||
#include "opencv2/core/utility.hpp" |
||||
#include "opencv2/core/utils/trace.hpp" |
||||
#include "utils.hpp" |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace kinfu |
||||
{ |
||||
HashTSDFVolume::HashTSDFVolume(float _voxelSize, cv::Affine3f _pose, float _raycastStepFactor, |
||||
float _truncDist, int _maxWeight, float _truncateThreshold, |
||||
int _volumeUnitRes, bool _zFirstMemOrder) |
||||
: Volume(_voxelSize, _pose, _raycastStepFactor), |
||||
maxWeight(_maxWeight), |
||||
truncateThreshold(_truncateThreshold), |
||||
volumeUnitResolution(_volumeUnitRes), |
||||
volumeUnitSize(voxelSize * volumeUnitResolution), |
||||
zFirstMemOrder(_zFirstMemOrder) |
||||
{ |
||||
truncDist = std::max(_truncDist, 4.0f * voxelSize); |
||||
} |
||||
|
||||
HashTSDFVolumeCPU::HashTSDFVolumeCPU(float _voxelSize, cv::Affine3f _pose, float _raycastStepFactor, |
||||
float _truncDist, int _maxWeight, float _truncateThreshold, |
||||
int _volumeUnitRes, bool _zFirstMemOrder) |
||||
: HashTSDFVolume(_voxelSize, _pose, _raycastStepFactor, _truncDist, _maxWeight, |
||||
_truncateThreshold, _volumeUnitRes, _zFirstMemOrder) |
||||
{ |
||||
} |
||||
|
||||
// zero volume, leave rest params the same
|
||||
void HashTSDFVolumeCPU::reset() |
||||
{ |
||||
CV_TRACE_FUNCTION(); |
||||
volumeUnits.clear(); |
||||
} |
||||
|
||||
struct AllocateVolumeUnitsInvoker : ParallelLoopBody |
||||
{ |
||||
AllocateVolumeUnitsInvoker(HashTSDFVolumeCPU& _volume, const Depth& _depth, Intr intrinsics, |
||||
cv::Affine3f cameraPose, float _depthFactor, int _depthStride = 4) |
||||
: ParallelLoopBody(), |
||||
volume(_volume), |
||||
depth(_depth), |
||||
reproj(intrinsics.makeReprojector()), |
||||
cam2vol(_volume.pose.inv() * cameraPose), |
||||
depthFactor(1.0f / _depthFactor), |
||||
depthStride(_depthStride) |
||||
{ |
||||
} |
||||
|
||||
virtual void operator()(const Range& range) const override |
||||
{ |
||||
for (int y = range.start; y < range.end; y += depthStride) |
||||
{ |
||||
const depthType* depthRow = depth[y]; |
||||
for (int x = 0; x < depth.cols; x += depthStride) |
||||
{ |
||||
depthType z = depthRow[x] * depthFactor; |
||||
if (z <= 0) |
||||
continue; |
||||
|
||||
Point3f camPoint = reproj(Point3f((float)x, (float)y, z)); |
||||
Point3f volPoint = cam2vol * camPoint; |
||||
|
||||
//! Find accessed TSDF volume unit for valid 3D vertex
|
||||
cv::Vec3i lower_bound = volume.volumeToVolumeUnitIdx( |
||||
volPoint - cv::Point3f(volume.truncDist, volume.truncDist, volume.truncDist)); |
||||
cv::Vec3i upper_bound = volume.volumeToVolumeUnitIdx( |
||||
volPoint + cv::Point3f(volume.truncDist, volume.truncDist, volume.truncDist)); |
||||
VolumeUnitIndexSet localAccessVolUnits; |
||||
for (int i = lower_bound[0]; i <= upper_bound[0]; i++) |
||||
for (int j = lower_bound[1]; j <= upper_bound[1]; j++) |
||||
for (int k = lower_bound[2]; k <= lower_bound[2]; k++) |
||||
{ |
||||
const cv::Vec3i tsdf_idx = cv::Vec3i(i, j, k); |
||||
if (!localAccessVolUnits.count(tsdf_idx)) |
||||
{ |
||||
localAccessVolUnits.emplace(tsdf_idx); |
||||
} |
||||
} |
||||
AutoLock al(mutex); |
||||
for (const auto& tsdf_idx : localAccessVolUnits) |
||||
{ |
||||
//! If the insert into the global set passes
|
||||
if (!volume.volumeUnits.count(tsdf_idx)) |
||||
{ |
||||
VolumeUnit volumeUnit; |
||||
cv::Point3i volumeDims(volume.volumeUnitResolution, |
||||
volume.volumeUnitResolution, |
||||
volume.volumeUnitResolution); |
||||
|
||||
cv::Affine3f subvolumePose = |
||||
volume.pose.translate(volume.volumeUnitIdxToVolume(tsdf_idx)); |
||||
volumeUnit.pVolume = cv::makePtr<TSDFVolumeCPU>( |
||||
volume.voxelSize, subvolumePose, volume.raycastStepFactor, |
||||
volume.truncDist, volume.maxWeight, volumeDims); |
||||
//! This volume unit will definitely be required for current integration
|
||||
volumeUnit.index = tsdf_idx; |
||||
volumeUnit.isActive = true; |
||||
volume.volumeUnits[tsdf_idx] = volumeUnit; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
HashTSDFVolumeCPU& volume; |
||||
const Depth& depth; |
||||
const Intr::Reprojector reproj; |
||||
const cv::Affine3f cam2vol; |
||||
const float depthFactor; |
||||
const int depthStride; |
||||
mutable Mutex mutex; |
||||
}; |
||||
|
||||
|
||||
void HashTSDFVolumeCPU::integrate(InputArray _depth, float depthFactor, |
||||
const cv::Affine3f& cameraPose, const Intr& intrinsics) |
||||
{ |
||||
CV_TRACE_FUNCTION(); |
||||
|
||||
CV_Assert(_depth.type() == DEPTH_TYPE); |
||||
Depth depth = _depth.getMat(); |
||||
|
||||
//! Compute volumes to be allocated
|
||||
AllocateVolumeUnitsInvoker allocate_i(*this, depth, intrinsics, cameraPose, depthFactor); |
||||
Range allocateRange(0, depth.rows); |
||||
parallel_for_(allocateRange, allocate_i); |
||||
|
||||
//! Get volumes that are in the current camera frame
|
||||
std::vector<Vec3i> totalVolUnits; |
||||
for (const auto& keyvalue : volumeUnits) |
||||
{ |
||||
totalVolUnits.push_back(keyvalue.first); |
||||
} |
||||
|
||||
//! Mark volumes in the camera frustum as active
|
||||
Range inFrustumRange(0, (int)volumeUnits.size()); |
||||
parallel_for_(inFrustumRange, [&](const Range& range) { |
||||
const Affine3f vol2cam(cameraPose.inv() * pose); |
||||
const Intr::Projector proj(intrinsics.makeProjector()); |
||||
|
||||
for (int i = range.start; i < range.end; ++i) |
||||
{ |
||||
cv::Vec3i tsdf_idx = totalVolUnits[i]; |
||||
VolumeUnitMap::iterator it = volumeUnits.find(tsdf_idx); |
||||
if (it == volumeUnits.end()) |
||||
return; |
||||
|
||||
Point3f volumeUnitPos = volumeUnitIdxToVolume(it->first); |
||||
Point3f volUnitInCamSpace = vol2cam * volumeUnitPos; |
||||
if (volUnitInCamSpace.z < 0 || volUnitInCamSpace.z > truncateThreshold) |
||||
{ |
||||
it->second.isActive = false; |
||||
return; |
||||
} |
||||
Point2f cameraPoint = proj(volUnitInCamSpace); |
||||
if (cameraPoint.x >= 0 && cameraPoint.y >= 0 && cameraPoint.x < depth.cols && |
||||
cameraPoint.y < depth.rows) |
||||
{ |
||||
assert(it != volumeUnits.end()); |
||||
it->second.isActive = true; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
//! Integrate the correct volumeUnits
|
||||
parallel_for_(Range(0, (int)totalVolUnits.size()), [&](const Range& range) { |
||||
for (int i = range.start; i < range.end; i++) |
||||
{ |
||||
cv::Vec3i tsdf_idx = totalVolUnits[i]; |
||||
VolumeUnitMap::iterator it = volumeUnits.find(tsdf_idx); |
||||
if (it == volumeUnits.end()) |
||||
return; |
||||
|
||||
VolumeUnit& volumeUnit = it->second; |
||||
if (volumeUnit.isActive) |
||||
{ |
||||
//! The volume unit should already be added into the Volume from the allocator
|
||||
volumeUnit.pVolume->integrate(depth, depthFactor, cameraPose, intrinsics); |
||||
//! Ensure all active volumeUnits are set to inactive for next integration
|
||||
volumeUnit.isActive = false; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
cv::Vec3i HashTSDFVolumeCPU::volumeToVolumeUnitIdx(cv::Point3f p) const |
||||
{ |
||||
return cv::Vec3i(cvFloor(p.x / volumeUnitSize), cvFloor(p.y / volumeUnitSize), |
||||
cvFloor(p.z / volumeUnitSize)); |
||||
} |
||||
|
||||
cv::Point3f HashTSDFVolumeCPU::volumeUnitIdxToVolume(cv::Vec3i volumeUnitIdx) const |
||||
{ |
||||
return cv::Point3f(volumeUnitIdx[0] * volumeUnitSize, volumeUnitIdx[1] * volumeUnitSize, |
||||
volumeUnitIdx[2] * volumeUnitSize); |
||||
} |
||||
|
||||
cv::Point3f HashTSDFVolumeCPU::voxelCoordToVolume(cv::Vec3i voxelIdx) const |
||||
{ |
||||
return cv::Point3f(voxelIdx[0] * voxelSize, voxelIdx[1] * voxelSize, voxelIdx[2] * voxelSize); |
||||
} |
||||
|
||||
cv::Vec3i HashTSDFVolumeCPU::volumeToVoxelCoord(cv::Point3f point) const |
||||
{ |
||||
return cv::Vec3i(cvFloor(point.x * voxelSizeInv), cvFloor(point.y * voxelSizeInv), |
||||
cvFloor(point.z * voxelSizeInv)); |
||||
} |
||||
|
||||
inline TsdfVoxel HashTSDFVolumeCPU::at(const cv::Vec3i& volumeIdx) const |
||||
{ |
||||
cv::Vec3i volumeUnitIdx = cv::Vec3i(cvFloor(volumeIdx[0] / volumeUnitResolution), |
||||
cvFloor(volumeIdx[1] / volumeUnitResolution), |
||||
cvFloor(volumeIdx[2] / volumeUnitResolution)); |
||||
|
||||
VolumeUnitMap::const_iterator it = volumeUnits.find(volumeUnitIdx); |
||||
if (it == volumeUnits.end()) |
||||
{ |
||||
TsdfVoxel dummy; |
||||
dummy.tsdf = 1.f; |
||||
dummy.weight = 0; |
||||
return dummy; |
||||
} |
||||
cv::Ptr<TSDFVolumeCPU> volumeUnit = |
||||
std::dynamic_pointer_cast<TSDFVolumeCPU>(it->second.pVolume); |
||||
|
||||
cv::Vec3i volUnitLocalIdx = volumeIdx - cv::Vec3i(volumeUnitIdx[0] * volumeUnitResolution, |
||||
volumeUnitIdx[1] * volumeUnitResolution, |
||||
volumeUnitIdx[2] * volumeUnitResolution); |
||||
|
||||
volUnitLocalIdx = |
||||
cv::Vec3i(abs(volUnitLocalIdx[0]), abs(volUnitLocalIdx[1]), abs(volUnitLocalIdx[2])); |
||||
return volumeUnit->at(volUnitLocalIdx); |
||||
} |
||||
|
||||
inline TsdfVoxel HashTSDFVolumeCPU::at(const cv::Point3f& point) const |
||||
{ |
||||
cv::Vec3i volumeUnitIdx = volumeToVolumeUnitIdx(point); |
||||
VolumeUnitMap::const_iterator it = volumeUnits.find(volumeUnitIdx); |
||||
if (it == volumeUnits.end()) |
||||
{ |
||||
TsdfVoxel dummy; |
||||
dummy.tsdf = 1.f; |
||||
dummy.weight = 0; |
||||
return dummy; |
||||
} |
||||
cv::Ptr<TSDFVolumeCPU> volumeUnit = |
||||
std::dynamic_pointer_cast<TSDFVolumeCPU>(it->second.pVolume); |
||||
|
||||
cv::Point3f volumeUnitPos = volumeUnitIdxToVolume(volumeUnitIdx); |
||||
cv::Vec3i volUnitLocalIdx = volumeToVoxelCoord(point - volumeUnitPos); |
||||
volUnitLocalIdx = |
||||
cv::Vec3i(abs(volUnitLocalIdx[0]), abs(volUnitLocalIdx[1]), abs(volUnitLocalIdx[2])); |
||||
return volumeUnit->at(volUnitLocalIdx); |
||||
} |
||||
|
||||
inline Point3f HashTSDFVolumeCPU::getNormalVoxel(Point3f point) const |
||||
{ |
||||
Vec3f pointVec(point); |
||||
Vec3f normal = Vec3f(0, 0, 0); |
||||
|
||||
Vec3f pointPrev = point; |
||||
Vec3f pointNext = point; |
||||
|
||||
for (int c = 0; c < 3; c++) |
||||
{ |
||||
pointPrev[c] -= voxelSize * 0.5f; |
||||
pointNext[c] += voxelSize * 0.5f; |
||||
|
||||
normal[c] = at(Point3f(pointNext)).tsdf - at(Point3f(pointPrev)).tsdf; |
||||
normal[c] *= 0.5f; |
||||
|
||||
pointPrev[c] = pointVec[c]; |
||||
pointNext[c] = pointVec[c]; |
||||
} |
||||
return normalize(normal); |
||||
} |
||||
|
||||
struct HashRaycastInvoker : ParallelLoopBody |
||||
{ |
||||
HashRaycastInvoker(Points& _points, Normals& _normals, const Affine3f& cameraPose, |
||||
const Intr& intrinsics, const HashTSDFVolumeCPU& _volume) |
||||
: ParallelLoopBody(), |
||||
points(_points), |
||||
normals(_normals), |
||||
volume(_volume), |
||||
tstep(_volume.truncDist * _volume.raycastStepFactor), |
||||
cam2vol(volume.pose.inv() * cameraPose), |
||||
vol2cam(cameraPose.inv() * volume.pose), |
||||
reproj(intrinsics.makeReprojector()) |
||||
{ |
||||
} |
||||
|
||||
virtual void operator()(const Range& range) const override |
||||
{ |
||||
const Point3f cam2volTrans = cam2vol.translation(); |
||||
const Matx33f cam2volRot = cam2vol.rotation(); |
||||
const Matx33f vol2camRot = vol2cam.rotation(); |
||||
|
||||
const float blockSize = volume.volumeUnitSize; |
||||
|
||||
for (int y = range.start; y < range.end; y++) |
||||
{ |
||||
ptype* ptsRow = points[y]; |
||||
ptype* nrmRow = normals[y]; |
||||
|
||||
for (int x = 0; x < points.cols; x++) |
||||
{ |
||||
//! Initialize default value
|
||||
Point3f point = nan3, normal = nan3; |
||||
|
||||
//! Ray origin and direction in the volume coordinate frame
|
||||
Point3f orig = cam2volTrans; |
||||
Point3f rayDirV = |
||||
normalize(Vec3f(cam2volRot * reproj(Point3f(float(x), float(y), 1.f)))); |
||||
|
||||
float tmin = 0; |
||||
float tmax = volume.truncateThreshold; |
||||
float tcurr = tmin; |
||||
|
||||
cv::Vec3i prevVolumeUnitIdx = |
||||
cv::Vec3i(std::numeric_limits<int>::min(), std::numeric_limits<int>::min(), |
||||
std::numeric_limits<int>::min()); |
||||
|
||||
float tprev = tcurr; |
||||
TsdfType prevTsdf = volume.truncDist; |
||||
cv::Ptr<TSDFVolumeCPU> currVolumeUnit; |
||||
while (tcurr < tmax) |
||||
{ |
||||
Point3f currRayPos = orig + tcurr * rayDirV; |
||||
cv::Vec3i currVolumeUnitIdx = volume.volumeToVolumeUnitIdx(currRayPos); |
||||
|
||||
VolumeUnitMap::const_iterator it = volume.volumeUnits.find(currVolumeUnitIdx); |
||||
|
||||
TsdfType currTsdf = prevTsdf; |
||||
int currWeight = 0; |
||||
float stepSize = 0.5f * blockSize; |
||||
cv::Vec3i volUnitLocalIdx; |
||||
|
||||
//! Does the subvolume exist in hashtable
|
||||
if (it != volume.volumeUnits.end()) |
||||
{ |
||||
currVolumeUnit = |
||||
std::dynamic_pointer_cast<TSDFVolumeCPU>(it->second.pVolume); |
||||
cv::Point3f currVolUnitPos = |
||||
volume.volumeUnitIdxToVolume(currVolumeUnitIdx); |
||||
volUnitLocalIdx = volume.volumeToVoxelCoord(currRayPos - currVolUnitPos); |
||||
|
||||
//! TODO: Figure out voxel interpolation
|
||||
TsdfVoxel currVoxel = currVolumeUnit->at(volUnitLocalIdx); |
||||
currTsdf = currVoxel.tsdf; |
||||
currWeight = currVoxel.weight; |
||||
stepSize = tstep; |
||||
} |
||||
//! Surface crossing
|
||||
if (prevTsdf > 0.f && currTsdf <= 0.f && currWeight > 0) |
||||
{ |
||||
float tInterp = |
||||
(tcurr * prevTsdf - tprev * currTsdf) / (prevTsdf - currTsdf); |
||||
if (!cvIsNaN(tInterp) && !cvIsInf(tInterp)) |
||||
{ |
||||
Point3f pv = orig + tInterp * rayDirV; |
||||
Point3f nv = volume.getNormalVoxel(pv); |
||||
|
||||
if (!isNaN(nv)) |
||||
{ |
||||
normal = vol2camRot * nv; |
||||
point = vol2cam * pv; |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
prevVolumeUnitIdx = currVolumeUnitIdx; |
||||
prevTsdf = currTsdf; |
||||
tprev = tcurr; |
||||
tcurr += stepSize; |
||||
} |
||||
ptsRow[x] = toPtype(point); |
||||
nrmRow[x] = toPtype(normal); |
||||
} |
||||
} |
||||
} |
||||
|
||||
Points& points; |
||||
Normals& normals; |
||||
const HashTSDFVolumeCPU& volume; |
||||
const float tstep; |
||||
const Affine3f cam2vol; |
||||
const Affine3f vol2cam; |
||||
const Intr::Reprojector reproj; |
||||
}; |
||||
|
||||
void HashTSDFVolumeCPU::raycast(const cv::Affine3f& cameraPose, const cv::kinfu::Intr& intrinsics, |
||||
cv::Size frameSize, cv::OutputArray _points, |
||||
cv::OutputArray _normals) const |
||||
{ |
||||
CV_TRACE_FUNCTION(); |
||||
CV_Assert(frameSize.area() > 0); |
||||
|
||||
_points.create(frameSize, POINT_TYPE); |
||||
_normals.create(frameSize, POINT_TYPE); |
||||
|
||||
Points points = _points.getMat(); |
||||
Normals normals = _normals.getMat(); |
||||
|
||||
HashRaycastInvoker ri(points, normals, cameraPose, intrinsics, *this); |
||||
|
||||
const int nstripes = -1; |
||||
parallel_for_(Range(0, points.rows), ri, nstripes); |
||||
} |
||||
|
||||
struct FetchPointsNormalsInvoker : ParallelLoopBody |
||||
{ |
||||
FetchPointsNormalsInvoker(const HashTSDFVolumeCPU& _volume, |
||||
const std::vector<Vec3i>& _totalVolUnits, |
||||
std::vector<std::vector<ptype>>& _pVecs, |
||||
std::vector<std::vector<ptype>>& _nVecs, bool _needNormals) |
||||
: ParallelLoopBody(), |
||||
volume(_volume), |
||||
totalVolUnits(_totalVolUnits), |
||||
pVecs(_pVecs), |
||||
nVecs(_nVecs), |
||||
needNormals(_needNormals) |
||||
{ |
||||
} |
||||
|
||||
virtual void operator()(const Range& range) const override |
||||
{ |
||||
std::vector<ptype> points, normals; |
||||
for (int i = range.start; i < range.end; i++) |
||||
{ |
||||
cv::Vec3i tsdf_idx = totalVolUnits[i]; |
||||
|
||||
VolumeUnitMap::const_iterator it = volume.volumeUnits.find(tsdf_idx); |
||||
Point3f base_point = volume.volumeUnitIdxToVolume(tsdf_idx); |
||||
if (it != volume.volumeUnits.end()) |
||||
{ |
||||
cv::Ptr<TSDFVolumeCPU> volumeUnit = |
||||
std::dynamic_pointer_cast<TSDFVolumeCPU>(it->second.pVolume); |
||||
std::vector<ptype> localPoints; |
||||
std::vector<ptype> localNormals; |
||||
for (int x = 0; x < volume.volumeUnitResolution; x++) |
||||
for (int y = 0; y < volume.volumeUnitResolution; y++) |
||||
for (int z = 0; z < volume.volumeUnitResolution; z++) |
||||
{ |
||||
cv::Vec3i voxelIdx(x, y, z); |
||||
TsdfVoxel voxel = volumeUnit->at(voxelIdx); |
||||
|
||||
if (voxel.tsdf != 1.f && voxel.weight != 0) |
||||
{ |
||||
Point3f point = base_point + volume.voxelCoordToVolume(voxelIdx); |
||||
localPoints.push_back(toPtype(point)); |
||||
if (needNormals) |
||||
{ |
||||
Point3f normal = volume.getNormalVoxel(point); |
||||
localNormals.push_back(toPtype(normal)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
AutoLock al(mutex); |
||||
pVecs.push_back(localPoints); |
||||
nVecs.push_back(localNormals); |
||||
} |
||||
} |
||||
} |
||||
|
||||
const HashTSDFVolumeCPU& volume; |
||||
std::vector<cv::Vec3i> totalVolUnits; |
||||
std::vector<std::vector<ptype>>& pVecs; |
||||
std::vector<std::vector<ptype>>& nVecs; |
||||
const TsdfVoxel* volDataStart; |
||||
bool needNormals; |
||||
mutable Mutex mutex; |
||||
}; |
||||
|
||||
void HashTSDFVolumeCPU::fetchPointsNormals(OutputArray _points, OutputArray _normals) const |
||||
{ |
||||
CV_TRACE_FUNCTION(); |
||||
|
||||
if (_points.needed()) |
||||
{ |
||||
std::vector<std::vector<ptype>> pVecs, nVecs; |
||||
|
||||
std::vector<Vec3i> totalVolUnits; |
||||
for (const auto& keyvalue : volumeUnits) |
||||
{ |
||||
totalVolUnits.push_back(keyvalue.first); |
||||
} |
||||
FetchPointsNormalsInvoker fi(*this, totalVolUnits, pVecs, nVecs, _normals.needed()); |
||||
Range range(0, (int)totalVolUnits.size()); |
||||
const int nstripes = -1; |
||||
parallel_for_(range, fi, nstripes); |
||||
std::vector<ptype> points, normals; |
||||
for (size_t i = 0; i < pVecs.size(); i++) |
||||
{ |
||||
points.insert(points.end(), pVecs[i].begin(), pVecs[i].end()); |
||||
normals.insert(normals.end(), nVecs[i].begin(), nVecs[i].end()); |
||||
} |
||||
|
||||
_points.create((int)points.size(), 1, POINT_TYPE); |
||||
if (!points.empty()) |
||||
Mat((int)points.size(), 1, POINT_TYPE, &points[0]).copyTo(_points.getMat()); |
||||
|
||||
if (_normals.needed()) |
||||
{ |
||||
_normals.create((int)normals.size(), 1, POINT_TYPE); |
||||
if (!normals.empty()) |
||||
Mat((int)normals.size(), 1, POINT_TYPE, &normals[0]).copyTo(_normals.getMat()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
struct PushNormals |
||||
{ |
||||
PushNormals(const HashTSDFVolumeCPU& _volume, Normals& _normals) |
||||
: volume(_volume), normals(_normals), invPose(volume.pose.inv()) |
||||
{ |
||||
} |
||||
|
||||
void operator()(const ptype& point, const int* position) const |
||||
{ |
||||
Point3f p = fromPtype(point); |
||||
Point3f n = nan3; |
||||
if (!isNaN(p)) |
||||
{ |
||||
Point3f voxelPoint = invPose * p; |
||||
n = volume.pose.rotation() * volume.getNormalVoxel(voxelPoint); |
||||
} |
||||
normals(position[0], position[1]) = toPtype(n); |
||||
} |
||||
const HashTSDFVolumeCPU& volume; |
||||
Normals& normals; |
||||
Affine3f invPose; |
||||
}; |
||||
|
||||
void HashTSDFVolumeCPU::fetchNormals(cv::InputArray _points, cv::OutputArray _normals) const |
||||
{ |
||||
CV_TRACE_FUNCTION(); |
||||
|
||||
if (_normals.needed()) |
||||
{ |
||||
Points points = _points.getMat(); |
||||
CV_Assert(points.type() == POINT_TYPE); |
||||
|
||||
_normals.createSameSize(_points, _points.type()); |
||||
Normals normals = _normals.getMat(); |
||||
|
||||
points.forEach(PushNormals(*this, normals)); |
||||
} |
||||
} |
||||
|
||||
cv::Ptr<HashTSDFVolume> makeHashTSDFVolume(float _voxelSize, cv::Affine3f _pose, |
||||
float _raycastStepFactor, float _truncDist, |
||||
int _maxWeight, float _truncateThreshold, |
||||
int _volumeUnitResolution) |
||||
{ |
||||
return cv::makePtr<HashTSDFVolumeCPU>(_voxelSize, _pose, _raycastStepFactor, _truncDist, |
||||
_maxWeight, _truncateThreshold, _volumeUnitResolution); |
||||
} |
||||
|
||||
} // namespace kinfu
|
||||
} // namespace cv
|
@ -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_HASH_TSDF_H__ |
||||
#define __OPENCV_HASH_TSDF_H__ |
||||
|
||||
#include <opencv2/rgbd/volume.hpp> |
||||
#include <unordered_map> |
||||
#include <unordered_set> |
||||
|
||||
#include "tsdf.hpp" |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace kinfu |
||||
{ |
||||
class HashTSDFVolume : public Volume |
||||
{ |
||||
public: |
||||
// dimension in voxels, size in meters
|
||||
//! Use fixed volume cuboid
|
||||
HashTSDFVolume(float _voxelSize, cv::Affine3f _pose, float _raycastStepFactor, float _truncDist, |
||||
int _maxWeight, float _truncateThreshold, int _volumeUnitRes, |
||||
bool zFirstMemOrder = true); |
||||
|
||||
virtual ~HashTSDFVolume() = default; |
||||
|
||||
public: |
||||
int maxWeight; |
||||
float truncDist; |
||||
float truncateThreshold; |
||||
int volumeUnitResolution; |
||||
float volumeUnitSize; |
||||
bool zFirstMemOrder; |
||||
}; |
||||
|
||||
struct VolumeUnit |
||||
{ |
||||
VolumeUnit() : pVolume(nullptr){}; |
||||
~VolumeUnit() = default; |
||||
|
||||
cv::Ptr<TSDFVolume> pVolume; |
||||
cv::Vec3i index; |
||||
bool isActive; |
||||
}; |
||||
|
||||
//! Spatial hashing
|
||||
struct tsdf_hash |
||||
{ |
||||
size_t operator()(const cv::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; |
||||
} |
||||
}; |
||||
|
||||
typedef std::unordered_set<cv::Vec3i, tsdf_hash> VolumeUnitIndexSet; |
||||
typedef std::unordered_map<cv::Vec3i, VolumeUnit, tsdf_hash> VolumeUnitMap; |
||||
|
||||
class HashTSDFVolumeCPU : public HashTSDFVolume |
||||
{ |
||||
public: |
||||
// dimension in voxels, size in meters
|
||||
HashTSDFVolumeCPU(float _voxelSize, cv::Affine3f _pose, float _raycastStepFactor, |
||||
float _truncDist, int _maxWeight, float _truncateThreshold, |
||||
int _volumeUnitRes, bool zFirstMemOrder = true); |
||||
|
||||
virtual void integrate(InputArray _depth, float depthFactor, const cv::Affine3f& cameraPose, |
||||
const cv::kinfu::Intr& intrinsics) override; |
||||
virtual void raycast(const cv::Affine3f& cameraPose, const cv::kinfu::Intr& intrinsics, |
||||
cv::Size frameSize, cv::OutputArray points, |
||||
cv::OutputArray normals) const override; |
||||
|
||||
virtual void fetchNormals(cv::InputArray points, cv::OutputArray _normals) const override; |
||||
virtual void fetchPointsNormals(cv::OutputArray points, cv::OutputArray normals) const override; |
||||
|
||||
virtual void reset() override; |
||||
|
||||
//! Return the voxel given the voxel index in the universal volume (1 unit = 1 voxel_length)
|
||||
virtual TsdfVoxel at(const cv::Vec3i& volumeIdx) const; |
||||
|
||||
//! Return the voxel given the point in volume coordinate system i.e., (metric scale 1 unit =
|
||||
//! 1m)
|
||||
virtual TsdfVoxel at(const cv::Point3f& point) const; |
||||
|
||||
inline TsdfType interpolateVoxel(const cv::Point3f& point) const; |
||||
Point3f getNormalVoxel(cv::Point3f p) const; |
||||
|
||||
//! Utility functions for coordinate transformations
|
||||
cv::Vec3i volumeToVolumeUnitIdx(cv::Point3f point) const; |
||||
cv::Point3f volumeUnitIdxToVolume(cv::Vec3i volumeUnitIdx) const; |
||||
|
||||
cv::Point3f voxelCoordToVolume(cv::Vec3i voxelIdx) const; |
||||
cv::Vec3i volumeToVoxelCoord(cv::Point3f point) const; |
||||
|
||||
public: |
||||
//! Hashtable of individual smaller volume units
|
||||
VolumeUnitMap volumeUnits; |
||||
}; |
||||
cv::Ptr<HashTSDFVolume> makeHashTSDFVolume(float _voxelSize, cv::Affine3f _pose, |
||||
float _raycastStepFactor, float _truncDist, |
||||
int _maxWeight, float truncateThreshold, |
||||
int volumeUnitResolution = 16); |
||||
} // namespace kinfu
|
||||
} // namespace cv
|
||||
#endif |
@ -0,0 +1,34 @@ |
||||
// 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/rgbd/volume.hpp> |
||||
|
||||
#include "tsdf.hpp" |
||||
#include "hash_tsdf.hpp" |
||||
|
||||
namespace cv |
||||
{ |
||||
namespace kinfu |
||||
{ |
||||
cv::Ptr<Volume> makeVolume(VolumeType _volumeType, float _voxelSize, cv::Affine3f _pose, |
||||
float _raycastStepFactor, float _truncDist, int _maxWeight, |
||||
float _truncateThreshold, Point3i _resolution) |
||||
{ |
||||
if (_volumeType == VolumeType::TSDF) |
||||
{ |
||||
return makeTSDFVolume(_voxelSize, _pose, _raycastStepFactor, _truncDist, _maxWeight, |
||||
_resolution); |
||||
} |
||||
else if (_volumeType == VolumeType::HASHTSDF) |
||||
{ |
||||
return makeHashTSDFVolume(_voxelSize, _pose, _raycastStepFactor, _truncDist, _maxWeight, |
||||
_truncateThreshold); |
||||
} |
||||
else |
||||
return nullptr; |
||||
} |
||||
|
||||
} // namespace kinfu
|
||||
} // namespace cv
|
Loading…
Reference in new issue