in body-tracking-samples/floor_detector_sample/FloorDetector.cpp [128:215]
std::optional<Samples::Plane> Samples::FloorDetector::TryDetectFloorPlane(
const std::vector<k4a_float3_t>& cloudPoints,
const k4a_imu_sample_t& imuSample,
const k4a_calibration_t& sensorCalibration,
size_t minimumFloorPointCount)
{
auto gravity = TryEstimateGravityVectorForDepthCamera(imuSample, sensorCalibration);
if (gravity.has_value() && !cloudPoints.empty())
{
// Up normal is opposite to gravity down vector.
Samples::Vector up = (gravity.value() * -1).Normalized();
// Compute elevation of cloud points (projections on the floor normal).
std::vector<float> offsets(cloudPoints.size());
for (size_t i = 0; i < cloudPoints.size(); ++i)
{
offsets[i] = up.Dot(Samples::Vector(cloudPoints[i]));
}
// There could be several horizontal planes in the scene (floor, tables, ceiling).
// For the floor, look for lowest N points whose elevations are within a small range from each other.
const float planeDisplacementRangeInMeters = 0.050f; // 5 cm in meters.
const float planeMaxTiltInDeg = 5.0f;
const int binAggregation = 6;
assert(binAggregation >= 1);
const float binSize = planeDisplacementRangeInMeters / binAggregation;
const auto histBins = Histogram(offsets, binSize);
// Cumulative histogram counts.
std::vector<size_t> cumulativeHistCounts(histBins.size());
cumulativeHistCounts[0] = histBins[0].count;
for (size_t i = 1; i < histBins.size(); ++i)
{
cumulativeHistCounts[i] = cumulativeHistCounts[i - 1] + histBins[i].count;
}
assert(cumulativeHistCounts.back() == offsets.size());
for (size_t i = 1; i + binAggregation < cumulativeHistCounts.size(); ++i)
{
size_t aggBinStart = i; // inclusive bin
size_t aggBinEnd = i + binAggregation; // exclusive bin
size_t inlierCount = cumulativeHistCounts[aggBinEnd - 1] - cumulativeHistCounts[aggBinStart - 1];
if (inlierCount > minimumFloorPointCount)
{
float offsetStart = histBins[aggBinStart].leftEdge; // inclusive
float offsetEnd = histBins[aggBinEnd].leftEdge; // exclusive
// Inlier indices.
std::vector<size_t> inlierIndices;
for (size_t j = 0; j < offsets.size(); ++j)
{
if (offsetStart <= offsets[j] && offsets[j] < offsetEnd)
{
inlierIndices.push_back(j);
}
}
// Fit plane to inlier points.
auto refinedPlane = FitPlaneToInlierPoints(cloudPoints, inlierIndices);
if (refinedPlane.has_value())
{
// Ensure normal is upward.
if (refinedPlane->Normal.Dot(up) < 0)
{
refinedPlane->Normal = refinedPlane->Normal * -1;
}
// Ensure normal is mostly vertical.
auto floorTiltInDeg = acos(refinedPlane->Normal.Dot(up)) * 180.0f / 3.14159265f;
if (floorTiltInDeg < planeMaxTiltInDeg)
{
// For reduced jitter, use gravity for floor normal.
refinedPlane->Normal = up;
return refinedPlane;
}
}
return {};
}
}
}
return {};
}